diff --git a/.DS_Store b/.DS_Store
index 08953d2..6c2932a 100644
Binary files a/.DS_Store and b/.DS_Store differ
diff --git a/day11/.DS_Store b/day11/.DS_Store
new file mode 100644
index 0000000..72d46b4
Binary files /dev/null and b/day11/.DS_Store differ
diff --git a/day11/README.md b/day11/README.md
old mode 100644
new mode 100755
index e69de29..0a5e9e5
--- a/day11/README.md
+++ b/day11/README.md
@@ -0,0 +1,55 @@
+# day 11
+
+## Instructions
+
+- setup project
+- clone to your github
+- Read the documentation https://www.apollographql.com/docs/
+- Create the following:
+
+```
+Create model
+
+movies
+- id
+- title
+- director_id
+- main_genre
+- status
+- review
+
+review
+- id
+- notes
+- movie_id
+
+director
+- id
+- name
+
+actors
+- id
+- name
+
+movie_actor
+- id
+- actor_id
+- movie_id
+
+genre
+- id
+- name
+
+genre_movie
+- id
+- movie_id
+- genre_id
+```
+
+- Create resolvers and make query for the 4 tables. Inside movies, we can get all actors in that movie
+
+- Create a query in apollo to get all movies with reviews > # provided by user
+
+- Create a mutation to add an actor to every movie for given genre
+
+- Everything must be done by end of date
diff --git a/day11/app.js b/day11/app.js
new file mode 100755
index 0000000..b248538
--- /dev/null
+++ b/day11/app.js
@@ -0,0 +1,158 @@
+'use strict'
+/*Powered By: Manaknightdigital Inc. https://manaknightdigital.com/ Year: 2020*/
+/**
+ * App
+ * @copyright 2020 Manaknightdigital Inc.
+ * @link https://manaknightdigital.com
+ * @license Proprietary Software licensing
+ * @author Ryan Wong
+ *
+ */
+require('dotenv').config()
+const express = require('express')
+const fs = require('fs')
+const path = require('path')
+const logger = require('morgan')
+const helmet = require('helmet')
+const cookieParser = require('cookie-parser')
+const cors = require('cors')
+const { ApolloServer } = require('apollo-server-express')
+const { graphqlUploadExpress } = require('graphql-upload')
+const body_parser = require('body-parser')
+
+const db = require('./models')
+const typeDefs = fs.readFileSync(
+ path.join(__dirname, '/types/schema.graphql'),
+ 'utf8'
+)
+const jwtService = require('./services/JwtService')
+const resolvers = require('./resolvers')
+const schemaDirectives = require('./directives')
+const { AuthenticationError } = require('./services/ErrorService')
+const { errorCodes } = require('./core/strings')
+const { formatGraphqlError } = require('./utils/formatError')
+
+const GRAPHQL_PATH = '/graphql'
+const ALLOWED_ROLE_IDS = [2]
+
+let app = express()
+
+app.use(logger('dev'))
+
+if (process.env.MODE === 'development') {
+ logger.token('graphql-query', (req) => {
+ const disallowedLogs = ['IntrospectionQuery']
+
+ if (req.method === 'POST' && req.originalUrl === GRAPHQL_PATH) {
+ const { query, variables, operationName } = req.body
+ return !disallowedLogs.includes(operationName)
+ ? `GRAPHQL: \nOperation Name: ${operationName} \nQuery: ${query} \nVariables: ${JSON.stringify(
+ variables
+ )}`
+ : ''
+ }
+ return ''
+ })
+ app.use(logger(':graphql-query'))
+}
+
+const server = new ApolloServer({
+ uploads: false,
+ typeDefs,
+ resolvers,
+ schemaDirectives,
+ context: async ({ req }) => {
+ const token = req.headers.authorization
+
+ if (!token) {
+ throw new AuthenticationError(
+ 'Invalid token',
+ errorCodes.token.INVALID_TOKEN
+ )
+ }
+ const cleanToken = token.replace('Bearer ', '')
+ const verify = jwtService.verifyAccessToken(cleanToken)
+
+ const roleId = verify?.role_id
+ const user = verify?.user
+ const credentialId = verify?.credential_id
+
+ if (!verify || !roleId || !user || !credentialId) {
+ throw new AuthenticationError(
+ 'Invalid token',
+ errorCodes.token.INVALID_TOKEN
+ )
+ }
+
+ if (!ALLOWED_ROLE_IDS.includes(+roleId)) {
+ throw new AuthenticationError(
+ 'Access Denied',
+ errorCodes.account.UNAUTHORIZED
+ )
+ }
+
+ return {
+ credentialId,
+ user,
+ db,
+ role: {
+ roleId,
+ allowedRoleIds: ALLOWED_ROLE_IDS,
+ },
+ }
+ },
+ formatError: formatGraphqlError,
+})
+
+if (process.NODE_ENV === 'maintenance') {
+ app.all('*', (req, res) => {
+ res.status(503).json({ message: 'website under maintenance' })
+ })
+}
+
+app.set('iocContainer', process.env)
+app.set('db', db)
+app.use(body_parser.json({ limit: '50mb' }))
+
+app.use(express.json())
+app.use(
+ express.urlencoded({
+ extended: false,
+ })
+)
+app.use(cors())
+app.set('view engine', 'eta')
+app.set('views', path.join(__dirname, '/views'))
+app.use(cookieParser())
+app.use(helmet())
+
+app.use(express.static(path.join(__dirname, '/public')))
+app.use(express.static(path.join(__dirname, '/uploads')))
+app.use(express.static(path.join(__dirname)));
+
+app.use(graphqlUploadExpress({ maxFileSize: 1000000000, maxFiles: 10 }))
+
+server.applyMiddleware({ app, path: GRAPHQL_PATH })
+
+
+app.use((err, req, res, next) => {
+ res.locals.message = err.message
+ res.locals.error = req.app.get('env') === 'development' ? err : {}
+
+ // render the error page
+ res.status(err.status || 500)
+ res.json({
+ message: err.message,
+ })
+})
+
+app.use((_, res, next) => {
+ return res
+ .status(400)
+ .send("
404: Page Not Found!
")
+})
+
+module.exports = {
+ app,
+ apollo: server,
+}
diff --git a/day11/core/helpers.js b/day11/core/helpers.js
new file mode 100755
index 0000000..b0188d1
--- /dev/null
+++ b/day11/core/helpers.js
@@ -0,0 +1,67 @@
+const fs = require('fs')
+const path = require('path')
+const sanitizeHtml = require('sanitize-html')
+
+module.exports = {
+ filterEmptyFields(object) {
+ Object.keys(object).forEach((key) => {
+ if (this.empty(object[key])) {
+ delete object[key]
+ }
+ })
+ return object
+ },
+ empty(value) {
+ return value === ''
+ },
+ getMappingKey(mappingFunction, value) {
+ return Object.keys(mappingFunction()).find(
+ (key) => mappingFunction()[key].toLowerCase() === value.toLowerCase()
+ )
+ },
+ checkFor(requiredFields) {
+ for (const [key, value] of Object.entries(requiredFields)) {
+ if (!value) {
+ throw new Error(`Must provide ${key}`)
+ }
+ }
+ return true
+ },
+ convertToCents(amount) {
+ return parseFloat(amount) * 100
+ },
+ convertFromCents(amount) {
+ return parseFloat(amount) / 100
+ },
+ inject_substitute(text, normalKey, value) {
+ text = text.replace(new RegExp('{{{' + normalKey + '}}}', 'g'), value)
+ return text
+ },
+ createDirectoriesRecursive(filePath) {
+ let fileDirectoryPath = path.dirname(filePath)
+ if (!fs.existsSync(fileDirectoryPath)) {
+ fs.mkdirSync(fileDirectoryPath, { recursive: true })
+ }
+ },
+ sanitizeInputs(body) {
+ if (Array.isArray(body)) {
+ body.forEach((item) => {
+ item = sanitizeHtml(item)
+ })
+ return body
+ }
+ if (this.isObject(body)) {
+ Object.keys(body).forEach((key) => {
+ body[key] = sanitizeHtml(body[key])
+ })
+ return body
+ }
+ return sanitizeHtml(body)
+ },
+ isObject(obj) {
+ return obj === Object(obj)
+ },
+ ucFirst(string) {
+ return string.charAt(0).toUpperCase() + string.slice(1)
+ },
+}
diff --git a/day11/core/models.js b/day11/core/models.js
new file mode 100755
index 0000000..1e94f41
--- /dev/null
+++ b/day11/core/models.js
@@ -0,0 +1,259 @@
+const { isInteger } = require('lodash');
+
+module.exports = async function (Table) {
+ Table._primaryKey = function () {
+ return 'id';
+ };
+ Table.getLast = async function (parameters) {
+ let where = parameters;
+ if (!where) {
+ where = {};
+ }
+ for (const key in where) {
+ const element = where[key];
+ if (element == undefined || element == null) {
+ delete where.key;
+ }
+ }
+ result = await Table.findAll({
+ limit: 1,
+ where: where,
+ order: [['id', 'DESC']],
+ });
+ return result[0];
+ };
+ Table.getAll = function (parameters) {
+ let where = parameters;
+ if (!where) {
+ where = {};
+ }
+ for (const key in where) {
+ const element = where[key];
+ if (element == undefined || element == null) {
+ delete where.key;
+ }
+ }
+
+ return Table.findAll({
+ where: where,
+ });
+ };
+ Table._count = function (parameters, include = []) {
+ let where = parameters;
+ if (!where) {
+ where = {};
+ }
+ for (const key in where) {
+ const element = where[key];
+ if (element == undefined || element == null) {
+ delete where.key;
+ }
+ }
+
+ Table._customCountingConditions(where);
+
+ return Table.count({
+ where: where,
+ include: include,
+ });
+ };
+
+ Table.getPaginated = function (page, limit, parameters, orderBy, direction, include = []) {
+ let where = parameters;
+
+ // for (const key in where) {
+ // if (where.hasOwnProperty.call(where, key)) {
+ // const element = where[key];
+ // if (element.length || isInteger(element)) {
+ // where[key] = element;
+ // } else {
+ // delete where[key];
+ // }
+ // }
+ // }
+
+ if (!page) {
+ page = 0;
+ }
+ if (!limit) {
+ limit = 10;
+ }
+ if (!where) {
+ where = {};
+ }
+ if (!orderBy) {
+ orderBy = Table._primaryKey();
+ }
+ if (!direction) {
+ direction = 'ASC';
+ }
+
+ for (const key in where) {
+ const element = where[key];
+ if (element == undefined || element == null) {
+ delete where.key;
+ }
+ }
+ console.log('Where down', where);
+
+ console.log('[[[[[[[', {
+ where: where,
+ offset: page * limit,
+ limit: limit,
+ order: [[orderBy, direction]],
+ include: include,
+ });
+
+ return Table.findAll({
+ where: where,
+ offset: page * limit,
+ limit: limit,
+ order: [[orderBy, direction]],
+ include: include,
+ });
+ };
+
+ Table.getAllByStatus = function (status) {
+ return Table.findAll({
+ where: {
+ status: status,
+ },
+ });
+ };
+
+ Table.getByField = function (field, value) {
+ return Table.findOne({
+ where: {
+ [field]: value,
+ },
+ });
+ };
+
+ Table.getByPK = async function (id, options) {
+ return await Table.findByPk(id, options);
+ };
+
+ Table.getByFields = function (parameters) {
+ let where = parameters;
+ if (!where) {
+ where = {};
+ }
+ for (const key in where) {
+ const element = where[key];
+ if (element == undefined || element == null) {
+ delete where.key;
+ }
+ }
+
+ return Table.findOne({
+ where: where,
+ });
+ };
+
+ Table.getAllByKeyValue = async function (field, parameters) {
+ let where = parameters;
+ if (!where) {
+ where = {};
+ }
+ let data = [];
+ const results = await Table.findAll({
+ where: where,
+ });
+
+ for (let i = 0; i < results.length; i++) {
+ const element = results[i];
+ let singleField = element[field];
+ data.push({ [element.id]: singleField });
+ }
+
+ return data;
+ };
+
+ Table.insert = async function (data, { returnAllFields = false } = {}) {
+ data = Table._preCreateProcessing(data);
+ const insertedRow = await Table.create(Table._filterAllowKeys(data));
+ if (returnAllFields === true) {
+ return insertedRow;
+ }
+ if (insertedRow) {
+ return insertedRow.id;
+ }
+ return false;
+ };
+
+ Table.batchInsert = async function (data) {
+ const insertedRow = await Table.bulkCreate(data, { returning: true });
+
+ if (insertedRow) {
+ return insertedRow;
+ }
+
+ return false;
+ };
+
+ Table.edit = async function (data, id) {
+ data = Table._postCreateProcessing(data);
+ const updateRow = await Table.update(Table._filterAllowKeys(data), {
+ where: {
+ id: id,
+ },
+ }).catch((error) => {
+ throw new Error(error);
+ });
+
+ return updateRow;
+ };
+
+ Table.editByField = async function (data, where) {
+ data = Table._postCreateProcessing(data);
+
+ const updateRow = await Table.update(Table._filterAllowKeys(data), {
+ where,
+ });
+
+ return updateRow;
+ };
+
+ Table.delete = async function (id) {
+ const updateRow = await Table.update(
+ {
+ status: 0,
+ },
+ {
+ where: {
+ id: id,
+ },
+ },
+ );
+ return updateRow;
+ };
+
+ Table.realDelete = async function (id) {
+ return Table.destroy({
+ where: {
+ id: id,
+ },
+ });
+ };
+
+ Table.realDeleteByFields = async function (parameters, id) {
+ let where = parameters;
+ if (!where) {
+ where = {
+ id: id,
+ };
+ } else {
+ where['id'] = id;
+ }
+ return Table.destroy({
+ where: where,
+ });
+ };
+ Table.realDeleteByUniqueField = async function (field, value) {
+ let where = {};
+ where[field] = value;
+ return Table.destroy({
+ where: where,
+ });
+ };
+};
diff --git a/day11/core/strings.js b/day11/core/strings.js
new file mode 100755
index 0000000..636e050
--- /dev/null
+++ b/day11/core/strings.js
@@ -0,0 +1,45 @@
+const translation = require('./translation.json')
+
+exports.errors = {
+ EMAIL_ADDRESS_NOT_FOUND: translation.xyzAccountDoesntExists,
+ EMAIL_ADDRESS_ALREADY_EXIST: translation.xyzAccount_Already_Exists,
+ PASSWORD_NOT_MATCH: translation.xyzPasswordsDoNotMatch,
+ INVALID_EMAIL_CONFIRMATION_CODE: translation.xyzInvalidEmailConfirmationCode,
+ INVALID_EMAIL_OR_PASSWORD: translation.xyzWrongEmailOrPassword,
+ ACCOUNT_IS_REGISTERED_WITH_GOOGLE:
+ translation.xyzAccountIsRegisteredUsingGoogle,
+ ACCOUNT_IS_REGISTERED_WITH_FACEBOOK:
+ translation.xyzAccountIsRegisteredUsingFacebook,
+ ACCOUNT_IS_REGISTERED_WITH_EMAIL_AND_PASSWORD:
+ translation.xyzAccountIsRegisteredUsingEmailAndPassword,
+ ACCOUNT_DOES_NOT_EXISTS: translation.xyzAccountDoesntExists,
+ CODE_DOES_NOT_EXIST: translation.xyzCodeInvalidOrExpired,
+}
+
+exports.errorCodes = {
+ token: {
+ INVALID_TOKEN: 'INVALID_TOKEN',
+ },
+ account: {
+ ACCOUNT_DOES_NOT_EXISTS: 'ACCOUNT_DOES_NOT_EXISTS',
+ ACCOUNT_NOT_VERIFIED: 'ACCOUNT_NOT_VERIFIED',
+ ACCOUNT_ALREADY_VERIFIED: 'ACCOUNT_ALREADY_VERIFIED',
+ UNAUTHORIZED: 'UNAUTHORIZED',
+ },
+ calendar: {
+ CALENDAR_EVENT_DOES_NOT_EXISTS: 'CALENDAR_EVENT_DOES_NOT_EXISTS',
+ },
+ note: {
+ NOTE_DOES_NOT_EXISTS: 'NOTE_DOES_NOT_EXISTS',
+ },
+ extra: {
+ CUSTOM_IMAGE_OR_VIDEO_ALREADY_EXISTS:
+ 'CUSTOM_IMAGE_OR_VIDEO_ALREADY_EXISTS',
+ CUSTOM_IMAGE_OR_VIDEO_DOES_NOT_EXISTS:
+ 'CUSTOM_IMAGE_OR_VIDEO_DOES_NOT_EXISTS',
+ INVALID_SYNC_CODE: 'INVALID_SYNC_CODE',
+ IFRAME_BLOCKED: 'IFRAME_BLOCKED',
+ INVALID_URL: 'INVALID_URL',
+ SYNC_CODE_ALREADY_EXISTS: 'SYNC_CODE_ALREADY_EXISTS',
+ },
+}
diff --git a/day11/core/translation.json b/day11/core/translation.json
new file mode 100644
index 0000000..edf0d4b
--- /dev/null
+++ b/day11/core/translation.json
@@ -0,0 +1,384 @@
+{
+ "xyzTitle": "Title",
+ "xyzDate": "Date",
+ "xyzIs Used": "Is Used",
+ "xyzVersion #": "Version #",
+ "xyzTime zone": "Time zone",
+ "xyzTime format": "Time format",
+ "xyzClock format": "Clock format",
+ "xyzDate format": "Date format",
+ "xyzEmails": "Emails",
+ "xyzTimes": "Times",
+ "xyzConfiguration": "Configuration",
+ "xyzEmail": "Email",
+ "xyzFirst Name": "First Name",
+ "xyzLast Name": "Last Name",
+ "xyzPassword": "Password",
+ "xyzEdit": "Edit",
+ "xyzDates": "Date",
+ "xyzFullDates": "Full Date",
+ "xyzAdd": "Add",
+ "xyzCode": "Code",
+ "xyzLink": "Link",
+ "xyzTitles": "Title",
+ "xyzReferree User": "Referree User",
+ "xyzReferrer User": "Referrer User",
+ "xyxType": "Type",
+ "xyzPhone": "Phone",
+ "xyzProfile Type": "Profile Type",
+ "xyzPaid": "Paid",
+ "xyzImage": "Image",
+ "xyzImage ID": "Image ID",
+ "xyzRefer Code": "Refer Code",
+ "xyzProfile": "Profile",
+ "xyzVerified": "Verified",
+ "xyzRole": "Role",
+ "xyzConfirmed": "Confirmed",
+ "xyzDashboard": "Dashboard",
+ "xyzPending": "Pending",
+ "xyzResult": "Result",
+ "xyzTokken": "Tokken",
+ "xyzData": "Data",
+ "xyzTime To Live": "Time To Live",
+ "xyzFullDate": "Full Date",
+ "xyzIssue at": "Issue at",
+ "xyzExpire at": "Expire at",
+ "xyzToken Type": "Token Type",
+ "xyzUser": "xUser",
+ "xyzFilter": "Filter",
+ "xyzSearch": "Search",
+ "xyzStripe Id": "Stripe Id",
+ "xyzAdmin": "Admin",
+ "xyzSystem": "Systems",
+ "xyzMedia Gallery": "Media Gallery",
+ "xyzSaved": "Saved",
+ "xyzLoad More": "Load More",
+ "xyzUpload": "Upload",
+ "xyzClose": "Close",
+ "xyzChoose": "Choose",
+ "xyzCrop & Upload": "Crop & Upload",
+ "xyzDetails": "Details",
+ "xyzView": "View",
+ "xyzSubmit": "Submit",
+ "xyzError": "Error",
+ "xyzBack": "Back",
+ "xyzAction": "Action",
+ "xyzStatus": "Status",
+ "xyzName": "Name",
+ "xyzMessage": "Message",
+ "xyzOR": "OR",
+ "xyzURL": "URL",
+ "xyzCaption": "Caption",
+ "xyzWidth": "Width",
+ "xyzHeight": "Height",
+ "xyzSMS Type": "SMS Type",
+ "xyzImage Type": "Image Type",
+ "xyzReplacement Tags": "Replacement Tags",
+ "xyzSMS Body": "SMS Body",
+ "xyzEmail Type": "Email Type",
+ "xyzSubject": "xyzSubject",
+ "xyzForgot_token": "Forgot_token",
+ "xyzAccess_token": "Access token",
+ "xyzEmail Body": "Email Body",
+ "xyzEdit Profile": "Edit Profile",
+ "xyzRefresh_token": "Refresh_token",
+ "xyzOther": "Other",
+ "xyzApi_key": "Api Key",
+ "xyzApi_secret": "Api Secret",
+ "xyzVerify": "Verify",
+ "xyzSign Up": "Sign Up",
+ "xyzRepeat Password": "Repeat Password",
+ "xyzRegister": "Register",
+ "xyzSign in with Google+": "Sign in with Google+",
+ "xyzSign in with Facebook": "Sign in with Facebook",
+ "xyzSign up New Account": "Sign up New Account",
+ "xyzForgot password?": "Forgot password?",
+ "xyzSign in": "Sign in",
+ "xyzReset Password": "Reset Password",
+ "xyzEmail address": "Email address",
+ "xyzForgot Password": "Forgot Password",
+ "xyzUpload a file": "Upload a file",
+ "xyzChoose Image": "Choose Image",
+ "xyzID": "ID",
+ "xyzPhoto": "Photo",
+ "xyzYes": "Yes",
+ "xyzNo": "No",
+ "xyzPersonal Information": "Personal Information",
+ "xyzUpload file size too big": "Upload file size too big",
+ "xyzUpload to S3 Failed": "xyzUpload to S3 Failed",
+ "xyzWrong email or password.": "Wrong email or password.",
+ "xyzUpload file missing": "xyzUpload file missing",
+ "xyzUpload file failed": "xyzUpload file failed",
+ "xyzSorry, your email is not a google email in our system.": "Sorry, your email is not a google email in our system.",
+ "xyzSorry, google cannot find your email.": "Sorry, google cannot find your email.",
+ "xyzSorry, facebook cannot find your email.": "Sorry, facebook cannot find your email.",
+ "xyzSorry, your email is not a facebook email in our system.": "Sorry, your email is not a facebook email in our system.",
+ "xyzThere was a problem creating your new account. Please try again.": "There was a problem creating your new account. Please try again.",
+ "xyzUser creation failed. Please try again.": "User creation failed. Please try again.",
+ "xyzYour Reset email was sent. Check your email.": "Your Reset email was sent. Check your email.",
+ "xyzEmail does not exist in our system.": "Email does not exist in our system.",
+ "xyzYour password was reset. Try to login now": "Your password was reset. Try to login now",
+ "xyzInvalid reset token to reset password.": "Invalid reset token to reset password.",
+ "xyzinvalid credentials": "invalid credentials",
+ "xyzUpload CSV File missing": "Upload CSV File missing",
+ "xyzNot CSV File": "Not CSV File",
+ "xyzGenerating SQL worked but insert error to the database": "Generating SQL worked but insert error to the database",
+ "xyzImport": "Import",
+ "xyzExport": "Export",
+ "xyzActive": "Active",
+ "xyzInactive": "Inactive",
+ "xyzAll": "All",
+ "xyzSuspend": "Suspend",
+ "xyzMember": "Member",
+ "xyzRemove": "Remove",
+ "xyzCreated At": "Created At",
+ "xyzUpdated At": "Updated At",
+ "xyzNot verified": "Not verified",
+ "xyzType": "Type",
+ "xyzis_required": "is required",
+ "xyzInvalid_email": "Invalid email",
+ "xyzshould_contain_at_least": "should contain at least",
+ "xyzcharacters": "characters",
+ "xyzshould_not_exceed": "should not exceed",
+ "xyzshould_be_unique": "should be unique",
+ "xyzvalue_cannot_be_less_than": "value cannot be less than",
+ "xyzvalue_cannot_be_greater_than": "value cannot be greater than",
+ "xyzSomething_went_wrong": "Something went wrong",
+ "xyzNew": "New",
+ "xyzcreated_successfully": "created successfully",
+ "xyzedited_successFully": "edited successfully",
+ "xyznot_found": "not found",
+ "xyzdeleted_successfully": "deleted successfully",
+ "xyzEmailIsRequired": "Email is required",
+ "xyzPasswordShouldBeAtLeastSixCharacters": "Password should be at least 6 characters long.",
+ "xyzInvalidEmailOrPassword": "Invalid email or password",
+ "xyzWrongEmailOrPassword": "Wrong email or password",
+ "xyzLogin": "Login",
+ "xyzForgot_password": "Forgot Password",
+ "xyzAccount_Already_Exists": "Account already exists",
+ "xyzLogin_with_Google": "Login with Google",
+ "xyzSignup_with_Google": "Login with Google",
+ "xyzLogin_with_Facebook": "Login with Facebook",
+ "xyzSignup_with_Facebook": "Login with Facebook",
+ "xyzPasswordIsRequired": "Password is required.",
+ "xyzPasswordsDoNotMatch": "Passwords do not match",
+ "xyzDontHaveAnAccount": "Don't have an account yet?",
+ "xyzAlreadyHaveAnAccount": "Already have an account?",
+ "xyzAccountDoesntExists": "Account doesn't exists.",
+ "xyzInvalid_token": "Invalid token",
+ "xyzBackToLogin": "Back to Login",
+ "xyzResetPassword": "Reset Password",
+ "xyzPasswordResetSuccessful": "Password reset successful",
+ "xyzlisted_successfully": "listed successfully",
+ "xyzFirstNameIsRequired": "First name is required",
+ "xyzLastNameIsRequired": "Last name is required",
+ "xyzVerificationCodeIsRequired": "Verification Code is re required.",
+ "xyzPasswordResetLinkIsSentToYourInbox": "A password reset link is sent to your inbox.",
+ "xyzReset": "Reset",
+ "xyzshould_be_equal_to": "should be equal to",
+ "xyzshould_be_greater_than_or_equal_to": "should be greater than or equal to",
+ "xyzshould_be_greater_than": "should be greater than",
+ "xyzshould_be_less_than_or_equal_to": "should be less than or equal to",
+ "xyzshould_be_less_than_to": "should be less than to",
+ "xyzshould_be_exist_in_the_list": "should be exist in the list",
+ "xyzshould_only_contain_alphabetic_characters": "should only contain alphabetic characters",
+ "xyzshould_only_contain_contains_letters_and_numbers": "should only contain contains letters and numbers",
+ "xyzshould_have_have_alpha-numeric_characters,_as_well_as_dashes_and_underscores": "should have have alpha-numeric characters, as well as dashes and underscores",
+ "xyzmust_be_numeric": "must be numeric",
+ "xyzmust_be_integer": "must be integer",
+ "xyzmust_be_decimal": "must be decimal",
+ "xyzmust_be_is_natural": "must be is natural",
+ "xyzmust_be_is_natural_no_zero": "must be is natural without zero",
+ "xyzmust_be_valid_url": "must be valid url",
+ "xyzmust_be_valid_ip": "must be valid ip",
+ "xyzmust_be_valid_base64_string": "must be valid base64 string",
+ "xyzshould_be_between": "should be between",
+ "xyzshould_be_digits_between": "should be digits between",
+ "xyzshould_be_date": "should be date",
+ "xyzshould_be_datetime": "should be datetime",
+ "xyzshould_be_contains": "should be contains",
+ "xyzshould_be": "should be",
+ "xyzshould_be_equals": "should be equals",
+ "xyzBulk_Delete": "Bulk Delete",
+ "xyzBulk_Edit": "Bulk Edit",
+ "xyzBulkDeleteSuccessful": "Bulk Delete Successful",
+ "xyzItemsWereDeleted": "item(s) were deleted.",
+ "xyzThankYouForContacting": "Thank your for contacting",
+ "xyzContact": "Contact",
+ "xyzMessageIsRequired": "Message is required",
+ "xyzFullNameIsRequired": "Full name is required",
+ "xyzFullName": "Full Name",
+ "xyzContactNotFound": "Contact not found",
+ "xyzContactDeletedSuccessfully": "Contact deleted successfully",
+ "xyzContactDetails": "Contact Details",
+ "xyzInvalidEmail": "Invalid email",
+ "xyzContactCreatedSuccessfully": "Contact created successfully",
+ "xyzRewriteURL": "Rewrite URL",
+ "xyzTwoFactorAuthentication": "Two factor authentication",
+ "xyzfor": "for",
+ "xyzis": "is",
+ "xyzcode": "code",
+ "xyzYourVerificationCodeIs": "Your verification code is",
+ "xyzAccountVerification": "Account Verification",
+ "xyzInvalidVerificationCode": "Invalid Verification Code",
+ "xyzNewVerificationCodeSent": "New verification code sent",
+ "xyzResendTheCode": "Resent the code",
+ "xyzVerificationCode": "Verification code",
+ "xyzUserID": "User ID",
+ "xyzOrganizationID": "Organization ID",
+ "xyzPermission": "Permission",
+ "xyzOrganizationName": "Organization name",
+ "xyzOrganizationStatus": "Organization status",
+ "xyzUserPermissions": "User permissions",
+ "xyzOrganizationPermissions": "Organization permissions",
+ "xyxRoute": "Route",
+ "xyzSave Data": "Save Data",
+ "xyzAllRightsReserved": "All Rights Reserved.",
+ "xyzInvalidEmailConfirmationCode": "Invalid email confirmation code",
+ "xyzImageId": "Image Id",
+ "xyzInvalidRefreshToken": "Invalid refresh token",
+ "xyzTokenIDIsRequired": "Token ID is required",
+ "xyzRefreshTokenIsRequired": "Refresh token is required",
+ "xyzGoogleTokenIDIsRequired": "Google token ID token is required",
+ "xyzGoogleAccessTokenIsRequired": "Google access token is required",
+ "xyzFacebookAccessTokenIsRequired": "Facebook access token is required",
+ "xyzAccountIsRegisteredUsingGoogle": "Account is registered using Google. Use that instead.",
+ "xyzAccountIsRegisteredUsingFacebook": "Account is registered using Facebook. Use that instead.",
+ "xyzAccountIsRegisteredUsingEmailAndPassword": "Account is registered using Facebook. Use that instead.",
+ "xyzEmailAssociatedWithFacebookCouldntBeFound": "Email associated with facebook couldn't be found",
+ "xyzConfirm_Password": "Confirm Password",
+ "xyzPage Title": "Page Title",
+ "xyzPage slug": "Page slug",
+ "xyzPage Link": "Page Link",
+ "xyzLayout": "Layout",
+ "xyzContent Template Path": "Content Template Path",
+ "xyzHeader Template Path": "Header Template Path",
+ "xyzFooter Template Path": "Footer Template Path",
+ "xyzPage Content": "Page Content",
+ "xyzMarketing Title": "Marketing Title",
+ "xyzMarketing Slug": "Marketing Slug",
+ "xyzExpire At": "Expire At",
+ "xyzPassword Protected": "Password Protected",
+ "xyzStripe Cards": "Stripe Cards",
+ "xyzStripe Checkout": "Stripe Checkout",
+ "xyzPayPal Subscriptions": "PayPal Subscriptions",
+ "xyzPayPal Plans": "PayPal Plans",
+ "xyzPayPal Services": "PayPal Services",
+ "xyzPayPal Products": "PayPal Products",
+ "xyzStripe Disputes": "Stripe Disputes",
+ "xyzStripe Invoices": "Stripe Invoices",
+ "xyzStripe Payments": "Stripe Payments",
+ "xyzStripe Coupons": "Stripe Coupons",
+ "xyzStripe Subscriptions": "Stripe Subscriptions",
+ "xyzStripe Plans": "Stripe Plans",
+ "xyzStripe Services": "Stripe Services",
+ "xyzStripe Products": "Stripe Products",
+ "xyzSchedule": "Schedule",
+ "xyzStripe Checkout Sessions": "Stripe Checkout Sessions",
+ "xyzService Stripe ID": "Service Stripe ID",
+ "xyzCategory": "Category",
+ "xyzCredential ID": "Credential ID",
+ "xyzPlan Interval": "Plan Interval",
+ "xyzBilling Info": "Billing Info",
+ "xyzPayPal ID": "PayPal ID",
+ "xyzPaypal ID": "Paypal ID",
+ "xyzPayment Plans": "Payment Plans",
+ "xyzPayment Services": "Payment Services",
+ "xyzPayer ID": "Payer ID",
+ "xyzPayment Product ID": "Payment Product ID",
+ "xyzPayment": "Payment",
+ "xyzService ID": "Service ID",
+ "xyzService": "Service",
+ "xyzPaypal Product ID": "Paypal Product ID",
+ "xyzPayPal Product ID": "PayPal Product ID",
+ "xyzCustomer Stripe ID": "Customer Stripe ID",
+ "xyzCustomer Paypal ID": "Customer Paypal ID",
+ "xyzStripe": "Stripe",
+ "xyzPayPal": "PayPal",
+ "xyzRegular": "Regular",
+ "xyzUser Details": "User Details",
+ "xyzLife Time Paid": "Life Time Paid",
+ "xyzLife Time Free": "Life Time Free",
+ "xyzTrial Paid": "Trial Paid",
+ "xyzTrial Free": "Trial Free",
+ "xyzInterval": "Interval",
+ "xyzCoupon Stripe ID": "Coupon Stripe ID",
+ "xyzProduct Name": "Product Name",
+ "xyzDescription": "Description",
+ "xyzStatement descriptor": "Statement descriptor",
+ "xyzUnit Label": "Unit Label",
+ "xyzShippable": "Shippable",
+ "xyzPricing": "Pricing",
+ "xyzAmount": "Amount",
+ "xyzDisplay Name": "Display Name",
+ "xyzincomplete": "incomplete",
+ "xyzincomplete_expired": "incomplete_expired",
+ "xyztrialing": "trialing",
+ "xyzpast_due": "past_due",
+ "xyzactive": "active",
+ "xyzcanceled": "canceled",
+ "xyzunpaid": "unpaid",
+ "xyzrefunded": "refunded",
+ "xyzday": "day",
+ "xyzweek": "week",
+ "xyzmonth": "month",
+ "xyzyear": "year",
+ "xyzStripe Customer ID": "Stripe Customer ID",
+ "xyzCurrent Period Start": "Current Period Start",
+ "xyzCurrent Period End": "Current Period End",
+ "xyzCurrent Period Start Date": "Current Period Start Date",
+ "xyzCurrent Period End Date": "Current Period End Date",
+ "xyzCancel At Period End": "Cancel At Period End",
+ "xyzPlan Object": "Plan Object",
+ "xyzOrder ID": "Order ID",
+ "xyzStripe ID": "Stripe ID",
+ "xyzCoupon ID": "Coupon ID",
+ "xyzSubscription Interval": "Subscription Interval",
+ "xyzInterval Count": "Interval Count",
+ "xyzTrial Period Days": "Trial Period Days",
+ "xyzTrial Start": "Trial Start",
+ "xyzTrial End": "Trial End",
+ "xyzCollection Method": "Collection Method",
+ "xyzUser ID": "User ID",
+ "xyzRole ID": "Role ID",
+ "xyzPlan ID": "Plan ID",
+ "xyzSettings": "Settings",
+ "xyzdetails": "details",
+ "xyzedited_successfully": "edited successfully",
+ "xyzname": "name",
+ "xyz": "",
+ "xyzbadge": "Badge",
+ "xyzbadge_id": "Badge ID",
+ "xyzuser": "User",
+ "xyzmessage": "Message",
+ "xyzstatus": "Status",
+ "xyztitle": "Title",
+ "xyzdate": "Date",
+ "xyztime": "Time",
+ "xyztimezone": "Timezone",
+ "xyzdashboard_code": "Dashboard Code",
+ "xyzlink": "link",
+ "xyzPost": "Post",
+ "xyzUserDoesNotExists": "User does not exists.",
+ "xyzSyncCode": "Sync Code",
+ "xyzFontColor": "Font Color",
+ "xyzTimezone": "Timezone",
+ "xyzPacific": "Pacific",
+ "xyzEastern": "Eastern",
+ "xyzTimeFormat": "Time Format",
+ "xyzAM/PM": "AM/PM",
+ "xyz24Hours": "24 hours",
+ "xyzClockFormat": "Clock Format",
+ "xyzDigital": "Digital",
+ "xyzAnalog": "Analog",
+ "xyzDateFormat": "Date Format",
+ "xyzStandard": "Standard",
+ "xyzLocale": "Locale",
+ "xyzLocation": "Location",
+ "xyzCodeInvalidOrExpired": "Code invalid or already expired",
+ "xyzLatitude": "Latitude",
+ "xyzLongitude": "Longitude",
+ "xyzStartDate": "Start Date",
+ "xyzEndDate": "End Date",
+ "xyzEventId": "Event Id"
+}
diff --git a/day11/core/utils.js b/day11/core/utils.js
new file mode 100755
index 0000000..77e58be
--- /dev/null
+++ b/day11/core/utils.js
@@ -0,0 +1,11 @@
+exports.validateEmail = (email) => {
+ const re =
+ /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
+
+ const reStartAndEnd = /^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}/g
+
+ if (re.test(email) && reStartAndEnd.test(email)) {
+ return true
+ }
+ return false
+}
diff --git a/day11/directives/VerifyUser.js b/day11/directives/VerifyUser.js
new file mode 100755
index 0000000..2c4afc2
--- /dev/null
+++ b/day11/directives/VerifyUser.js
@@ -0,0 +1,48 @@
+const { SchemaDirectiveVisitor, ApolloError } = require('apollo-server-express');
+const { defaultFieldResolver } = require('graphql');
+
+const { errorCodes, errors } = require('../core/strings');
+const { GraphqlError, AuthenticationError } = require('../services/ErrorService');
+
+class VerifyUser extends SchemaDirectiveVisitor {
+ visitFieldDefinition(field) {
+ const resolver = field.resolve || defaultFieldResolver;
+
+ let User;
+ let Credential;
+
+ field.resolve = async (root, args, context, info) => {
+ const { user, credentialId, db, role } = context;
+ Credential = await db.credential.getByFields({
+ status: 1,
+ id: credentialId,
+ role_id: role.roleId,
+ });
+ if (!Credential) {
+ throw new ApolloError(errors.ACCOUNT_DOES_NOT_EXISTS, errorCodes.account.ACCOUNT_DOES_NOT_EXISTS);
+ }
+ User = await db.user.getByFields({
+ status: 1,
+ id: user.id,
+ });
+ if (!User) {
+ throw new ApolloError(errors.ACCOUNT_DOES_NOT_EXISTS, errorCodes.account.ACCOUNT_DOES_NOT_EXISTS);
+ }
+
+ return resolver(
+ root,
+ args,
+ {
+ ...context,
+ directives: {
+ ...(context.directives || {}),
+ ...(User && Credential ? { verifyUser: { user: JSON.parse(JSON.stringify(User)), credential: JSON.parse(JSON.stringify(Credential)) } } : {}),
+ },
+ },
+ info,
+ );
+ };
+ }
+}
+
+module.exports = VerifyUser;
diff --git a/day11/directives/index.js b/day11/directives/index.js
new file mode 100755
index 0000000..e3d62ed
--- /dev/null
+++ b/day11/directives/index.js
@@ -0,0 +1,5 @@
+const VerifyUser = require('./VerifyUser');
+
+module.exports = {
+ verifyUser: VerifyUser,
+};
diff --git a/day11/models/activity_log.js b/day11/models/activity_log.js
new file mode 100755
index 0000000..f3aa596
--- /dev/null
+++ b/day11/models/activity_log.js
@@ -0,0 +1,143 @@
+/*Powered By: Manaknightdigital Inc. https://manaknightdigital.com/ Year: 2021*/
+/**
+ * activity_log Model
+ * @copyright 2021 Manaknightdigital Inc.
+ * @link https://manaknightdigital.com
+ * @license Proprietary Software licensing
+ * @author Ryan Wong
+ *
+ */
+
+const moment = require("moment");
+;
+const { Op } = require("sequelize");
+const { intersection } = require('lodash');
+const coreModel = require('./../core/models');
+
+module.exports = (sequelize, DataTypes) => {
+ const Activity_log = sequelize.define(
+ "activity_log",
+ {
+
+ id: {
+ type: DataTypes.INTEGER,
+ primaryKey: true,
+ autoIncrement: true,
+ },
+ name: DataTypes.STRING,
+ action: { type: DataTypes.STRING, validate: {} },
+ data: DataTypes.STRING,
+ status: DataTypes.INTEGER,
+ created_at: DataTypes.DATEONLY,
+ updated_at: DataTypes.DATE,
+ },
+ {
+ timestamps: true,
+ freezeTableName: true,
+ tableName: "activity_log",
+ },
+ {
+ underscoredAll: false,
+ underscored: false,
+ }
+ );
+
+ coreModel.call(this, Activity_log);
+
+ Activity_log._preCreateProcessing = function (data) {
+
+ return data;
+ };
+ Activity_log._postCreateProcessing = function (data) {
+
+ return data;
+ };
+ Activity_log._customCountingConditions = function (data) {
+
+ return data;
+ };
+
+ Activity_log._filterAllowKeys = function (data) {
+ let cleanData = {};
+ let allowedFields = Activity_log.allowFields();
+ allowedFields.push(Activity_log._primaryKey());
+
+ for (const key in data) {
+ if (allowedFields.includes(key)) {
+ cleanData[key] = data[key];
+ }
+ }
+ return cleanData;
+ };
+
+ Activity_log.timeDefaultMapping = function () {
+ let results = [];
+ for (let i = 0; i < 24; i++) {
+ for (let j = 0; j < 60; j++) {
+ let hour = i < 10 ? "0".i : i;
+ let min = j < 10 ? "0".j : j;
+ results[i * 60 + j] = `${hour}:${min}`;
+ }
+ }
+ return results;
+ };
+
+ Activity_log.associate = function (models) { };
+
+
+ Activity_log.status_mapping = function (status) {
+ const mapping = { "0": "Inactive", "1": "Active", "2": "Suspend" }
+
+ if (arguments.length === 0) return mapping;
+ else return mapping[status];
+ };
+
+
+ Activity_log.allowFields = function () {
+ return ['id', 'name', 'action', 'data', 'status',];
+ };
+
+ Activity_log.labels = function () {
+ return ['ID', 'Name', 'Action', 'Data', 'Status',];
+ };
+
+ Activity_log.validationRules = function () {
+ return [
+ ['id', 'ID', ''],
+ ['name', 'Name', ''],
+ ['action', 'Action', 'required'],
+ ['data', 'Data', 'required'],
+ ['status', 'Status', 'required|integer'],
+ ];
+ };
+
+ Activity_log.validationEditRules = function () {
+ return [
+ ['id', 'ID', ''],
+ ['name', 'Name', ''],
+ ['action', 'Action', 'required'],
+ ['data', 'Data', 'required'],
+ ['status', 'Status', 'required|integer'],
+ ];
+ };
+
+
+
+
+
+
+ // ex
+ Activity_log.intersection = function (fields) {
+ if (fields) {
+ return intersection(
+ [
+ 'id', 'name', 'action', 'data', 'status', 'created_at', 'updated_at',
+ ],
+ Object.keys(fields),
+ );
+ } else return [];
+ };
+
+
+ return Activity_log;
+};
diff --git a/day11/models/admin_operation.js b/day11/models/admin_operation.js
new file mode 100755
index 0000000..0319b62
--- /dev/null
+++ b/day11/models/admin_operation.js
@@ -0,0 +1,156 @@
+/*Powered By: Manaknightdigital Inc. https://manaknightdigital.com/ Year: 2021*/
+/**
+ * admin_operation Model
+ * @copyright 2021 Manaknightdigital Inc.
+ * @link https://manaknightdigital.com
+ * @license Proprietary Software licensing
+ * @author Ryan Wong
+ *
+ */
+
+const moment = require("moment");
+;
+const { Op } = require("sequelize");
+const { intersection } = require('lodash');
+const coreModel = require('./../core/models');
+
+module.exports = (sequelize, DataTypes) => {
+ const Admin_operation = sequelize.define(
+ "admin_operation",
+ {
+ user_id: DataTypes.INTEGER,
+ action: DataTypes.STRING,
+ detail: DataTypes.TEXT,
+ last_ip: { type: DataTypes.STRING, validate: {} },
+ user_agent: { type: DataTypes.STRING, validate: {} },
+ status: DataTypes.INTEGER,
+ created_at: DataTypes.DATEONLY,
+ updated_at: DataTypes.DATE,
+ },
+ {
+ timestamps: true,
+ freezeTableName: true,
+ tableName: "admin_operation",
+ },
+ {
+ underscoredAll: false,
+ underscored: false,
+ }
+ );
+
+ coreModel.call(this, Admin_operation);
+
+ Admin_operation._preCreateProcessing = function (data) {
+ data.status = 1;
+ return data;
+ };
+ Admin_operation._postCreateProcessing = function (data) {
+
+ return data;
+ };
+ Admin_operation._customCountingConditions = function (data) {
+
+ return data;
+ };
+
+ Admin_operation._filterAllowKeys = function (data) {
+ let cleanData = {};
+ let allowedFields = Admin_operation.allowFields();
+ allowedFields.push(Admin_operation._primaryKey());
+
+ for (const key in data) {
+ if (allowedFields.includes(key)) {
+ cleanData[key] = data[key];
+ }
+ }
+ return cleanData;
+ };
+
+ Admin_operation.timeDefaultMapping = function () {
+ let results = [];
+ for (let i = 0; i < 24; i++) {
+ for (let j = 0; j < 60; j++) {
+ let hour = i < 10 ? "0".i : i;
+ let min = j < 10 ? "0".j : j;
+ results[i * 60 + j] = `${hour}:${min}`;
+ }
+ }
+ return results;
+ };
+
+ Admin_operation.associate = function (models) {
+ Admin_operation.belongsTo(models.user, {
+ foreignKey: "user_id",
+ as: "user",
+ constraints: false,
+ })
+ };
+
+
+ Admin_operation.status_mapping = function (status) {
+ const mapping = { "0": "Inactive", "1": "Active" }
+
+ if (arguments.length === 0) return mapping;
+ else return mapping[status];
+ };
+
+
+ Admin_operation.allowFields = function () {
+ return ['user_id', 'action', 'detail', 'last_ip', 'user_agent', 'status',];
+ };
+
+ Admin_operation.labels = function () {
+ return ['User', 'Action', 'Detail', 'Last IP', 'User Agent', 'Status',];
+ };
+
+ Admin_operation.validationRules = function () {
+ return [
+ ['user_id', 'User', 'required|integer'],
+ ['action', 'Action', 'required|max[50]'],
+ ['detail', 'Detail', 'required'],
+ ['last_ip', 'Last IP', 'required'],
+ ['user_agent', 'User Agent', 'required'],
+ ['status', 'Status', 'required|integer'],
+ ];
+ };
+
+ Admin_operation.validationEditRules = function () {
+ return [
+ ['user_id', 'User', ''],
+ ['action', 'Action', ''],
+ ['detail', 'Detail', ''],
+ ['last_ip', 'Last IP', ''],
+ ['user_agent', 'User Agent', ''],
+ ['status', 'Status', 'required|integer'],
+ ];
+ };
+
+
+
+ Admin_operation.get_user_paginated = function (db, ...rest) {
+ return Admin_operation.getPaginated(...rest, [{
+ model: db.user,
+ as: "user"
+ }])
+ }
+
+
+ Admin_operation.get_admin_operation_user = (id, db) => {
+ return Admin_operation.findByPk(id, { include: [{ model: db.user, as: "user" }] });
+ };
+
+ // ex
+ Admin_operation.intersection = function (fields) {
+ if (fields) {
+ return intersection(
+ [
+ 'user_id', 'action', 'detail', 'last_ip', 'user_agent', 'status', 'created_at', 'updated_at',
+ ],
+ Object.keys(fields),
+ );
+ } else return [];
+ };
+
+
+ return Admin_operation;
+};
diff --git a/day11/models/calendar.js b/day11/models/calendar.js
new file mode 100755
index 0000000..8505fb5
--- /dev/null
+++ b/day11/models/calendar.js
@@ -0,0 +1,164 @@
+/*Powered By: Manaknightdigital Inc. https://manaknightdigital.com/ Year: 2021*/
+/**
+ * calendar Model
+ * @copyright 2021 Manaknightdigital Inc.
+ * @link https://manaknightdigital.com
+ * @license Proprietary Software licensing
+ * @author Ryan Wong
+ *
+ */
+
+const moment = require("moment");
+;
+const { Op } = require("sequelize");
+const { intersection } = require('lodash');
+const coreModel = require('./../core/models');
+
+module.exports = (sequelize, DataTypes) => {
+ const Calendar = sequelize.define(
+ "calendar",
+ {
+
+ id: {
+ type: DataTypes.INTEGER,
+ primaryKey: true,
+ autoIncrement: true,
+ },
+ event_id: {
+ type: DataTypes.STRING,
+ unique: true
+ },
+ title: DataTypes.STRING,
+ start_date: DataTypes.DATE,
+ end_date: DataTypes.DATE,
+ status: DataTypes.INTEGER,
+ created_at: DataTypes.DATEONLY,
+ updated_at: DataTypes.DATE,
+ },
+ {
+ timestamps: true,
+ freezeTableName: true,
+ tableName: "calendar",
+ },
+ {
+ underscoredAll: false,
+ underscored: false,
+ }
+ );
+
+ coreModel.call(this, Calendar);
+
+ Calendar._preCreateProcessing = function (data) {
+ data.status = 1;
+ return data;
+ };
+ Calendar._postCreateProcessing = function (data) {
+
+ return data;
+ };
+ Calendar._customCountingConditions = function (data) {
+
+ return data;
+ };
+
+ Calendar._filterAllowKeys = function (data) {
+ let cleanData = {};
+ let allowedFields = Calendar.allowFields();
+ allowedFields.push(Calendar._primaryKey());
+
+ for (const key in data) {
+ if (allowedFields.includes(key)) {
+ cleanData[key] = data[key];
+ }
+ }
+ return cleanData;
+ };
+
+ Calendar.timeDefaultMapping = function () {
+ let results = [];
+ for (let i = 0; i < 24; i++) {
+ for (let j = 0; j < 60; j++) {
+ let hour = i < 10 ? "0".i : i;
+ let min = j < 10 ? "0".j : j;
+ results[i * 60 + j] = `${hour}:${min}`;
+ }
+ }
+ return results;
+ };
+
+ Calendar.associate = function (models) {
+ Calendar.belongsTo(models.user, {
+ foreignKey: "user_id",
+ as: "user",
+ constraints: false,
+ })
+ };
+
+
+ Calendar.status_mapping = function (status) {
+ const mapping = { "0": "Inactive", "1": "Active" }
+
+ if (arguments.length === 0) return mapping;
+ else return mapping[status];
+ };
+
+
+ Calendar.allowFields = function () {
+ return ["user_id", 'id', 'event_id', 'title', 'start_date', 'end_date', 'status',];
+ };
+
+ Calendar.labels = function () {
+ return ['ID', 'Event Id', 'Title', 'Start Date', 'End Date', 'Status',];
+ };
+
+ Calendar.validationRules = function () {
+ return [
+ ['id', 'ID', ''],
+ ['event_id', 'Event Id', ''],
+ ['title', 'Title', 'required'],
+ ['start_date', 'Start Date', 'required'],
+ ['end_date', 'End Date', 'required'],
+ ['status', 'Status', 'required|integer'],
+ ];
+ };
+
+ Calendar.validationEditRules = function () {
+ return [
+ ['id', 'ID', ''],
+ ['event_id', 'Event Id', ''],
+ ['title', 'Title', 'required'],
+ ['start_date', 'Start Date', 'required'],
+ ['end_date', 'End Date', 'required'],
+ ['status', 'Status', 'required|integer'],
+ ];
+ };
+
+
+
+ Calendar.get_user_paginated = function (db, ...rest) {
+ return Calendar.getPaginated(...rest, [{
+ model: db.user,
+ as: "user"
+ }])
+ }
+
+
+ Calendar.get_calendar_user = (id, db) => {
+ return Calendar.findByPk(id, { include: [{ model: db.user, as: "user" }] });
+ };
+
+ // ex
+ Calendar.intersection = function (fields) {
+ if (fields) {
+ return intersection(
+ [
+ 'id', 'event_id', 'title', 'start_date', 'end_date', 'status', 'created_at', 'updated_at',
+ ],
+ Object.keys(fields),
+ );
+ } else return [];
+ };
+
+
+ return Calendar;
+};
diff --git a/day11/models/code.js b/day11/models/code.js
new file mode 100755
index 0000000..2f6ecd7
--- /dev/null
+++ b/day11/models/code.js
@@ -0,0 +1,152 @@
+/*Powered By: Manaknightdigital Inc. https://manaknightdigital.com/ Year: 2021*/
+/**
+ * code Model
+ * @copyright 2021 Manaknightdigital Inc.
+ * @link https://manaknightdigital.com
+ * @license Proprietary Software licensing
+ * @author Ryan Wong
+ *
+ */
+
+const moment = require('moment')
+
+const { Op } = require('sequelize')
+const { intersection } = require('lodash')
+const coreModel = require('./../core/models')
+
+module.exports = (sequelize, DataTypes) => {
+ const Code = sequelize.define(
+ 'code',
+ {
+ id: {
+ type: DataTypes.INTEGER,
+ primaryKey: true,
+ autoIncrement: true,
+ },
+ code: { type: DataTypes.STRING, unique: true },
+ is_used: DataTypes.INTEGER,
+ status: DataTypes.INTEGER,
+ created_at: DataTypes.DATEONLY,
+ updated_at: DataTypes.DATE,
+ },
+ {
+ timestamps: true,
+ freezeTableName: true,
+ tableName: 'code',
+ },
+ {
+ underscoredAll: false,
+ underscored: false,
+ }
+ )
+
+ coreModel.call(this, Code)
+
+ Code._preCreateProcessing = function (data) {
+ return data
+ }
+ Code._postCreateProcessing = function (data) {
+ return data
+ }
+ Code._customCountingConditions = function (data) {
+ return data
+ }
+
+ Code._filterAllowKeys = function (data) {
+ let cleanData = {}
+ let allowedFields = Code.allowFields()
+ allowedFields.push(Code._primaryKey())
+
+ for (const key in data) {
+ if (allowedFields.includes(key)) {
+ cleanData[key] = data[key]
+ }
+ }
+ return cleanData
+ }
+
+ Code.timeDefaultMapping = function () {
+ let results = []
+ for (let i = 0; i < 24; i++) {
+ for (let j = 0; j < 60; j++) {
+ let hour = i < 10 ? '0'.i : i
+ let min = j < 10 ? '0'.j : j
+ results[i * 60 + j] = `${hour}:${min}`
+ }
+ }
+ return results
+ }
+
+ Code.associate = function (models) {
+ Code.belongsTo(models.user, {
+ foreignKey: 'user_id',
+ as: 'user',
+ constraints: false,
+ })
+ }
+
+ Code.is_used_mapping = function (is_used) {
+ const mapping = { 0: 'No', 1: 'Yes' }
+
+ if (arguments.length === 0) return mapping
+ else return mapping[is_used]
+ }
+
+ Code.status_mapping = function (status) {
+ const mapping = { 0: 'Inactive', 1: 'Active', 2: 'Suspend' }
+
+ if (arguments.length === 0) return mapping
+ else return mapping[status]
+ }
+
+ Code.allowFields = function () {
+ return ['user_id', 'id', 'code', 'is_used', 'status']
+ }
+
+ Code.labels = function () {
+ return ['ID', 'Code', 'Is Used', 'Status']
+ }
+
+ Code.validationRules = function () {
+ return [
+ ['id', 'ID', ''],
+ ['code', 'Code', 'required'],
+ ['is_used', 'Is Used', ''],
+ ['status', 'Status', 'required|integer'],
+ ]
+ }
+
+ Code.validationEditRules = function () {
+ return [
+ ['id', 'ID', ''],
+ ['code', 'Code', 'required'],
+ ['is_used', 'Is Used', ''],
+ ['status', 'Status', 'required|integer'],
+ ]
+ }
+
+ Code.get_user_paginated = function (db, ...rest) {
+ return Code.getPaginated(...rest, [
+ {
+ model: db.user,
+ as: 'user',
+ },
+ ])
+ }
+
+ Code.get_code_user = (id, db) => {
+ return Code.findByPk(id, { include: [{ model: db.user, as: 'user' }] })
+ }
+
+ // ex
+ Code.intersection = function (fields) {
+ if (fields) {
+ return intersection(
+ ['id', 'code', 'is_used', 'status', 'created_at', 'updated_at'],
+ Object.keys(fields)
+ )
+ } else return []
+ }
+
+ return Code
+}
diff --git a/day11/models/credential.js b/day11/models/credential.js
new file mode 100755
index 0000000..508d738
--- /dev/null
+++ b/day11/models/credential.js
@@ -0,0 +1,198 @@
+/*Powered By: Manaknightdigital Inc. https://manaknightdigital.com/ Year: 2021*/
+/**
+ * credential Model
+ * @copyright 2021 Manaknightdigital Inc.
+ * @link https://manaknightdigital.com
+ * @license Proprietary Software licensing
+ * @author Ryan Wong
+ *
+ */
+
+const moment = require("moment");
+;
+const { Op } = require("sequelize");
+const { intersection } = require('lodash');
+const coreModel = require('./../core/models');
+
+module.exports = (sequelize, DataTypes) => {
+ const Credential = sequelize.define(
+ "credential",
+ {
+
+ id: {
+ type: DataTypes.INTEGER,
+ primaryKey: true,
+ autoIncrement: true,
+ },
+ email: {
+ type: DataTypes.STRING,
+ unique: true
+ },
+ password: DataTypes.STRING,
+ type: DataTypes.STRING,
+ verify: DataTypes.INTEGER,
+ role_id: DataTypes.INTEGER,
+ status: DataTypes.INTEGER,
+ two_factor_authentication: DataTypes.INTEGER,
+ force_password_change: DataTypes.BOOLEAN,
+ created_at: DataTypes.DATEONLY,
+ updated_at: DataTypes.DATE,
+ },
+ {
+ timestamps: true,
+ freezeTableName: true,
+ tableName: "credential",
+ },
+ {
+ underscoredAll: false,
+ underscored: false,
+ }
+ );
+
+ coreModel.call(this, Credential);
+
+ Credential._preCreateProcessing = function (data) {
+ data.status = 1;
+ data.two_factor_authentication = 0;
+ return data;
+ };
+ Credential._postCreateProcessing = function (data) {
+
+ return data;
+ };
+ Credential._customCountingConditions = function (data) {
+
+ return data;
+ };
+
+ Credential._filterAllowKeys = function (data) {
+ let cleanData = {};
+ let allowedFields = Credential.allowFields();
+ allowedFields.push(Credential._primaryKey());
+
+ for (const key in data) {
+ if (allowedFields.includes(key)) {
+ cleanData[key] = data[key];
+ }
+ }
+ return cleanData;
+ };
+
+ Credential.timeDefaultMapping = function () {
+ let results = [];
+ for (let i = 0; i < 24; i++) {
+ for (let j = 0; j < 60; j++) {
+ let hour = i < 10 ? "0".i : i;
+ let min = j < 10 ? "0".j : j;
+ results[i * 60 + j] = `${hour}:${min}`;
+ }
+ }
+ return results;
+ };
+
+ Credential.associate = function (models) {
+ Credential.belongsTo(models.user, {
+ foreignKey: "user_id",
+ as: "user",
+ constraints: false,
+ })
+ };
+
+
+ Credential.verify_mapping = function (verify) {
+ const mapping = { "0": "Not verified", "1": "Verified" }
+
+ if (arguments.length === 0) return mapping;
+ else return mapping[verify];
+ };
+
+
+ Credential.status_mapping = function (status) {
+ const mapping = { "0": "Inactive", "1": "Active", "2": "Suspend" }
+
+ if (arguments.length === 0) return mapping;
+ else return mapping[status];
+ };
+
+
+ Credential.role_id_mapping = function (role_id) {
+ const mapping = { "1": "Member", "2": "Admin" }
+
+ if (arguments.length === 0) return mapping;
+ else return mapping[role_id];
+ };
+
+
+ Credential.two_factor_authentication_mapping = function (two_factor_authentication) {
+ const mapping = { "0": "No", "1": "Yes" }
+
+ if (arguments.length === 0) return mapping;
+ else return mapping[two_factor_authentication];
+ };
+
+
+ Credential.allowFields = function () {
+ return ["user_id", 'id', 'email', 'password', 'type', 'verify', 'role_id', 'status', 'two_factor_authentication', 'force_password_change',];
+ };
+
+ Credential.labels = function () {
+ return ['ID', 'Email', 'Password', 'Type', 'Verified', 'Role', 'Status', 'Two factor authentication', '',];
+ };
+
+ Credential.validationRules = function () {
+ return [
+ ['id', 'ID', ''],
+ ['email', 'Email', 'required|valid_email'],
+ ['password', 'Password', 'required'],
+ ['type', 'Type', ''],
+ ['verify', 'Verified', ''],
+ ['role_id', 'Role', ''],
+ ['status', 'Status', ''],
+ ['two_factor_authentication', 'Two factor authentication', ''],
+ ['force_password_change', '', ''],
+ ];
+ };
+
+ Credential.validationEditRules = function () {
+ return [
+ ['id', 'ID', ''],
+ ['email', 'Email', 'required|valid_email'],
+ ['password', 'Password', ''],
+ ['type', 'Type', ''],
+ ['verify', 'Verified', ''],
+ ['role_id', 'Role', ''],
+ ['status', 'Status', 'required|in_list[0,1,2]'],
+ ['two_factor_authentication', 'Two factor authentication', ''],
+ ['force_password_change', '', ''],
+ ];
+ };
+
+
+
+ Credential.get_user_paginated = function (db, ...rest) {
+ return Credential.getPaginated(...rest, [{
+ model: db.user,
+ as: "user"
+ }])
+ }
+
+
+ Credential.get_credential_user = (id, db) => {
+ return Credential.findByPk(id, { include: [{ model: db.user, as: "user" }] });
+ };
+
+ // ex
+ Credential.intersection = function (fields) {
+ if (fields) {
+ return intersection(
+ [
+ 'id', 'email', 'password', 'type', 'verify', 'role_id', 'status', 'two_factor_authentication', 'force_password_change', 'created_at', 'updated_at',
+ ],
+ Object.keys(fields),
+ );
+ } else return [];
+ };
+
+
+ return Credential;
+};
diff --git a/day11/models/email.js b/day11/models/email.js
new file mode 100755
index 0000000..360289f
--- /dev/null
+++ b/day11/models/email.js
@@ -0,0 +1,149 @@
+/*Powered By: Manaknightdigital Inc. https://manaknightdigital.com/ Year: 2021*/
+/**
+ * email Model
+ * @copyright 2021 Manaknightdigital Inc.
+ * @link https://manaknightdigital.com
+ * @license Proprietary Software licensing
+ * @author Ryan Wong
+ *
+ */
+
+const moment = require("moment");
+;
+const { Op } = require("sequelize");
+const { intersection } = require('lodash');
+const coreModel = require('./../core/models');
+
+module.exports = (sequelize, DataTypes) => {
+ const Email = sequelize.define(
+ "email",
+ {
+ slug: {
+ type: DataTypes.STRING,
+ unique: true
+ },
+ subject: DataTypes.TEXT,
+ tag: DataTypes.TEXT,
+ html: DataTypes.TEXT,
+ status: DataTypes.INTEGER,
+ created_at: DataTypes.DATEONLY,
+ updated_at: DataTypes.DATE,
+ },
+ {
+ timestamps: true,
+ freezeTableName: true,
+ tableName: "email",
+ },
+ {
+ underscoredAll: false,
+ underscored: false,
+ }
+ );
+
+ coreModel.call(this, Email);
+
+ Email._preCreateProcessing = function (data) {
+ data.status = 1;
+ return data;
+ };
+ Email._postCreateProcessing = function (data) {
+
+ return data;
+ };
+ Email._customCountingConditions = function (data) {
+
+ return data;
+ };
+
+ Email._filterAllowKeys = function (data) {
+ let cleanData = {};
+ let allowedFields = Email.allowFields();
+ allowedFields.push(Email._primaryKey());
+
+ for (const key in data) {
+ if (allowedFields.includes(key)) {
+ cleanData[key] = data[key];
+ }
+ }
+ return cleanData;
+ };
+
+ Email.timeDefaultMapping = function () {
+ let results = [];
+ for (let i = 0; i < 24; i++) {
+ for (let j = 0; j < 60; j++) {
+ let hour = i < 10 ? "0".i : i;
+ let min = j < 10 ? "0".j : j;
+ results[i * 60 + j] = `${hour}:${min}`;
+ }
+ }
+ return results;
+ };
+
+ Email.associate = function (models) { };
+
+
+ Email.status_mapping = function (status) {
+ const mapping = { "0": "Inactive", "1": "Active" }
+
+ if (arguments.length === 0) return mapping;
+ else return mapping[status];
+ };
+
+
+ Email.type_mapping = function (type) {
+ const mapping = { "0": "Forgot_token", "1": "Access token", "2": "Refresh_token", "3": "Other", "4": "Api Key", "5": "Api Secret", "6": "Verify" }
+
+ if (arguments.length === 0) return mapping;
+ else return mapping[type];
+ };
+
+
+ Email.allowFields = function () {
+ return ['slug', 'subject', 'tag', 'html', 'status',];
+ };
+
+ Email.labels = function () {
+ return ['Email Type', 'Subject', 'Replacement Tags', 'Email Body', 'Status',];
+ };
+
+ Email.validationRules = function () {
+ return [
+ ['slug', 'Email Type', 'required|is_unique[email.slug]'],
+ ['subject', 'Subject', 'required'],
+ ['tag', 'Replacement Tags', 'required'],
+ ['html', 'Email Body', 'required'],
+ ['status', 'Status', 'required|integer'],
+ ];
+ };
+
+ Email.validationEditRules = function () {
+ return [
+ ['slug', 'Email Type', ''],
+ ['subject', 'Subject', 'required'],
+ ['tag', 'Replacement Tags', ''],
+ ['html', 'Email Body', 'required'],
+ ['status', 'Status', 'required|integer'],
+ ];
+ };
+
+
+
+
+
+
+ // ex
+ Email.intersection = function (fields) {
+ if (fields) {
+ return intersection(
+ [
+ 'slug', 'subject', 'tag', 'html', 'status', 'created_at', 'updated_at',
+ ],
+ Object.keys(fields),
+ );
+ } else return [];
+ };
+
+
+ return Email;
+};
diff --git a/day11/models/image.js b/day11/models/image.js
new file mode 100755
index 0000000..3d3b622
--- /dev/null
+++ b/day11/models/image.js
@@ -0,0 +1,173 @@
+/*Powered By: Manaknightdigital Inc. https://manaknightdigital.com/ Year: 2021*/
+/**
+ * image Model
+ * @copyright 2021 Manaknightdigital Inc.
+ * @link https://manaknightdigital.com
+ * @license Proprietary Software licensing
+ * @author Ryan Wong
+ *
+ */
+
+const moment = require("moment");
+;
+const { Op } = require("sequelize");
+const { intersection } = require('lodash');
+const coreModel = require('./../core/models');
+
+module.exports = (sequelize, DataTypes) => {
+ const Image = sequelize.define(
+ "image",
+ {
+
+ id: {
+ type: DataTypes.INTEGER,
+ primaryKey: true,
+ autoIncrement: true,
+ },
+ url: DataTypes.TEXT,
+ caption: DataTypes.TEXT,
+ width: DataTypes.INTEGER,
+ height: DataTypes.INTEGER,
+ type: DataTypes.INTEGER,
+ status: DataTypes.INTEGER,
+ created_at: DataTypes.DATEONLY,
+ updated_at: DataTypes.DATE,
+ },
+ {
+ timestamps: true,
+ freezeTableName: true,
+ tableName: "image",
+ },
+ {
+ underscoredAll: false,
+ underscored: false,
+ }
+ );
+
+ coreModel.call(this, Image);
+
+ Image._preCreateProcessing = function (data) {
+ data.status = 1;
+ data.refer = 1;
+ return data;
+ };
+ Image._postCreateProcessing = function (data) {
+
+ return data;
+ };
+ Image._customCountingConditions = function (data) {
+
+ return data;
+ };
+
+ Image._filterAllowKeys = function (data) {
+ let cleanData = {};
+ let allowedFields = Image.allowFields();
+ allowedFields.push(Image._primaryKey());
+
+ for (const key in data) {
+ if (allowedFields.includes(key)) {
+ cleanData[key] = data[key];
+ }
+ }
+ return cleanData;
+ };
+
+ Image.timeDefaultMapping = function () {
+ let results = [];
+ for (let i = 0; i < 24; i++) {
+ for (let j = 0; j < 60; j++) {
+ let hour = i < 10 ? "0".i : i;
+ let min = j < 10 ? "0".j : j;
+ results[i * 60 + j] = `${hour}:${min}`;
+ }
+ }
+ return results;
+ };
+
+ Image.associate = function (models) {
+ Image.belongsTo(models.user, {
+ foreignKey: "user_id",
+ as: "user",
+ constraints: false,
+ })
+ };
+
+
+ Image.type_mapping = function (type) {
+ const mapping = { "0": "Server Hosted", "1": "External Link", "2": "S3", "3": "Cloudinary", "4": "File", "5": "External File", "6": "Custom" }
+
+ if (arguments.length === 0) return mapping;
+ else return mapping[type];
+ };
+
+
+ Image.status_mapping = function (status) {
+ const mapping = { "0": "Inactive", "1": "Active" }
+
+ if (arguments.length === 0) return mapping;
+ else return mapping[status];
+ };
+
+
+ Image.allowFields = function () {
+ return ["user_id", 'id', 'url', 'caption', 'width', 'height', 'type', 'status',];
+ };
+
+ Image.labels = function () {
+ return ['ID', 'URL', 'Caption', 'Width', 'Height', 'Image Type', 'Status',];
+ };
+
+ Image.validationRules = function () {
+ return [
+ ['id', 'ID', ''],
+ ['url', 'URL', 'required'],
+ ['caption', 'Caption', ''],
+ ['width', 'Width', ''],
+ ['height', 'Height', ''],
+ ['type', 'Image Type', ''],
+ ['status', 'Status', 'required|integer'],
+ ];
+ };
+
+ Image.validationEditRules = function () {
+ return [
+ ['id', 'ID', ''],
+ ['url', 'URL', 'required'],
+ ['caption', 'Caption', ''],
+ ['width', 'Width', ''],
+ ['height', 'Height', ''],
+ ['type', 'Image Type', ''],
+ ['status', 'Status', 'required|integer'],
+ ];
+ };
+
+
+
+ Image.get_user_paginated = function (db, ...rest) {
+ return Image.getPaginated(...rest, [{
+ model: db.user,
+ as: "user"
+ }])
+ }
+
+
+ Image.get_image_user = (id, db) => {
+ return Image.findByPk(id, { include: [{ model: db.user, as: "user" }] });
+ };
+
+ // ex
+ Image.intersection = function (fields) {
+ if (fields) {
+ return intersection(
+ [
+ 'id', 'url', 'caption', 'width', 'height', 'type', 'status', 'created_at', 'updated_at',
+ ],
+ Object.keys(fields),
+ );
+ } else return [];
+ };
+
+
+ return Image;
+};
diff --git a/day11/models/index.js b/day11/models/index.js
new file mode 100755
index 0000000..c04c2f2
--- /dev/null
+++ b/day11/models/index.js
@@ -0,0 +1,69 @@
+'use strict';
+/*Powered By: Manaknightdigital Inc. https://manaknightdigital.com/ Year: 2020*/
+/**
+ * Sequelize File
+ * @copyright 2020 Manaknightdigital Inc.
+ * @link https://manaknightdigital.com
+ * @license Proprietary Software licensing
+ * @author Ryan Wong
+ *
+ */
+const fs = require('fs');
+const path = require('path');
+let Sequelize = require('sequelize');
+const { DataTypes } = require('sequelize');
+const basename = path.basename(__filename);
+const config = {
+ DB_DATABASE: 'mysql',
+ DB_USERNAME: 'root',
+ DB_PASSWORD: 'root',
+ DB_ADAPTER: 'mysql',
+ DB_NAME: 'day_1',
+ DB_HOSTNAME: 'localhost',
+ DB_PORT: 3306,
+};
+
+let db = {};
+
+let sequelize = new Sequelize(config.DB_DATABASE, config.DB_USERNAME, config.DB_PASSWORD, {
+ dialect: config.DB_ADAPTER,
+ username: config.DB_USERNAME,
+ password: config.DB_PASSWORD,
+ database: config.DB_NAME,
+ host: config.DB_HOSTNAME,
+ port: config.DB_PORT,
+ logging: console.log,
+ timezone: '-04:00',
+ pool: {
+ maxConnections: 1,
+ minConnections: 0,
+ maxIdleTime: 100,
+ },
+ define: {
+ timestamps: false,
+ underscoredAll: true,
+ underscored: true,
+ },
+});
+
+// sequelize.sync({ force: true });
+
+fs.readdirSync(__dirname)
+ .filter((file) => {
+ return file.indexOf('.') !== 0 && file !== basename && file.slice(-3) === '.js';
+ })
+ .forEach((file) => {
+ var model = require(path.join(__dirname, file))(sequelize, DataTypes);
+ db[model.name] = model;
+ });
+
+Object.keys(db).forEach((modelName) => {
+ if (db[modelName].associate) {
+ db[modelName].associate(db);
+ }
+});
+
+db.sequelize = sequelize;
+db.Sequelize = Sequelize;
+
+module.exports = db;
\ No newline at end of file
diff --git a/day11/models/link.js b/day11/models/link.js
new file mode 100755
index 0000000..d8e3d14
--- /dev/null
+++ b/day11/models/link.js
@@ -0,0 +1,152 @@
+/*Powered By: Manaknightdigital Inc. https://manaknightdigital.com/ Year: 2021*/
+/**
+ * link Model
+ * @copyright 2021 Manaknightdigital Inc.
+ * @link https://manaknightdigital.com
+ * @license Proprietary Software licensing
+ * @author Ryan Wong
+ *
+ */
+
+const moment = require("moment");
+;
+const { Op } = require("sequelize");
+const { intersection } = require('lodash');
+const coreModel = require('./../core/models');
+
+module.exports = (sequelize, DataTypes) => {
+ const Link = sequelize.define(
+ "link",
+ {
+
+ id: {
+ type: DataTypes.INTEGER,
+ primaryKey: true,
+ autoIncrement: true,
+ },
+ link: DataTypes.STRING,
+ status: DataTypes.INTEGER,
+ created_at: DataTypes.DATEONLY,
+ updated_at: DataTypes.DATE,
+ },
+ {
+ timestamps: true,
+ freezeTableName: true,
+ tableName: "link",
+ },
+ {
+ underscoredAll: false,
+ underscored: false,
+ }
+ );
+
+ coreModel.call(this, Link);
+
+ Link._preCreateProcessing = function (data) {
+ data.status = 1;
+ return data;
+ };
+ Link._postCreateProcessing = function (data) {
+
+ return data;
+ };
+ Link._customCountingConditions = function (data) {
+
+ return data;
+ };
+
+ Link._filterAllowKeys = function (data) {
+ let cleanData = {};
+ let allowedFields = Link.allowFields();
+ allowedFields.push(Link._primaryKey());
+
+ for (const key in data) {
+ if (allowedFields.includes(key)) {
+ cleanData[key] = data[key];
+ }
+ }
+ return cleanData;
+ };
+
+ Link.timeDefaultMapping = function () {
+ let results = [];
+ for (let i = 0; i < 24; i++) {
+ for (let j = 0; j < 60; j++) {
+ let hour = i < 10 ? "0".i : i;
+ let min = j < 10 ? "0".j : j;
+ results[i * 60 + j] = `${hour}:${min}`;
+ }
+ }
+ return results;
+ };
+
+ Link.associate = function (models) {
+ Link.belongsTo(models.user, {
+ foreignKey: "user_id",
+ as: "user",
+ constraints: false,
+ })
+ };
+
+
+ Link.status_mapping = function (status) {
+ const mapping = { "0": "Inactive", "1": "Active" }
+
+ if (arguments.length === 0) return mapping;
+ else return mapping[status];
+ };
+
+
+ Link.allowFields = function () {
+ return ["user_id", 'id', 'link', 'status',];
+ };
+
+ Link.labels = function () {
+ return ['ID', 'Link', 'Status',];
+ };
+
+ Link.validationRules = function () {
+ return [
+ ['id', 'ID', ''],
+ ['link', 'Link', 'required'],
+ ['status', 'Status', 'required|integer'],
+ ];
+ };
+
+ Link.validationEditRules = function () {
+ return [
+ ['id', 'ID', ''],
+ ['link', 'Link', 'required'],
+ ['status', 'Status', 'required|integer'],
+ ];
+ };
+
+
+
+ Link.get_user_paginated = function (db, ...rest) {
+ return Link.getPaginated(...rest, [{
+ model: db.user,
+ as: "user"
+ }])
+ }
+
+
+ Link.get_link_user = (id, db) => {
+ return Link.findByPk(id, { include: [{ model: db.user, as: "user" }] });
+ };
+
+ // ex
+ Link.intersection = function (fields) {
+ if (fields) {
+ return intersection(
+ [
+ 'id', 'link', 'status', 'created_at', 'updated_at',
+ ],
+ Object.keys(fields),
+ );
+ } else return [];
+ };
+
+
+ return Link;
+};
diff --git a/day11/models/member_operation.js b/day11/models/member_operation.js
new file mode 100755
index 0000000..fb2f667
--- /dev/null
+++ b/day11/models/member_operation.js
@@ -0,0 +1,153 @@
+/*Powered By: Manaknightdigital Inc. https://manaknightdigital.com/ Year: 2021*/
+/**
+ * member_operation Model
+ * @copyright 2021 Manaknightdigital Inc.
+ * @link https://manaknightdigital.com
+ * @license Proprietary Software licensing
+ * @author Ryan Wong
+ *
+ */
+
+const moment = require("moment");
+;
+const { Op } = require("sequelize");
+const { intersection } = require('lodash');
+const coreModel = require('./../core/models');
+
+module.exports = (sequelize, DataTypes) => {
+ const Member_operation = sequelize.define(
+ "member_operation",
+ {
+ action: DataTypes.STRING,
+ detail: DataTypes.TEXT,
+ last_ip: { type: DataTypes.STRING, validate: {} },
+ user_agent: { type: DataTypes.STRING, validate: {} },
+ status: DataTypes.INTEGER,
+ created_at: DataTypes.DATEONLY,
+ updated_at: DataTypes.DATE,
+ },
+ {
+ timestamps: true,
+ freezeTableName: true,
+ tableName: "member_operation",
+ },
+ {
+ underscoredAll: false,
+ underscored: false,
+ }
+ );
+
+ coreModel.call(this, Member_operation);
+
+ Member_operation._preCreateProcessing = function (data) {
+ data.status = 1;
+ return data;
+ };
+ Member_operation._postCreateProcessing = function (data) {
+
+ return data;
+ };
+ Member_operation._customCountingConditions = function (data) {
+
+ return data;
+ };
+
+ Member_operation._filterAllowKeys = function (data) {
+ let cleanData = {};
+ let allowedFields = Member_operation.allowFields();
+ allowedFields.push(Member_operation._primaryKey());
+
+ for (const key in data) {
+ if (allowedFields.includes(key)) {
+ cleanData[key] = data[key];
+ }
+ }
+ return cleanData;
+ };
+
+ Member_operation.timeDefaultMapping = function () {
+ let results = [];
+ for (let i = 0; i < 24; i++) {
+ for (let j = 0; j < 60; j++) {
+ let hour = i < 10 ? "0".i : i;
+ let min = j < 10 ? "0".j : j;
+ results[i * 60 + j] = `${hour}:${min}`;
+ }
+ }
+ return results;
+ };
+
+ Member_operation.associate = function (models) {
+ Member_operation.belongsTo(models.user, {
+ foreignKey: "user_id",
+ as: "user",
+ constraints: false,
+ })
+ };
+
+
+ Member_operation.status_mapping = function (status) {
+ const mapping = { "0": "Inactive", "1": "Active" }
+
+ if (arguments.length === 0) return mapping;
+ else return mapping[status];
+ };
+
+
+ Member_operation.allowFields = function () {
+ return ["user_id", 'action', 'detail', 'last_ip', 'user_agent', 'status',];
+ };
+
+ Member_operation.labels = function () {
+ return ['Action', 'Detail', 'Last IP', 'User Agent', 'Status',];
+ };
+
+ Member_operation.validationRules = function () {
+ return [
+ ['action', 'Action', 'required|max[50]'],
+ ['detail', 'Detail', 'required'],
+ ['last_ip', 'Last IP', 'required'],
+ ['user_agent', 'User Agent', 'required'],
+ ['status', 'Status', 'required|integer'],
+ ];
+ };
+
+ Member_operation.validationEditRules = function () {
+ return [
+ ['action', 'Action', ''],
+ ['detail', 'Detail', ''],
+ ['last_ip', 'Last IP', ''],
+ ['user_agent', 'User Agent', ''],
+ ['status', 'Status', 'required|integer'],
+ ];
+ };
+
+
+
+ Member_operation.get_user_paginated = function (db, ...rest) {
+ return Member_operation.getPaginated(...rest, [{
+ model: db.user,
+ as: "user"
+ }])
+ }
+
+
+ Member_operation.get_member_operation_user = (id, db) => {
+ return Member_operation.findByPk(id, { include: [{ model: db.user, as: "user" }] });
+ };
+
+ // ex
+ Member_operation.intersection = function (fields) {
+ if (fields) {
+ return intersection(
+ [
+ 'action', 'detail', 'last_ip', 'user_agent', 'status', 'created_at', 'updated_at',
+ ],
+ Object.keys(fields),
+ );
+ } else return [];
+ };
+
+
+ return Member_operation;
+};
diff --git a/day11/models/note.js b/day11/models/note.js
new file mode 100755
index 0000000..990bfa8
--- /dev/null
+++ b/day11/models/note.js
@@ -0,0 +1,152 @@
+/*Powered By: Manaknightdigital Inc. https://manaknightdigital.com/ Year: 2021*/
+/**
+ * note Model
+ * @copyright 2021 Manaknightdigital Inc.
+ * @link https://manaknightdigital.com
+ * @license Proprietary Software licensing
+ * @author Ryan Wong
+ *
+ */
+
+const moment = require("moment");
+;
+const { Op } = require("sequelize");
+const { intersection } = require('lodash');
+const coreModel = require('./../core/models');
+
+module.exports = (sequelize, DataTypes) => {
+ const Note = sequelize.define(
+ "note",
+ {
+
+ id: {
+ type: DataTypes.INTEGER,
+ primaryKey: true,
+ autoIncrement: true,
+ },
+ message: DataTypes.STRING,
+ status: DataTypes.INTEGER,
+ created_at: DataTypes.DATEONLY,
+ updated_at: DataTypes.DATE,
+ },
+ {
+ timestamps: true,
+ freezeTableName: true,
+ tableName: "note",
+ },
+ {
+ underscoredAll: false,
+ underscored: false,
+ }
+ );
+
+ coreModel.call(this, Note);
+
+ Note._preCreateProcessing = function (data) {
+ data.status = 1;
+ return data;
+ };
+ Note._postCreateProcessing = function (data) {
+
+ return data;
+ };
+ Note._customCountingConditions = function (data) {
+
+ return data;
+ };
+
+ Note._filterAllowKeys = function (data) {
+ let cleanData = {};
+ let allowedFields = Note.allowFields();
+ allowedFields.push(Note._primaryKey());
+
+ for (const key in data) {
+ if (allowedFields.includes(key)) {
+ cleanData[key] = data[key];
+ }
+ }
+ return cleanData;
+ };
+
+ Note.timeDefaultMapping = function () {
+ let results = [];
+ for (let i = 0; i < 24; i++) {
+ for (let j = 0; j < 60; j++) {
+ let hour = i < 10 ? "0".i : i;
+ let min = j < 10 ? "0".j : j;
+ results[i * 60 + j] = `${hour}:${min}`;
+ }
+ }
+ return results;
+ };
+
+ Note.associate = function (models) {
+ Note.belongsTo(models.user, {
+ foreignKey: "user_id",
+ as: "user",
+ constraints: false,
+ })
+ };
+
+
+ Note.status_mapping = function (status) {
+ const mapping = { "0": "Inactive", "1": "Active" }
+
+ if (arguments.length === 0) return mapping;
+ else return mapping[status];
+ };
+
+
+ Note.allowFields = function () {
+ return ["user_id", 'id', 'message', 'status',];
+ };
+
+ Note.labels = function () {
+ return ['ID', 'Message', 'Status',];
+ };
+
+ Note.validationRules = function () {
+ return [
+ ['id', 'ID', ''],
+ ['message', 'Message', 'required'],
+ ['status', 'Status', 'required|integer'],
+ ];
+ };
+
+ Note.validationEditRules = function () {
+ return [
+ ['id', 'ID', ''],
+ ['message', 'Message', 'required'],
+ ['status', 'Status', 'required|integer'],
+ ];
+ };
+
+
+
+ Note.get_user_paginated = function (db, ...rest) {
+ return Note.getPaginated(...rest, [{
+ model: db.user,
+ as: "user"
+ }])
+ }
+
+
+ Note.get_note_user = (id, db) => {
+ return Note.findByPk(id, { include: [{ model: db.user, as: "user" }] });
+ };
+
+ // ex
+ Note.intersection = function (fields) {
+ if (fields) {
+ return intersection(
+ [
+ 'id', 'message', 'status', 'created_at', 'updated_at',
+ ],
+ Object.keys(fields),
+ );
+ } else return [];
+ };
+
+
+ return Note;
+};
diff --git a/day11/models/profile.js b/day11/models/profile.js
new file mode 100755
index 0000000..c86d175
--- /dev/null
+++ b/day11/models/profile.js
@@ -0,0 +1,158 @@
+/*Powered By: Manaknightdigital Inc. https://manaknightdigital.com/ Year: 2021*/
+/**
+ * profile Model
+ * @copyright 2021 Manaknightdigital Inc.
+ * @link https://manaknightdigital.com
+ * @license Proprietary Software licensing
+ * @author Ryan Wong
+ *
+ */
+
+const moment = require("moment");
+;
+const { Op } = require("sequelize");
+const { intersection } = require('lodash');
+const coreModel = require('./../core/models');
+
+module.exports = (sequelize, DataTypes) => {
+ const Profile = sequelize.define(
+ "profile",
+ {
+
+ id: {
+ type: DataTypes.INTEGER,
+ primaryKey: true,
+ autoIncrement: true,
+ },
+ timezone: DataTypes.STRING,
+ dashboard_code: DataTypes.STRING,
+ code: DataTypes.STRING,
+ status: DataTypes.INTEGER,
+ created_at: DataTypes.DATEONLY,
+ updated_at: DataTypes.DATE,
+ },
+ {
+ timestamps: true,
+ freezeTableName: true,
+ tableName: "profile",
+ },
+ {
+ underscoredAll: false,
+ underscored: false,
+ }
+ );
+
+ coreModel.call(this, Profile);
+
+ Profile._preCreateProcessing = function (data) {
+ data.status = 1;
+ return data;
+ };
+ Profile._postCreateProcessing = function (data) {
+
+ return data;
+ };
+ Profile._customCountingConditions = function (data) {
+
+ return data;
+ };
+
+ Profile._filterAllowKeys = function (data) {
+ let cleanData = {};
+ let allowedFields = Profile.allowFields();
+ allowedFields.push(Profile._primaryKey());
+
+ for (const key in data) {
+ if (allowedFields.includes(key)) {
+ cleanData[key] = data[key];
+ }
+ }
+ return cleanData;
+ };
+
+ Profile.timeDefaultMapping = function () {
+ let results = [];
+ for (let i = 0; i < 24; i++) {
+ for (let j = 0; j < 60; j++) {
+ let hour = i < 10 ? "0".i : i;
+ let min = j < 10 ? "0".j : j;
+ results[i * 60 + j] = `${hour}:${min}`;
+ }
+ }
+ return results;
+ };
+
+ Profile.associate = function (models) {
+ Profile.belongsTo(models.user, {
+ foreignKey: "user_id",
+ as: "profile",
+ constraints: false,
+ })
+ };
+
+
+ Profile.status_mapping = function (status) {
+ const mapping = { "0": "Inactive", "1": "Active" }
+
+ if (arguments.length === 0) return mapping;
+ else return mapping[status];
+ };
+
+
+ Profile.allowFields = function () {
+ return ["user_id", 'id', 'timezone', 'dashboard_code', 'code', 'status',];
+ };
+
+ Profile.labels = function () {
+ return ['ID', 'Timezone', 'Dashboard Code', 'Code', 'Status',];
+ };
+
+ Profile.validationRules = function () {
+ return [
+ ['id', 'ID', ''],
+ ['timezone', 'Timezone', 'required'],
+ ['dashboard_code', 'Dashboard Code', 'required'],
+ ['code', 'Code', ''],
+ ['status', 'Status', 'required|integer'],
+ ];
+ };
+
+ Profile.validationEditRules = function () {
+ return [
+ ['id', 'ID', ''],
+ ['timezone', 'Timezone', 'required'],
+ ['dashboard_code', 'Dashboard Code', 'required'],
+ ['code', 'Code', ''],
+ ['status', 'Status', 'required|integer'],
+ ];
+ };
+
+
+
+ Profile.get_user_paginated = function (db, ...rest) {
+ return Profile.getPaginated(...rest, [{
+ model: db.user,
+ as: "profile"
+ }])
+ }
+
+
+ Profile.get_profile_user = (id, db) => {
+ return Profile.findByPk(id, { include: [{ model: db.user, as: "user" }] });
+ };
+
+ // ex
+ Profile.intersection = function (fields) {
+ if (fields) {
+ return intersection(
+ [
+ 'id', 'timezone', 'dashboard_code', 'code', 'status', 'created_at', 'updated_at',
+ ],
+ Object.keys(fields),
+ );
+ } else return [];
+ };
+
+
+ return Profile;
+};
diff --git a/day11/models/refer_log.js b/day11/models/refer_log.js
new file mode 100755
index 0000000..27559db
--- /dev/null
+++ b/day11/models/refer_log.js
@@ -0,0 +1,163 @@
+/*Powered By: Manaknightdigital Inc. https://manaknightdigital.com/ Year: 2021*/
+/**
+ * refer_log Model
+ * @copyright 2021 Manaknightdigital Inc.
+ * @link https://manaknightdigital.com
+ * @license Proprietary Software licensing
+ * @author Ryan Wong
+ *
+ */
+
+const moment = require("moment");
+;
+const { Op } = require("sequelize");
+const { intersection } = require('lodash');
+const coreModel = require('./../core/models');
+
+module.exports = (sequelize, DataTypes) => {
+ const Refer_log = sequelize.define(
+ "refer_log",
+ {
+
+ id: {
+ type: DataTypes.INTEGER,
+ primaryKey: true,
+ autoIncrement: true,
+ },
+ referrer_user_id: DataTypes.INTEGER,
+ type: DataTypes.INTEGER,
+ status: DataTypes.INTEGER,
+ created_at: DataTypes.DATEONLY,
+ updated_at: DataTypes.DATE,
+ },
+ {
+ timestamps: true,
+ freezeTableName: true,
+ tableName: "refer_log",
+ },
+ {
+ underscoredAll: false,
+ underscored: false,
+ }
+ );
+
+ coreModel.call(this, Refer_log);
+
+ Refer_log._preCreateProcessing = function (data) {
+ data.status = 1;
+ return data;
+ };
+ Refer_log._postCreateProcessing = function (data) {
+
+ return data;
+ };
+ Refer_log._customCountingConditions = function (data) {
+
+ return data;
+ };
+
+ Refer_log._filterAllowKeys = function (data) {
+ let cleanData = {};
+ let allowedFields = Refer_log.allowFields();
+ allowedFields.push(Refer_log._primaryKey());
+
+ for (const key in data) {
+ if (allowedFields.includes(key)) {
+ cleanData[key] = data[key];
+ }
+ }
+ return cleanData;
+ };
+
+ Refer_log.timeDefaultMapping = function () {
+ let results = [];
+ for (let i = 0; i < 24; i++) {
+ for (let j = 0; j < 60; j++) {
+ let hour = i < 10 ? "0".i : i;
+ let min = j < 10 ? "0".j : j;
+ results[i * 60 + j] = `${hour}:${min}`;
+ }
+ }
+ return results;
+ };
+
+ Refer_log.associate = function (models) {
+ Refer_log.belongsTo(models.user, {
+ foreignKey: "user_id",
+ as: "user",
+ constraints: false,
+ })
+ };
+
+
+ Refer_log.status_mapping = function (status) {
+ const mapping = { "0": "Pending", "1": "Confirmed", "2": "Paid" }
+
+ if (arguments.length === 0) return mapping;
+ else return mapping[status];
+ };
+
+
+ Refer_log.type_mapping = function (type) {
+ const mapping = { "0": "user" }
+
+ if (arguments.length === 0) return mapping;
+ else return mapping[type];
+ };
+
+
+ Refer_log.allowFields = function () {
+ return ['id', 'referrer_user_id', 'type', 'status',];
+ };
+
+ Refer_log.labels = function () {
+ return ['ID', 'Referrer User', 'Type', 'Status',];
+ };
+
+ Refer_log.validationRules = function () {
+ return [
+ ['id', 'ID', ''],
+ ['referrer_user_id', 'Referrer User', 'required|integer'],
+ ['type', 'Type', 'required|integer'],
+ ['status', 'Status', 'required|integer'],
+ ];
+ };
+
+ Refer_log.validationEditRules = function () {
+ return [
+ ['id', 'ID', ''],
+ ['referrer_user_id', 'Referrer User', ''],
+ ['type', 'Type', 'required|integer'],
+ ['status', 'Status', ''],
+ ];
+ };
+
+
+
+ Refer_log.get_user_paginated = function (db, ...rest) {
+ return Refer_log.getPaginated(...rest, [{
+ model: db.user,
+ as: "user"
+ }])
+ }
+
+
+ Refer_log.get_refer_log_user = (id, db) => {
+ return Refer_log.findByPk(id, { include: [{ model: db.user, as: "user" }] });
+ };
+
+ // ex
+ Refer_log.intersection = function (fields) {
+ if (fields) {
+ return intersection(
+ [
+ 'id', 'referrer_user_id', 'type', 'status', 'created_at', 'updated_at',
+ ],
+ Object.keys(fields),
+ );
+ } else return [];
+ };
+
+
+ return Refer_log;
+};
diff --git a/day11/models/role.js b/day11/models/role.js
new file mode 100755
index 0000000..dec6f7d
--- /dev/null
+++ b/day11/models/role.js
@@ -0,0 +1,137 @@
+/*Powered By: Manaknightdigital Inc. https://manaknightdigital.com/ Year: 2021*/
+/**
+ * role Model
+ * @copyright 2021 Manaknightdigital Inc.
+ * @link https://manaknightdigital.com
+ * @license Proprietary Software licensing
+ * @author Ryan Wong
+ *
+ */
+
+const moment = require("moment");
+;
+const { Op } = require("sequelize");
+const { intersection } = require('lodash');
+const coreModel = require('./../core/models');
+
+module.exports = (sequelize, DataTypes) => {
+ const Role = sequelize.define(
+ "role",
+ {
+
+ id: {
+ type: DataTypes.INTEGER,
+ primaryKey: true,
+ autoIncrement: true,
+ },
+ name: { type: DataTypes.STRING, validate: {} },
+ status: DataTypes.INTEGER,
+ created_at: DataTypes.DATEONLY,
+ updated_at: DataTypes.DATE,
+ },
+ {
+ timestamps: true,
+ freezeTableName: true,
+ tableName: "role",
+ },
+ {
+ underscoredAll: false,
+ underscored: false,
+ }
+ );
+
+ coreModel.call(this, Role);
+
+ Role._preCreateProcessing = function (data) {
+ data.status = 1;
+ return data;
+ };
+ Role._postCreateProcessing = function (data) {
+
+ return data;
+ };
+ Role._customCountingConditions = function (data) {
+
+ return data;
+ };
+
+ Role._filterAllowKeys = function (data) {
+ let cleanData = {};
+ let allowedFields = Role.allowFields();
+ allowedFields.push(Role._primaryKey());
+
+ for (const key in data) {
+ if (allowedFields.includes(key)) {
+ cleanData[key] = data[key];
+ }
+ }
+ return cleanData;
+ };
+
+ Role.timeDefaultMapping = function () {
+ let results = [];
+ for (let i = 0; i < 24; i++) {
+ for (let j = 0; j < 60; j++) {
+ let hour = i < 10 ? "0".i : i;
+ let min = j < 10 ? "0".j : j;
+ results[i * 60 + j] = `${hour}:${min}`;
+ }
+ }
+ return results;
+ };
+
+ Role.associate = function (models) { };
+
+
+ Role.status_mapping = function (status) {
+ const mapping = { "0": "Pending", "1": "Confirmed", "2": "Paid" }
+
+ if (arguments.length === 0) return mapping;
+ else return mapping[status];
+ };
+
+
+ Role.allowFields = function () {
+ return ['id', 'name', 'status',];
+ };
+
+ Role.labels = function () {
+ return ['ID', 'Role Name', 'Status',];
+ };
+
+ Role.validationRules = function () {
+ return [
+ ['id', 'ID', ''],
+ ['name', 'Role Name', 'required'],
+ ['status', 'Status', 'required|integer'],
+ ];
+ };
+
+ Role.validationEditRules = function () {
+ return [
+ ['id', 'ID', ''],
+ ['name', 'Role Name', 'required'],
+ ['status', 'Status', ''],
+ ];
+ };
+
+
+
+
+
+
+ // ex
+ Role.intersection = function (fields) {
+ if (fields) {
+ return intersection(
+ [
+ 'id', 'name', 'status', 'created_at', 'updated_at',
+ ],
+ Object.keys(fields),
+ );
+ } else return [];
+ };
+
+
+ return Role;
+};
diff --git a/day11/models/setting.js b/day11/models/setting.js
new file mode 100755
index 0000000..290080d
--- /dev/null
+++ b/day11/models/setting.js
@@ -0,0 +1,165 @@
+/*Powered By: Manaknightdigital Inc. https://manaknightdigital.com/ Year: 2021*/
+/**
+ * setting Model
+ * @copyright 2021 Manaknightdigital Inc.
+ * @link https://manaknightdigital.com
+ * @license Proprietary Software licensing
+ * @author Ryan Wong
+ *
+ */
+
+const moment = require("moment");
+;
+const { Op } = require("sequelize");
+const { intersection } = require('lodash');
+const coreModel = require('./../core/models');
+
+module.exports = (sequelize, DataTypes) => {
+ const Setting = sequelize.define(
+ "setting",
+ {
+
+ id: {
+ type: DataTypes.INTEGER,
+ primaryKey: true,
+ autoIncrement: true,
+ },
+ key: {
+ type: DataTypes.STRING,
+ unique: true
+ },
+ type: DataTypes.INTEGER,
+ value: DataTypes.TEXT,
+ maintenance: DataTypes.INTEGER,
+ status: DataTypes.INTEGER,
+ created_at: DataTypes.DATEONLY,
+ updated_at: DataTypes.DATE,
+ },
+ {
+ timestamps: true,
+ freezeTableName: true,
+ tableName: "setting",
+ },
+ {
+ underscoredAll: false,
+ underscored: false,
+ }
+ );
+
+ coreModel.call(this, Setting);
+
+ Setting._preCreateProcessing = function (data) {
+ data.status = 1;
+ return data;
+ };
+ Setting._postCreateProcessing = function (data) {
+
+ return data;
+ };
+ Setting._customCountingConditions = function (data) {
+
+ return data;
+ };
+
+ Setting._filterAllowKeys = function (data) {
+ let cleanData = {};
+ let allowedFields = Setting.allowFields();
+ allowedFields.push(Setting._primaryKey());
+
+ for (const key in data) {
+ if (allowedFields.includes(key)) {
+ cleanData[key] = data[key];
+ }
+ }
+ return cleanData;
+ };
+
+ Setting.timeDefaultMapping = function () {
+ let results = [];
+ for (let i = 0; i < 24; i++) {
+ for (let j = 0; j < 60; j++) {
+ let hour = i < 10 ? "0".i : i;
+ let min = j < 10 ? "0".j : j;
+ results[i * 60 + j] = `${hour}:${min}`;
+ }
+ }
+ return results;
+ };
+
+ Setting.associate = function (models) { };
+
+
+ Setting.type_mapping = function (type) {
+ const mapping = { "0": "text", "1": "select", "2": "number", "3": "image", "4": "read_only" }
+
+ if (arguments.length === 0) return mapping;
+ else return mapping[type];
+ };
+
+
+ Setting.maintenance_mapping = function (maintenance) {
+ const mapping = { "0": "No", "1": "Yes" }
+
+ if (arguments.length === 0) return mapping;
+ else return mapping[maintenance];
+ };
+
+
+ Setting.status_mapping = function (status) {
+ const mapping = { "0": "Pending", "1": "Confirmed", "2": "Paid" }
+
+ if (arguments.length === 0) return mapping;
+ else return mapping[status];
+ };
+
+
+ Setting.allowFields = function () {
+ return ['id', 'key', 'type', 'value', 'maintenance', 'status',];
+ };
+
+ Setting.labels = function () {
+ return ['ID', 'Setting Field', 'Setting Type', 'Setting Value', 'Setting Type', 'Status',];
+ };
+
+ Setting.validationRules = function () {
+ return [
+ ['id', 'ID', ''],
+ ['key', 'Setting Field', 'required'],
+ ['type', 'Setting Type', 'required'],
+ ['value', 'Setting Value', 'required'],
+ ['maintenance', 'Setting Type', 'required'],
+ ['status', 'Status', 'required|integer'],
+ ];
+ };
+
+ Setting.validationEditRules = function () {
+ return [
+ ['id', 'ID', ''],
+ ['key', 'Setting Field', ''],
+ ['type', 'Setting Type', ''],
+ ['value', 'Setting Value', 'required'],
+ ['maintenance', 'Setting Type', ''],
+ ['status', 'Status', 'required|integer'],
+ ];
+ };
+
+
+
+
+
+
+ // ex
+ Setting.intersection = function (fields) {
+ if (fields) {
+ return intersection(
+ [
+ 'id', 'key', 'type', 'value', 'maintenance', 'status', 'created_at', 'updated_at',
+ ],
+ Object.keys(fields),
+ );
+ } else return [];
+ };
+
+
+ return Setting;
+};
diff --git a/day11/models/sms.js b/day11/models/sms.js
new file mode 100755
index 0000000..d5d53f8
--- /dev/null
+++ b/day11/models/sms.js
@@ -0,0 +1,135 @@
+/*Powered By: Manaknightdigital Inc. https://manaknightdigital.com/ Year: 2021*/
+/**
+ * sms Model
+ * @copyright 2021 Manaknightdigital Inc.
+ * @link https://manaknightdigital.com
+ * @license Proprietary Software licensing
+ * @author Ryan Wong
+ *
+ */
+
+const moment = require("moment");
+;
+const { Op } = require("sequelize");
+const { intersection } = require('lodash');
+const coreModel = require('./../core/models');
+
+module.exports = (sequelize, DataTypes) => {
+ const Sms = sequelize.define(
+ "sms",
+ {
+ slug: { type: DataTypes.STRING, validate: {} },
+ tag: DataTypes.TEXT,
+ content: DataTypes.TEXT,
+ status: DataTypes.INTEGER,
+ created_at: DataTypes.DATEONLY,
+ updated_at: DataTypes.DATE,
+ },
+ {
+ timestamps: true,
+ freezeTableName: true,
+ tableName: "sms",
+ },
+ {
+ underscoredAll: false,
+ underscored: false,
+ }
+ );
+
+ coreModel.call(this, Sms);
+
+ Sms._preCreateProcessing = function (data) {
+ data.status = 1;
+ return data;
+ };
+ Sms._postCreateProcessing = function (data) {
+
+ return data;
+ };
+ Sms._customCountingConditions = function (data) {
+
+ return data;
+ };
+
+ Sms._filterAllowKeys = function (data) {
+ let cleanData = {};
+ let allowedFields = Sms.allowFields();
+ allowedFields.push(Sms._primaryKey());
+
+ for (const key in data) {
+ if (allowedFields.includes(key)) {
+ cleanData[key] = data[key];
+ }
+ }
+ return cleanData;
+ };
+
+ Sms.timeDefaultMapping = function () {
+ let results = [];
+ for (let i = 0; i < 24; i++) {
+ for (let j = 0; j < 60; j++) {
+ let hour = i < 10 ? "0".i : i;
+ let min = j < 10 ? "0".j : j;
+ results[i * 60 + j] = `${hour}:${min}`;
+ }
+ }
+ return results;
+ };
+
+ Sms.associate = function (models) { };
+
+
+ Sms.status_mapping = function (status) {
+ const mapping = { "0": "Inactive", "1": "Active" }
+
+ if (arguments.length === 0) return mapping;
+ else return mapping[status];
+ };
+
+
+ Sms.allowFields = function () {
+ return ['slug', 'tag', 'content', 'status',];
+ };
+
+ Sms.labels = function () {
+ return ['SMS Slug', 'Replacement Tags', 'SMS Body', 'Status',];
+ };
+
+ Sms.validationRules = function () {
+ return [
+ ['slug', 'SMS Slug', 'required|is_unique[sms.slug]'],
+ ['tag', 'Replacement Tags', 'required'],
+ ['content', 'SMS Body', 'required'],
+ ['status', 'Status', 'required|integer'],
+ ];
+ };
+
+ Sms.validationEditRules = function () {
+ return [
+ ['slug', 'SMS Slug', ''],
+ ['tag', 'Replacement Tags', ''],
+ ['content', 'SMS Body', 'required'],
+ ['status', 'Status', 'required|integer'],
+ ];
+ };
+
+
+
+
+
+
+ // ex
+ Sms.intersection = function (fields) {
+ if (fields) {
+ return intersection(
+ [
+ 'slug', 'tag', 'content', 'status', 'created_at', 'updated_at',
+ ],
+ Object.keys(fields),
+ );
+ } else return [];
+ };
+
+
+ return Sms;
+};
diff --git a/day11/models/token.js b/day11/models/token.js
new file mode 100755
index 0000000..7f411a8
--- /dev/null
+++ b/day11/models/token.js
@@ -0,0 +1,174 @@
+/*Powered By: Manaknightdigital Inc. https://manaknightdigital.com/ Year: 2021*/
+/**
+ * token Model
+ * @copyright 2021 Manaknightdigital Inc.
+ * @link https://manaknightdigital.com
+ * @license Proprietary Software licensing
+ * @author Ryan Wong
+ *
+ */
+
+const moment = require("moment");
+;
+const { Op } = require("sequelize");
+const { intersection } = require('lodash');
+const coreModel = require('./../core/models');
+
+module.exports = (sequelize, DataTypes) => {
+ const Token = sequelize.define(
+ "token",
+ {
+
+ id: {
+ type: DataTypes.INTEGER,
+ primaryKey: true,
+ autoIncrement: true,
+ },
+ token: DataTypes.TEXT,
+ data: DataTypes.TEXT,
+ type: DataTypes.INTEGER,
+ ttl: DataTypes.INTEGER,
+ issue_at: DataTypes.DATE,
+ expire_at: DataTypes.DATE,
+ status: DataTypes.INTEGER,
+
+ },
+ {
+ timestamps: false,
+ freezeTableName: true,
+ tableName: "token",
+ },
+ {
+ underscoredAll: false,
+ underscored: false,
+ }
+ );
+
+ coreModel.call(this, Token);
+
+ Token._preCreateProcessing = function (data) {
+ data.status = 1;
+ return data;
+ };
+ Token._postCreateProcessing = function (data) {
+
+ return data;
+ };
+ Token._customCountingConditions = function (data) {
+
+ return data;
+ };
+
+ Token._filterAllowKeys = function (data) {
+ let cleanData = {};
+ let allowedFields = Token.allowFields();
+ allowedFields.push(Token._primaryKey());
+
+ for (const key in data) {
+ if (allowedFields.includes(key)) {
+ cleanData[key] = data[key];
+ }
+ }
+ return cleanData;
+ };
+
+ Token.timeDefaultMapping = function () {
+ let results = [];
+ for (let i = 0; i < 24; i++) {
+ for (let j = 0; j < 60; j++) {
+ let hour = i < 10 ? "0".i : i;
+ let min = j < 10 ? "0".j : j;
+ results[i * 60 + j] = `${hour}:${min}`;
+ }
+ }
+ return results;
+ };
+
+ Token.associate = function (models) {
+ Token.belongsTo(models.user, {
+ foreignKey: "user_id",
+ as: "user",
+ constraints: false,
+ })
+ };
+
+
+ Token.status_mapping = function (status) {
+ const mapping = { "0": "Inactive", "1": "Active" }
+
+ if (arguments.length === 0) return mapping;
+ else return mapping[status];
+ };
+
+
+ Token.type_mapping = function (type) {
+ const mapping = { "0": "Forgot_token", "1": "Access token", "2": "Refresh_token", "3": "Other", "4": "Api Key", "5": "Api Secret", "6": "Verify" }
+
+ if (arguments.length === 0) return mapping;
+ else return mapping[type];
+ };
+
+
+ Token.allowFields = function () {
+ return ["user_id", 'id', 'token', 'data', 'type', 'ttl', 'issue_at', 'expire_at', 'status',];
+ };
+
+ Token.labels = function () {
+ return ['ID', 'Token', 'Data', 'Token Type', 'Time To Live', 'Issue at', 'Expire at', 'Status',];
+ };
+
+ Token.validationRules = function () {
+ return [
+ ['id', 'ID', ''],
+ ['token', 'Token', 'required'],
+ ['data', 'Data', 'required'],
+ ['type', 'Token Type', 'required|integer'],
+ ['ttl', 'Time To Live', 'required|integer'],
+ ['issue_at', 'Issue at', 'required'],
+ ['expire_at', 'Expire at', 'required'],
+ ['status', 'Status', 'required|integer'],
+ ];
+ };
+
+ Token.validationEditRules = function () {
+ return [
+ ['id', 'ID', ''],
+ ['token', 'Token', 'required'],
+ ['data', 'Data', 'required'],
+ ['type', 'Token Type', 'required|integer'],
+ ['ttl', 'Time To Live', 'required|integer'],
+ ['issue_at', 'Issue at', 'required'],
+ ['expire_at', 'Expire at', 'required'],
+ ['status', 'Status', 'required|integer'],
+ ];
+ };
+
+
+
+ Token.get_user_paginated = function (db, ...rest) {
+ return Token.getPaginated(...rest, [{
+ model: db.user,
+ as: "user"
+ }])
+ }
+
+
+ Token.get_token_user = (id, db) => {
+ return Token.findByPk(id, { include: [{ model: db.user, as: "user" }] });
+ };
+
+ // ex
+ Token.intersection = function (fields) {
+ if (fields) {
+ return intersection(
+ [
+ 'id', 'token', 'data', 'type', 'ttl', 'issue_at', 'expire_at', 'status', 'created_at', 'updated_at',
+ ],
+ Object.keys(fields),
+ );
+ } else return [];
+ };
+
+
+ return Token;
+};
diff --git a/day11/models/user.js b/day11/models/user.js
new file mode 100755
index 0000000..4225e0d
--- /dev/null
+++ b/day11/models/user.js
@@ -0,0 +1,482 @@
+/*Powered By: Manaknightdigital Inc. https://manaknightdigital.com/ Year: 2021*/
+/**
+ * user Model
+ * @copyright 2021 Manaknightdigital Inc.
+ * @link https://manaknightdigital.com
+ * @license Proprietary Software licensing
+ * @author Ryan Wong
+ *
+ */
+
+const moment = require('moment')
+
+const { Op } = require('sequelize')
+const { intersection } = require('lodash')
+const coreModel = require('./../core/models')
+
+module.exports = (sequelize, DataTypes) => {
+ const User = sequelize.define(
+ 'user',
+ {
+ id: {
+ type: DataTypes.INTEGER,
+ primaryKey: true,
+ autoIncrement: true,
+ },
+ status: DataTypes.INTEGER,
+ first_name: DataTypes.STRING,
+ last_name: DataTypes.STRING,
+ phone: DataTypes.STRING,
+ image: DataTypes.TEXT,
+ image_id: DataTypes.INTEGER,
+ refer: DataTypes.STRING,
+ profile_id: DataTypes.INTEGER,
+ role_id: DataTypes.INTEGER,
+ stripe_uid: DataTypes.STRING,
+ paypal_uid: DataTypes.STRING,
+ font_color: DataTypes.STRING,
+ time_zone: DataTypes.STRING,
+ time_format: DataTypes.INTEGER,
+ clock_format: DataTypes.INTEGER,
+ date_format: DataTypes.INTEGER,
+ location: DataTypes.STRING,
+ lat: DataTypes.FLOAT,
+ lng: DataTypes.FLOAT,
+ expire_at: DataTypes.DATEONLY,
+ created_at: DataTypes.DATEONLY,
+ updated_at: DataTypes.DATE,
+ },
+ {
+ timestamps: true,
+ freezeTableName: true,
+ tableName: 'user',
+ },
+ {
+ underscoredAll: false,
+ underscored: false,
+ }
+ )
+
+ coreModel.call(this, User)
+
+ User._preCreateProcessing = function (data) {
+ data.image = 'https://i.imgur.com/AzJ7DRw.png'
+ data.refer = Math.round(Math.random() * 1000000000000, 0)
+ data.status = 1
+ data.verify = 0
+ data.font_color = '#ffffff'
+ data.time_zone = 'UTC'
+ data.time_format = 1
+ data.clock_format = 1
+ data.date_format = 1
+ data.location = ''
+ if (!data.profile_id) {
+ data.profile_id = 0
+ }
+ if (data.type) {
+ data.type = 'n'
+ }
+ return data
+ }
+ User._postCreateProcessing = function (data) {
+ if (data.password && data.password.length < 1) {
+ delete data.password
+ }
+ if (data.image && data.image.length < 1) {
+ delete data.image
+ }
+
+ return data
+ }
+ User._customCountingConditions = function (data) {
+ return data
+ }
+
+ User._filterAllowKeys = function (data) {
+ let cleanData = {}
+ let allowedFields = User.allowFields()
+ allowedFields.push(User._primaryKey())
+
+ for (const key in data) {
+ if (allowedFields.includes(key)) {
+ cleanData[key] = data[key]
+ }
+ }
+ return cleanData
+ }
+
+ User.timeDefaultMapping = function () {
+ let results = []
+ for (let i = 0; i < 24; i++) {
+ for (let j = 0; j < 60; j++) {
+ let hour = i < 10 ? '0'.i : i
+ let min = j < 10 ? '0'.j : j
+ results[i * 60 + j] = `${hour}:${min}`
+ }
+ }
+ return results
+ }
+
+ User.associate = function (models) {
+ User.hasMany(models.refer_log, {
+ foreignKey: 'user_id',
+ constraints: false,
+ })
+ User.hasOne(models.credential, {
+ foreignKey: 'user_id',
+ constraints: false,
+ })
+ User.hasMany(models.token, {
+ foreignKey: 'user_id',
+ constraints: false,
+ })
+ User.hasOne(models.code, {
+ foreignKey: 'user_id',
+ constraints: false,
+ })
+ User.hasMany(models.admin_operation, {
+ foreignKey: 'user_id',
+ constraints: false,
+ })
+ User.hasMany(models.member_operation, {
+ foreignKey: 'user_id',
+ constraints: false,
+ })
+ User.hasMany(models.image, {
+ foreignKey: 'user_id',
+ constraints: false,
+ })
+ User.hasMany(models.note, {
+ foreignKey: 'user_id',
+ constraints: false,
+ })
+ User.hasMany(models.calendar, {
+ foreignKey: 'user_id',
+ constraints: false,
+ })
+ User.hasOne(models.profile, {
+ foreignKey: 'user_id',
+ constraints: false,
+ })
+ User.hasMany(models.link, {
+ foreignKey: 'user_id',
+ constraints: false,
+ })
+ }
+
+ User.status_mapping = function (status) {
+ const mapping = { 0: 'Inactive', 1: 'Active', 2: 'Suspend' }
+
+ if (arguments.length === 0) return mapping
+ else return mapping[status]
+ }
+
+ User.time_format_mapping = function (time_format) {
+ const mapping = { 1: 'AM/PM', 2: '24 hours' }
+
+ if (arguments.length === 0) return mapping
+ else return mapping[time_format]
+ }
+
+ User.clock_format_mapping = function (clock_format) {
+ const mapping = { 1: 'Digital', 2: 'Analog' }
+
+ if (arguments.length === 0) return mapping
+ else return mapping[clock_format]
+ }
+
+ User.date_format_mapping = function (date_format) {
+ const mapping = {
+ 1: 'Standard (dd/mm/yyyy)',
+ 2: 'Locale (Wed, April 1, 2021)',
+ }
+
+ if (arguments.length === 0) return mapping
+ else return mapping[date_format]
+ }
+
+ User.allowFields = function () {
+ return [
+ 'id',
+ 'status',
+ 'first_name',
+ 'last_name',
+ 'phone',
+ 'image',
+ 'image_id',
+ 'refer',
+ 'profile_id',
+ 'role_id',
+ 'stripe_uid',
+ 'paypal_uid',
+ 'font_color',
+ 'time_zone',
+ 'time_format',
+ 'clock_format',
+ 'date_format',
+ 'location',
+ 'lat',
+ 'lng',
+ 'expire_at',
+ ]
+ }
+
+ User.labels = function () {
+ return [
+ 'ID',
+ 'Status',
+ 'First Name',
+ 'Last Name',
+ 'Phone #',
+ 'Image',
+ 'Image ID',
+ 'Refer Code',
+ 'Profile ID',
+ 'Role ID',
+ 'Stripe ID',
+ 'PayPal ID',
+ 'Font Color',
+ 'Timezone',
+ 'Time Format',
+ 'Clock Format',
+ 'Date Format',
+ 'Location',
+ 'Latitude',
+ 'Longitude',
+ 'Expire At',
+ ]
+ }
+
+ User.validationRules = function () {
+ return [
+ ['id', 'ID', ''],
+ ['status', 'Status', ''],
+ ['first_name', 'First Name', 'required'],
+ ['last_name', 'Last Name', 'required'],
+ ['phone', 'Phone #', ''],
+ ['image', 'Image', ''],
+ ['image_id', 'Image ID', ''],
+ ['refer', 'Refer Code', ''],
+ ['profile_id', 'Profile ID', ''],
+ ['role_id', 'Role ID', ''],
+ ['stripe_uid', 'Stripe ID', ''],
+ ['paypal_uid', 'PayPal ID', ''],
+ ['font_color', 'Font Color', ''],
+ ['time_zone', 'Timezone', 'required'],
+ ['time_format', 'Time Format', 'required|integer'],
+ ['clock_format', 'Clock Format', 'required|integer'],
+ ['date_format', 'Date Format', 'required|integer'],
+ ['location', 'Location', ''],
+ ['lat', 'Latitude', 'required|integer'],
+ ['lng', 'Longitude', ''],
+ ['expire_at', 'Expire At', ''],
+ ]
+ }
+
+ User.validationEditRules = function () {
+ return [
+ ['id', 'ID', ''],
+ ['status', 'Status', ''],
+ ['first_name', 'First Name', ''],
+ ['last_name', 'Last Name', ''],
+ ['phone', 'Phone #', ''],
+ ['image', 'Image', ''],
+ ['image_id', 'Image ID', ''],
+ ['refer', 'Refer Code', ''],
+ ['profile_id', 'Profile ID', ''],
+ ['role_id', 'Role ID', ''],
+ ['stripe_uid', 'Stripe ID', ''],
+ ['paypal_uid', 'PayPal ID', ''],
+ ['font_color', 'Font Color', ''],
+ ['time_zone', 'Timezone', 'required'],
+ ['time_format', 'Time Format', 'required|integer'],
+ ['clock_format', 'Clock Format', 'required|integer'],
+ ['date_format', 'Date Format', 'required|integer'],
+ ['location', 'Location', ''],
+ ['lat', 'Latitude', ''],
+ ['lng', 'Longitude', ''],
+ ['expire_at', 'Expire At', ''],
+ ]
+ }
+
+ User.get_refer_log_paginated = function (db, ...rest) {
+ return User.getPaginated(...rest, [
+ {
+ model: db.refer_log,
+ as: 'refer_log',
+ },
+ ])
+ }
+
+ User.get_credential_paginated = function (db, ...rest) {
+ return User.getPaginated(...rest, [
+ {
+ model: db.credential,
+ as: 'credential',
+ },
+ ])
+ }
+
+ User.get_token_paginated = function (db, ...rest) {
+ return User.getPaginated(...rest, [
+ {
+ model: db.token,
+ as: 'token',
+ },
+ ])
+ }
+
+ User.get_code_paginated = function (db, ...rest) {
+ return User.getPaginated(...rest, [
+ {
+ model: db.code,
+ as: 'code',
+ },
+ ])
+ }
+
+ User.get_admin_operation_paginated = function (db, ...rest) {
+ return User.getPaginated(...rest, [
+ {
+ model: db.admin_operation,
+ as: 'admin_operation',
+ },
+ ])
+ }
+
+ User.get_member_operation_paginated = function (db, ...rest) {
+ return User.getPaginated(...rest, [
+ {
+ model: db.member_operation,
+ as: 'member_operation',
+ },
+ ])
+ }
+
+ User.get_image_paginated = function (db, ...rest) {
+ return User.getPaginated(...rest, [
+ {
+ model: db.image,
+ as: 'image',
+ },
+ ])
+ }
+
+ User.get_note_paginated = function (db, ...rest) {
+ return User.getPaginated(...rest, [
+ {
+ model: db.note,
+ as: 'note',
+ },
+ ])
+ }
+
+ User.get_calendar_paginated = function (db, ...rest) {
+ return User.getPaginated(...rest, [
+ {
+ model: db.calendar,
+ as: 'calendar',
+ },
+ ])
+ }
+
+ User.get_profile_paginated = function (db, ...rest) {
+ return User.getPaginated(...rest, [
+ {
+ model: db.profile,
+ as: 'profile',
+ },
+ ])
+ }
+
+ User.get_link_paginated = function (db, ...rest) {
+ return User.getPaginated(...rest, [
+ {
+ model: db.link,
+ as: 'link',
+ },
+ ])
+ }
+
+ User.get_user_refer_log = (id, db) => {
+ return User.findByPk(id, {
+ include: [{ model: db.refer_log, as: 'refer_log' }],
+ })
+ }
+ User.get_user_credential = (id, db) => {
+ return User.findByPk(id, {
+ include: [{ model: db.credential, as: 'credential' }],
+ })
+ }
+ User.get_user_token = (id, db) => {
+ return User.findByPk(id, { include: [{ model: db.token, as: 'token' }] })
+ }
+ User.get_user_code = (id, db) => {
+ return User.findByPk(id, { include: [{ model: db.code, as: 'code' }] })
+ }
+ User.get_user_admin_operation = (id, db) => {
+ return User.findByPk(id, {
+ include: [{ model: db.admin_operation, as: 'admin_operation' }],
+ })
+ }
+ User.get_user_member_operation = (id, db) => {
+ return User.findByPk(id, {
+ include: [{ model: db.member_operation, as: 'member_operation' }],
+ })
+ }
+ User.get_user_image = (id, db) => {
+ return User.findByPk(id, { include: [{ model: db.image, as: 'image' }] })
+ }
+ User.get_user_note = (id, db) => {
+ return User.findByPk(id, { include: [{ model: db.note, as: 'note' }] })
+ }
+ User.get_user_calendar = (id, db) => {
+ return User.findByPk(id, {
+ include: [{ model: db.calendar, as: 'calendar' }],
+ })
+ }
+ User.get_user_profile = (id, db) => {
+ return User.findByPk(id, {
+ include: [{ model: db.profile, as: 'profile' }],
+ })
+ }
+ User.get_user_link = (id, db) => {
+ return User.findByPk(id, { include: [{ model: db.link, as: 'link' }] })
+ }
+
+ // ex
+ User.intersection = function (fields) {
+ if (fields) {
+ return intersection(
+ [
+ 'id',
+ 'status',
+ 'first_name',
+ 'last_name',
+ 'phone',
+ 'image',
+ 'image_id',
+ 'refer',
+ 'profile_id',
+ 'role_id',
+ 'stripe_uid',
+ 'paypal_uid',
+ 'font_color',
+ 'time_zone',
+ 'time_format',
+ 'clock_format',
+ 'date_format',
+ 'location',
+ 'lat',
+ 'lng',
+ 'expire_at',
+ 'created_at',
+ 'updated_at',
+ ],
+ Object.keys(fields)
+ )
+ } else return []
+ }
+
+ return User
+}
diff --git a/day11/package.json b/day11/package.json
new file mode 100755
index 0000000..3fa5f37
--- /dev/null
+++ b/day11/package.json
@@ -0,0 +1,41 @@
+{
+ "name": "day11",
+ "version": "1.0.0",
+ "description": "",
+ "scripts": {},
+ "keywords": [],
+ "author": "Ryan Wong",
+ "private": true,
+ "license": "Private",
+ "dependencies": {
+ "apollo-server-express": "^2.11.0",
+ "axios": "^0.19.2",
+ "body-parser": "^1.19.0",
+ "cookie-parser": "~1.4.4",
+ "cors": "^2.8.5",
+ "dateformat": "^4.5.1",
+ "dotenv": "^8.2.0",
+ "eta": "^1.12",
+ "express": "^4.17.1",
+ "express-session": "^1.17.1",
+ "fast-csv": "^4.3.6",
+ "firebase-admin": "^8.12.1",
+ "fs-extra": "^9.1.0",
+ "graphql": "^15.5.1",
+ "graphql-fields": "^2.0.3",
+ "graphql-upload": "^12.0.0",
+ "helmet": "^3.19.0",
+ "jsonwebtoken": "^8.5.1",
+ "lodash": "^4.17.21",
+ "moment": "^2.26.0",
+ "morgan": "~1.9.1",
+ "mysql2": "^2.2.5",
+ "nanoid": "^3.2.0",
+ "node-input-validator": "^4.3.2",
+ "nodemailer": "^6.4.11",
+ "path": "^0.12.7",
+ "request-promise": "^4.2.6",
+ "sequelize": "^6.15.1",
+ "winston": "^3.3.3"
+ }
+}
diff --git a/day11/resolvers/.gitkeep b/day11/resolvers/.gitkeep
new file mode 100755
index 0000000..e69de29
diff --git a/day11/resolvers/all/.gitkeep b/day11/resolvers/all/.gitkeep
new file mode 100755
index 0000000..e69de29
diff --git a/day11/resolvers/all/allActivityLog.js b/day11/resolvers/all/allActivityLog.js
new file mode 100755
index 0000000..9e9ecb2
--- /dev/null
+++ b/day11/resolvers/all/allActivityLog.js
@@ -0,0 +1,56 @@
+"use strict";
+/*Powered By: Manaknightdigital Inc. https://manaknightdigital.com/ Year: 2021*/
+/**
+ * activity_log Resolve All
+ * @copyright 2021 Manaknightdigital Inc.
+ * @link https://manaknightdigital.com
+ * @license Proprietary Software licensing
+ * @author Ryan Wong
+ *
+ */
+const { ApolloError } = require('apollo-server-express');
+const Sequelize = require('sequelize');
+const { last } = require('lodash');
+const graphqlFields = require('graphql-fields');
+
+module.exports = async (_, { first, after }, { db, credential }, info) => {
+ //Check Auth if user allowed
+ try {
+ const attributes = db.activity_log.intersection(graphqlFields(info).edges.node);
+
+ const options = {
+ where: {},
+ limit: first,
+ attributes,
+ };
+
+ if (after) {
+ options.where = {
+ id: {
+ [Sequelize.Op.gt]: after,
+ },
+ };
+ }
+
+ const { count, rows } = await db.activity_log.findAndCountAll(options);
+
+ const edges = rows.map((activity_log) => ({
+ cursor: activity_log.id,
+ node: activity_log,
+ }));
+
+ const pageInfo = {
+ endCursor: last(edges).cursor,
+ hasNextPage: 0 < count - first,
+ };
+
+ return {
+ edges,
+ pageInfo,
+ };
+
+ } catch (error) {
+ console.log('activity_log -> error', error);
+ return new ApolloError('InternalServerError');
+ }
+}
diff --git a/day11/resolvers/all/allCalendar.js b/day11/resolvers/all/allCalendar.js
new file mode 100755
index 0000000..6663184
--- /dev/null
+++ b/day11/resolvers/all/allCalendar.js
@@ -0,0 +1,56 @@
+"use strict";
+/*Powered By: Manaknightdigital Inc. https://manaknightdigital.com/ Year: 2021*/
+/**
+ * calendar Resolve All
+ * @copyright 2021 Manaknightdigital Inc.
+ * @link https://manaknightdigital.com
+ * @license Proprietary Software licensing
+ * @author Ryan Wong
+ *
+ */
+const { ApolloError } = require('apollo-server-express');
+const Sequelize = require('sequelize');
+const { last } = require('lodash');
+const graphqlFields = require('graphql-fields');
+
+module.exports = async (_, { first, after }, { db, credential }, info) => {
+ //Check Auth if user allowed
+ try {
+ const attributes = db.calendar.intersection(graphqlFields(info).edges.node);
+
+ const options = {
+ where: {},
+ limit: first,
+ attributes,
+ };
+
+ if (after) {
+ options.where = {
+ id: {
+ [Sequelize.Op.gt]: after,
+ },
+ };
+ }
+
+ const { count, rows } = await db.calendar.findAndCountAll(options);
+
+ const edges = rows.map((calendar) => ({
+ cursor: calendar.id,
+ node: calendar,
+ }));
+
+ const pageInfo = {
+ endCursor: last(edges)?.cursor,
+ hasNextPage: 0 < count - first,
+ };
+
+ return {
+ edges,
+ pageInfo,
+ };
+
+ } catch (error) {
+ console.log('calendar -> error', error);
+ return new ApolloError('InternalServerError');
+ }
+}
diff --git a/day11/resolvers/all/allCode.js b/day11/resolvers/all/allCode.js
new file mode 100755
index 0000000..b11aa89
--- /dev/null
+++ b/day11/resolvers/all/allCode.js
@@ -0,0 +1,56 @@
+"use strict";
+/*Powered By: Manaknightdigital Inc. https://manaknightdigital.com/ Year: 2021*/
+/**
+ * code Resolve All
+ * @copyright 2021 Manaknightdigital Inc.
+ * @link https://manaknightdigital.com
+ * @license Proprietary Software licensing
+ * @author Ryan Wong
+ *
+ */
+const { ApolloError } = require('apollo-server-express');
+const Sequelize = require('sequelize');
+const { last } = require('lodash');
+const graphqlFields = require('graphql-fields');
+
+module.exports = async (_, { first, after }, { db, credential }, info) => {
+ //Check Auth if user allowed
+ try {
+ const attributes = db.code.intersection(graphqlFields(info).edges.node);
+
+ const options = {
+ where: {},
+ limit: first,
+ attributes,
+ };
+
+ if (after) {
+ options.where = {
+ id: {
+ [Sequelize.Op.gt]: after,
+ },
+ };
+ }
+
+ const { count, rows } = await db.code.findAndCountAll(options);
+
+ const edges = rows.map((code) => ({
+ cursor: code.id,
+ node: code,
+ }));
+
+ const pageInfo = {
+ endCursor: last(edges).cursor,
+ hasNextPage: 0 < count - first,
+ };
+
+ return {
+ edges,
+ pageInfo,
+ };
+
+ } catch (error) {
+ console.log('code -> error', error);
+ return new ApolloError('InternalServerError');
+ }
+}
diff --git a/day11/resolvers/all/allCredential.js b/day11/resolvers/all/allCredential.js
new file mode 100755
index 0000000..b2bf3d1
--- /dev/null
+++ b/day11/resolvers/all/allCredential.js
@@ -0,0 +1,56 @@
+"use strict";
+/*Powered By: Manaknightdigital Inc. https://manaknightdigital.com/ Year: 2021*/
+/**
+ * credential Resolve All
+ * @copyright 2021 Manaknightdigital Inc.
+ * @link https://manaknightdigital.com
+ * @license Proprietary Software licensing
+ * @author Ryan Wong
+ *
+ */
+const { ApolloError } = require('apollo-server-express');
+const Sequelize = require('sequelize');
+const { last } = require('lodash');
+const graphqlFields = require('graphql-fields');
+
+module.exports = async (_, { first, after }, { db, credential }, info) => {
+ //Check Auth if user allowed
+ try {
+ const attributes = db.credential.intersection(graphqlFields(info).edges.node);
+
+ const options = {
+ where: {},
+ limit: first,
+ attributes,
+ };
+
+ if (after) {
+ options.where = {
+ id: {
+ [Sequelize.Op.gt]: after,
+ },
+ };
+ }
+
+ const { count, rows } = await db.credential.findAndCountAll(options);
+
+ const edges = rows.map((credential) => ({
+ cursor: credential.id,
+ node: credential,
+ }));
+
+ const pageInfo = {
+ endCursor: last(edges).cursor,
+ hasNextPage: 0 < count - first,
+ };
+
+ return {
+ edges,
+ pageInfo,
+ };
+
+ } catch (error) {
+ console.log('credential -> error', error);
+ return new ApolloError('InternalServerError');
+ }
+}
diff --git a/day11/resolvers/all/allImage.js b/day11/resolvers/all/allImage.js
new file mode 100755
index 0000000..9dbaf2b
--- /dev/null
+++ b/day11/resolvers/all/allImage.js
@@ -0,0 +1,56 @@
+"use strict";
+/*Powered By: Manaknightdigital Inc. https://manaknightdigital.com/ Year: 2021*/
+/**
+ * image Resolve All
+ * @copyright 2021 Manaknightdigital Inc.
+ * @link https://manaknightdigital.com
+ * @license Proprietary Software licensing
+ * @author Ryan Wong
+ *
+ */
+const { ApolloError } = require('apollo-server-express');
+const Sequelize = require('sequelize');
+const { last } = require('lodash');
+const graphqlFields = require('graphql-fields');
+
+module.exports = async (_, { first, after }, { db, credential }, info) => {
+ //Check Auth if user allowed
+ try {
+ const attributes = db.image.intersection(graphqlFields(info).edges.node);
+
+ const options = {
+ where: {},
+ limit: first,
+ attributes,
+ };
+
+ if (after) {
+ options.where = {
+ id: {
+ [Sequelize.Op.gt]: after,
+ },
+ };
+ }
+
+ const { count, rows } = await db.image.findAndCountAll(options);
+
+ const edges = rows.map((image) => ({
+ cursor: image.id,
+ node: image,
+ }));
+
+ const pageInfo = {
+ endCursor: last(edges).cursor,
+ hasNextPage: 0 < count - first,
+ };
+
+ return {
+ edges,
+ pageInfo,
+ };
+
+ } catch (error) {
+ console.log('image -> error', error);
+ return new ApolloError('InternalServerError');
+ }
+}
diff --git a/day11/resolvers/all/allLink.js b/day11/resolvers/all/allLink.js
new file mode 100755
index 0000000..8f01210
--- /dev/null
+++ b/day11/resolvers/all/allLink.js
@@ -0,0 +1,56 @@
+"use strict";
+/*Powered By: Manaknightdigital Inc. https://manaknightdigital.com/ Year: 2021*/
+/**
+ * link Resolve All
+ * @copyright 2021 Manaknightdigital Inc.
+ * @link https://manaknightdigital.com
+ * @license Proprietary Software licensing
+ * @author Ryan Wong
+ *
+ */
+const { ApolloError } = require('apollo-server-express');
+const Sequelize = require('sequelize');
+const { last } = require('lodash');
+const graphqlFields = require('graphql-fields');
+
+module.exports = async (_, { first, after }, { db, credential }, info) => {
+ //Check Auth if user allowed
+ try {
+ const attributes = db.link.intersection(graphqlFields(info).edges.node);
+
+ const options = {
+ where: {},
+ limit: first,
+ attributes,
+ };
+
+ if (after) {
+ options.where = {
+ id: {
+ [Sequelize.Op.gt]: after,
+ },
+ };
+ }
+
+ const { count, rows } = await db.link.findAndCountAll(options);
+
+ const edges = rows.map((link) => ({
+ cursor: link.id,
+ node: link,
+ }));
+
+ const pageInfo = {
+ endCursor: last(edges)?.cursor,
+ hasNextPage: 0 < count - first,
+ };
+
+ return {
+ edges,
+ pageInfo,
+ };
+
+ } catch (error) {
+ console.log('link -> error', error);
+ return new ApolloError('InternalServerError');
+ }
+}
diff --git a/day11/resolvers/all/allLinks.js b/day11/resolvers/all/allLinks.js
new file mode 100755
index 0000000..8570792
--- /dev/null
+++ b/day11/resolvers/all/allLinks.js
@@ -0,0 +1,56 @@
+"use strict";
+/*Powered By: Manaknightdigital Inc. https://manaknightdigital.com/ Year: 2021*/
+/**
+ * links Resolve All
+ * @copyright 2021 Manaknightdigital Inc.
+ * @link https://manaknightdigital.com
+ * @license Proprietary Software licensing
+ * @author Ryan Wong
+ *
+ */
+const { ApolloError } = require('apollo-server-express');
+const Sequelize = require('sequelize');
+const { last } = require('lodash');
+const graphqlFields = require('graphql-fields');
+
+module.exports = async (_, { first, after }, { db, credential }, info) => {
+ //Check Auth if user allowed
+ try {
+ const attributes = db.links.intersection(graphqlFields(info).edges.node);
+
+ const options = {
+ where: {},
+ limit: first,
+ attributes,
+ };
+
+ if (after) {
+ options.where = {
+ id: {
+ [Sequelize.Op.gt]: after,
+ },
+ };
+ }
+
+ const { count, rows } = await db.links.findAndCountAll(options);
+
+ const edges = rows.map((links) => ({
+ cursor: links.id,
+ node: links,
+ }));
+
+ const pageInfo = {
+ endCursor: last(edges).cursor,
+ hasNextPage: 0 < count - first,
+ };
+
+ return {
+ edges,
+ pageInfo,
+ };
+
+ } catch (error) {
+ console.log('links -> error', error);
+ return new ApolloError('InternalServerError');
+ }
+}
diff --git a/day11/resolvers/all/allNote.js b/day11/resolvers/all/allNote.js
new file mode 100755
index 0000000..b75d166
--- /dev/null
+++ b/day11/resolvers/all/allNote.js
@@ -0,0 +1,56 @@
+"use strict";
+/*Powered By: Manaknightdigital Inc. https://manaknightdigital.com/ Year: 2021*/
+/**
+ * note Resolve All
+ * @copyright 2021 Manaknightdigital Inc.
+ * @link https://manaknightdigital.com
+ * @license Proprietary Software licensing
+ * @author Ryan Wong
+ *
+ */
+const { ApolloError } = require('apollo-server-express');
+const Sequelize = require('sequelize');
+const { last } = require('lodash');
+const graphqlFields = require('graphql-fields');
+
+module.exports = async (_, { first, after }, { db, credential }, info) => {
+ //Check Auth if user allowed
+ try {
+ const attributes = db.note.intersection(graphqlFields(info).edges.node);
+
+ const options = {
+ where: {},
+ limit: first,
+ attributes,
+ };
+
+ if (after) {
+ options.where = {
+ id: {
+ [Sequelize.Op.gt]: after,
+ },
+ };
+ }
+
+ const { count, rows } = await db.note.findAndCountAll(options);
+
+ const edges = rows.map((note) => ({
+ cursor: note.id,
+ node: note,
+ }));
+
+ const pageInfo = {
+ endCursor: last(edges)?.cursor,
+ hasNextPage: 0 < count - first,
+ };
+
+ return {
+ edges,
+ pageInfo,
+ };
+
+ } catch (error) {
+ console.log('note -> error', error);
+ return new ApolloError('InternalServerError');
+ }
+}
diff --git a/day11/resolvers/all/allNotes.js b/day11/resolvers/all/allNotes.js
new file mode 100755
index 0000000..07d7881
--- /dev/null
+++ b/day11/resolvers/all/allNotes.js
@@ -0,0 +1,57 @@
+'use strict';
+/*Powered By: Manaknightdigital Inc. https://manaknightdigital.com/ Year: 2021*/
+/**
+ * notes Resolve All
+ * @copyright 2021 Manaknightdigital Inc.
+ * @link https://manaknightdigital.com
+ * @license Proprietary Software licensing
+ * @author Ryan Wong
+ *
+ */
+const { ApolloError } = require('apollo-server-express');
+const Sequelize = require('sequelize');
+const { last } = require('lodash');
+const graphqlFields = require('graphql-fields');
+const { QueryTypes } = require('sequelize');
+
+module.exports = async (_, { first, after }, { db, credential }, info) => {
+ //Check Auth if user allowed
+ try {
+ const attributes = db.notes.intersection(graphqlFields(info).edges.node);
+
+ const options = {
+ where: {},
+ limit: first,
+ attributes,
+ };
+ const users = await sequelize.query('SELECT * FROM `notes`', { type: QueryTypes.SELECT });
+ console.log('Notes are: ' + users);
+ if (after) {
+ options.where = {
+ id: {
+ [Sequelize.Op.gt]: after,
+ },
+ };
+ }
+
+ const { count, rows } = await db.notes.findAndCountAll(options);
+
+ const edges = rows.map((notes) => ({
+ cursor: notes.id,
+ node: notes,
+ }));
+
+ const pageInfo = {
+ endCursor: last(edges).cursor,
+ hasNextPage: 0 < count - first,
+ };
+
+ return {
+ edges,
+ pageInfo,
+ };
+ } catch (error) {
+ console.log('notes -> error', error);
+ return new ApolloError('InternalServerError');
+ }
+};
diff --git a/day11/resolvers/all/allProfile.js b/day11/resolvers/all/allProfile.js
new file mode 100755
index 0000000..1a87ea0
--- /dev/null
+++ b/day11/resolvers/all/allProfile.js
@@ -0,0 +1,56 @@
+"use strict";
+/*Powered By: Manaknightdigital Inc. https://manaknightdigital.com/ Year: 2021*/
+/**
+ * profile Resolve All
+ * @copyright 2021 Manaknightdigital Inc.
+ * @link https://manaknightdigital.com
+ * @license Proprietary Software licensing
+ * @author Ryan Wong
+ *
+ */
+const { ApolloError } = require('apollo-server-express');
+const Sequelize = require('sequelize');
+const { last } = require('lodash');
+const graphqlFields = require('graphql-fields');
+
+module.exports = async (_, { first, after }, { db, credential }, info) => {
+ //Check Auth if user allowed
+ try {
+ const attributes = db.profile.intersection(graphqlFields(info).edges.node);
+
+ const options = {
+ where: {},
+ limit: first,
+ attributes,
+ };
+
+ if (after) {
+ options.where = {
+ id: {
+ [Sequelize.Op.gt]: after,
+ },
+ };
+ }
+
+ const { count, rows } = await db.profile.findAndCountAll(options);
+
+ const edges = rows.map((profile) => ({
+ cursor: profile.id,
+ node: profile,
+ }));
+
+ const pageInfo = {
+ endCursor: last(edges).cursor,
+ hasNextPage: 0 < count - first,
+ };
+
+ return {
+ edges,
+ pageInfo,
+ };
+
+ } catch (error) {
+ console.log('profile -> error', error);
+ return new ApolloError('InternalServerError');
+ }
+}
diff --git a/day11/resolvers/all/allReferLog.js b/day11/resolvers/all/allReferLog.js
new file mode 100755
index 0000000..adcc577
--- /dev/null
+++ b/day11/resolvers/all/allReferLog.js
@@ -0,0 +1,56 @@
+"use strict";
+/*Powered By: Manaknightdigital Inc. https://manaknightdigital.com/ Year: 2021*/
+/**
+ * refer_log Resolve All
+ * @copyright 2021 Manaknightdigital Inc.
+ * @link https://manaknightdigital.com
+ * @license Proprietary Software licensing
+ * @author Ryan Wong
+ *
+ */
+const { ApolloError } = require('apollo-server-express');
+const Sequelize = require('sequelize');
+const { last } = require('lodash');
+const graphqlFields = require('graphql-fields');
+
+module.exports = async (_, { first, after }, { db, credential }, info) => {
+ //Check Auth if user allowed
+ try {
+ const attributes = db.refer_log.intersection(graphqlFields(info).edges.node);
+
+ const options = {
+ where: {},
+ limit: first,
+ attributes,
+ };
+
+ if (after) {
+ options.where = {
+ id: {
+ [Sequelize.Op.gt]: after,
+ },
+ };
+ }
+
+ const { count, rows } = await db.refer_log.findAndCountAll(options);
+
+ const edges = rows.map((refer_log) => ({
+ cursor: refer_log.id,
+ node: refer_log,
+ }));
+
+ const pageInfo = {
+ endCursor: last(edges).cursor,
+ hasNextPage: 0 < count - first,
+ };
+
+ return {
+ edges,
+ pageInfo,
+ };
+
+ } catch (error) {
+ console.log('refer_log -> error', error);
+ return new ApolloError('InternalServerError');
+ }
+}
diff --git a/day11/resolvers/all/allUser.js b/day11/resolvers/all/allUser.js
new file mode 100755
index 0000000..2bf4480
--- /dev/null
+++ b/day11/resolvers/all/allUser.js
@@ -0,0 +1,56 @@
+"use strict";
+/*Powered By: Manaknightdigital Inc. https://manaknightdigital.com/ Year: 2021*/
+/**
+ * user Resolve All
+ * @copyright 2021 Manaknightdigital Inc.
+ * @link https://manaknightdigital.com
+ * @license Proprietary Software licensing
+ * @author Ryan Wong
+ *
+ */
+const { ApolloError } = require('apollo-server-express');
+const Sequelize = require('sequelize');
+const { last } = require('lodash');
+const graphqlFields = require('graphql-fields');
+
+module.exports = async (_, { first, after }, { db, credential }, info) => {
+ //Check Auth if user allowed
+ try {
+ const attributes = db.user.intersection(graphqlFields(info).edges.node);
+
+ const options = {
+ where: {},
+ limit: first,
+ attributes,
+ };
+
+ if (after) {
+ options.where = {
+ id: {
+ [Sequelize.Op.gt]: after,
+ },
+ };
+ }
+
+ const { count, rows } = await db.user.findAndCountAll(options);
+
+ const edges = rows.map((user) => ({
+ cursor: user.id,
+ node: user,
+ }));
+
+ const pageInfo = {
+ endCursor: last(edges).cursor,
+ hasNextPage: 0 < count - first,
+ };
+
+ return {
+ edges,
+ pageInfo,
+ };
+
+ } catch (error) {
+ console.log('user -> error', error);
+ return new ApolloError('InternalServerError');
+ }
+}
diff --git a/day11/resolvers/create/.gitkeep b/day11/resolvers/create/.gitkeep
new file mode 100755
index 0000000..e69de29
diff --git a/day11/resolvers/create/createActivityLog.js b/day11/resolvers/create/createActivityLog.js
new file mode 100755
index 0000000..61f66c7
--- /dev/null
+++ b/day11/resolvers/create/createActivityLog.js
@@ -0,0 +1,36 @@
+"use strict";
+/*Powered By: Manaknightdigital Inc. https://manaknightdigital.com/ Year: 2021*/
+/**
+ * activity_log Resolve Add
+ * @copyright 2021 Manaknightdigital Inc.
+ * @link https://manaknightdigital.com
+ * @license Proprietary Software licensing
+ * @author Ryan Wong
+ *
+ */
+
+
+
+const { ApolloError ,UserInputError} = require('apollo-server-express');
+const { Validator } = require('node-input-validator');
+
+module.exports = async (parent, args, {db}, info) => {
+ try {
+ const { name } = args;
+ const v = new Validator({ }, { });
+
+ v.check().then(function (matched) {
+ if (!matched) {
+ Object.keys(v.errors).forEach((error) => {
+ return new UserInputError(v.errors[error].message);
+ });
+ }
+ });
+
+
+ return await db.activity_log.insert({ name },{returnAllFields: true});
+ } catch (error) {
+ console.log('create_activity_log -> error', error);
+ return new ApolloError('InternalServerError');
+ }
+};
diff --git a/day11/resolvers/create/createCalendar.js b/day11/resolvers/create/createCalendar.js
new file mode 100755
index 0000000..60a388e
--- /dev/null
+++ b/day11/resolvers/create/createCalendar.js
@@ -0,0 +1,36 @@
+"use strict";
+/*Powered By: Manaknightdigital Inc. https://manaknightdigital.com/ Year: 2021*/
+/**
+ * calendar Resolve Add
+ * @copyright 2021 Manaknightdigital Inc.
+ * @link https://manaknightdigital.com
+ * @license Proprietary Software licensing
+ * @author Ryan Wong
+ *
+ */
+
+
+
+const { ApolloError ,UserInputError} = require('apollo-server-express');
+const { Validator } = require('node-input-validator');
+
+module.exports = async (parent, args, {db}, info) => {
+ try {
+ const { } = args;
+ const v = new Validator({ }, { });
+
+ v.check().then(function (matched) {
+ if (!matched) {
+ Object.keys(v.errors).forEach((error) => {
+ return new UserInputError(v.errors[error].message);
+ });
+ }
+ });
+
+
+ return await db.calendar.insert({ },{returnAllFields: true});
+ } catch (error) {
+ console.log('create_calendar -> error', error);
+ return new ApolloError('InternalServerError');
+ }
+};
diff --git a/day11/resolvers/create/createCode.js b/day11/resolvers/create/createCode.js
new file mode 100755
index 0000000..86941ac
--- /dev/null
+++ b/day11/resolvers/create/createCode.js
@@ -0,0 +1,36 @@
+"use strict";
+/*Powered By: Manaknightdigital Inc. https://manaknightdigital.com/ Year: 2021*/
+/**
+ * code Resolve Add
+ * @copyright 2021 Manaknightdigital Inc.
+ * @link https://manaknightdigital.com
+ * @license Proprietary Software licensing
+ * @author Ryan Wong
+ *
+ */
+
+
+
+const { ApolloError ,UserInputError} = require('apollo-server-express');
+const { Validator } = require('node-input-validator');
+
+module.exports = async (parent, args, {db}, info) => {
+ try {
+ const { code } = args;
+ const v = new Validator({ code: args.code}, { code: "required" });
+
+ v.check().then(function (matched) {
+ if (!matched) {
+ Object.keys(v.errors).forEach((error) => {
+ return new UserInputError(v.errors[error].message);
+ });
+ }
+ });
+
+
+ return await db.code.insert({ code },{returnAllFields: true});
+ } catch (error) {
+ console.log('create_code -> error', error);
+ return new ApolloError('InternalServerError');
+ }
+};
diff --git a/day11/resolvers/create/createCredential.js b/day11/resolvers/create/createCredential.js
new file mode 100755
index 0000000..dc8fe0f
--- /dev/null
+++ b/day11/resolvers/create/createCredential.js
@@ -0,0 +1,40 @@
+"use strict";
+/*Powered By: Manaknightdigital Inc. https://manaknightdigital.com/ Year: 2021*/
+/**
+ * credential Resolve Add
+ * @copyright 2021 Manaknightdigital Inc.
+ * @link https://manaknightdigital.com
+ * @license Proprietary Software licensing
+ * @author Ryan Wong
+ *
+ */
+
+
+
+const { ApolloError ,UserInputError} = require('apollo-server-express');
+const { Validator } = require('node-input-validator');
+
+module.exports = async (parent, args, {db}, info) => {
+ try {
+ const { email,
+ password } = args;
+ const v = new Validator({ email: args.email,
+ password: args.password}, { email: "required|valid_email",
+ password: "required" });
+
+ v.check().then(function (matched) {
+ if (!matched) {
+ Object.keys(v.errors).forEach((error) => {
+ return new UserInputError(v.errors[error].message);
+ });
+ }
+ });
+
+
+ return await db.credential.insert({ email,
+ password },{returnAllFields: true});
+ } catch (error) {
+ console.log('create_credential -> error', error);
+ return new ApolloError('InternalServerError');
+ }
+};
diff --git a/day11/resolvers/create/createImage.js b/day11/resolvers/create/createImage.js
new file mode 100755
index 0000000..112bab9
--- /dev/null
+++ b/day11/resolvers/create/createImage.js
@@ -0,0 +1,38 @@
+"use strict";
+/*Powered By: Manaknightdigital Inc. https://manaknightdigital.com/ Year: 2021*/
+/**
+ * image Resolve Add
+ * @copyright 2021 Manaknightdigital Inc.
+ * @link https://manaknightdigital.com
+ * @license Proprietary Software licensing
+ * @author Ryan Wong
+ *
+ */
+
+
+
+const { ApolloError ,UserInputError} = require('apollo-server-express');
+const { Validator } = require('node-input-validator');
+
+module.exports = async (parent, args, {db}, info) => {
+ try {
+ const { url,
+ caption } = args;
+ const v = new Validator({ url: args.url}, { url: "required" });
+
+ v.check().then(function (matched) {
+ if (!matched) {
+ Object.keys(v.errors).forEach((error) => {
+ return new UserInputError(v.errors[error].message);
+ });
+ }
+ });
+
+
+ return await db.image.insert({ url,
+ caption },{returnAllFields: true});
+ } catch (error) {
+ console.log('create_image -> error', error);
+ return new ApolloError('InternalServerError');
+ }
+};
diff --git a/day11/resolvers/create/createLink.js b/day11/resolvers/create/createLink.js
new file mode 100755
index 0000000..b84197e
--- /dev/null
+++ b/day11/resolvers/create/createLink.js
@@ -0,0 +1,73 @@
+'use strict';
+/*Powered By: Manaknightdigital Inc. https://manaknightdigital.com/ Year: 2021*/
+/**
+ * links Resolve Add
+ * @copyright 2021 Manaknightdigital Inc.
+ * @link https://manaknightdigital.com
+ * @license Proprietary Software licensing
+ * @author Ryan Wong
+ *
+ */
+
+const request = require('request-promise');
+
+const { validateInputForGraphql } = require('../../services/ValidationService');
+const { formatError } = require('../../utils/formatError');
+const { errorCodes } = require('../../core/strings');
+
+const inputValidations = {
+ Mutation: {
+ createLink: (resolver = () => null) => {
+ return validateInputForGraphql(
+ resolver,
+ {
+ link: 'required|string',
+ },
+ {
+ 'link.required': 'Link field is required.',
+ 'link.string': 'Link field should be a string.',
+ },
+ );
+ },
+ },
+};
+
+module.exports = inputValidations.Mutation.createLink(async (_, { link }, { db, user }) => {
+ try {
+ const previousLinks = await db.link.getAll({ user_id: user.id, status: 1 });
+ try {
+ const urlResponse = await request({ uri: link, resolveWithFullResponse: true, method: 'GET' });
+ const headers = urlResponse?.headers;
+ const xFrameHeader = headers['x-frame-options'];
+ if (xFrameHeader && (xFrameHeader === 'SAMEORIGIN' || xFrameHeader === 'DENY')) {
+ return {
+ success: false,
+ message: 'Iframe blocked to given link.',
+ code: errorCodes.extra.IFRAME_BLOCKED,
+ };
+ }
+ } catch (error) {
+ return {
+ success: false,
+ message: 'Link invalid or not available at the moment.',
+ code: errorCodes.extra.INVALID_URL,
+ };
+ }
+
+ if (previousLinks?.length) {
+ await db.link.update(
+ { status: 0 },
+ {
+ where: { id: previousLinks?.map((link) => link.id) },
+ },
+ );
+ }
+ await db.link.insert({ link, user_id: user.id });
+ return {
+ success: true,
+ message: 'Link inserted successfully.',
+ };
+ } catch (error) {
+ return formatError(error);
+ }
+});
diff --git a/day11/resolvers/create/createNote.js b/day11/resolvers/create/createNote.js
new file mode 100755
index 0000000..316a1c5
--- /dev/null
+++ b/day11/resolvers/create/createNote.js
@@ -0,0 +1,36 @@
+"use strict";
+/*Powered By: Manaknightdigital Inc. https://manaknightdigital.com/ Year: 2021*/
+/**
+ * note Resolve Add
+ * @copyright 2021 Manaknightdigital Inc.
+ * @link https://manaknightdigital.com
+ * @license Proprietary Software licensing
+ * @author Ryan Wong
+ *
+ */
+
+
+
+const { ApolloError ,UserInputError} = require('apollo-server-express');
+const { Validator } = require('node-input-validator');
+
+module.exports = async (parent, args, {db}, info) => {
+ try {
+ const { } = args;
+ const v = new Validator({ }, { });
+
+ v.check().then(function (matched) {
+ if (!matched) {
+ Object.keys(v.errors).forEach((error) => {
+ return new UserInputError(v.errors[error].message);
+ });
+ }
+ });
+
+
+ return await db.note.insert({ },{returnAllFields: true});
+ } catch (error) {
+ console.log('create_note -> error', error);
+ return new ApolloError('InternalServerError');
+ }
+};
diff --git a/day11/resolvers/create/createProfile.js b/day11/resolvers/create/createProfile.js
new file mode 100755
index 0000000..45cade5
--- /dev/null
+++ b/day11/resolvers/create/createProfile.js
@@ -0,0 +1,40 @@
+"use strict";
+/*Powered By: Manaknightdigital Inc. https://manaknightdigital.com/ Year: 2021*/
+/**
+ * profile Resolve Add
+ * @copyright 2021 Manaknightdigital Inc.
+ * @link https://manaknightdigital.com
+ * @license Proprietary Software licensing
+ * @author Ryan Wong
+ *
+ */
+
+
+
+const { ApolloError ,UserInputError} = require('apollo-server-express');
+const { Validator } = require('node-input-validator');
+
+module.exports = async (parent, args, {db}, info) => {
+ try {
+ const { timezone,
+ dashboard_code } = args;
+ const v = new Validator({ timezone: args.timezone,
+ dashboard_code: args.dashboard_code}, { timezone: "required",
+ dashboard_code: "required" });
+
+ v.check().then(function (matched) {
+ if (!matched) {
+ Object.keys(v.errors).forEach((error) => {
+ return new UserInputError(v.errors[error].message);
+ });
+ }
+ });
+
+
+ return await db.profile.insert({ timezone,
+ dashboard_code },{returnAllFields: true});
+ } catch (error) {
+ console.log('create_profile -> error', error);
+ return new ApolloError('InternalServerError');
+ }
+};
diff --git a/day11/resolvers/create/createReferLog.js b/day11/resolvers/create/createReferLog.js
new file mode 100755
index 0000000..9d56ee3
--- /dev/null
+++ b/day11/resolvers/create/createReferLog.js
@@ -0,0 +1,40 @@
+"use strict";
+/*Powered By: Manaknightdigital Inc. https://manaknightdigital.com/ Year: 2021*/
+/**
+ * refer_log Resolve Add
+ * @copyright 2021 Manaknightdigital Inc.
+ * @link https://manaknightdigital.com
+ * @license Proprietary Software licensing
+ * @author Ryan Wong
+ *
+ */
+
+
+
+const { ApolloError ,UserInputError} = require('apollo-server-express');
+const { Validator } = require('node-input-validator');
+
+module.exports = async (parent, args, {db}, info) => {
+ try {
+ const { type,
+ status } = args;
+ const v = new Validator({ type: args.type,
+ status: args.status}, { type: "required|integer",
+ status: "required|integer" });
+
+ v.check().then(function (matched) {
+ if (!matched) {
+ Object.keys(v.errors).forEach((error) => {
+ return new UserInputError(v.errors[error].message);
+ });
+ }
+ });
+
+
+ return await db.refer_log.insert({ type,
+ status },{returnAllFields: true});
+ } catch (error) {
+ console.log('create_refer_log -> error', error);
+ return new ApolloError('InternalServerError');
+ }
+};
diff --git a/day11/resolvers/create/createUser.js b/day11/resolvers/create/createUser.js
new file mode 100755
index 0000000..3daceca
--- /dev/null
+++ b/day11/resolvers/create/createUser.js
@@ -0,0 +1,39 @@
+'use strict';
+/*Powered By: Manaknightdigital Inc. https://manaknightdigital.com/ Year: 2021*/
+/**
+ * user Resolve Add
+ * @copyright 2021 Manaknightdigital Inc.
+ * @link https://manaknightdigital.com
+ * @license Proprietary Software licensing
+ * @author Ryan Wong
+ *
+ */
+
+const { ApolloError, UserInputError } = require('apollo-server-express');
+const { Validator } = require('node-input-validator');
+
+module.exports = async (parent, args, { db }, info) => {
+ try {
+ const { first_name, last_name, phone } = args;
+ const v = new Validator(
+ {
+ first_name: args.first_name,
+ last_name: args.last_name,
+ },
+ { first_name: 'required', last_name: 'required' },
+ );
+
+ v.check().then(function (matched) {
+ if (!matched) {
+ Object.keys(v.errors).forEach((error) => {
+ return new UserInputError(v.errors[error].message);
+ });
+ }
+ });
+
+ return await db.user.insert({ first_name, last_name, phone }, { returnAllFields: true });
+ } catch (error) {
+ console.log('create_user -> error', error);
+ return new ApolloError('InternalServerError');
+ }
+};
diff --git a/day11/resolvers/custom/.gitkeep b/day11/resolvers/custom/.gitkeep
new file mode 100755
index 0000000..e69de29
diff --git a/day11/resolvers/delete/.gitkeep b/day11/resolvers/delete/.gitkeep
new file mode 100755
index 0000000..e69de29
diff --git a/day11/resolvers/delete/deactivateAllLinks.js b/day11/resolvers/delete/deactivateAllLinks.js
new file mode 100755
index 0000000..795a47f
--- /dev/null
+++ b/day11/resolvers/delete/deactivateAllLinks.js
@@ -0,0 +1,32 @@
+'use strict';
+/*Powered By: Manaknightdigital Inc. https://manaknightdigital.com/ Year: 2021*/
+/**
+ * links Resolve Deactivate
+ * @copyright 2021 Manaknightdigital Inc.
+ * @link https://manaknightdigital.com
+ * @license Proprietary Software licensing
+ * @author Ryan Wong
+ *
+ */
+
+const { formatError } = require('../../utils/formatError');
+
+module.exports = async (_, __, { db, user }) => {
+ try {
+ const previousLinks = await db.link.getAll({ user_id: user.id, status: 1 });
+ if (previousLinks?.length) {
+ await db.link.update(
+ { status: 0 },
+ {
+ where: { id: previousLinks?.map((link) => link.id) },
+ },
+ );
+ }
+ return {
+ success: true,
+ message: 'All links deactivated successfully.',
+ };
+ } catch (error) {
+ return formatError(error);
+ }
+};
diff --git a/day11/resolvers/delete/deleteActivityLog.js b/day11/resolvers/delete/deleteActivityLog.js
new file mode 100755
index 0000000..325aed2
--- /dev/null
+++ b/day11/resolvers/delete/deleteActivityLog.js
@@ -0,0 +1,23 @@
+"use strict";
+/*Powered By: Manaknightdigital Inc. https://manaknightdigital.com/ Year: 2021*/
+/**
+ * activity_log Resolve Delete
+ * @copyright 2021 Manaknightdigital Inc.
+ * @link https://manaknightdigital.com
+ * @license Proprietary Software licensing
+ * @author Ryan Wong
+ *
+ */
+
+const { ApolloError } = require('apollo-server-express');
+
+module.exports = async (parent, args, {db}, info) => {
+ //Check Auth if user allowed
+ try {
+
+ return await db.activity_log.realDelete(args.id);
+ } catch (error) {
+ console.log('delete_activity_log -> error', error);
+ return new ApolloError('InternalServerError');
+ }
+}
\ No newline at end of file
diff --git a/day11/resolvers/delete/deleteCalendar.js b/day11/resolvers/delete/deleteCalendar.js
new file mode 100755
index 0000000..40b48ac
--- /dev/null
+++ b/day11/resolvers/delete/deleteCalendar.js
@@ -0,0 +1,23 @@
+"use strict";
+/*Powered By: Manaknightdigital Inc. https://manaknightdigital.com/ Year: 2021*/
+/**
+ * calendar Resolve Delete
+ * @copyright 2021 Manaknightdigital Inc.
+ * @link https://manaknightdigital.com
+ * @license Proprietary Software licensing
+ * @author Ryan Wong
+ *
+ */
+
+const { ApolloError } = require('apollo-server-express');
+
+module.exports = async (parent, args, {db}, info) => {
+ //Check Auth if user allowed
+ try {
+
+ return await db.calendar.realDelete(args.id);
+ } catch (error) {
+ console.log('delete_calendar -> error', error);
+ return new ApolloError('InternalServerError');
+ }
+}
\ No newline at end of file
diff --git a/day11/resolvers/delete/deleteCode.js b/day11/resolvers/delete/deleteCode.js
new file mode 100755
index 0000000..4faf7d3
--- /dev/null
+++ b/day11/resolvers/delete/deleteCode.js
@@ -0,0 +1,23 @@
+"use strict";
+/*Powered By: Manaknightdigital Inc. https://manaknightdigital.com/ Year: 2021*/
+/**
+ * code Resolve Delete
+ * @copyright 2021 Manaknightdigital Inc.
+ * @link https://manaknightdigital.com
+ * @license Proprietary Software licensing
+ * @author Ryan Wong
+ *
+ */
+
+const { ApolloError } = require('apollo-server-express');
+
+module.exports = async (parent, args, {db}, info) => {
+ //Check Auth if user allowed
+ try {
+
+ return await db.code.realDelete(args.id);
+ } catch (error) {
+ console.log('delete_code -> error', error);
+ return new ApolloError('InternalServerError');
+ }
+}
\ No newline at end of file
diff --git a/day11/resolvers/delete/deleteCredential.js b/day11/resolvers/delete/deleteCredential.js
new file mode 100755
index 0000000..70224cc
--- /dev/null
+++ b/day11/resolvers/delete/deleteCredential.js
@@ -0,0 +1,23 @@
+"use strict";
+/*Powered By: Manaknightdigital Inc. https://manaknightdigital.com/ Year: 2021*/
+/**
+ * credential Resolve Delete
+ * @copyright 2021 Manaknightdigital Inc.
+ * @link https://manaknightdigital.com
+ * @license Proprietary Software licensing
+ * @author Ryan Wong
+ *
+ */
+
+const { ApolloError } = require('apollo-server-express');
+
+module.exports = async (parent, args, {db}, info) => {
+ //Check Auth if user allowed
+ try {
+
+ return await db.credential.realDelete(args.id);
+ } catch (error) {
+ console.log('delete_credential -> error', error);
+ return new ApolloError('InternalServerError');
+ }
+}
\ No newline at end of file
diff --git a/day11/resolvers/delete/deleteImage.js b/day11/resolvers/delete/deleteImage.js
new file mode 100755
index 0000000..082245c
--- /dev/null
+++ b/day11/resolvers/delete/deleteImage.js
@@ -0,0 +1,23 @@
+"use strict";
+/*Powered By: Manaknightdigital Inc. https://manaknightdigital.com/ Year: 2021*/
+/**
+ * image Resolve Delete
+ * @copyright 2021 Manaknightdigital Inc.
+ * @link https://manaknightdigital.com
+ * @license Proprietary Software licensing
+ * @author Ryan Wong
+ *
+ */
+
+const { ApolloError } = require('apollo-server-express');
+
+module.exports = async (parent, args, {db}, info) => {
+ //Check Auth if user allowed
+ try {
+
+ return await db.image.realDelete(args.id);
+ } catch (error) {
+ console.log('delete_image -> error', error);
+ return new ApolloError('InternalServerError');
+ }
+}
\ No newline at end of file
diff --git a/day11/resolvers/delete/deleteLink.js b/day11/resolvers/delete/deleteLink.js
new file mode 100755
index 0000000..6903018
--- /dev/null
+++ b/day11/resolvers/delete/deleteLink.js
@@ -0,0 +1,23 @@
+"use strict";
+/*Powered By: Manaknightdigital Inc. https://manaknightdigital.com/ Year: 2021*/
+/**
+ * link Resolve Delete
+ * @copyright 2021 Manaknightdigital Inc.
+ * @link https://manaknightdigital.com
+ * @license Proprietary Software licensing
+ * @author Ryan Wong
+ *
+ */
+
+const { ApolloError } = require('apollo-server-express');
+
+module.exports = async (parent, args, {db}, info) => {
+ //Check Auth if user allowed
+ try {
+
+ return await db.link.realDelete(args.id);
+ } catch (error) {
+ console.log('delete_link -> error', error);
+ return new ApolloError('InternalServerError');
+ }
+}
\ No newline at end of file
diff --git a/day11/resolvers/delete/deleteNote.js b/day11/resolvers/delete/deleteNote.js
new file mode 100755
index 0000000..39f04a9
--- /dev/null
+++ b/day11/resolvers/delete/deleteNote.js
@@ -0,0 +1,23 @@
+"use strict";
+/*Powered By: Manaknightdigital Inc. https://manaknightdigital.com/ Year: 2021*/
+/**
+ * note Resolve Delete
+ * @copyright 2021 Manaknightdigital Inc.
+ * @link https://manaknightdigital.com
+ * @license Proprietary Software licensing
+ * @author Ryan Wong
+ *
+ */
+
+const { ApolloError } = require('apollo-server-express');
+
+module.exports = async (parent, args, {db}, info) => {
+ //Check Auth if user allowed
+ try {
+
+ return await db.note.realDelete(args.id);
+ } catch (error) {
+ console.log('delete_note -> error', error);
+ return new ApolloError('InternalServerError');
+ }
+}
\ No newline at end of file
diff --git a/day11/resolvers/delete/deleteProfile.js b/day11/resolvers/delete/deleteProfile.js
new file mode 100755
index 0000000..54f8387
--- /dev/null
+++ b/day11/resolvers/delete/deleteProfile.js
@@ -0,0 +1,23 @@
+"use strict";
+/*Powered By: Manaknightdigital Inc. https://manaknightdigital.com/ Year: 2021*/
+/**
+ * profile Resolve Delete
+ * @copyright 2021 Manaknightdigital Inc.
+ * @link https://manaknightdigital.com
+ * @license Proprietary Software licensing
+ * @author Ryan Wong
+ *
+ */
+
+const { ApolloError } = require('apollo-server-express');
+
+module.exports = async (parent, args, {db}, info) => {
+ //Check Auth if user allowed
+ try {
+
+ return await db.profile.realDelete(args.id);
+ } catch (error) {
+ console.log('delete_profile -> error', error);
+ return new ApolloError('InternalServerError');
+ }
+}
\ No newline at end of file
diff --git a/day11/resolvers/delete/deleteReferLog.js b/day11/resolvers/delete/deleteReferLog.js
new file mode 100755
index 0000000..d352e4f
--- /dev/null
+++ b/day11/resolvers/delete/deleteReferLog.js
@@ -0,0 +1,23 @@
+"use strict";
+/*Powered By: Manaknightdigital Inc. https://manaknightdigital.com/ Year: 2021*/
+/**
+ * refer_log Resolve Delete
+ * @copyright 2021 Manaknightdigital Inc.
+ * @link https://manaknightdigital.com
+ * @license Proprietary Software licensing
+ * @author Ryan Wong
+ *
+ */
+
+const { ApolloError } = require('apollo-server-express');
+
+module.exports = async (parent, args, {db}, info) => {
+ //Check Auth if user allowed
+ try {
+
+ return await db.refer_log.realDelete(args.id);
+ } catch (error) {
+ console.log('delete_refer_log -> error', error);
+ return new ApolloError('InternalServerError');
+ }
+}
\ No newline at end of file
diff --git a/day11/resolvers/delete/deleteUser.js b/day11/resolvers/delete/deleteUser.js
new file mode 100755
index 0000000..34f7503
--- /dev/null
+++ b/day11/resolvers/delete/deleteUser.js
@@ -0,0 +1,23 @@
+"use strict";
+/*Powered By: Manaknightdigital Inc. https://manaknightdigital.com/ Year: 2021*/
+/**
+ * user Resolve Delete
+ * @copyright 2021 Manaknightdigital Inc.
+ * @link https://manaknightdigital.com
+ * @license Proprietary Software licensing
+ * @author Ryan Wong
+ *
+ */
+
+const { ApolloError } = require('apollo-server-express');
+
+module.exports = async (parent, args, {db}, info) => {
+ //Check Auth if user allowed
+ try {
+
+ return await db.user.realDelete(args.id);
+ } catch (error) {
+ console.log('delete_user -> error', error);
+ return new ApolloError('InternalServerError');
+ }
+}
\ No newline at end of file
diff --git a/day11/resolvers/index.js b/day11/resolvers/index.js
new file mode 100755
index 0000000..e560e45
--- /dev/null
+++ b/day11/resolvers/index.js
@@ -0,0 +1,54 @@
+/*Powered By: Manaknightdigital Inc. https://manaknightdigital.com/ Year: {{{year}}}*/
+/**
+ * Resolve Index
+ * @copyright {{{year}}} Manaknightdigital Inc.
+ * @link https://manaknightdigital.com
+ * @license Proprietary Software licensing
+ * @author Ryan Wong
+ *
+ */
+const { GraphQLUpload } = require('graphql-upload');
+
+const updateUserResolver = require('./update/updateUser');
+const singleUserResolver = require('./single/singleUser');
+const typeUserResolver = require('./type/typeUser');
+
+const createLinkResolver = require('./create/createLink');
+const typeLinkResolver = require('./type/typeLink');
+const singleLinkResolver = require('./single/singleLink');
+const deactivateAllLinksResolver = require('./delete/deactivateAllLinks');
+
+const calendarResolver = require('./custom/calendar');
+const noteResolver = require('./custom/note');
+const customImageResolver = require('./custom/image');
+const uploadFileMutationResolver = require('./custom/uploadFile');
+
+const connectionStepsResolver = require('./custom/connectionSteps');
+
+
+module.exports = {
+ Upload: GraphQLUpload,
+ Query: {
+ user: singleUserResolver,
+ link: singleLinkResolver,
+ ...calendarResolver.Query,
+ ...customImageResolver.Query,
+ ...noteResolver.Query,
+ ...connectionStepsResolver.Query
+ },
+ Mutation: {
+ updateUser: updateUserResolver,
+ createLink: createLinkResolver,
+ deactivateAllLinks: deactivateAllLinksResolver,
+ uploadFile: uploadFileMutationResolver,
+ ...calendarResolver.Mutation,
+ ...customImageResolver.Mutation,
+ ...noteResolver.Mutation,
+ },
+
+ ...calendarResolver.Type,
+ ...noteResolver.Type,
+
+ User: typeUserResolver,
+ Link: typeLinkResolver,
+};
diff --git a/day11/resolvers/relation/.gitkeep b/day11/resolvers/relation/.gitkeep
new file mode 100755
index 0000000..e69de29
diff --git a/day11/resolvers/relation/relationEvent.js b/day11/resolvers/relation/relationEvent.js
new file mode 100755
index 0000000..eb47433
--- /dev/null
+++ b/day11/resolvers/relation/relationEvent.js
@@ -0,0 +1,8 @@
+"use strict";
+module.exports = (parent, args, context, info) => {
+ if (parent.event) {
+ return parent.event;
+ } else {
+ return parent.getEvent();
+ }
+};
diff --git a/day11/resolvers/relation/relationImage.js b/day11/resolvers/relation/relationImage.js
new file mode 100755
index 0000000..923a71f
--- /dev/null
+++ b/day11/resolvers/relation/relationImage.js
@@ -0,0 +1,8 @@
+"use strict";
+module.exports = (parent, args, context, info) => {
+ if (parent.image) {
+ return parent.image;
+ } else {
+ return parent.getImage();
+ }
+};
diff --git a/day11/resolvers/relation/relationProfile.js b/day11/resolvers/relation/relationProfile.js
new file mode 100755
index 0000000..4bd4258
--- /dev/null
+++ b/day11/resolvers/relation/relationProfile.js
@@ -0,0 +1,8 @@
+"use strict";
+module.exports = (parent, args, context, info) => {
+ if (parent.profile) {
+ return parent.profile;
+ } else {
+ return parent.getProfile();
+ }
+};
diff --git a/day11/resolvers/relation/relationReferrerUser.js b/day11/resolvers/relation/relationReferrerUser.js
new file mode 100755
index 0000000..c06f722
--- /dev/null
+++ b/day11/resolvers/relation/relationReferrerUser.js
@@ -0,0 +1,8 @@
+"use strict";
+module.exports = (parent, args, context, info) => {
+ if (parent.referrer_user) {
+ return parent.referrer_user;
+ } else {
+ return parent.getReferrerUser();
+ }
+};
diff --git a/day11/resolvers/relation/relationRole.js b/day11/resolvers/relation/relationRole.js
new file mode 100755
index 0000000..979b2a7
--- /dev/null
+++ b/day11/resolvers/relation/relationRole.js
@@ -0,0 +1,8 @@
+"use strict";
+module.exports = (parent, args, context, info) => {
+ if (parent.role) {
+ return parent.role;
+ } else {
+ return parent.getRole();
+ }
+};
diff --git a/day11/resolvers/single/.gitkeep b/day11/resolvers/single/.gitkeep
new file mode 100755
index 0000000..e69de29
diff --git a/day11/resolvers/single/singleActivityLog.js b/day11/resolvers/single/singleActivityLog.js
new file mode 100755
index 0000000..af30c9e
--- /dev/null
+++ b/day11/resolvers/single/singleActivityLog.js
@@ -0,0 +1,25 @@
+"use strict";
+/*Powered By: Manaknightdigital Inc. https://manaknightdigital.com/ Year: 2021*/
+/**
+ * activity_log Resolve Single
+ * @copyright 2021 Manaknightdigital Inc.
+ * @link https://manaknightdigital.com
+ * @license Proprietary Software licensing
+ * @author Ryan Wong
+ *
+ */
+
+
+const { ApolloError } = require('apollo-server-express');
+const graphqlFields = require('graphql-fields');
+
+module.exports = async (_, {id}, {db}, info) => {
+ try {
+ const attributes = db.activity_log.intersection(graphqlFields(info));
+
+ return await db.activity_log.getByPK(id);
+ } catch (error) {
+ console.log('single_activity_log -> error', error);
+ return new ApolloError('InternalServerError');
+ }
+};
\ No newline at end of file
diff --git a/day11/resolvers/single/singleCalendar.js b/day11/resolvers/single/singleCalendar.js
new file mode 100755
index 0000000..95ef89f
--- /dev/null
+++ b/day11/resolvers/single/singleCalendar.js
@@ -0,0 +1,25 @@
+"use strict";
+/*Powered By: Manaknightdigital Inc. https://manaknightdigital.com/ Year: 2021*/
+/**
+ * calendar Resolve Single
+ * @copyright 2021 Manaknightdigital Inc.
+ * @link https://manaknightdigital.com
+ * @license Proprietary Software licensing
+ * @author Ryan Wong
+ *
+ */
+
+
+const { ApolloError } = require('apollo-server-express');
+const graphqlFields = require('graphql-fields');
+
+module.exports = async (_, {id}, {db}, info) => {
+ try {
+ const attributes = db.calendar.intersection(graphqlFields(info));
+
+ return await db.calendar.getByPK(id);
+ } catch (error) {
+ console.log('single_calendar -> error', error);
+ return new ApolloError('InternalServerError');
+ }
+};
\ No newline at end of file
diff --git a/day11/resolvers/single/singleCode.js b/day11/resolvers/single/singleCode.js
new file mode 100755
index 0000000..916aced
--- /dev/null
+++ b/day11/resolvers/single/singleCode.js
@@ -0,0 +1,25 @@
+"use strict";
+/*Powered By: Manaknightdigital Inc. https://manaknightdigital.com/ Year: 2021*/
+/**
+ * code Resolve Single
+ * @copyright 2021 Manaknightdigital Inc.
+ * @link https://manaknightdigital.com
+ * @license Proprietary Software licensing
+ * @author Ryan Wong
+ *
+ */
+
+
+const { ApolloError } = require('apollo-server-express');
+const graphqlFields = require('graphql-fields');
+
+module.exports = async (_, {id}, {db}, info) => {
+ try {
+ const attributes = db.code.intersection(graphqlFields(info));
+
+ return await db.code.getByPK(id);
+ } catch (error) {
+ console.log('single_code -> error', error);
+ return new ApolloError('InternalServerError');
+ }
+};
\ No newline at end of file
diff --git a/day11/resolvers/single/singleCredential.js b/day11/resolvers/single/singleCredential.js
new file mode 100755
index 0000000..7647034
--- /dev/null
+++ b/day11/resolvers/single/singleCredential.js
@@ -0,0 +1,25 @@
+"use strict";
+/*Powered By: Manaknightdigital Inc. https://manaknightdigital.com/ Year: 2021*/
+/**
+ * credential Resolve Single
+ * @copyright 2021 Manaknightdigital Inc.
+ * @link https://manaknightdigital.com
+ * @license Proprietary Software licensing
+ * @author Ryan Wong
+ *
+ */
+
+
+const { ApolloError } = require('apollo-server-express');
+const graphqlFields = require('graphql-fields');
+
+module.exports = async (_, {id}, {db}, info) => {
+ try {
+ const attributes = db.credential.intersection(graphqlFields(info));
+
+ return await db.credential.getByPK(id);
+ } catch (error) {
+ console.log('single_credential -> error', error);
+ return new ApolloError('InternalServerError');
+ }
+};
\ No newline at end of file
diff --git a/day11/resolvers/single/singleImage.js b/day11/resolvers/single/singleImage.js
new file mode 100755
index 0000000..bd2f7d0
--- /dev/null
+++ b/day11/resolvers/single/singleImage.js
@@ -0,0 +1,25 @@
+"use strict";
+/*Powered By: Manaknightdigital Inc. https://manaknightdigital.com/ Year: 2021*/
+/**
+ * image Resolve Single
+ * @copyright 2021 Manaknightdigital Inc.
+ * @link https://manaknightdigital.com
+ * @license Proprietary Software licensing
+ * @author Ryan Wong
+ *
+ */
+
+
+const { ApolloError } = require('apollo-server-express');
+const graphqlFields = require('graphql-fields');
+
+module.exports = async (_, {id}, {db}, info) => {
+ try {
+ const attributes = db.image.intersection(graphqlFields(info));
+
+ return await db.image.getByPK(id);
+ } catch (error) {
+ console.log('single_image -> error', error);
+ return new ApolloError('InternalServerError');
+ }
+};
\ No newline at end of file
diff --git a/day11/resolvers/single/singleLink.js b/day11/resolvers/single/singleLink.js
new file mode 100755
index 0000000..bb0c412
--- /dev/null
+++ b/day11/resolvers/single/singleLink.js
@@ -0,0 +1,27 @@
+'use strict';
+/*Powered By: Manaknightdigital Inc. https://manaknightdigital.com/ Year: 2021*/
+/**
+ * Link Resolve Single
+ * @copyright 2021 Manaknightdigital Inc.
+ * @link https://manaknightdigital.com
+ * @license Proprietary Software licensing
+ * @author Ryan Wong
+ *
+ */
+
+const { formatError } = require('../../utils/formatError');
+
+module.exports = async (_, __, { db, user }) => {
+ try {
+ const link = await db.link.getByFields({
+ status: 1,
+ user_id: user.id,
+ });
+ return {
+ success: true,
+ data: link,
+ };
+ } catch (error) {
+ return formatError(error);
+ }
+};
diff --git a/day11/resolvers/single/singleNote.js b/day11/resolvers/single/singleNote.js
new file mode 100755
index 0000000..cad3434
--- /dev/null
+++ b/day11/resolvers/single/singleNote.js
@@ -0,0 +1,25 @@
+"use strict";
+/*Powered By: Manaknightdigital Inc. https://manaknightdigital.com/ Year: 2021*/
+/**
+ * note Resolve Single
+ * @copyright 2021 Manaknightdigital Inc.
+ * @link https://manaknightdigital.com
+ * @license Proprietary Software licensing
+ * @author Ryan Wong
+ *
+ */
+
+
+const { ApolloError } = require('apollo-server-express');
+const graphqlFields = require('graphql-fields');
+
+module.exports = async (_, {id}, {db}, info) => {
+ try {
+ const attributes = db.note.intersection(graphqlFields(info));
+
+ return await db.note.getByPK(id);
+ } catch (error) {
+ console.log('single_note -> error', error);
+ return new ApolloError('InternalServerError');
+ }
+};
\ No newline at end of file
diff --git a/day11/resolvers/single/singleProfile.js b/day11/resolvers/single/singleProfile.js
new file mode 100755
index 0000000..dd937bd
--- /dev/null
+++ b/day11/resolvers/single/singleProfile.js
@@ -0,0 +1,25 @@
+"use strict";
+/*Powered By: Manaknightdigital Inc. https://manaknightdigital.com/ Year: 2021*/
+/**
+ * profile Resolve Single
+ * @copyright 2021 Manaknightdigital Inc.
+ * @link https://manaknightdigital.com
+ * @license Proprietary Software licensing
+ * @author Ryan Wong
+ *
+ */
+
+
+const { ApolloError } = require('apollo-server-express');
+const graphqlFields = require('graphql-fields');
+
+module.exports = async (_, {id}, {db}, info) => {
+ try {
+ const attributes = db.profile.intersection(graphqlFields(info));
+
+ return await db.profile.getByPK(id);
+ } catch (error) {
+ console.log('single_profile -> error', error);
+ return new ApolloError('InternalServerError');
+ }
+};
\ No newline at end of file
diff --git a/day11/resolvers/single/singleReferLog.js b/day11/resolvers/single/singleReferLog.js
new file mode 100755
index 0000000..ebf5427
--- /dev/null
+++ b/day11/resolvers/single/singleReferLog.js
@@ -0,0 +1,25 @@
+"use strict";
+/*Powered By: Manaknightdigital Inc. https://manaknightdigital.com/ Year: 2021*/
+/**
+ * refer_log Resolve Single
+ * @copyright 2021 Manaknightdigital Inc.
+ * @link https://manaknightdigital.com
+ * @license Proprietary Software licensing
+ * @author Ryan Wong
+ *
+ */
+
+
+const { ApolloError } = require('apollo-server-express');
+const graphqlFields = require('graphql-fields');
+
+module.exports = async (_, {id}, {db}, info) => {
+ try {
+ const attributes = db.refer_log.intersection(graphqlFields(info));
+
+ return await db.refer_log.getByPK(id);
+ } catch (error) {
+ console.log('single_refer_log -> error', error);
+ return new ApolloError('InternalServerError');
+ }
+};
\ No newline at end of file
diff --git a/day11/resolvers/single/singleUser.js b/day11/resolvers/single/singleUser.js
new file mode 100755
index 0000000..178fdb4
--- /dev/null
+++ b/day11/resolvers/single/singleUser.js
@@ -0,0 +1,17 @@
+'use strict';
+/*Powered By: Manaknightdigital Inc. https://manaknightdigital.com/ Year: 2021*/
+/**
+ * User Resolve Single
+ * @copyright 2021 Manaknightdigital Inc.
+ * @link https://manaknightdigital.com
+ * @license Proprietary Software licensing
+ * @author Ryan Wong
+ *
+ */
+
+module.exports = async (_, __, { directives: { verifyUser } }) => {
+ return {
+ success: true,
+ data: verifyUser?.user,
+ };
+};
diff --git a/day11/resolvers/type/typeActivityLog.js b/day11/resolvers/type/typeActivityLog.js
new file mode 100755
index 0000000..2f1e3c3
--- /dev/null
+++ b/day11/resolvers/type/typeActivityLog.js
@@ -0,0 +1,6 @@
+const { ApolloError } = require('apollo-server-express');
+const graphqlFields = require('graphql-fields');
+
+module.exports = {
+
+}
\ No newline at end of file
diff --git a/day11/resolvers/type/typeCalendar.js b/day11/resolvers/type/typeCalendar.js
new file mode 100755
index 0000000..cf16ade
--- /dev/null
+++ b/day11/resolvers/type/typeCalendar.js
@@ -0,0 +1,15 @@
+const { ApolloError } = require('apollo-server-express');
+const graphqlFields = require('graphql-fields');
+
+module.exports = {
+ async event({ event_id }, _, { db }, info) {
+ try {
+ const attributes = db.event.intersection(graphqlFields(info));
+
+ return await db.event.getByPK(event_id, { attributes });
+ } catch (error) {
+ console.log('event -> error', error);
+ return new ApolloError('InternalServerError');
+ }
+ },
+}
\ No newline at end of file
diff --git a/day11/resolvers/type/typeCode.js b/day11/resolvers/type/typeCode.js
new file mode 100755
index 0000000..2f1e3c3
--- /dev/null
+++ b/day11/resolvers/type/typeCode.js
@@ -0,0 +1,6 @@
+const { ApolloError } = require('apollo-server-express');
+const graphqlFields = require('graphql-fields');
+
+module.exports = {
+
+}
\ No newline at end of file
diff --git a/day11/resolvers/type/typeCredential.js b/day11/resolvers/type/typeCredential.js
new file mode 100755
index 0000000..2f1e3c3
--- /dev/null
+++ b/day11/resolvers/type/typeCredential.js
@@ -0,0 +1,6 @@
+const { ApolloError } = require('apollo-server-express');
+const graphqlFields = require('graphql-fields');
+
+module.exports = {
+
+}
\ No newline at end of file
diff --git a/day11/resolvers/type/typeImage.js b/day11/resolvers/type/typeImage.js
new file mode 100755
index 0000000..2f1e3c3
--- /dev/null
+++ b/day11/resolvers/type/typeImage.js
@@ -0,0 +1,6 @@
+const { ApolloError } = require('apollo-server-express');
+const graphqlFields = require('graphql-fields');
+
+module.exports = {
+
+}
\ No newline at end of file
diff --git a/day11/resolvers/type/typeLink.js b/day11/resolvers/type/typeLink.js
new file mode 100755
index 0000000..2f1e3c3
--- /dev/null
+++ b/day11/resolvers/type/typeLink.js
@@ -0,0 +1,6 @@
+const { ApolloError } = require('apollo-server-express');
+const graphqlFields = require('graphql-fields');
+
+module.exports = {
+
+}
\ No newline at end of file
diff --git a/day11/resolvers/type/typeNote.js b/day11/resolvers/type/typeNote.js
new file mode 100755
index 0000000..2f1e3c3
--- /dev/null
+++ b/day11/resolvers/type/typeNote.js
@@ -0,0 +1,6 @@
+const { ApolloError } = require('apollo-server-express');
+const graphqlFields = require('graphql-fields');
+
+module.exports = {
+
+}
\ No newline at end of file
diff --git a/day11/resolvers/type/typeProfile.js b/day11/resolvers/type/typeProfile.js
new file mode 100755
index 0000000..2f1e3c3
--- /dev/null
+++ b/day11/resolvers/type/typeProfile.js
@@ -0,0 +1,6 @@
+const { ApolloError } = require('apollo-server-express');
+const graphqlFields = require('graphql-fields');
+
+module.exports = {
+
+}
\ No newline at end of file
diff --git a/day11/resolvers/type/typeReferLog.js b/day11/resolvers/type/typeReferLog.js
new file mode 100755
index 0000000..2f1e3c3
--- /dev/null
+++ b/day11/resolvers/type/typeReferLog.js
@@ -0,0 +1,6 @@
+const { ApolloError } = require('apollo-server-express');
+const graphqlFields = require('graphql-fields');
+
+module.exports = {
+
+}
\ No newline at end of file
diff --git a/day11/resolvers/type/typeUser.js b/day11/resolvers/type/typeUser.js
new file mode 100755
index 0000000..b8eb24d
--- /dev/null
+++ b/day11/resolvers/type/typeUser.js
@@ -0,0 +1,12 @@
+module.exports = {
+ async sync_code(user, _, { db }) {
+ try {
+ const syncCode = await db.code.getByFields({
+ user_id: user.id,
+ })
+ return syncCode?.code
+ } catch (error) {
+ return null
+ }
+ },
+}
diff --git a/day11/resolvers/update/.gitkeep b/day11/resolvers/update/.gitkeep
new file mode 100755
index 0000000..e69de29
diff --git a/day11/resolvers/update/updateActivityLog.js b/day11/resolvers/update/updateActivityLog.js
new file mode 100755
index 0000000..24ea43b
--- /dev/null
+++ b/day11/resolvers/update/updateActivityLog.js
@@ -0,0 +1,32 @@
+"use strict";
+/*Powered By: Manaknightdigital Inc. https://manaknightdigital.com/ Year: 2021*/
+/**
+ * activity_log Resolve Update
+ * @copyright 2021 Manaknightdigital Inc.
+ * @link https://manaknightdigital.com
+ * @license Proprietary Software licensing
+ * @author Ryan Wong
+ *
+ */
+
+const { ApolloError ,UserInputError} = require('apollo-server-express');
+const { Validator } = require('node-input-validator');
+
+module.exports = async (parent, args, {db}, info) => {
+ try {
+ const { name } = args;
+ const v = new Validator({ }, { });
+
+ v.check().then(function (matched) {
+ if (!matched) {
+ Object.keys(v.errors).forEach((error) => {
+ return new UserInputError(v.errors[error].message);
+ });
+ }
+ });
+ return await db.activity_log.edit({ name }, args.id);
+ } catch (error) {
+ console.log('update_activity_log -> error', error);
+ return new ApolloError('InternalServerError');
+ }
+};
diff --git a/day11/resolvers/update/updateCalendar.js b/day11/resolvers/update/updateCalendar.js
new file mode 100755
index 0000000..e0fbbdd
--- /dev/null
+++ b/day11/resolvers/update/updateCalendar.js
@@ -0,0 +1,32 @@
+"use strict";
+/*Powered By: Manaknightdigital Inc. https://manaknightdigital.com/ Year: 2021*/
+/**
+ * calendar Resolve Update
+ * @copyright 2021 Manaknightdigital Inc.
+ * @link https://manaknightdigital.com
+ * @license Proprietary Software licensing
+ * @author Ryan Wong
+ *
+ */
+
+const { ApolloError ,UserInputError} = require('apollo-server-express');
+const { Validator } = require('node-input-validator');
+
+module.exports = async (parent, args, {db}, info) => {
+ try {
+ const { } = args;
+ const v = new Validator({ }, { });
+
+ v.check().then(function (matched) {
+ if (!matched) {
+ Object.keys(v.errors).forEach((error) => {
+ return new UserInputError(v.errors[error].message);
+ });
+ }
+ });
+ return await db.calendar.edit({ }, args.id);
+ } catch (error) {
+ console.log('update_calendar -> error', error);
+ return new ApolloError('InternalServerError');
+ }
+};
diff --git a/day11/resolvers/update/updateCode.js b/day11/resolvers/update/updateCode.js
new file mode 100755
index 0000000..9c751b0
--- /dev/null
+++ b/day11/resolvers/update/updateCode.js
@@ -0,0 +1,32 @@
+"use strict";
+/*Powered By: Manaknightdigital Inc. https://manaknightdigital.com/ Year: 2021*/
+/**
+ * code Resolve Update
+ * @copyright 2021 Manaknightdigital Inc.
+ * @link https://manaknightdigital.com
+ * @license Proprietary Software licensing
+ * @author Ryan Wong
+ *
+ */
+
+const { ApolloError ,UserInputError} = require('apollo-server-express');
+const { Validator } = require('node-input-validator');
+
+module.exports = async (parent, args, {db}, info) => {
+ try {
+ const { code } = args;
+ const v = new Validator({ code: args.code}, { code: "required" });
+
+ v.check().then(function (matched) {
+ if (!matched) {
+ Object.keys(v.errors).forEach((error) => {
+ return new UserInputError(v.errors[error].message);
+ });
+ }
+ });
+ return await db.code.edit({ code }, args.id);
+ } catch (error) {
+ console.log('update_code -> error', error);
+ return new ApolloError('InternalServerError');
+ }
+};
diff --git a/day11/resolvers/update/updateCredential.js b/day11/resolvers/update/updateCredential.js
new file mode 100755
index 0000000..3ffced0
--- /dev/null
+++ b/day11/resolvers/update/updateCredential.js
@@ -0,0 +1,35 @@
+"use strict";
+/*Powered By: Manaknightdigital Inc. https://manaknightdigital.com/ Year: 2021*/
+/**
+ * credential Resolve Update
+ * @copyright 2021 Manaknightdigital Inc.
+ * @link https://manaknightdigital.com
+ * @license Proprietary Software licensing
+ * @author Ryan Wong
+ *
+ */
+
+const { ApolloError ,UserInputError} = require('apollo-server-express');
+const { Validator } = require('node-input-validator');
+
+module.exports = async (parent, args, {db}, info) => {
+ try {
+ const { email,
+ password } = args;
+ const v = new Validator({ email: args.email,
+ password: args.password}, { email: "required|valid_email" });
+
+ v.check().then(function (matched) {
+ if (!matched) {
+ Object.keys(v.errors).forEach((error) => {
+ return new UserInputError(v.errors[error].message);
+ });
+ }
+ });
+ return await db.credential.edit({ email,
+ password }, args.id);
+ } catch (error) {
+ console.log('update_credential -> error', error);
+ return new ApolloError('InternalServerError');
+ }
+};
diff --git a/day11/resolvers/update/updateImage.js b/day11/resolvers/update/updateImage.js
new file mode 100755
index 0000000..87ddf3a
--- /dev/null
+++ b/day11/resolvers/update/updateImage.js
@@ -0,0 +1,34 @@
+"use strict";
+/*Powered By: Manaknightdigital Inc. https://manaknightdigital.com/ Year: 2021*/
+/**
+ * image Resolve Update
+ * @copyright 2021 Manaknightdigital Inc.
+ * @link https://manaknightdigital.com
+ * @license Proprietary Software licensing
+ * @author Ryan Wong
+ *
+ */
+
+const { ApolloError ,UserInputError} = require('apollo-server-express');
+const { Validator } = require('node-input-validator');
+
+module.exports = async (parent, args, {db}, info) => {
+ try {
+ const { url,
+ caption } = args;
+ const v = new Validator({ url: args.url}, { url: "required" });
+
+ v.check().then(function (matched) {
+ if (!matched) {
+ Object.keys(v.errors).forEach((error) => {
+ return new UserInputError(v.errors[error].message);
+ });
+ }
+ });
+ return await db.image.edit({ url,
+ caption }, args.id);
+ } catch (error) {
+ console.log('update_image -> error', error);
+ return new ApolloError('InternalServerError');
+ }
+};
diff --git a/day11/resolvers/update/updateLink.js b/day11/resolvers/update/updateLink.js
new file mode 100755
index 0000000..d2442a5
--- /dev/null
+++ b/day11/resolvers/update/updateLink.js
@@ -0,0 +1,36 @@
+"use strict";
+/*Powered By: Manaknightdigital Inc. https://manaknightdigital.com/ Year: 2021*/
+/**
+ * link Resolve Update
+ * @copyright 2021 Manaknightdigital Inc.
+ * @link https://manaknightdigital.com
+ * @license Proprietary Software licensing
+ * @author Ryan Wong
+ *
+ */
+
+const { ApolloError ,UserInputError} = require('apollo-server-express');
+const { Validator } = require('node-input-validator');
+
+module.exports = async (parent, args, {db}, info) => {
+ try {
+ const { link,
+ status } = args;
+ const v = new Validator({ link: args.link,
+ status: args.status}, { link: "required",
+ status: "required|integer" });
+
+ v.check().then(function (matched) {
+ if (!matched) {
+ Object.keys(v.errors).forEach((error) => {
+ return new UserInputError(v.errors[error].message);
+ });
+ }
+ });
+ return await db.link.edit({ link,
+ status }, args.id);
+ } catch (error) {
+ console.log('update_link -> error', error);
+ return new ApolloError('InternalServerError');
+ }
+};
diff --git a/day11/resolvers/update/updateNote.js b/day11/resolvers/update/updateNote.js
new file mode 100755
index 0000000..2649841
--- /dev/null
+++ b/day11/resolvers/update/updateNote.js
@@ -0,0 +1,32 @@
+"use strict";
+/*Powered By: Manaknightdigital Inc. https://manaknightdigital.com/ Year: 2021*/
+/**
+ * note Resolve Update
+ * @copyright 2021 Manaknightdigital Inc.
+ * @link https://manaknightdigital.com
+ * @license Proprietary Software licensing
+ * @author Ryan Wong
+ *
+ */
+
+const { ApolloError ,UserInputError} = require('apollo-server-express');
+const { Validator } = require('node-input-validator');
+
+module.exports = async (parent, args, {db}, info) => {
+ try {
+ const { } = args;
+ const v = new Validator({ }, { });
+
+ v.check().then(function (matched) {
+ if (!matched) {
+ Object.keys(v.errors).forEach((error) => {
+ return new UserInputError(v.errors[error].message);
+ });
+ }
+ });
+ return await db.note.edit({ }, args.id);
+ } catch (error) {
+ console.log('update_note -> error', error);
+ return new ApolloError('InternalServerError');
+ }
+};
diff --git a/day11/resolvers/update/updateProfile.js b/day11/resolvers/update/updateProfile.js
new file mode 100755
index 0000000..211477e
--- /dev/null
+++ b/day11/resolvers/update/updateProfile.js
@@ -0,0 +1,36 @@
+"use strict";
+/*Powered By: Manaknightdigital Inc. https://manaknightdigital.com/ Year: 2021*/
+/**
+ * profile Resolve Update
+ * @copyright 2021 Manaknightdigital Inc.
+ * @link https://manaknightdigital.com
+ * @license Proprietary Software licensing
+ * @author Ryan Wong
+ *
+ */
+
+const { ApolloError ,UserInputError} = require('apollo-server-express');
+const { Validator } = require('node-input-validator');
+
+module.exports = async (parent, args, {db}, info) => {
+ try {
+ const { timezone,
+ dashboard_code } = args;
+ const v = new Validator({ timezone: args.timezone,
+ dashboard_code: args.dashboard_code}, { timezone: "required",
+ dashboard_code: "required" });
+
+ v.check().then(function (matched) {
+ if (!matched) {
+ Object.keys(v.errors).forEach((error) => {
+ return new UserInputError(v.errors[error].message);
+ });
+ }
+ });
+ return await db.profile.edit({ timezone,
+ dashboard_code }, args.id);
+ } catch (error) {
+ console.log('update_profile -> error', error);
+ return new ApolloError('InternalServerError');
+ }
+};
diff --git a/day11/resolvers/update/updateReferLog.js b/day11/resolvers/update/updateReferLog.js
new file mode 100755
index 0000000..39e9bf1
--- /dev/null
+++ b/day11/resolvers/update/updateReferLog.js
@@ -0,0 +1,35 @@
+"use strict";
+/*Powered By: Manaknightdigital Inc. https://manaknightdigital.com/ Year: 2021*/
+/**
+ * refer_log Resolve Update
+ * @copyright 2021 Manaknightdigital Inc.
+ * @link https://manaknightdigital.com
+ * @license Proprietary Software licensing
+ * @author Ryan Wong
+ *
+ */
+
+const { ApolloError ,UserInputError} = require('apollo-server-express');
+const { Validator } = require('node-input-validator');
+
+module.exports = async (parent, args, {db}, info) => {
+ try {
+ const { type,
+ status } = args;
+ const v = new Validator({ type: args.type,
+ status: args.status}, { type: "required|integer" });
+
+ v.check().then(function (matched) {
+ if (!matched) {
+ Object.keys(v.errors).forEach((error) => {
+ return new UserInputError(v.errors[error].message);
+ });
+ }
+ });
+ return await db.refer_log.edit({ type,
+ status }, args.id);
+ } catch (error) {
+ console.log('update_refer_log -> error', error);
+ return new ApolloError('InternalServerError');
+ }
+};
diff --git a/day11/resolvers/update/updateUser.js b/day11/resolvers/update/updateUser.js
new file mode 100755
index 0000000..a58f33c
--- /dev/null
+++ b/day11/resolvers/update/updateUser.js
@@ -0,0 +1,127 @@
+'use strict'
+/*Powered By: Manaknightdigital Inc. https://manaknightdigital.com/ Year: 2021*/
+/**
+ * Update User Resolver
+ * @copyright 2021 Manaknightdigital Inc.
+ * @link https://manaknightdigital.com
+ * @license Proprietary Software licensing
+ * @author Ryan Wong
+ *
+ */
+
+const TimezoneService = require('../../services/TimezoneService')
+
+const { validateInputForGraphql } = require('../../services/ValidationService')
+const { formatError } = require('../../utils/formatError')
+const { errorCodes } = require('../../core/strings')
+
+const timezoneService = new TimezoneService()
+
+const inputValidations = {
+ Mutation: {
+ updateUser: (resolver = () => null) => {
+ return validateInputForGraphql(
+ resolver,
+ {
+ time_zone: 'string',
+ time_format: 'integer|min:1|max:2',
+ clock_format: 'integer|min:1|max:2',
+ date_format: 'integer|min:1|max:2',
+ location: 'string',
+ lat: 'decimal',
+ lng: 'decimal',
+ },
+ {
+ 'time_zone.string': 'Timezone should be a string.',
+ 'time_format.integer':
+ 'Time format field should be an integer. Can be 1 for `AM/PM format` and 2 for `24 hours format`.',
+ 'time_format.min':
+ 'Invalid value. Can be 1 for `AM/PM format` and 2 for `24 hours format`.',
+ 'time_format.max':
+ 'Invalid value.Can be 1 for `AM/PM format` and 2 for `24 hours format`.',
+
+ 'clock_format.integer':
+ 'Clock format field should be an integer. Can be 1 for `Digital` and 2 for `Analog`.',
+ 'clock_format.min':
+ 'Invalid value. Can be 1 for `Digital` and 2 for `Analog`.',
+ 'clock_format.max':
+ 'Invalid value. Can be 1 for `Digital` and 2 for `Analog`.',
+
+ 'date_format.integer':
+ 'Date format field should be an integer. Can be 1 for `Standard (dd-mm-yyyy)` and 2 for `Locale (1st April 2021)`.',
+ 'date_format.min':
+ 'Invalid value. Can be 1 for `Standard (dd-mm-yyyy)` and 2 for `Locale (1st April 2021)`.',
+ 'date_format.max':
+ 'Invalid value. Can be 1 for `Standard (dd-mm-yyyy)` and 2 for `Locale (1st April 2021)`.',
+ }
+ )
+ },
+ },
+}
+
+module.exports = inputValidations.Mutation.updateUser(
+ async (
+ _,
+ {
+ sync_code,
+ font_color,
+ time_zone,
+ time_format,
+ clock_format,
+ date_format,
+ location,
+ lat,
+ lng,
+ },
+ { db, user }
+ ) => {
+ try {
+ if (time_zone?.length) {
+ const isValidTimezone = timezoneService.validateTimeZone(time_zone)
+ if (!isValidTimezone) {
+ return {
+ success: false,
+ message:
+ 'Invalid timezone. Pass the correct timezone abbreviation.',
+ }
+ }
+ }
+
+ if (sync_code?.length) {
+ const syncCodeExists = await db.code.getByFields({
+ code: sync_code,
+ })
+ if (syncCodeExists && +syncCodeExists.user_id !== +user.id) {
+ return {
+ success: false,
+ message: 'Sync code already exists.',
+ code: errorCodes.extra.SYNC_CODE_ALREADY_EXISTS,
+ }
+ }
+ await db.code.editByField({ code: sync_code }, { user_id: user.id })
+ }
+
+ const fields = {
+ ...(font_color?.length ? { font_color } : {}),
+ ...(time_zone?.length ? { time_zone } : {}),
+ ...(time_format ? { time_format } : {}),
+ ...(clock_format ? { clock_format } : {}),
+ ...(date_format ? { date_format } : {}),
+ ...(location?.length ? { location } : {}),
+ ...((lat !== undefined || lat !== null) &&
+ (lng !== undefined || lng !== null)
+ ? { lat, lng }
+ : {}),
+ }
+ if (Object.entries(fields)?.length) {
+ await db.user.edit(fields, user.id)
+ }
+ return {
+ success: true,
+ message: 'User settings updated successfully.',
+ }
+ } catch (error) {
+ return formatError(error)
+ }
+ }
+)
diff --git a/day11/server.js b/day11/server.js
new file mode 100755
index 0000000..ecf406d
--- /dev/null
+++ b/day11/server.js
@@ -0,0 +1,18 @@
+'use strict';
+/*Powered By: Manaknightdigital Inc. https://manaknightdigital.com/ Year: 2020*/
+/**
+ * Server
+ * @copyright 2020 Manaknightdigital Inc.
+ * @link https://manaknightdigital.com
+ * @license Proprietary Software licensing
+ * @author Ryan Wong
+ *
+ */
+const { app, apollo } = require('./app');
+
+const PORT = 3001;
+
+app.listen(PORT, () => {
+ console.log('Server running at ', true ? `http://localhost:${PORT}` : 'process.env.BASE_URL');
+ console.log('GraphQL running at ', true ? `http://localhost:${PORT}${apollo.graphqlPath}` : `${'process.env.BASE_URL'}${apollo.graphqlPath}`);
+});
diff --git a/day11/services/.gitkeep b/day11/services/.gitkeep
new file mode 100755
index 0000000..e69de29
diff --git a/day11/services/AclService.js b/day11/services/AclService.js
new file mode 100755
index 0000000..e69de29
diff --git a/day11/services/AuthService.js b/day11/services/AuthService.js
new file mode 100755
index 0000000..6283ab1
--- /dev/null
+++ b/day11/services/AuthService.js
@@ -0,0 +1,377 @@
+/*Powered By: Manaknightdigital Inc. https://manaknightdigital.com/ Year: 2021*/
+/**
+ * Auth Service
+ * @copyright 2021 Manaknightdigital Inc.
+ * @link https://manaknightdigital.com
+ * @license Proprietary Software licensing
+ * @author Ryan Wong
+ *
+ */
+
+const { Op } = require('sequelize')
+
+const passwordService = require('./PasswordService')
+const mailService = require('./MailService')
+const generateCode = require('../utils/generateCode')
+
+const db = require('../models')
+
+const errors = {
+ EMAIL_ADDRESS_NOT_FOUND: 'EMAIL_ADDRESS_NOT_FOUND',
+ EMAIL_ADDRESS_ALREADY_EXIST: 'EMAIL_ADDRESS_ALREADY_EXIST',
+ PASSWORD_NOT_MATCH: 'PASSWORD_NOT_MATCH',
+ INVALID_EMAIL_CONFIRMATION_CODE: 'INVALID_EMAIL_CONFIRMATION_CODE',
+ INVALID_EMAIL_OR_PASSWORD: 'INVALID_EMAIL_OR_PASSWORD',
+ ACCOUNT_ALREADY_VERIFIED: 'ACCOUNT_ALREADY_VERIFIED',
+ CODE_DOES_NOT_EXIST: 'CODE_DOES_NOT_EXIST',
+}
+
+module.exports = {
+ /**
+ * Register new user with email and password
+ * @name authService.register
+ * @param {String} email user new email address
+ * @param {String} password user new password
+ * @returns {Promise.<{credential:String, user:String}>} payload to generate jwt access and refresh token
+ * @example
+ * const payload = await authService.register(req.body.email, req.body.password)
+ */
+ register: async function (email, password, role_id, code, user_details = {}) {
+ let User
+ let Credential
+
+ try {
+ const isEmailAddressExist = await db.credential.getByFields({
+ email,
+ })
+ ;``
+
+ if (isEmailAddressExist)
+ throw new Error(errors.EMAIL_ADDRESS_ALREADY_EXIST)
+ const codeExists = await db.code.getByFields({
+ code,
+ is_used: { [Op.or]: [0, null] },
+ user_id: { [Op.or]: [0, null] },
+ })
+ if (!codeExists) throw new Error(errors.CODE_DOES_NOT_EXIST)
+
+ const hashedPassword = await passwordService.hash(password)
+
+ User = await db.user.insert(
+ { ...user_details, role_id },
+ { returnAllFields: true }
+ )
+
+ Credential = await db.credential.insert(
+ {
+ email: email,
+ password: hashedPassword,
+ user_id: User.id,
+ type: 'n',
+ verify: 0,
+ role_id,
+ status: 1,
+ },
+ { returnAllFields: true }
+ )
+ await db.code.edit(
+ { user_id: User.id, is_used: 1, status: 1 },
+ codeExists.id
+ )
+ return { credential: Credential.id, user: User }
+ } catch (error) {
+ if (Credential) {
+ await db.credential.realDelete(Credential.id)
+ }
+ if (User) {
+ await db.user.realDelete(User.id)
+ }
+
+ throw new Error(error.message)
+ }
+ },
+ /**
+ * Login user with email and password
+ * @name authService.login
+ * @param {String} email user email address
+ * @param {String} password user password
+ * @returns {Promise.<{credential:String, user:String}>} payload to generate jwt access and refresh token
+ * @example
+ * const payload = await authService.login(req.body.email, req.body.password)
+ */
+ login: async function (email, password, role_id) {
+ const isEmailAddressExist = await db.credential.getByFields({
+ email,
+ status: 1,
+ role_id,
+ type: 'n',
+ })
+
+ if (!isEmailAddressExist) throw new Error(errors.EMAIL_ADDRESS_NOT_FOUND)
+
+ const { password: hashedPassword, id, user_id } = isEmailAddressExist
+
+ const user = await db.user.getByFields({
+ id: user_id,
+ status: 1,
+ })
+
+ if (!user) {
+ throw new Error(errors.EMAIL_ADDRESS_NOT_FOUND)
+ }
+
+ const isPasswordMatch = await passwordService.compareHash(
+ password,
+ hashedPassword
+ )
+
+ if (!isPasswordMatch) throw new Error(errors.INVALID_EMAIL_OR_PASSWORD)
+
+ return { credential: id, user }
+ },
+ /**
+ * Send email and save to database
+ * @name authService.forgotPassword
+ * @param {String} email user email address
+ * @return {Promise.}
+ * @example
+ * await authService.forgotPassword(req.body.email)
+ */
+ forgotPassword: async function (email) {
+ try {
+ const isEmailAddressExist = await db.credential.getByFields({
+ where: {
+ email: email,
+ },
+ })
+
+ if (!isEmailAddressExist) throw new Error(errors.EMAIL_ADDRESS_NOT_FOUND)
+
+ const { user_id } = isEmailAddressExist
+
+ const getUser = await db.user.getByPk(user_id)
+
+ const verificationCode = generateCode(6)
+
+ mailService.initialize({
+ hostname: process.env.EMAIL_SMTP_SMTP_HOST,
+ port: process.env.EMAIL_SMTP_SMTP_PORT,
+ username: process.env.EMAIL_SMTP_SMTP_USER,
+ password: EMAIL_SMTP_SMTP_PASS,
+ from: process.env.MAIL_FROM,
+ to: email,
+ })
+
+ const mailTemplate = await mailService.template('reset-password')
+
+ const injectedMailTemplate = mailService.inject(
+ {
+ body: mailTemplate.body,
+ subject: mailTemplate.subject,
+ },
+ {
+ username: `${getUser.first_name} ${getUser.last_name}`,
+ verification_code: verificationCode,
+ }
+ )
+
+ await mailService.send(injectedMailTemplate)
+
+ await db.token.insert({ token: verificationCode, user_id })
+ } catch (error) {
+ throw new Error(error)
+ }
+ },
+ /**
+ * Verify forgot password confirmation code
+ * @name authService.verifyForgotPassword
+ * @param {code} code confirmation code
+ * @returns {Promise.<{credential:String, user:String}>} payload to generate jwt access and refresh token
+ * @example
+ * const payload = await authService.verifyForgotPassword(req.body.code)
+ */
+ verifyForgotPassword: async function (code) {
+ try {
+ const Token = await db.token.findOne({
+ where: {
+ token: code,
+ },
+ })
+
+ const Credential = await db.credential.getByFields({
+ user_id: Token.user_id,
+ })
+
+ return { credential: Credential.id, user: Credential.user_id }
+ } catch (error) {
+ throw new Error(error)
+ }
+ },
+ /**
+ * Reset password
+ * @name authService.resetPassword
+ * @param {String} password user new password
+ * @param {String} credential_id user credential id
+ * @example
+ * await authService.resetPassword(req.body.password, credential_id)
+ */
+ resetPassword: async function (password, credential_id) {
+ try {
+ const hashedPassword = await passwordService.hash(password)
+
+ await db.credential.edit(
+ {
+ password: hashedPassword,
+ },
+ credential_id
+ )
+ } catch (error) {
+ throw new Error(error)
+ }
+ },
+ /**
+ * Email confirmation
+ * @name authService.emailConfirmation
+ * @param {String} email user email address
+ * @example
+ * await authService.emailConfirmation(email)
+ */
+ emailConfirmation: async function (email) {
+ try {
+ const isEmailAddressExist = await db.credential.getByFields({
+ where: {
+ email: email,
+ },
+ })
+
+ if (!isEmailAddressExist) throw new Error(errors.EMAIL_ADDRESS_NOT_FOUND)
+
+ const { user_id } = isEmailAddressExist
+
+ const getUser = await db.user.getByPk(user_id)
+
+ const confirmationCode = generateCode(6)
+
+ mailService.initialize({
+ hostname: process.env.EMAIL_SMTP_SMTP_HOST,
+ port: process.env.EMAIL_SMTP_SMTP_PORT,
+ username: process.env.EMAIL_SMTP_SMTP_USER,
+ password: EMAIL_SMTP_SMTP_PASS,
+ from: process.env.MAIL_FROM,
+ to: email,
+ })
+
+ const mailTemplate = await mailService.template('email-confirmation')
+
+ const injectedMailTemplate = mailService.inject(
+ {
+ body: mailTemplate.body,
+ subject: mailTemplate.subject,
+ },
+ {
+ username: `${getUser.first_name} ${getUser.last_name}`,
+ confirmation_code: confirmationCode,
+ }
+ )
+
+ await mailService.send(injectedMailTemplate)
+
+ await db.token.insert({ token: confirmationCode, user_id, type: 6 })
+ } catch (error) {
+ throw new Error(error)
+ }
+ },
+ /**
+ * Verify Email address
+ * @name authService.emailVerify
+ * @param {String} token email confirmation code
+ * @param {string} user_id user id
+ * @example
+ * await authService.emailVerify(email, user_id)
+ */
+ emailVerify: async function (token, user_id) {
+ try {
+ const isTokenExist = await db.token.getByFields({
+ where: {
+ user_id,
+ token,
+ type: 6,
+ },
+ })
+
+ if (!isTokenExist) throw new Error(errors.INVALID_EMAIL_CONFIRMATION_CODE)
+
+ await db.token.realDelete(isTokenExist.id)
+ } catch (error) {
+ throw new Error(error)
+ }
+ },
+ /**
+ * check if user need to change password before logging in
+ * @name authService.forcePasswordChange
+ * @param {string} user_id user id
+ */
+ forcePasswordChange: async function (user_id) {
+ try {
+ const { profile_id } = await db.user.getByPk(user_id)
+ const { force_password_change } = await db.profile.getByPk(profile_id)
+
+ if (force_password_change) return true
+ else return false
+ } catch (error) {
+ throw new Error(error)
+ }
+ },
+ verifyAccount: async ({ email, roleId }) => {
+ const credential = await db.credential.getByFields({
+ email,
+ status: 1,
+ role_id: roleId,
+ })
+
+ if (!credential) {
+ throw new Error(errors.EMAIL_ADDRESS_NOT_FOUND)
+ }
+
+ if (
+ credential.type !== 'n' ||
+ (credential.type === 'n' && credential.verify)
+ ) {
+ throw new Error(errors.ACCOUNT_ALREADY_VERIFIED)
+ }
+
+ mailService.initialize({
+ hostname: process.env.EMAIL_SMTP_SMTP_HOST,
+ port: process.env.EMAIL_SMTP_SMTP_PORT,
+ username: process.env.EMAIL_SMTP_SMTP_USER,
+ password: process.env.EMAIL_SMTP_SMTP_PASS,
+ from: process.env.MAIL_FROM,
+ to: email,
+ })
+
+ const accountConfirmationMailTemplate = await mailService.template(
+ 'verify-account'
+ )
+
+ const token = Buffer.from(JSON.stringify(credential.id))?.toString('base64')
+
+ const BASE_URL = process.env.BASE_URL
+ const link =
+ (BASE_URL?.endsWith('/') ? BASE_URL?.slice(0, -1) : BASE_URL) +
+ '/member/verify-account/' +
+ token
+
+ const accountConfirmationMailTemplateFinal = mailService.inject(
+ {
+ body: accountConfirmationMailTemplate.html,
+ subject: accountConfirmationMailTemplate.subject,
+ },
+ {
+ email,
+ link,
+ }
+ )
+
+ await mailService.send(accountConfirmationMailTemplateFinal)
+ },
+}
diff --git a/day11/services/BarcodeService.js b/day11/services/BarcodeService.js
new file mode 100755
index 0000000..8f1e349
--- /dev/null
+++ b/day11/services/BarcodeService.js
@@ -0,0 +1,24 @@
+const barcode = require('barcode');
+
+module.exports = {
+ /**
+ * Generate barcode
+ * @param {string} string barcode text
+ * @param {{width?: number, height?: number}} param1 width and height for barcode image
+ * @returns {Promise.}
+ */
+ generateBarcode: async function (string, { width = 400, height = 100 }) {
+ return new Promise((resolve, reject) => {
+ const code128 = barcode('Code128', {
+ data: string,
+ width,
+ height,
+ });
+
+ code128.getBase64(function (error, base64String) {
+ if (error) reject(error);
+ else resolve(base64String);
+ });
+ });
+ },
+};
diff --git a/day11/services/CsvService.js b/day11/services/CsvService.js
new file mode 100755
index 0000000..2621e36
--- /dev/null
+++ b/day11/services/CsvService.js
@@ -0,0 +1,84 @@
+const multer = require('multer');
+const converter = require('json-2-csv');
+const fs = require('fs');
+const path = require('path');
+const csv = require('fast-csv');
+const db = require('./../models');
+const upload_folder = path.resolve(__dirname, '../uploads');
+
+module.exports = {
+ /**
+ * Import CSV file
+ * @param {request} req
+ * @param {response} res
+ */
+ csv_preview: function (req, res) {
+ return new Promise((resolve, reject) => {
+ const upload = multer({ dest: path.resolve(upload_folder) }).single('file');
+ upload(req, res, async function (error) {
+ if (error) {
+ reject(error);
+ }
+ let data = [];
+ fs.createReadStream(path.resolve(upload_folder, req.file.path))
+ .pipe(csv.parse())
+ .on('error', (error) => reject(error))
+ .on('data', (row) => {
+ data.push(row);
+ })
+ .on('end', (rowCount) => {
+ fs.unlinkSync(path.resolve(upload_folder, req.file.path));
+ console.log(`Parsed ${rowCount} rows`);
+ resolve(data);
+ });
+ });
+ });
+ },
+ csv_import: async function (req, res) {
+ return new Promise((resolve, reject) => {
+ const table = req.params.model;
+ const upload = multer({ dest: path.resolve(upload_folder) }).single('file');
+ let data = [];
+ upload(req, res, async function (error) {
+ if (error) {
+ throw new Error(error);
+ }
+ fs.createReadStream(path.resolve(upload_folder, req.file.path))
+ .pipe(csv.parse({ headers: true }))
+ .on('error', (error) => reject(error))
+ .on('data', (row) => {
+ data.push(row);
+ })
+ .on('end', async (rowCount) => {
+ fs.unlinkSync(path.resolve(upload_folder, req.file.path));
+ console.log(`Parsed ${rowCount} rows`);
+ await db[table].batchInsert(data).catch((error) => {
+ console.log(error);
+ reject(error);
+ });
+ resolve(data);
+ });
+ });
+ });
+ },
+ /**
+ * Export CSV file
+ * @param {request} req
+ * @param {response} res
+ */
+ csv_export: async function (req, res) {
+ try {
+ let fields = await db[req.table].getAll(req.where);
+ fields = JSON.stringify(fields); // do this needed?
+
+ const csv = await converter.json2csvAsync(fields);
+
+ res.header('Content-Type', 'text/csv');
+ res.attachment(req.table + '.csv');
+
+ return res.send(csv);
+ } catch (error) {
+ console.log(error);
+ }
+ },
+};
diff --git a/day11/services/EncryptDecryptService.js b/day11/services/EncryptDecryptService.js
new file mode 100755
index 0000000..beee8d3
--- /dev/null
+++ b/day11/services/EncryptDecryptService.js
@@ -0,0 +1,15 @@
+const crypto = require('crypto');
+
+module.exports = {
+ iv: crypto.randomBytes(16).toString('hex').slice(0, 16),
+ encrypt: function (message, secret) {
+ const encrypter = crypto.createCipheriv('aes-256-cbc', secret, this.iv);
+ let encryptMessage = encrypter.update(message, 'utf-8', 'hex');
+ return (encryptMessage += encrypter.final('hex'));
+ },
+ decrypt: function (encryptMessage, secret) {
+ const decrypter = crypto.createDecipheriv('aes-256-cbc', secret, this.iv);
+ let decryptedMessage = decrypter.update(encryptMessage, 'hex', 'utf8');
+ return (decryptedMessage += decrypter.final('utf8'));
+ },
+};
diff --git a/day11/services/ErrorService.js b/day11/services/ErrorService.js
new file mode 100755
index 0000000..c04be43
--- /dev/null
+++ b/day11/services/ErrorService.js
@@ -0,0 +1,36 @@
+const { AuthenticationError: AuthenticationErrorNative } = require('apollo-server-express');
+const { GraphQLError } = require('graphql');
+
+class ErrorService extends Error {
+ constructor(name = null, message = null, code = null) {
+ super();
+ this.name = name;
+ this.message = message;
+ this.code = code;
+ this.stack = null;
+ }
+}
+
+class GraphqlErrorService extends GraphQLError {
+ constructor(message = '', code) {
+ super(message);
+ this.message = message;
+ this.code = code;
+ this.stack = null;
+ }
+}
+
+class AuthenticationError extends AuthenticationErrorNative {
+ constructor(message = null, code = null) {
+ super(message);
+ this.extensions.code = code;
+ this.code = code;
+ this.stack = null;
+ }
+}
+
+module.exports = {
+ Error: ErrorService,
+ GraphqlError: GraphqlErrorService,
+ AuthenticationError,
+};
diff --git a/day11/services/FirebaseService.js b/day11/services/FirebaseService.js
new file mode 100755
index 0000000..dec7a64
--- /dev/null
+++ b/day11/services/FirebaseService.js
@@ -0,0 +1,166 @@
+var firebase = require('firebase-admin');
+var serviceAccount = require('./serviceAccountKey.json');
+
+/*
+Examples:
+Write:
+firebaseService.write('notification', 1, {
+ a: 1,
+ b: 'd',
+ c:['a','w'],
+ d: [{a:1},{b:2}]
+})
+Read Once:
+firebaseService.read('notification', 1).then(function(notification){
+ console.log(notification);
+ });
+Update:
+firebaseService.update('notification', 1, {a:2});
+usersRef.update({
+ "alanisawesome/nickname": "Alan The Machine",
+ "gracehop/nickname": "Amazing Grace"
+});
+Read On Listeners:
+ref.on("value", function(snapshot) {
+ console.log(snapshot.val());
+}, function (errorObject) {
+ console.log("The read failed: " + errorObject.code);
+});
+Read Previous Listener:
+ref.on("child_added", function(snapshot, prevChildKey) {
+ var newPost = snapshot.val();
+ console.log("Author: " + newPost.author);
+ console.log("Title: " + newPost.title);
+ console.log("Previous Post ID: " + prevChildKey);
+});
+
+Child Changed Listener:
+// Get the data on a post that has changed
+ref.on("child_changed", function(snapshot) {
+ var changedPost = snapshot.val();
+ console.log("The updated post title is " + changedPost.title);
+});
+
+Child Removed Listener:
+// Get the data on a post that has been removed
+ref.on("child_removed", function(snapshot) {
+ var deletedPost = snapshot.val();
+ console.log("The blog post titled '" + deletedPost.title + "' has been deleted");
+});
+Order By Listener:
+ref.orderByChild("height").on("child_added", function(snapshot) {
+ console.log(snapshot.key + " was " + snapshot.val().height + " meters tall");
+});
+
+Order By key:
+var ref = db.ref("dinosaurs");
+ref.orderByKey().on("child_added", function(snapshot) {
+ console.log(snapshot.key);
+});
+
+Limited:
+var ref = db.ref("dinosaurs");
+ref.orderByChild("weight").limitToLast(2).on("child_added", function(snapshot) {
+ console.log(snapshot.key);
+});
+
+var ref = db.ref("dinosaurs");
+ ref.orderByChild("height").startAt(3).on("child_added", function(snapshot) {
+ console.log(snapshot.key);
+ });
+
+var ref = db.ref("dinosaurs");
+ref.orderByKey().endAt("pterodactyl").on("child_added", function(snapshot) {
+ console.log(snapshot.key);
+});
+
+var ref = db.ref("dinosaurs");
+ref.orderByKey().startAt("b").endAt("b\uf8ff").on("child_added", function(snapshot) {
+ console.log(snapshot.key);
+});
+Equal To:
+var ref = db.ref("dinosaurs");
+ref.orderByChild("height").equalTo(25).on("child_added", function(snapshot) {
+ console.log(snapshot.key);
+});
+ */
+function FirebaseService() {
+ this._transporter = firebase.initializeApp(
+ {
+ // credential: firebase.credential.cert('./serviceAccountKey.json'),
+ credential: firebase.credential.cert(serviceAccount),
+ databaseURL: 'https://konfer-243320.firebaseio.com',
+ },
+ // , 'flashbid-prod'
+ );
+ console.log('Prod Firebase');
+ this._database = this._transporter.database();
+ this._messaging = this._transporter.messaging();
+
+ this.write = function (table, id, payload) {
+ return this._database.ref(table + '/' + id).set(payload);
+ };
+
+ this.read = function (table, id) {
+ return this._database
+ .ref(table + '/' + id)
+ .once('value')
+ .then(function (snapshot) {
+ return snapshot.val();
+ });
+ };
+
+ this.update = function (table, id, payload) {
+ const ref = this._database.ref(table).child(id);
+ ref.update(payload);
+ };
+
+ this.updateSpecific = function (table, id, payload) {
+ return this._database.ref(table + '/' + id).update(payload);
+ };
+
+ this.pushToList = function (table, id, listField, payload) {
+ const ref = this._database.ref(table).child(id).child(listField).push();
+ ref.set(payload);
+ };
+
+ this.sendPushNotification = function (
+ id,
+ title,
+ subs,
+ description,
+ deviceId,
+ ) {
+ // This registration token comes from the client FCM SDKs.
+
+ // See the "Defining the message payload" section above for details
+ // on how to define a message payload.
+ var payload = {
+ notification: {
+ title: title,
+ body: description,
+ },
+ data: {
+ title: subs.text,
+ body: subs.desc,
+ action: subs.action,
+ id: id,
+ },
+ };
+
+ // Set the message as high priority and have it expire after 24 hours.
+ var options = {
+ priority: 'high',
+ timeToLive: 60 * 60 * 24,
+ };
+
+ console.log('payload', payload);
+ console.log('device id', deviceId);
+
+ // Send a message to the device corresponding to the provided
+ // registration token with the provided options.
+ return this._messaging.sendToDevice(deviceId, payload, options);
+ };
+}
+
+module.exports = FirebaseService;
diff --git a/day11/services/HtmlToPdf.js b/day11/services/HtmlToPdf.js
new file mode 100755
index 0000000..f40c154
--- /dev/null
+++ b/day11/services/HtmlToPdf.js
@@ -0,0 +1,30 @@
+const html_to_pdf = require('html-pdf-node');
+
+module.exports = {
+ html_to_pdf_with_content: async function (content, options) {
+ const file = { content };
+
+ return new Promise(function (resolve, reject) {
+ html_to_pdf
+ .generatePdf(file, options)
+ .then((pdfBuffer) => {
+ resolve(pdfBuffer);
+ })
+ .catch((error) => reject(error));
+ });
+ },
+ html_to_pdf_with_url: async function (url, options) {
+ const file = { url };
+
+ return new Promise(function (resolve, reject) {
+ html_to_pdf
+ .generatePdf(file, options)
+ .then((pdfBuffer) => {
+ resolve(pdfBuffer);
+ })
+ .catch((error) => reject(error));
+ });
+ },
+};
+
+// pdf to html https://www.npmjs.com/package/pdf2html
diff --git a/day11/services/JwtService.js b/day11/services/JwtService.js
new file mode 100755
index 0000000..af4aa26
--- /dev/null
+++ b/day11/services/JwtService.js
@@ -0,0 +1,101 @@
+/*Powered By: Manaknightdigital Inc. https://manaknightdigital.com/ Year: 2020*/
+/**
+ * JWT Service
+ * @copyright 2020 Manaknightdigital Inc.
+ * @link https://manaknightdigital.com
+ * @license Proprietary Software licensing
+ * @author Ryan Wong
+ *
+ */
+var jwt = require('jsonwebtoken');
+var dotenv = require('dotenv');
+dotenv.config();
+dotenv.config({ path: './src/.env' });
+
+module.exports = {
+ createAccessToken: function (payload, expireIn = process.env.DYNAMIC_CONFIG_JWT_EXPIRE_AT) {
+ const secret = process.env.DYNAMIC_CONFIG_JWT_KEY;
+
+ return jwt.sign(payload, secret, {
+ expiresIn: Number(expireIn),
+ algorithm: 'HS256',
+ });
+ },
+ createRefreshToken: function (payload) {
+ return jwt.sign(payload, process.env.DYNAMIC_CONFIG_JWT_KEY, {
+ expiresIn: Number(process.env.DYNAMIC_CONFIG_JWT_REFRESH_EXPIRE_AT),
+ algorithm: 'HS256',
+ });
+ },
+
+ verifyAccessToken: function (token) {
+ try {
+ return jwt.verify(token, process.env.DYNAMIC_CONFIG_JWT_KEY);
+ } catch (err) {
+ return false;
+ }
+ },
+ verifyRefreshToken: function (token) {
+ try {
+ return jwt.verify(token, process.env.DYNAMIC_CONFIG_JWT_KEY);
+ } catch (err) {
+ return false;
+ }
+ },
+ generateString: function (length) {
+ let d = new Date().getTime();
+ const time = new Date().getTime();
+ const uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx-xxxx'.replace(/[xy]/g, function (c) {
+ let r = (d + Math.random() * 16) % 16 | 0;
+ d = Math.floor(d / 16);
+ return (c == 'x' ? r : (r & 0x7) | 0x8).toString(16);
+ });
+
+ return (uuid.toUpperCase() + '-' + time.toString()).substring(0, length);
+ },
+ generateUUID: function () {
+ let d = new Date().getTime();
+ const uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
+ let r = (d + Math.random() * 16) % 16 | 0;
+ d = Math.floor(d / 16);
+ return (c == 'x' ? r : (r & 0x7) | 0x8).toString(16);
+ });
+ return uuid.toUpperCase();
+ },
+ getToken: function (req) {
+ if (req.headers.authorization && req.headers.authorization.split(' ')[0] === 'Bearer') {
+ return req.headers.authorization.split(' ')[1];
+ } else if (req.query && req.query.token) {
+ return req.query.token;
+ }
+
+ return null;
+ },
+ verifyTokenMiddleware: function (role) {
+ const self = this;
+ return function (req, res, next) {
+ const token = self.getToken(req);
+ if (!token) {
+ return res.status(401).json({
+ success: false,
+ code: 'UNAUTHORIZED',
+ message: 'Access denied',
+ });
+ }
+ const result = self.verifyAccessToken(token);
+ const roleId = result?.role_id;
+ const user = result?.user;
+ const credentialId = result?.credential_id;
+
+ if (!result || !roleId || !user || !credentialId) {
+ return res.status(401).json({
+ success: false,
+ code: 'TOKEN_INVALID',
+ message: 'Invalid token',
+ });
+ }
+ req.tokenPayload = result;
+ next();
+ };
+ },
+};
diff --git a/day11/services/LoggingService.js b/day11/services/LoggingService.js
new file mode 100755
index 0000000..4711dd9
--- /dev/null
+++ b/day11/services/LoggingService.js
@@ -0,0 +1,37 @@
+const winston = require("winston");
+winston.level = process.env.LOG_LEVEL;
+const tsFormat = () => new Date().toLocaleString();
+const logger = winston.createLogger({
+ level: "info",
+ format: winston.format.json(),
+ transports: [
+ //
+ // - Write to all logs with level `info` and below to `combined.log`
+ // - Write all logs error (and below) to `error.log`.
+ //
+ new winston.transports.File({
+ filename: "error.log",
+ level: "error",
+ timestamp: tsFormat,
+ }),
+ new winston.transports.File({
+ filename: "combined.log",
+ timestamp: tsFormat,
+ }),
+ ],
+});
+
+//
+// If we're not in production then log to the `console` with the format:
+// `${info.level}: ${info.message} JSON.stringify({ ...rest }) `
+if (process.env.NODE_ENV != "production") {
+ logger.add(
+ new winston.transports.Console({
+ format: winston.format.simple(),
+ timestamp: tsFormat,
+ colorize: true,
+ })
+ );
+}
+
+module.exports = logger;
diff --git a/day11/services/MailService.js b/day11/services/MailService.js
new file mode 100755
index 0000000..fbffb2d
--- /dev/null
+++ b/day11/services/MailService.js
@@ -0,0 +1,99 @@
+const nodemailer = require('nodemailer');
+
+const db = require('../models');
+
+module.exports = {
+ /** @private */
+ transport: null,
+ /** @private */
+ from: null,
+ /** @private */
+ to: null,
+ /**
+ * Nodemailer initializer
+ * @name mailService.initialize
+ * @param {{hostname: String, port: Number, username: String, password: String, from: String, to: String}} config Nodemailer configuration
+ * @returns {Void}
+ */
+ initialize: function (config) {
+ this.transport = nodemailer.createTransport({
+ host: config.hostname,
+ port: config.port,
+ auth: {
+ user: config.username,
+ pass: config.password,
+ },
+ });
+
+ this.from = config.from;
+ this.to = config.to;
+ },
+ /**
+ * Get email template from database
+ * @name mailService.template
+ * @param {String} slug email template slug
+ * @reject {Error}
+ * @returns {Promise.<{body: String, subject: String}>} email template
+ */
+ template: function (slug) {
+ return new Promise(function (resolve, reject) {
+ db.email
+ .findOne({ where: { slug } })
+ .then((response) => {
+ if (!response) {
+ return reject(`TEMPLATE_NOT_FOUND`);
+ } else resolve(response);
+ })
+ .catch((error) => {
+ reject(error);
+ });
+ });
+ },
+ /**
+ * Inject values into email template
+ * @name mailService.inject
+ * @param {{body: String, subject: String}} template email template
+ * @param {Object.} payload template values
+ * @returns {{from: String, to: String, subject: String, text: String}} Value injected email template
+ */
+ inject: function (template, payload) {
+ let mailBody = template.body;
+ let mailSubject = template.subject;
+
+ for (const key in payload) {
+ const value = payload[key];
+ mailBody = mailBody.replace(new RegExp('{{{' + key + '}}}', 'g'), value);
+ }
+
+ for (const key in payload) {
+ const value = payload[key];
+ mailSubject = mailSubject.replace(
+ new RegExp('{{{' + key + '}}}', 'g'),
+ value,
+ );
+ }
+
+ return {
+ from: this.from,
+ to: this.to,
+ subject: mailSubject,
+ html: mailBody,
+ };
+ },
+ /**
+ * Send email
+ * @name mailService.send
+ * @param {nodemailer.SendMailOptions} template email template
+ * @reject {Error} send mail error
+ * @returns {Promise.} send mail info
+ */
+ send: function (template) {
+ let self = this;
+ return new Promise(function (resolve, reject) {
+ self.transport
+ .sendMail(template)
+ .then((response) => resolve(response))
+ .catch((error) => reject(error));
+ });
+ },
+};
diff --git a/day11/services/MaintenanceService.js b/day11/services/MaintenanceService.js
new file mode 100755
index 0000000..e69de29
diff --git a/day11/services/OAuthService.js b/day11/services/OAuthService.js
new file mode 100755
index 0000000..03728eb
--- /dev/null
+++ b/day11/services/OAuthService.js
@@ -0,0 +1,210 @@
+const querystring = require('querystring');
+const axios = require('axios').default;
+
+const db = require('../models');
+
+module.exports = {
+ google: {
+ /**
+ * Generates authentication URL
+ * @name oAuth.google.generateAuthURL
+ * @param {{redirect_uri: String, client_id: String}} config
+ * @returns {String} authentication URL
+ */
+ generateAuthURL(config) {
+ const rootUrl = 'https://accounts.google.com/o/oauth2/v2/auth';
+
+ const options = {
+ redirect_uri: config.redirect_uri,
+ client_id: config.client_id,
+ access_type: 'offline',
+ response_type: 'code',
+ prompt: 'consent',
+ scope: [
+ 'https://www.googleapis.com/auth/userinfo.profile',
+ 'https://www.googleapis.com/auth/userinfo.email',
+ ].join(' '),
+ };
+
+ return `${rootUrl}?${querystring.stringify(options)}`;
+ },
+ /**
+ * Generates authentication Token
+ * @name oAuth.google.generateAuthToken
+ * @param {{auth_code: String, client_id: String, client_secret: String, redirect_uri: String}} config
+ * @returns {Promise.<{{access_token: String, expires_in: String, refresh_token: String, scope: String, token_type: String id_token: String}}>} authentication token
+ */
+ generateAuthToken(config) {
+ return new Promise(function (resolve, reject) {
+ const url = 'https://oauth2.googleapis.com/token';
+
+ const buildQuerystring = querystring.stringify({
+ code: config.auth_code,
+ client_id: config.client_id,
+ client_secret: config.client_secret,
+ redirect_uri: config.redirect_uri,
+ grant_type: 'authorization_code',
+ });
+
+ axios.default
+ .post(url, buildQuerystring, {
+ headers: {
+ 'Content-Type': 'application/x-www-form-urlencoded',
+ },
+ })
+ .then((response) => resolve(response.data))
+ .catch((error) => reject(error));
+ });
+ },
+ /**
+ * Retrieve user information
+ * @name oAuth.google.getUserInfo
+ * @param {{access_token: String, expires_in: String, refresh_token: String, scope: String, id_token: String}} config
+ * @returns {Promise.<{id: string, email: string, verified_email: true, name: string, given_name: string, family_name: string, picture: string, locale: string}>} user information
+ */
+ getUserInfo(config, isAPI = false) {
+ return new Promise(function (resolve, reject) {
+ axios
+ .get(
+ `https://www.googleapis.com/oauth2/v1/userinfo?alt=json&access_token=${config.access_token}`,
+ isAPI
+ ? {}
+ : {
+ headers: {
+ Authorization: `Bearer ${config.id_token}`,
+ },
+ },
+ )
+ .then((response) => resolve(response.data))
+ .catch((error) => reject(error));
+ });
+ },
+ },
+ facebook: {
+ /**
+ * Generates authentication URL
+ * @name oAuth.facebook.generateAuthURL
+ * @param {{redirect_uri: String, client_id: String}} config
+ * @returns {String} authentication URL
+ */
+ generateAuthURL(config) {
+ const stringifiedParams = querystring.stringify({
+ client_id: config.client_id,
+ redirect_uri: process.env.DYNAMIC_CONFIG_FACEBOOK_REDIRECT_URI,
+ scope: ['email', 'user_friends'].join(','),
+ response_type: 'code',
+ auth_type: 'rerequest',
+ display: 'popup',
+ });
+
+ return `https://www.facebook.com/v4.0/dialog/oauth?${stringifiedParams}`;
+ },
+ /**
+ * Generates authentication Token
+ * @name oAuth.facebook.generateAuthToken
+ * @param {{auth_code: String, client_id: String, client_secret: String, redirect_uri: String}} config
+ * @returns {Promise.<{access_token: String, token_type: String, expires_in: String}>} authentication token
+ */
+ async generateAuthToken(config) {
+ const url = 'https://graph.facebook.com/v4.0/oauth/access_token';
+
+ const { data } = await axios({
+ url,
+ method: 'GET',
+ params: {
+ code: config.auth_code,
+ client_id: config.client_id,
+ client_secret: config.client_secret,
+ redirect_uri: config.redirect_uri,
+ },
+ });
+
+ return data;
+ },
+ /**
+ * Retrieve user information
+ * @name oAuth.facebook.getUserInfo
+ * @param {{access_token: String, token_type: String, expires_in: String}} config
+ * @returns {Promise.<{id: string, email: string, first_name: string, last_name: string, image: string}>} user information
+ */
+ async getUserInfo(config) {
+ const { data } = await axios({
+ url: 'https://graph.facebook.com/me',
+ method: 'GET',
+ params: {
+ fields: ['id', 'email', 'first_name', 'last_name'].join(','),
+ access_token: config.access_token,
+ },
+ });
+
+ return data;
+ },
+ },
+ async authenticate(user) {
+ let User;
+ let Credential;
+ try {
+ const isEmailExist = await db.credential.getByField('email', user.email);
+ if (isEmailExist) {
+ // Check status and role
+ if (+isEmailExist.status === 0 || +user.role === +isEmailExist.role) {
+ throw new Error('EMAIL_ADDRESS_NOT_FOUND');
+ }
+ // Check provider type type
+ const type = isEmailExist.type === user.provider;
+ if (!type) {
+ throw new Error(
+ 'ACCOUNT_IS_REGISTERED_WITH_' +
+ (user.provider === 'n'
+ ? 'EMAIL_AND_PASSWORD'
+ : user.provider === 'g'
+ ? 'GOOGLE'
+ : 'FACEBOOK'),
+ );
+ }
+ const userExists = await db.user.getByPK(isEmailExist.user_id);
+
+ if (!userExists) {
+ throw new Error('EMAIL_ADDRESS_NOT_FOUND');
+ }
+
+ return {
+ credential: isEmailExist.id,
+ user: userExists,
+ };
+ }
+
+ User = await db.user.insert(
+ {
+ first_name: user.first_name,
+ last_name: user.last_name,
+ image: user.image,
+ },
+ { returnAllFields: true },
+ );
+
+ Credential = await db.credential.insert(
+ {
+ user_id: User.id,
+ email: user.email,
+ type: user.provider,
+ role_id: user.role_id,
+ status: 1,
+ },
+ { returnAllFields: true },
+ );
+
+ return { credential: Credential.id, user: User };
+ } catch (error) {
+ if (User) {
+ User.destroy();
+ }
+
+ if (Credential) {
+ Credential.destroy();
+ }
+
+ throw new Error(error.message);
+ }
+ },
+};
diff --git a/day11/services/PaginationService.js b/day11/services/PaginationService.js
new file mode 100755
index 0000000..36c8659
--- /dev/null
+++ b/day11/services/PaginationService.js
@@ -0,0 +1,64 @@
+/*Powered By: Manaknightdigital Inc. https://manaknightdigital.com/ Year: 2020*/
+/**
+ * Pagination Service
+ * @copyright 2020 Manaknightdigital Inc.
+ * @link https://manaknightdigital.com
+ * @license Proprietary Software licensing
+ * @author Ryan Wong
+ *
+ */
+module.exports = function (page, numItems) {
+ this.page = page ? Number(page) : 1;
+ this.numItems = numItems ? Number(numItems) : 20;
+ this.data = [];
+ this.count = 0;
+ this.numPages = 0;
+
+ this.getItems = function () {
+ return this.data;
+ };
+
+ this.getPage = function () {
+ return this.page;
+ };
+
+ this.getCount = function () {
+ return this.count;
+ };
+
+ this.setCount = function (count) {
+ this.count = count;
+ };
+
+ this.getNumPages = function () {
+ return this.numPages;
+ };
+
+ this.setItems = function (data) {
+ if (data.length == 1 && !data[0].id) {
+ this.count = 0;
+ this.numPages = 1;
+ this.data = [data[0]];
+ return this;
+ }
+ this.data = data;
+ this.numPages = this.count > 1 ? Math.ceil(this.count / this.numItems) : 1;
+
+ this.data = this.data.map(function (transaction) {
+ delete transaction.num;
+ return transaction.toJSON();
+ });
+
+ return this;
+ };
+
+ this.getOffset = function () {
+ return (this.page - 1) * this.numItems;
+ };
+
+ this.getLimit = function () {
+ return this.numItems;
+ };
+
+ return this;
+};
diff --git a/day11/services/PasswordService.js b/day11/services/PasswordService.js
new file mode 100755
index 0000000..af0c490
--- /dev/null
+++ b/day11/services/PasswordService.js
@@ -0,0 +1,22 @@
+;
+
+module.exports = {
+ /**
+ * Hash password
+ * @param {String} password password string to hash
+ * @returns {Promise.} hashed password
+ */
+ hash: async function (password) {
+ const salt = await bcrypt.genSalt(10);
+ return await bcrypt.hash(password, salt);
+ },
+ /**
+ * Compare hashed password with password string
+ * @param {String} password password string
+ * @param {String} hashedPassword hashed password
+ * @returns {Promise.} return true if hashed password match with password string else return false
+ */
+ compareHash: async function (password, hashedPassword) {
+ return await bcrypt.compare(password, hashedPassword);
+ },
+};
diff --git a/day11/services/PaymentService.js b/day11/services/PaymentService.js
new file mode 100755
index 0000000..480b592
--- /dev/null
+++ b/day11/services/PaymentService.js
@@ -0,0 +1,680 @@
+require('dotenv').config(); //require .env for stripe configurations
+const moment = require('moment');
+const stripe = require('./StripeApi'); //this is Payment Service
+const paypal = require('./PaypalApi');
+const db = require('../models');
+const userModel = db.user;
+const stripeSubscriptionsModel = db.stripe_subscriptions;
+const stripeSubscriptionsLogModel = db.stripe_subscriptions_log;
+const paymentPlansModel = db.payment_plans;
+const stripeProductsModel = db.stripe_products;
+const paymentServicesModel = db.payment_services;
+const stripeCardsModel = db.stripe_cards;
+
+module.exports = new Service();
+function Service() {
+ this.userId = 0;
+ this.roleId = 0;
+ this.currency = process.env.STRIPE_CURRENCY;
+ this.prorate = process.env.STRIPE_PRORATE;
+ this.forceCancel = process.env.STRIPE_FORCE_CANCEL;
+
+ this.setUserId = function (userId) {
+ this.userId = userId;
+ };
+ this.setRoleId = function (roleId) {
+ this.roleId = roleId;
+ };
+ this.setCurrency = function (currency) {
+ this.currency = currency;
+ };
+ this.setProrationType = function (prorate) {
+ this.prorate = prorate;
+ };
+ this.setCancelType = function (forceCancel) {
+ this.forceCancel = forceCancel;
+ };
+ let convertToCents = function (amount) {
+ return amount * 100;
+ };
+ let handleDBError = function (error) {
+ console.error(error);
+ throw new Error('Internal error: Database error');
+ };
+ let handleRequestError = function (error) {
+ console.error(error);
+ throw new Error('Internal error: Request error');
+ };
+
+ this.subscribe = async function (subscriptionParams, plan) {
+ var subscription = await stripe.createSubscription(subscriptionParams);
+ //create model entry
+ let subscriptionModelEntry = {
+ stripe_id: subscription.id ?? '',
+ cancel_at_period_end: subscription.cancel_at_period_end ?? null,
+ current_period_start: moment(new Date(subscription.current_period_start * 1000)).format('YYYY-MM-DD') ?? null,
+ current_period_end: moment(new Date(subscription.current_period_end * 1000)).format('YYYY-MM-DD') ?? null,
+ user_id: this.userId ?? null,
+ role_id: this.roleId ?? null,
+ plan_id: plan?.id ?? null,
+ coupon_stripe_id: subscription.discount?.coupon?.id ?? '',
+ customer_stripe_id: subscription.customer ?? '',
+ collection_method: subscription.collection_method ?? '',
+ interval: stripeSubscriptionsModel.inverse_interval_mapping(subscription.plan?.interval) ?? 0,
+ interval_count: subscription.plan?.interval_count ?? '',
+ trial_period_days: subscription.plan?.trial_period_days ?? 0,
+ trial_end: subscription.trial_end ? moment(new Date(subscription.trial_end * 1000)).format('YYYY-MM-DD') : null,
+ trial_start: subscription.trial_start ? moment(new Date(subscription.subscriptiontrial_start * 1000)).format('YYYY-MM-DD') : null,
+ status: stripeSubscriptionsModel.inverse_status_mapping(subscription.status) ?? null,
+ };
+
+ let subscriptionCreatedId = await stripeSubscriptionsModel.insert(subscriptionModelEntry);
+ if (!subscriptionCreatedId) {
+ throw new Error('Subscription is not found.');
+ }
+
+ let subscriptionLogUpdated = await this.updateSubscriptionLog(subscriptionCreatedId, subscription.status, plan);
+ if (!subscriptionLogUpdated) {
+ throw new Error('Error while editing subscription log');
+ }
+ return subscriptionCreatedId;
+ };
+ this.updateSubscriptionLog = async function (subscriptionCreatedId, subscriptionCreatedStatus, plan) {
+ let subscriptionLog = await stripeSubscriptionsLogModel.getLast({ user_id: this.userId, role_id: this.roleId });
+
+ if (!subscriptionLog) {
+ let subscriptionLogModelEntry = {
+ user_id: this.userId,
+ role_id: this.roleId,
+ plan_id: plan?.id ?? null,
+ subscription_id: subscriptionCreatedId,
+ type: plan?.type ?? null,
+ status: subscriptionCreatedStatus == 'active' || subscriptionCreatedStatus == 'trialing' ? 1 : 0,
+ };
+ if (!(await stripeSubscriptionsLogModel.insert(subscriptionLogModelEntry))) {
+ throw new Error('Subscription log add not successfull');
+ }
+ } else {
+ let subscriptionLogModelEntry = {
+ plan_id: plan?.id ?? null,
+ subscription_id: subscriptionCreatedId,
+ type: plan?.type ?? null,
+ status: subscriptionCreatedStatus == 'active' || subscriptionCreatedStatus == 'trialing' ? 1 : 0,
+ };
+
+ if (!(await stripeSubscriptionsLogModel.edit(subscriptionLogModelEntry, subscriptionLog.id))) {
+ throw new Error('Subscription log edit not successfull');
+ }
+ }
+ return true;
+ };
+ this.createRegularSubscription = async function (user, plan, card, coupon) {
+ let subscriptionParams = {
+ customer: user.stripe_id,
+ items: [{ price: plan.stripe_id }],
+ trial_from_plan: true,
+ default_payment_method: card.stripe_card_id,
+ coupon: coupon?.stripe_id ?? null,
+ };
+ return await this.subscribe(subscriptionParams, plan);
+ };
+ this.createLifetimeSubscription = async function (user, plan, card, coupon) {
+ //create invoice, finalize and pay it.
+ let lifetimePlanStripeObject = await stripe.retrievePrice(plan.stripe_id);
+ let invoiceItemParams = {
+ customer: user.stripe_id,
+ price: plan.stripe_id,
+ discounts: [{ coupon: coupon?.stripe_id }],
+ };
+ let invoiceItemCreated = await stripe.createInvoiceItem(invoiceItemParams);
+
+ let invoiceParams = {
+ customer: user.stripe_id,
+ default_payment_method: card?.stripe_card_id ?? null,
+ };
+ let invoiceCreated = await stripe.createInvoice(invoiceParams);
+
+ let invoicePaid = await stripe.payInvoice(invoiceCreated.id);
+
+ let subscriptionModelEntry = {
+ stripe_id: invoiceItemCreated.id ?? '',
+ cancel_at_period_end: null,
+ current_period_start: moment().format('YYYY-MM-DD') ?? null,
+ current_period_end: null,
+ user_id: this.userId ?? null,
+ role_id: this.roleId ?? null,
+ plan_id: plan?.id ?? null,
+ coupon_stripe_id: invoiceItemCreated.discounts.length > 0 ? coupon?.stripe_id : '',
+ customer_stripe_id: invoicePaid.customer ?? '',
+ collection_method: invoicePaid.collection_method ?? '',
+ interval: 4,
+ interval_count: null,
+ trial_period_days: 0,
+ trial_end: null,
+ trial_start: null,
+ status: 4,
+ };
+
+ let subscriptionCreatedId = await stripeSubscriptionsModel.insert(subscriptionModelEntry);
+ if (!subscriptionCreatedId) {
+ throw new Error('Subscription is not found.');
+ }
+
+ let subscriptionLogUpdated = await this.updateSubscriptionLog(subscriptionCreatedId, 'active', plan);
+ if (!subscriptionLogUpdated) {
+ throw new Error('Error while editing subscription log');
+ }
+
+ return subscriptionCreatedId;
+ };
+ let calculateFullPeriod = function (interval, interval_count = 1) {
+ let totalNumberOfDays = paymentPlansModel.interval_days_mapping()[interval] * interval_count;
+ let cancelAtEpoch = moment().add(totalNumberOfDays, 'days').unix();
+ return cancelAtEpoch;
+ };
+ this.createTrialSubscription = async function (user, plan, card, coupon) {
+ let trialPlanFullPeriod = calculateFullPeriod(plan.interval, plan.interval_count);
+ let subscriptionParams = {
+ customer: user.stripe_id,
+ items: [{ price: plan.stripe_id }],
+ trial_from_plan: true,
+ default_payment_method: card.stripe_card_id,
+ coupon: coupon?.stripe_id ?? null,
+ cancel_at: trialPlanFullPeriod,
+ };
+ return await this.subscribe(subscriptionParams, plan);
+ };
+ this.cancelRegularSubscription = async function (subscription) {
+ if (this.forceCancel === 'true') {
+ var cancellationParams = {
+ subscriptionId: subscription.stripe_id,
+ params: {
+ invoice_now: true,
+ prorate: this.prorate,
+ },
+ };
+ let subscriptionCanceled = await stripe.cancelSubscription(cancellationParams);
+
+ let newStatus = stripeSubscriptionsModel.inverse_status_mapping()[subscriptionCanceled.status];
+ await stripeSubscriptionsModel.edit({ status: newStatus }, subscription.id);
+ } else {
+ let updateParams = {
+ subscriptionId: subscription.stripe_id,
+ params: {
+ cancel_at_period_end: true,
+ },
+ };
+ await stripe.updateSubscription(updateParams);
+ await stripeSubscriptionsModel.edit({ cancel_at_period_end: 1 }, subscription.id);
+ }
+ };
+ this.cancelLifetimeSubscription = async function (subscription) {
+ //TODO check if we refund on this or not
+ //we could make a fixed refund amount
+ //right now we just cancel it locally and refund is never as it is a onetime product thing.
+ let subscriptionEdited = await stripeSubscriptionsModel.edit({ status: 5 }, parseInt(subscription.id));
+ if (!subscriptionEdited) {
+ throw new Error("Couldn't edit subscription");
+ }
+ let subscriptionLog = await stripeSubscriptionsLogModel.getByFields({ subscription_id: parseInt(subscription.id), status: 1 });
+ if (!subscriptionLog) {
+ throw new Error("Couldn't find relative active subscription log");
+ }
+ let subscriptionLogEdited = await stripeSubscriptionsLogModel.edit({ status: 0 }, subscriptionLog.id);
+ if (subscriptionLogEdited) {
+ return true;
+ }
+ throw new Error("Couldn't Edit subscription log");
+ };
+ this.createCustomerWithoutCard = async function (params) {
+ let createdCustomer = await stripe.createCustomer(params);
+
+ let userModelEntry = {
+ stripe_id: createdCustomer.id,
+ };
+ let modelEditedUser = await this.userModel.edit(userModelEntry, this.userId);
+ if (!modelEditedUser) {
+ throw new Error('Internal error: Error editing user stripe id');
+ }
+ return modelEditedUser;
+ };
+ this.createCustomer = async function (params) {
+ let createdCustomer = await stripe.createCustomer(params);
+ let userModelEntry = {
+ stripe_id: createdCustomer.id,
+ };
+ let editedUserModel = await userModel.edit(userModelEntry, this.userId);
+ if (!editedUserModel) {
+ throw new Error('Error editing user stripe id');
+ }
+
+ let customerCardParams = {
+ customerId: createdCustomer.id,
+ cardId: createdCustomer.default_source,
+ };
+ let card = await stripe.retrieveCard(customerCardParams);
+
+ if (card) {
+ cardModelEntry = {
+ card_last: card.last4 ?? '',
+ card_brand: card.brand ?? '',
+ card_exp_month: card.exp_month ?? '',
+ exp_year: card.exp_year ?? '',
+ card_name: createdCustomer.metadata.card_name ?? '',
+ stripe_card_customer: card.customer,
+ stripe_card_id: card.id,
+ is_default: createdCustomer.metadata.is_default,
+ user_id: this.userId ?? null,
+ role_id: this.roleId ?? null,
+ };
+ var modelCreatedCard = await stripeCardsModel.insert(cardModelEntry);
+ if (!modelCreatedCard) {
+ throw new Error('Internal error: Error adding card');
+ }
+ }
+ return modelCreatedCard;
+ };
+
+ this.adminCreateProductPrice = async function (productStripeId, price, productLocalId) {
+ let priceArgs = {
+ unit_amount: convertToCents(price),
+ currency: this.currency,
+ product: productStripeId,
+ };
+ let priceCreated = await stripe.createPrice(priceArgs);
+
+ let productModelEditEntry = {
+ price,
+ price_stripe_id: priceCreated.id,
+ };
+ let modelEditedProduct = await stripeProductsModel.edit(productModelEditEntry, productLocalId);
+ if (!modelEditedProduct) {
+ throw new Error('Internal Error: error setting price');
+ }
+ return priceCreated;
+ };
+
+ this.adminCreateProduct = async function (stripeProductParam, paypalProductParam, localProductType) {
+ if (!stripeProductParam.name) {
+ throw new Error('Must provide a name for your product');
+ }
+ await paypal.setAccessToken();
+ let createdStripeProduct = await stripe.createProduct(stripeProductParam);
+ if (localProductType == 0) {
+ let productModelEntry = {
+ stripe_id: createdStripeProduct.id,
+ name: createdStripeProduct.name,
+ status: createdStripeProduct.active ? 1 : 0,
+ description: createdStripeProduct.description ?? '',
+ images: createdStripeProduct.images.join(';;;'),
+ shippable: createdStripeProduct.shippable ?? 0,
+ unit_label: createdStripeProduct.unit_label ?? '',
+ statement_descriptor: createdStripeProduct.statement_descriptor ?? '',
+ };
+
+ let modelCreatedStripeProduct = await stripeProductsModel.insert(productModelEntry);
+
+ if (!modelCreatedStripeProduct) {
+ throw new Error('Internal error: error adding a product');
+ }
+ return {
+ productStripeId: createdStripeProduct.id,
+ productLocalId: modelCreatedStripeProduct,
+ };
+ } else if (localProductType == 1) {
+ let createdPaypalProduct = await paypal.createProduct(paypalProductParam);
+
+ let paymentServiceModelEntry = {
+ name: createdStripeProduct.name,
+ stripe_id: createdStripeProduct.id,
+ paypal_id: createdPaypalProduct.data.id,
+ status: createdStripeProduct.active ? 1 : 0,
+ image: createdStripeProduct.images.length > 0 ? createdStripeProduct.images[0] : '',
+ url: createdStripeProduct.url ?? '',
+ category: createdStripeProduct.metadata.category,
+ description: createdStripeProduct.description ?? '',
+ };
+
+ let modelCreatedServiceId = await paymentServicesModel.insert(paymentServiceModelEntry);
+
+ let service = paymentServicesModel.getByPK(modelCreatedServiceId);
+
+ return {
+ serviceProductStripeId: service.stripe_id,
+ serviceProductPaypalId: service.paypal_id,
+ serviceProductLocalId: service.id,
+ };
+ }
+ return false;
+ };
+
+ /**
+ * [adminCreateSubscriptionPlan allows an admin to create subscription plan through admin portal]
+ * @param {object} planParams [object that contain plan parameters]
+ * @param {integer} planType [integer that represent plan type in relative to stripe plans table] @see /models/stripe_plans.js type_mapping()
+ * @param {integer} localProductId [integer that represent product id in relative to stripe products table]
+ * @return {integer} [the id of the created plan in database]
+ */
+ this.adminCreateSubscriptionPlan = async function (params, planType, localServiceId) {
+ if (!params.nickname) {
+ throw new Error('Must have a display name');
+ }
+ if (!localServiceId) {
+ throw new Error('Must provide system product id');
+ }
+ if (!planType) {
+ throw new Error('Must provide plan type');
+ }
+ await paypal.setAccessToken();
+ let service = await paymentServicesModel.getByPK(localServiceId);
+
+ let stripeSubscriptionPlanParams = {
+ nickname: params.nickname,
+ currency: process.env.STRIPE_CURRENCY,
+ unit_amount: convertToCents(params.amount),
+ product: service.stripe_id,
+ recurring: {
+ interval: paymentPlansModel.interval_mapping()[params.interval],
+ interval_count: params.interval_count,
+ trial_period_days: params.trial_period_days,
+ },
+ active: parseInt(params.status) === 1 ? true : false,
+ };
+ let paypalSubscriptionPlanParams = {
+ product_id: service.paypal_id,
+ name: params.nickname,
+ billing_cycles: [
+ {
+ frequency: {
+ interval_unit: paymentPlansModel.interval_mapping()[params.interval].toUpperCase(),
+ interval_count: params.interval_count,
+ },
+ tenure_type: 'REGULAR',
+ sequence: 1,
+ total_cycles: 0,
+ pricing_scheme: {
+ fixed_price: {
+ value: params.amount,
+ currency_code: this.currency.toUpperCase(),
+ },
+ },
+ },
+ ],
+ payment_preferences: {
+ auto_bill_outstanding: true,
+ setup_fee_failure_action: 'CANCEL',
+ payment_failure_threshold: 0,
+ },
+ };
+ if (params.trial_period_days > 0) {
+ paypalSubscriptionPlanParams.billing_cycles[0].sequence = 2;
+ paypalSubscriptionPlanParams.billing_cycles.unshift({
+ frequency: {
+ interval_unit: 'DAY',
+ interval_count: parseInt(params.trial_period_days),
+ },
+ tenure_type: 'TRIAL',
+ sequence: 1,
+ total_cycles: 1,
+ });
+ }
+
+ let stripePlanCreated = await stripe.createPrice(stripeSubscriptionPlanParams);
+ let paypalPlanCreated = await paypal.createSubscriptionPlan(paypalSubscriptionPlanParams);
+
+ let planModelEntry = {
+ stripe_product_id: stripePlanCreated.product,
+ paypal_product_id: paypalPlanCreated.data.product_id,
+ stripe_id: stripePlanCreated.id,
+ paypal_id: paypalPlanCreated.data.id,
+ amount: stripePlanCreated.unit_amount / 100,
+ interval: paymentPlansModel.inverse_interval_mapping(stripePlanCreated.recurring.interval),
+ interval_count: stripePlanCreated.recurring.interval_count,
+ trial_period_days: stripePlanCreated.recurring.trial_period_days ?? 0,
+ nickname: stripePlanCreated.nickname,
+ service_id: localServiceId,
+ status: stripePlanCreated.active ? 1 : 0,
+ type: parseInt(planType),
+ };
+
+ let modelCreatedPlan = await paymentPlansModel.insert(planModelEntry);
+
+ if (!modelCreatedPlan) {
+ throw new Error('Internal error: error creating plan');
+ }
+ return modelCreatedPlan;
+ };
+
+ this._createStripeLifetimePlan = async function (params, service) {
+ //stripe doesn't have lifetime plans
+ //how it works is a price is created with no recurring parameter as a single time product
+ //which acts and dealt with internally within our system as a lifetime plan.
+ let stripeSubscriptionPlanParams = {
+ nickname: params.nickname,
+ currency: this.currency,
+ unit_amount: convertToCents(params.amount),
+ product: service.stripe_id,
+ active: parseInt(params.status) === 1 ? true : false,
+ };
+ return await stripe.createPrice(stripeSubscriptionPlanParams);
+ };
+ this._createPaypalLifetimePlan = async function (params, service) {
+ //paypal doesn't allow lifetime plan
+ //how it works is i create a blan with a trial with 999 years and setup fee for subscription as the main amount for the lifetime plan
+ //paypal needs to have a regular plan so after the 999 years there is a regular plan with 0.01 amount money
+ //this works for both lifetime paid and free plans.
+ await paypal.setAccessToken();
+
+ let paypalSubscriptionPlanParams = {
+ product_id: service.paypal_id,
+ name: params.nickname,
+ billing_cycles: [
+ {
+ frequency: {
+ interval_unit: 'YEAR',
+ interval_count: 1,
+ },
+ tenure_type: 'TRIAL',
+ sequence: 1,
+ total_cycles: 999,
+ },
+ {
+ frequency: {
+ interval_unit: 'YEAR',
+ interval_count: 1,
+ },
+ tenure_type: 'REGULAR',
+ sequence: 2,
+ total_cycles: 0,
+ pricing_scheme: {
+ fixed_price: {
+ value: '0.01',
+ currency_code: this.currency.toUpperCase(),
+ },
+ },
+ },
+ ],
+ payment_preferences: {
+ auto_bill_outstanding: true,
+ setup_fee: {
+ value: params.amount,
+ currency_code: this.currency.toUpperCase(),
+ },
+ setup_fee_failure_action: 'CANCEL',
+ payment_failure_threshold: 0,
+ },
+ };
+ return await paypal.createPlan(paypalSubscriptionPlanParams);
+ };
+ this._destroyStripeLifetimePlan = async function (planStripeId) {
+ //stripe doesn't allow deleting a price. will be set to unactive instead
+ return await stripe.updatePrice({ priceId: planStripeId, params: { active: false } });
+ };
+ this._destroyPaypalLifetimePlan = async function (planPaypalId) {
+ //paypal doesn't allow deleting a price. will be set to unactive instead
+ return await paypal.deactivatePlan(planPaypalId);
+ };
+
+ this.adminCreateLifetimePlan = async function (params, planType, localServiceId) {
+ if (!params.nickname) {
+ throw new Error('Must have a display name');
+ }
+ if (!localServiceId) {
+ throw new Error('Must provide system product id');
+ }
+ if (!planType) {
+ throw new Error('Must provide plan type');
+ }
+ if (parseInt(params.interval) !== 4) {
+ throw new Error('Interval must be "forever".');
+ }
+
+ let service = await paymentServicesModel.getByPK(localServiceId);
+
+ let stripePlanCreated = await this._createStripeLifetimePlan(params, service);
+ let paypalPlanCreated = await this._createPaypalLifetimePlan(params, service);
+
+ if (!stripePlanCreated && paypalPlanCreated) {
+ await this._destroyPaypalLifetimePlan(paypalPlanCreated.data.id);
+ throw new Error('Internal Error: Something happended while creating the plans.');
+ }
+ if (!paypalPlanCreated && stripePlanCreated) {
+ await this._destroyStripeLifetimePlan(stripePlanCreated.id);
+ throw new Error('Internal Error: Something happended while creating the plans.');
+ }
+
+ let planModelEntry = {
+ stripe_product_id: stripePlanCreated?.product ?? '',
+ paypal_product_id: paypalPlanCreated?.data?.product_id ?? '',
+ stripe_id: stripePlanCreated?.id ?? '',
+ paypal_id: paypalPlanCreated?.data?.id ?? '',
+ amount: stripePlanCreated?.unit_amount ? stripePlanCreated.unit_amount / 100 : params.amount,
+ interval: 4,
+ interval_count: 1,
+ trial_period_days: 0,
+ nickname: stripePlanCreated?.nickname ?? paypalPlanCreated.data.name,
+ service_id: localServiceId,
+ status: stripePlanCreated?.active == true ? 1 : params.status,
+ type: parseInt(planType),
+ };
+
+ let modelCreatedPlan = await paymentPlansModel.insert(planModelEntry);
+
+ if (!modelCreatedPlan) {
+ throw new Error('Internal error: error creating plan');
+ }
+ return modelCreatedPlan;
+ };
+
+ this.adminCreateTrialPlan = async function (planParams, planType, localServiceId) {
+ if (Object.keys(planParams.recurring).length === 0 || planParams.recurring.constructor !== Object) {
+ throw new Error('Must have a recurring parameter (subscription interval)');
+ }
+ if (!planParams.nickname) {
+ throw new Error('Must have a display name');
+ }
+ if (!localServiceId) {
+ throw new Error('Must provide system product id');
+ }
+ let priceCreated = await stripe.createPrice(planParams);
+
+ let planModelEntry = {
+ stripe_product_id: priceCreated.product,
+ stripe_id: priceCreated.id,
+ amount: priceCreated.unit_amount / 100,
+ interval: paymentPlansModel.inverse_interval_mapping(priceCreated.recurring.interval),
+ interval_count: priceCreated.recurring.interval_count,
+ trial_period_days: priceCreated.recurring.trial_period_days ?? 0,
+ nickname: priceCreated.nickname,
+ service_id: localServiceId,
+ status: priceCreated.active ? 1 : 0,
+ type: parseInt(planType),
+ };
+ let modelCreatedPlan = await paymentPlansModel.insert(planModelEntry);
+ if (!modelCreatedPlan) {
+ throw new Error('Internal error: error creating plan');
+ }
+ return modelCreatedPlan;
+ };
+ this.adminCancelSubscription = async function (subscriptionId) {
+ let subscriptionToCancel = await stripeSubscriptionsModel.getByPK(subscriptionId);
+ let subscriptionPlan = await paymentPlansModel.getByPK(subscriptionToCancel.plan_id);
+ if (!subscriptionToCancel || !subscriptionPlan) {
+ throw new Error('No subscription or plan of that id');
+ }
+ // let subscirptionType = subscriptionPlan.type;
+ let subscirptionType = 3;
+ //if normal subscription
+ switch (subscirptionType) {
+ case 0:
+ case 3:
+ case 4:
+ let cancelSubscriptionParams = {
+ subscriptionId: subscriptionToCancel.stripe_id,
+ params: {
+ prorate: this.prorate,
+ invoice_now: true,
+ },
+ };
+ let subscriptionCanceled = await stripe.cancelSubscription(cancelSubscriptionParams);
+ let localCancellationParams = {
+ status: stripeSubscriptionsModel.inverse_status_mapping()[subscriptionCanceled.status],
+ };
+ await stripeSubscriptionsModel.edit(localCancellationParams, subscriptionId);
+ return subscriptionCanceled;
+ default:
+ throw new Error('Something wrong');
+ }
+ };
+ this.userAddCard = async function (cardParams) {
+ let createdCard = await stripe.createCard(cardParams);
+
+ if (createdCard) {
+ if (createdCard.metadata.is_default == '1') {
+ let customerUpdateParams = {
+ customerId: createdCard.customer,
+ params: { default_source: createdCard.id },
+ };
+ await stripe.updateCustomer(customerUpdateParams);
+ let defaultCards = await stripeCardsModel.getAll({ is_default: 1 });
+ if (defaultCards.length > 0) {
+ defaultCards.forEach(async (card) => {
+ await stripeCardsModel.edit({ is_default: 0 }, card.id);
+ });
+ }
+ }
+ cardModelEntry = {
+ card_last: createdCard.last4 ?? '',
+ card_brand: createdCard.brand ?? '',
+ card_exp_month: createdCard.exp_month ?? '',
+ exp_year: createdCard.exp_year ?? '',
+ card_name: createdCard.metadata.card_name ?? '',
+ stripe_card_customer: createdCard.customer,
+ stripe_card_id: createdCard.id,
+ is_default: createdCard.metadata.is_default,
+ user_id: this.userId ?? null,
+ role_id: this.roleId ?? null,
+ };
+ var modelCreatedCard = await stripeCardsModel.insert(cardModelEntry);
+ if (!modelCreatedCard) {
+ throw new Error('Internal error: Error adding card');
+ }
+ }
+ return modelCreatedCard;
+ };
+ this.changePlan = async function (currentSubscription, newPlan, user, card, couponId = '') {
+ //if current subscriptions is canceled
+ if (!currentSubscription || currentSubscription.status == 5) {
+ let subscriptionParams = {
+ customer: user.stripe_id,
+ items: { plan: newPlan.id },
+ coupon: couponId,
+ };
+ return await this.subscribe(subscriptionParams, newPlan);
+ }
+ //user has subscription (upgrade/ downgrade)
+ if (currentSubscription && currentSubscription.status != 5) {
+ if (currentSubscription.plan_id == newPlan.id) throw new Error('Same Plan');
+ }
+ };
+}
diff --git a/day11/services/PaypalApi.js b/day11/services/PaypalApi.js
new file mode 100755
index 0000000..5fdfed6
--- /dev/null
+++ b/day11/services/PaypalApi.js
@@ -0,0 +1,300 @@
+require('dotenv').config();
+
+const qs = require('qs'); //parse url form encoded params
+const paypalBaseUrl = process.env.PAYPAL_BASE_URL;
+const paypalSandboxBaseUrl = process.env.PAYPAL_SANDBOX_BASE_URL;
+
+if (process.env.MODE === 'development') {
+ var axios = require('axios').create({
+ baseURL: paypalSandboxBaseUrl,
+ });
+} else if (process.env.MODE === 'production') {
+ var axios = require('axios').create({
+ baseURL: paypalBaseUrl,
+ });
+}
+let accessToken = '';
+
+module.exports = new Service();
+function Service() {
+ // this.error = async function () {
+ // Error.call(this);
+
+ // };
+ this.setAccessToken = async function () {
+ accessToken = await this._getPaypalAccessToken().catch((error) => {
+ throw error;
+ });
+ return accessToken;
+ };
+ this._getPaypalAccessToken = async function () {
+ let data = await axios({
+ method: 'post',
+ url: '/v1/oauth2/token',
+ auth: {
+ username: process.env.PAYPAL_CLIENT_ID,
+ password: process.env.PAYPAL_SECRET,
+ },
+ headers: {
+ 'Content-Type': 'application/x-www-form-urlencoded',
+ },
+ data: qs.stringify({
+ grant_type: 'client_credentials',
+ }),
+ }).catch((error) => {
+ throw error;
+ });
+ return data.data.access_token;
+ };
+ this.createProduct = async function (params) {
+ let requiredFields = ['name', 'type'];
+ requiredFields.forEach((field) => {
+ if (!params[field]) {
+ throw new Error(`Must have "${field}" parameter`);
+ }
+ });
+ params = this.filterParams(params);
+ let config = {
+ url: '/v1/catalogs/products',
+ data: params,
+ };
+ try {
+ return await this.axiosPost(config);
+ } catch (error) {
+ console.dir(error.response, { depth: null });
+ throw new Error(error.response?.data?.message ?? 'Internal Error: Error creating product');
+ }
+ };
+ this.getPlans = async function (paginationParams) {
+ let config = {
+ url: '/v1/billing/plans',
+ data: paginationParams,
+ };
+ try {
+ return await this.axiosGet(config);
+ } catch (error) {
+ console.error(error.response);
+ throw new Error('Internal Error: Error getting plans');
+ }
+ };
+ this.getProducts = async function (paginationParams) {
+ let config = {
+ url: '/v1/catalogs/products',
+ data: paginationParams,
+ };
+ try {
+ return await this.axiosGet(config);
+ } catch (error) {
+ console.error(error.response);
+ throw new Error('Internal Error: Error getting products');
+ }
+ };
+ this.getProductDetails = async function (productId) {
+ let config = {
+ url: `/v1/catalogs/products/${productId}`,
+ data: {},
+ };
+ try {
+ return await this.axiosGet(config);
+ } catch (error) {
+ console.error(error.response);
+ throw new Error('Internal Error: Error getting product details');
+ }
+ };
+
+ this.getSubscriptions = async function (paginationParams) {
+ let createdProduct = await axios({
+ method: 'get',
+ url: '/v1/billing/plans',
+ data: qs.stringify(paginationParams),
+ headers: {
+ 'Content-Type': 'application/json',
+ Authorization: `Bearer ${accessToken}`,
+ },
+ }).catch((error) => {
+ throw error;
+ });
+ return createdProduct.data;
+ };
+ this.createSubscription = async function (params) {
+ let config = {
+ url: '/v1/billing/subscriptions',
+ data: params,
+ };
+ return await this.axiosPost(config);
+ };
+ this.createPlan = async function (params) {
+ let config = {
+ url: '/v1/billing/plans',
+ data: params,
+ };
+ try {
+ return await this.axiosPost(config);
+ } catch (error) {
+ console.dir(error.response.data, { depth: null });
+ throw new Error('Internal Error: Error creating paypal subscription plan');
+ }
+ };
+ this.deactivatePlan = async function (planId) {
+ let config = {
+ url: `/v1/billing/plans/${planId}/deactivate`,
+ data: {},
+ };
+ try {
+ return await this.axiosPost(config);
+ } catch (error) {
+ console.dir(error.response.data, { depth: null });
+ throw new Error('Internal Error: Error deactivating paypal subscription plan');
+ }
+ };
+ this.getPlanDetails = async function (planId) {
+ let config = {
+ url: `/v1/billing/plans/${planId}`,
+ data: {},
+ };
+ try {
+ return await this.axiosGet(config);
+ } catch (error) {
+ console.dir(error.response.data, { depth: null });
+ throw new Error('Internal Error: Error getting subscription details');
+ }
+ };
+ this.getSubscriptionDetails = async function (subscriptionId) {
+ let config = {
+ url: `/v1/billing/subscriptions/${subscriptionId}`,
+ data: {},
+ };
+ try {
+ return await this.axiosGet(config);
+ } catch (error) {
+ console.dir(error.response.data, { depth: null });
+ throw new Error('Internal Error: Error getting subscription details');
+ }
+ };
+ this.updateProduct = async function (params, productId) {
+ let config = {
+ url: `/v1/catalogs/products/${productId}`,
+ data: params,
+ };
+ try {
+ return await this.axiosPatch(config);
+ } catch (error) {
+ console.dir(error.response.data, { depth: null });
+ throw new Error('Internal Error: Error updating product');
+ }
+ };
+ this.updatePlan = async function (params, planId) {
+ let config = {
+ url: `/v1/billing/plans/${planId}`,
+ data: params,
+ };
+ try {
+ return await this.axiosPatch(config);
+ } catch (error) {
+ console.dir(error.response.data, { depth: null });
+ throw new Error('Internal Error: Error updating plan');
+ }
+ };
+ this.updatePlanPricing = async function (params, planId) {
+ let config = {
+ url: `/v1/billing/plans/${planId}/update-pricing-schemes`,
+ data: params,
+ };
+ try {
+ return await this.axiosPost(config);
+ } catch (error) {
+ console.dir(error.response.data, { depth: null });
+ throw new Error('Internal Error: Error updating plan');
+ }
+ };
+ this.activatePlan = async function (planId) {
+ let config = {
+ url: `/v1/billing/plans/${planId}/activate`,
+ data: {},
+ };
+ try {
+ return await this.axiosPost(config);
+ } catch (error) {
+ console.dir(error.response.data, { depth: null });
+ throw new Error('Internal Error: Error updating plan');
+ }
+ };
+ this.deactivatePlan = async function (planId) {
+ let config = {
+ url: `/v1/billing/plans/${planId}/deactivate`,
+ data: {},
+ };
+ try {
+ return await this.axiosPost(config);
+ } catch (error) {
+ console.dir(error.response.data, { depth: null });
+ throw new Error('Internal Error: Error updating plan');
+ }
+ };
+ this.retrievePlan = async function (params, planId) {
+ let config = {
+ url: `/v1/billing/subscriptions/${planId}`,
+ data: params,
+ };
+ return await this.axiosGet(config);
+ };
+ this.axiosGet = async function (config) {
+ return await axios({
+ method: 'get',
+ url: config.url,
+ data: qs.stringify(config.data ?? {}),
+ headers: {
+ 'Content-Type': 'application/json',
+ Authorization: `Bearer ${accessToken}`,
+ },
+ });
+ };
+
+ this.axiosPost = async function (config) {
+ return await axios({
+ method: 'post',
+ url: config.url,
+ data: JSON.stringify(config.data),
+ headers: {
+ 'Content-Type': 'application/json',
+ Accept: 'application/json',
+ Prefer: 'return=representation',
+ Authorization: `Bearer ${accessToken}`,
+ },
+ });
+ };
+
+ this.axiosPatch = async function (config) {
+ return await axios({
+ method: 'patch',
+ url: config.url,
+ data: JSON.stringify(config.data),
+ headers: {
+ 'Content-Type': 'application/json',
+ Accept: 'application/json',
+ Authorization: `Bearer ${accessToken}`,
+ },
+ });
+ };
+
+ /**
+ * [filterParams filters paramters from null, undefined, empty strings, empty arrays and empty objects as it can cause unwanted changes]
+ * @param {object} params [object that can contain one more object inside]
+ * @return {object} [object with only truth variables]
+ */
+ this.filterParams = function (params) {
+ Object.keys(params).forEach((param) => {
+ if (this.empty(params[param]) || params[param].length === 0) {
+ console.log(`Parameter empty, null or undefined`);
+ delete params[param];
+ } else if (params[param].constructor === Object && Object.entries(params[param]).length === 0) {
+ console.log(`Parameter object empty`);
+ delete params[param];
+ } else if (params[param].constructor === Object && Object.entries(params[param]).length > 0) {
+ this.filterParams(params[param]);
+ }
+ });
+ return params;
+ };
+ this.empty = (value) => value === null || value === undefined;
+}
diff --git a/day11/services/PaypalService.js b/day11/services/PaypalService.js
new file mode 100755
index 0000000..ffc6eaf
--- /dev/null
+++ b/day11/services/PaypalService.js
@@ -0,0 +1,705 @@
+require('dotenv').config(); //require .env for stripe configurations
+// const moment = require('moment'); //this is Payment Service
+const paypal = require('./PaypalApi');
+const db = require('../models');
+const { update } = require('lodash');
+const paypalPlansModel = db.paypal_plans;
+const paypalServicesModel = db.paypal_services;
+const paypalProductsModel = db.paypal_products;
+
+module.exports = new Service();
+function Service() {
+ this.userId = 0;
+ this.roleId = 0;
+ this.currency = process.env.PAYPAL_CURRENCY;
+ // this.prorate = process.env.STRIPE_PRORATE;
+ // this.forceCancel = process.env.STRIPE_FORCE_CANCEL;
+
+ this.setUserId = function (userId) {
+ this.userId = userId;
+ };
+ this.setRoleId = function (roleId) {
+ this.roleId = roleId;
+ };
+ this.setCurrency = function (currency) {
+ this.currency = currency;
+ };
+ // this.setProrationType = function (prorate) {
+ // this.prorate = prorate;
+ // };
+ // this.setCancelType = function (forceCancel) {
+ // this.forceCancel = forceCancel;
+ // };
+
+ // this.subscribe = async function (subscriptionParams, plan) {
+ // var subscription = await stripe.createSubscription(subscriptionParams);
+ // //create model entry
+ // let subscriptionModelEntry = {
+ // stripe_id: subscription.id ?? '',
+ // cancel_at_period_end: subscription.cancel_at_period_end ?? null,
+ // current_period_start: moment(new Date(subscription.current_period_start * 1000)).format('YYYY-MM-DD') ?? null,
+ // current_period_end: moment(new Date(subscription.current_period_end * 1000)).format('YYYY-MM-DD') ?? null,
+ // user_id: this.userId ?? null,
+ // role_id: this.roleId ?? null,
+ // plan_id: plan?.id ?? null,
+ // coupon_stripe_id: subscription.discount?.coupon?.id ?? '',
+ // customer_stripe_id: subscription.customer ?? '',
+ // collection_method: subscription.collection_method ?? '',
+ // interval: stripeSubscriptionsModel.inverse_interval_mapping(subscription.plan?.interval) ?? 0,
+ // interval_count: subscription.plan?.interval_count ?? '',
+ // trial_period_days: subscription.plan?.trial_period_days ?? 0,
+ // trial_end: subscription.trial_end ? moment(new Date(subscription.trial_end * 1000)).format('YYYY-MM-DD') : null,
+ // trial_start: subscription.trial_start ? moment(new Date(subscription.subscriptiontrial_start * 1000)).format('YYYY-MM-DD') : null,
+ // status: stripeSubscriptionsModel.inverse_status_mapping(subscription.status) ?? null,
+ // };
+
+ // let subscriptionCreatedId = await stripeSubscriptionsModel.insert(subscriptionModelEntry);
+ // if (!subscriptionCreatedId) {
+ // throw new Error('Subscription is not found.');
+ // }
+
+ // let subscriptionLogUpdated = await this.updateSubscriptionLog(subscriptionCreatedId, subscription.status, plan);
+ // if (!subscriptionLogUpdated) {
+ // throw new Error('Error while editing subscription log');
+ // }
+ // return subscriptionCreatedId;
+ // };
+ // this.updateSubscriptionLog = async function (subscriptionCreatedId, subscriptionCreatedStatus, plan) {
+ // let subscriptionLog = await stripeSubscriptionsLogModel.getLast({ user_id: this.userId, role_id: this.roleId });
+
+ // if (!subscriptionLog) {
+ // let subscriptionLogModelEntry = {
+ // user_id: this.userId,
+ // role_id: this.roleId,
+ // plan_id: plan?.id ?? null,
+ // subscription_id: subscriptionCreatedId,
+ // type: plan?.type ?? null,
+ // status: subscriptionCreatedStatus == 'active' || subscriptionCreatedStatus == 'trialing' ? 1 : 0,
+ // };
+ // if (!(await stripeSubscriptionsLogModel.insert(subscriptionLogModelEntry))) {
+ // throw new Error('Subscription log add not successfull');
+ // }
+ // } else {
+ // let subscriptionLogModelEntry = {
+ // plan_id: plan?.id ?? null,
+ // subscription_id: subscriptionCreatedId,
+ // type: plan?.type ?? null,
+ // status: subscriptionCreatedStatus == 'active' || subscriptionCreatedStatus == 'trialing' ? 1 : 0,
+ // };
+
+ // if (!(await stripeSubscriptionsLogModel.edit(subscriptionLogModelEntry, subscriptionLog.id))) {
+ // throw new Error('Subscription log edit not successfull');
+ // }
+ // }
+ // return true;
+ // };
+ // this.createRegularSubscription = async function (user, plan, card, coupon) {
+ // let subscriptionParams = {
+ // customer: user.stripe_id,
+ // items: [{ price: plan.stripe_id }],
+ // trial_from_plan: true,
+ // default_payment_method: card.stripe_card_id,
+ // coupon: coupon?.stripe_id ?? null,
+ // };
+ // return await this.subscribe(subscriptionParams, plan);
+ // };
+ // this.createLifetimeSubscription = async function (user, plan, card, coupon) {
+ // //create invoice, finalize and pay it.
+ // let lifetimePlanStripeObject = await stripe.retrievePrice(plan.stripe_id);
+ // let invoiceItemParams = {
+ // customer: user.stripe_id,
+ // price: plan.stripe_id,
+ // discounts: [{ coupon: coupon?.stripe_id }],
+ // };
+ // let invoiceItemCreated = await stripe.createInvoiceItem(invoiceItemParams);
+
+ // let invoiceParams = {
+ // customer: user.stripe_id,
+ // default_payment_method: card?.stripe_card_id ?? null,
+ // };
+ // let invoiceCreated = await stripe.createInvoice(invoiceParams);
+
+ // let invoicePaid = await stripe.payInvoice(invoiceCreated.id);
+
+ // let subscriptionModelEntry = {
+ // stripe_id: invoiceItemCreated.id ?? '',
+ // cancel_at_period_end: null,
+ // current_period_start: moment().format('YYYY-MM-DD') ?? null,
+ // current_period_end: null,
+ // user_id: this.userId ?? null,
+ // role_id: this.roleId ?? null,
+ // plan_id: plan?.id ?? null,
+ // coupon_stripe_id: invoiceItemCreated.discounts.length > 0 ? coupon?.stripe_id : '',
+ // customer_stripe_id: invoicePaid.customer ?? '',
+ // collection_method: invoicePaid.collection_method ?? '',
+ // interval: 4,
+ // interval_count: null,
+ // trial_period_days: 0,
+ // trial_end: null,
+ // trial_start: null,
+ // status: 4,
+ // };
+
+ // let subscriptionCreatedId = await stripeSubscriptionsModel.insert(subscriptionModelEntry);
+ // if (!subscriptionCreatedId) {
+ // throw new Error('Subscription is not found.');
+ // }
+
+ // let subscriptionLogUpdated = await this.updateSubscriptionLog(subscriptionCreatedId, 'active', plan);
+ // if (!subscriptionLogUpdated) {
+ // throw new Error('Error while editing subscription log');
+ // }
+
+ // return subscriptionCreatedId;
+ // };
+ // let calculateFullPeriod = function (interval, interval_count = 1) {
+ // let totalNumberOfDays = paypalPlansModel.interval_days_mapping()[interval] * interval_count;
+ // let cancelAtEpoch = moment().add(totalNumberOfDays, 'days').unix();
+ // return cancelAtEpoch;
+ // };
+ // this.createTrialSubscription = async function (user, plan, card, coupon) {
+ // let trialPlanFullPeriod = calculateFullPeriod(plan.interval, plan.interval_count);
+ // let subscriptionParams = {
+ // customer: user.stripe_id,
+ // items: [{ price: plan.stripe_id }],
+ // trial_from_plan: true,
+ // default_payment_method: card.stripe_card_id,
+ // coupon: coupon?.stripe_id ?? null,
+ // cancel_at: trialPlanFullPeriod,
+ // };
+ // return await this.subscribe(subscriptionParams, plan);
+ // };
+ // this.cancelRegularSubscription = async function (subscription) {
+ // if (this.forceCancel === 'true') {
+ // var cancellationParams = {
+ // subscriptionId: subscription.stripe_id,
+ // params: {
+ // invoice_now: true,
+ // prorate: this.prorate,
+ // },
+ // };
+ // let subscriptionCanceled = await stripe.cancelSubscription(cancellationParams);
+
+ // let newStatus = stripeSubscriptionsModel.inverse_status_mapping()[subscriptionCanceled.status];
+ // await stripeSubscriptionsModel.edit({ status: newStatus }, subscription.id);
+ // } else {
+ // let updateParams = {
+ // subscriptionId: subscription.stripe_id,
+ // params: {
+ // cancel_at_period_end: true,
+ // },
+ // };
+ // await stripe.updateSubscription(updateParams);
+ // await stripeSubscriptionsModel.edit({ cancel_at_period_end: 1 }, subscription.id);
+ // }
+ // };
+ // this.cancelLifetimeSubscription = async function (subscription) {
+ // //TODO check if we refund on this or not
+ // //we could make a fixed refund amount
+ // //right now we just cancel it locally and refund is never as it is a onetime product thing.
+ // let subscriptionEdited = await stripeSubscriptionsModel.edit({ status: 5 }, parseInt(subscription.id));
+ // if (!subscriptionEdited) {
+ // throw new Error("Couldn't edit subscription");
+ // }
+ // let subscriptionLog = await stripeSubscriptionsLogModel.getByFields({ subscription_id: parseInt(subscription.id), status: 1 });
+ // if (!subscriptionLog) {
+ // throw new Error("Couldn't find relative active subscription log");
+ // }
+ // let subscriptionLogEdited = await stripeSubscriptionsLogModel.edit({ status: 0 }, subscriptionLog.id);
+ // if (subscriptionLogEdited) {
+ // return true;
+ // }
+ // throw new Error("Couldn't Edit subscription log");
+ // };
+
+ // this.adminCreateProductPrice = async function (productStripeId, price, productLocalId) {
+ // let priceArgs = {
+ // unit_amount: convertToCents(price),
+ // currency: this.currency,
+ // product: productStripeId,
+ // };
+ // let priceCreated = await stripe.createPrice(priceArgs);
+
+ // let productModelEditEntry = {
+ // price,
+ // price_stripe_id: priceCreated.id,
+ // };
+ // let modelEditedProduct = await stripeProductsModel.edit(productModelEditEntry, productLocalId);
+ // if (!modelEditedProduct) {
+ // throw new Error('Internal Error: error setting price');
+ // }
+ // return priceCreated;
+ // };
+
+ this._createPaypalProduct = async function (params) {
+ let productParams = {
+ name: params.name,
+ description: params.description,
+ type: params.type,
+ category: params.category,
+ image_url: params.image,
+ home_url: params.url,
+ };
+ return await paypal.createProduct(productParams);
+ };
+ this.adminUpdateProduct = async function (category, paypalId) {
+ await paypal.setAccessToken();
+ let updateParams = [
+ {
+ op: 'replace',
+ path: '/category',
+ value: category,
+ },
+ ];
+ await paypal.updateProduct(updateParams, paypalId);
+ return true;
+ };
+
+ this.adminCreateProduct = async function (params, type) {
+ if (!params.name) {
+ throw new Error('Must provide a name for your product');
+ }
+ if (!params.type) {
+ throw new Error('Must provide a type for your product');
+ }
+
+ await paypal.setAccessToken();
+
+ let createdPaypalProduct = await this._createPaypalProduct(params);
+
+ let paypalProductModelEntry = {
+ name: createdPaypalProduct.data.name,
+ paypal_id: createdPaypalProduct.data.id,
+ image: createdPaypalProduct.data.image_url ?? '',
+ type: createdPaypalProduct.data.type ?? '',
+ url: createdPaypalProduct.data.home_url ?? '',
+ category: createdPaypalProduct.data.category ?? '',
+ description: createdPaypalProduct.data.description ?? '',
+ status: params.status,
+ };
+
+ switch (params.type) {
+ case 'SERVICE': {
+ var modelCreatedProductId = await paypalServicesModel.insert(paypalProductModelEntry);
+ var product = await paypalServicesModel.getByPK(modelCreatedProductId);
+ break;
+ }
+ case 'PHYSICAL':
+ case 'DIGITAL': {
+ var modelCreatedProductId = await paypalProductsModel.insert(paypalProductModelEntry);
+ var product = await paypalProductsModel.getByPK(modelCreatedProductId);
+ break;
+ }
+ }
+ if (!modelCreatedProductId) {
+ throw new Error('Internal Error: Error adding product to our system');
+ }
+
+ return {
+ productPaypalId: product.paypal_id,
+ productLocalId: product.id,
+ };
+ };
+
+ /**
+ * [adminCreateRegularPlan allows an admin to create subscription plan through admin portal]
+ * @param {object} planParams [object that contain plan parameters]
+ * @param {integer} planType [integer that represent plan type in relative to stripe plans table] @see /models/stripe_plans.js type_mapping()
+ * @param {integer} localProductId [integer that represent product id in relative to stripe products table]
+ * @return {integer} [the id of the created plan in database]
+ */
+ this.adminCreateRegularPlan = async function (params) {
+ if (!params.name) {
+ throw new Error('Must have a display name');
+ }
+ if (!params.type) {
+ throw new Error('Must provide plan type');
+ }
+ if (!params.service_id) {
+ throw new Error('Must attach a service');
+ }
+ await paypal.setAccessToken();
+ let service = await paypalServicesModel.getByPK(params.service_id);
+
+ let paypalSubscriptionPlanParams = {
+ product_id: service.paypal_id,
+ name: params.name,
+ billing_cycles: [
+ {
+ frequency: {
+ interval_unit: paypalPlansModel.interval_unit_mapping()[params.interval_unit].toUpperCase(),
+ interval_count: params.interval_count,
+ },
+ tenure_type: 'REGULAR',
+ sequence: 1,
+ total_cycles: 0,
+ pricing_scheme: {
+ fixed_price: {
+ value: params.pricing,
+ currency_code: this.currency.toUpperCase(),
+ },
+ },
+ },
+ ],
+ payment_preferences: {
+ auto_bill_outstanding: true,
+ setup_fee_failure_action: 'CANCEL',
+ payment_failure_threshold: 0,
+ },
+ };
+ if (params.trial_period_days > 0) {
+ paypalSubscriptionPlanParams.billing_cycles[0].sequence = 2;
+ paypalSubscriptionPlanParams.billing_cycles.unshift({
+ frequency: {
+ interval_unit: 'DAY',
+ interval_count: parseInt(params.trial_period_days),
+ },
+ tenure_type: 'TRIAL',
+ sequence: 1,
+ total_cycles: 1,
+ });
+ }
+
+ let paypalPlanCreated = await paypal.createPlan(paypalSubscriptionPlanParams);
+
+ let hasTrial = paypalPlanCreated.data.billing_cycles[1] ? true : false;
+ let trialPlanInfo = hasTrial ? paypalPlanCreated.data.billing_cycles[0] : null;
+ let regularPlanInfo = hasTrial ? paypalPlanCreated.data.billing_cycles[1] : paypalPlanCreated.data.billing_cycles[0];
+ let price = regularPlanInfo.pricing_scheme.fixed_price.value;
+ let interval_unit = regularPlanInfo.frequency.interval_unit;
+ let interval_count = regularPlanInfo.frequency.interval_count;
+
+ let planModelEntry = {
+ name: paypalPlanCreated.data.name,
+ type: params.type,
+ paypal_id: paypalPlanCreated.data.id,
+ description: paypalPlanCreated.data.description,
+ paypal_product_id: paypalPlanCreated.data.product_id,
+ pricing: price,
+ interval_unit: this.getMappingKey(paypalPlansModel.interval_unit_mapping, interval_unit.toLowerCase()),
+ interval_count: interval_count,
+ service_id: params.service_id,
+ trial_period_days: trialPlanInfo ? trialPlanInfo.frequency.interval_count : 0,
+ status: this.getMappingKey(paypalPlansModel.status_mapping, paypalPlanCreated.data.status),
+ };
+
+ let modelCreatedPlan = await paypalPlansModel.insert(planModelEntry);
+
+ if (!modelCreatedPlan) {
+ throw new Error('Internal error: error creating plan');
+ }
+ return modelCreatedPlan;
+ };
+
+ this.updateRegularPlan = async function (params, currentSettings) {
+ //if plan has a trial
+ await paypal.setAccessToken();
+ let planPaypalId = currentSettings.paypal_id;
+
+ if (currentSettings.status == 0 && params.status == 1) {
+ await paypal.activatePlan(planPaypalId);
+ } else if (currentSettings.status == 1 && params.status == 0) {
+ await this._destroyPaypalPlan(planPaypalId);
+ return { status: 0 };
+ } else if (currentSettings.status == 0 && params.status == 0) {
+ throw new Error("Can't edit a plan if it is inactive");
+ }
+
+ let regularPlanIndex = currentSettings.trial_period_days ? 2 : 1;
+
+ let updateParams = [
+ {
+ op: 'replace',
+ path: '/name',
+ value: params.name,
+ },
+ ];
+
+ await paypal.updatePlan(updateParams, planPaypalId);
+
+ updateParams = {
+ pricing_schemes: [
+ {
+ billing_cycle_sequence: regularPlanIndex,
+ pricing_scheme: {
+ fixed_price: {
+ value: params.pricing,
+ currency_code: this.currency.toUpperCase(),
+ },
+ },
+ },
+ ],
+ };
+
+ await paypal.updatePlanPricing(updateParams, planPaypalId);
+
+ return { name: params.name, pricing: params.pricing, status: params.status };
+ };
+
+ this.adminCreateLifetimePlan = async function (params) {
+ //paypal doesn't allow lifetime plan
+ //how it works is i create a blan with a trial with 999 years and setup fee for subscription as the main amount for the lifetime plan
+ //paypal needs to have a regular plan so after the 999 years there is a regular plan with 0.01 amount money
+ //this works for both lifetime paid and free plans.
+
+ if (!params.name) {
+ throw new Error('Must have a display name');
+ }
+ if (!params.type) {
+ throw new Error('Must provide plan type');
+ }
+ if (!params.service_id) {
+ throw new Error('Must attach a service');
+ }
+ if (parseInt(params.interval_unit) !== 4) {
+ throw new Error('Interval must be "forever".');
+ }
+
+ let service = await paypalServicesModel.getByPK(params.service_id);
+
+ await paypal.setAccessToken();
+
+ let planParams = {
+ product_id: service.paypal_id,
+ name: params.name,
+ billing_cycles: [
+ {
+ frequency: {
+ interval_unit: 'YEAR',
+ interval_count: 1,
+ },
+ tenure_type: 'TRIAL',
+ sequence: 1,
+ total_cycles: 999,
+ },
+ {
+ frequency: {
+ interval_unit: 'YEAR',
+ interval_count: 1,
+ },
+ tenure_type: 'REGULAR',
+ sequence: 2,
+ total_cycles: 0,
+ pricing_scheme: {
+ fixed_price: {
+ value: '0.01',
+ currency_code: this.currency.toUpperCase(),
+ },
+ },
+ },
+ ],
+ payment_preferences: {
+ auto_bill_outstanding: true,
+ setup_fee: {
+ value: params.pricing,
+ currency_code: this.currency.toUpperCase(),
+ },
+ setup_fee_failure_action: 'CANCEL',
+ payment_failure_threshold: 0,
+ },
+ };
+ let paypalPlanCreated = await paypal.createPlan(planParams);
+
+ let price = paypalPlanCreated.data.payment_preferences.setup_fee.value;
+
+ let planModelEntry = {
+ name: paypalPlanCreated.data.name,
+ type: params.type,
+ paypal_id: paypalPlanCreated.data.id,
+ description: paypalPlanCreated.data.description,
+ paypal_product_id: paypalPlanCreated.data.product_id,
+ pricing: price,
+ interval_unit: 4,
+ interval_count: null,
+ service_id: params.service_id,
+ trial_period_days: null,
+ status: this.getMappingKey(paypalPlansModel.status_mapping, paypalPlanCreated.data.status),
+ };
+
+ let modelCreatedPlan = await paypalPlansModel.insert(planModelEntry);
+
+ if (!modelCreatedPlan) {
+ throw new Error('Internal error: error creating plan');
+ }
+ return modelCreatedPlan;
+ };
+
+ this.adminCreateTrialPlan = async function (params) {
+ if (!params.name) {
+ throw new Error('Must have a display name');
+ }
+ if (!params.type) {
+ throw new Error('Must provide plan type');
+ }
+ if (!params.service_id) {
+ throw new Error('Must attach a service');
+ }
+ if (parseInt(params.interval_unit) === 4) {
+ throw new Error('Interval can\'t be "forever".');
+ }
+ let service = await paypalServicesModel.getByPK(params.service_id);
+
+ await paypal.setAccessToken();
+
+ let planParams = {
+ product_id: service.paypal_id,
+ name: params.name,
+ billing_cycles: [
+ // {
+ // frequency: {
+ // interval_unit: 'YEAR',
+ // interval_count: 1,
+ // },
+ // tenure_type: 'TRIAL',
+ // sequence: 1,
+ // total_cycles: 999,
+ // },
+ {
+ frequency: {
+ interval_unit: paypalPlansModel.interval_unit_mapping()[params.interval_unit].toUpperCase(),
+ interval_count: params.interval_count,
+ },
+ tenure_type: 'REGULAR',
+ sequence: 1,
+ total_cycles: 1,
+ pricing_scheme: {
+ fixed_price: {
+ value: params.pricing,
+ currency_code: this.currency.toUpperCase(),
+ },
+ },
+ },
+ ],
+ payment_preferences: {
+ auto_bill_outstanding: true,
+ // setup_fee: {
+ // value: params.pricing,
+ // currency_code: this.currency.toUpperCase(),
+ // },
+ setup_fee_failure_action: 'CANCEL',
+ payment_failure_threshold: 0,
+ },
+ };
+ let paypalPlanCreated = await paypal.createPlan(planParams);
+
+ let planInfo = paypalPlanCreated.data.billing_cycles[0];
+ let price = planInfo.pricing_scheme.fixed_price.value;
+ let interval_unit = planInfo.frequency.interval_unit;
+ let interval_count = planInfo.frequency.interval_count;
+
+ let planModelEntry = {
+ name: paypalPlanCreated.data.name,
+ type: params.type,
+ paypal_id: paypalPlanCreated.data.id,
+ description: paypalPlanCreated.data.description,
+ paypal_product_id: paypalPlanCreated.data.product_id,
+ pricing: price,
+ interval_unit: this.getMappingKey(paypalPlansModel.interval_unit_mapping, interval_unit.toLowerCase()),
+ interval_count: interval_count,
+ service_id: params.service_id,
+ trial_period_days: 0,
+ status: this.getMappingKey(paypalPlansModel.status_mapping, paypalPlanCreated.data.status),
+ };
+
+ let modelCreatedPlan = await paypalPlansModel.insert(planModelEntry);
+
+ if (!modelCreatedPlan) {
+ throw new Error('Internal error: error creating plan');
+ }
+ return modelCreatedPlan;
+ };
+
+ this._destroyStripeLifetimePlan = async function (planStripeId) {
+ //stripe doesn't allow deleting a price. will be set to unactive instead
+ return await stripe.updatePrice({ priceId: planStripeId, params: { active: false } });
+ };
+ this._destroyPaypalPlan = async function (planPaypalId) {
+ //paypal doesn't allow deleting a price. will be set to unactive instead
+ return await paypal.deactivatePlan(planPaypalId);
+ };
+
+ // this.adminCancelSubscription = async function (subscriptionId) {
+ // let subscriptionToCancel = await stripeSubscriptionsModel.getByPK(subscriptionId);
+ // let subscriptionPlan = await paypalPlansModel.getByPK(subscriptionToCancel.plan_id);
+ // if (!subscriptionToCancel || !subscriptionPlan) {
+ // throw new Error('No subscription or plan of that id');
+ // }
+ // // let subscirptionType = subscriptionPlan.type;
+ // let subscirptionType = 3;
+ // //if normal subscription
+ // switch (subscirptionType) {
+ // case 0:
+ // case 3:
+ // case 4:
+ // let cancelSubscriptionParams = {
+ // subscriptionId: subscriptionToCancel.stripe_id,
+ // params: {
+ // prorate: this.prorate,
+ // invoice_now: true,
+ // },
+ // };
+ // let subscriptionCanceled = await stripe.cancelSubscription(cancelSubscriptionParams);
+ // let localCancellationParams = {
+ // status: stripeSubscriptionsModel.inverse_status_mapping()[subscriptionCanceled.status],
+ // };
+ // await stripeSubscriptionsModel.edit(localCancellationParams, subscriptionId);
+ // return subscriptionCanceled;
+ // default:
+ // throw new Error('Something wrong');
+ // }
+ // };
+ // this.userAddCard = async function (cardParams) {
+ // let createdCard = await stripe.createCard(cardParams);
+
+ // if (createdCard) {
+ // if (createdCard.metadata.is_default == '1') {
+ // let customerUpdateParams = {
+ // customerId: createdCard.customer,
+ // params: { default_source: createdCard.id },
+ // };
+ // await stripe.updateCustomer(customerUpdateParams);
+ // let defaultCards = await stripeCardsModel.getAll({ is_default: 1 });
+ // if (defaultCards.length > 0) {
+ // defaultCards.forEach(async (card) => {
+ // await stripeCardsModel.edit({ is_default: 0 }, card.id);
+ // });
+ // }
+ // }
+ // cardModelEntry = {
+ // card_last: createdCard.last4 ?? '',
+ // card_brand: createdCard.brand ?? '',
+ // card_exp_month: createdCard.exp_month ?? '',
+ // exp_year: createdCard.exp_year ?? '',
+ // card_name: createdCard.metadata.card_name ?? '',
+ // stripe_card_customer: createdCard.customer,
+ // stripe_card_id: createdCard.id,
+ // is_default: createdCard.metadata.is_default,
+ // user_id: this.userId ?? null,
+ // role_id: this.roleId ?? null,
+ // };
+ // var modelCreatedCard = await stripeCardsModel.insert(cardModelEntry);
+ // if (!modelCreatedCard) {
+ // throw new Error('Internal error: Error adding card');
+ // }
+ // }
+ // return modelCreatedCard;
+ // };
+ // this.changePlan = async function (currentSubscription, newPlan, user, card, couponId = '') {
+ // //if current subscriptions is canceled
+ // if (!currentSubscription || currentSubscription.status == 5) {
+ // let subscriptionParams = {
+ // customer: user.stripe_id,
+ // items: { plan: newPlan.id },
+ // coupon: couponId,
+ // };
+ // return await this.subscribe(subscriptionParams, newPlan);
+ // }
+ // //user has subscription (upgrade/ downgrade)
+ // if (currentSubscription && currentSubscription.status != 5) {
+ // if (currentSubscription.plan_id == newPlan.id) throw new Error('Same Plan');
+ // }
+ // };
+ this.getMappingKey = function (mappingFunction, value) {
+ return Object.keys(mappingFunction()).find((key) => mappingFunction()[key] === value);
+ };
+}
diff --git a/day11/services/PermissionService.js b/day11/services/PermissionService.js
new file mode 100755
index 0000000..3f746bc
--- /dev/null
+++ b/day11/services/PermissionService.js
@@ -0,0 +1,17 @@
+module.exports = {
+ verifyPermission: function (role, roleName) {
+ return function (req, res, next) {
+ const permissions = req.session.permissions || [];
+
+ const isAllowedPermission = permissions.find(
+ (permission) => `/${roleName}${permission}` === req.originalUrl,
+ );
+
+ if (isAllowedPermission) {
+ return next();
+ } else {
+ return res.redirect(`/${roleName}/dashboard`);
+ }
+ };
+ },
+};
diff --git a/day11/services/PowerByService.js b/day11/services/PowerByService.js
new file mode 100755
index 0000000..fc8a832
--- /dev/null
+++ b/day11/services/PowerByService.js
@@ -0,0 +1,4 @@
+module.exports = function (req, res, next) {
+ res.setHeader("X-Powered-By", "Manaknightdigital Inc.");
+ next();
+};
diff --git a/day11/services/ProfileService.js b/day11/services/ProfileService.js
new file mode 100755
index 0000000..dc1a3c0
--- /dev/null
+++ b/day11/services/ProfileService.js
@@ -0,0 +1,52 @@
+const db = require('../models');
+
+module.exports = {
+ /**
+ * Get user profile from user and credential table
+ * @name profileService.get_profile
+ * @param {string} user_id user id
+ * @returns {Promise.<{email: string, password: string, first_name: string, last_name: string}>} profile fields
+ */
+ async get_profile(user_id) {
+ try {
+ const { email, password } = await db.credential.getByField({ user_id });
+ const { first_name, last_name } = await db.user.getByPK(user_id);
+
+ return {
+ email,
+ password,
+ first_name,
+ last_name,
+ };
+ } catch (error) {
+ throw new Error(error.message);
+ }
+ },
+ /**
+ * Edit user profile update user and credential tables
+ * @param {string} user_id user id
+ * @param {{email: string, password: string, first_name: string, last_name: string}} profile user profile
+ * @returns {Promise.<{email: string, password: string, first_name: string, last_name: string}>} updated profile fields
+ */
+ async edit_profile(user_id, profile) {
+ try {
+ await db.credential.editByField(
+ { email: profile.email, password: profile.password },
+ { user_id },
+ );
+ await db.user.edit(
+ { first_name: profile.first_name, last_name: profile.last_name },
+ user_id,
+ );
+
+ return {
+ email,
+ password,
+ first_name,
+ last_name,
+ };
+ } catch (error) {
+ throw new Error(error.message);
+ }
+ },
+};
diff --git a/day11/services/PushNotification.js b/day11/services/PushNotification.js
new file mode 100755
index 0000000..8867d5e
--- /dev/null
+++ b/day11/services/PushNotification.js
@@ -0,0 +1,90 @@
+const admin = require('firebase-admin');
+
+class PushNotification {
+ /**
+ * Initialize firebase admin
+ * {@link https://firebase.google.com/docs/admin/setup#initialize-sdk Initialize the SDK}.
+ */
+ constructor() {
+ this.admin = admin.initializeApp({
+ credential: admin.credential.applicationDefault(),
+ });
+
+ this.messaging = this.admin.messaging();
+ }
+
+ /**
+ * Generate payload
+ * @param {{body: string, title: string, image_url: string}} payload
+ * @param {admin.messaging.MessagingPayload} override override default payload
+ */
+ generatePayload(payload, override) {
+ return {
+ notification: {
+ body: payload.body,
+ title: payload.title,
+ },
+ data: payload,
+ apns: {
+ payload: {
+ aps: {
+ 'mutable-content': 1,
+ },
+ },
+ fcm_options: {
+ image: payload.image_url,
+ },
+ },
+ android: {
+ notification: {
+ image: payload.image_url,
+ },
+ },
+ ...override,
+ };
+ }
+
+ /**
+ * Generate options
+ * @param {admin.messaging.MessagingOptions} override override default options
+ */
+ generateOptions(override) {
+ return {
+ contentAvailable: true,
+ priority: 'high',
+ timeToLive: 60 * 60 * 24,
+ ...override,
+ };
+ }
+
+ /**
+ * Send push notification
+ * @param {string|string[]} deviceToken
+ * @param {admin.messaging.MessagingPayload} payload
+ * @param {admin.messaging.MessagingOptions} options
+ * @returns {Promise.}
+ * @example
+ * const pushNotification = new PushNotification();
+ * const payload = pushNotification.generatePayload({}) // {} = custom configuration
+ * const options = pushNotification.generateOptions({}); // {} = custom configuration
+ *
+ * pushNotification
+ * .sendPushNotification('device_token', payload, options)
+ * .then((response) => {
+ * console.log('message send successfully');
+ * })
+ * .catch((error) => {
+ * console.log('error occurred');
+ * });
+ */
+ sendPushNotification(deviceToken, payload, options) {
+ return new Promise((resolve, reject) => {
+ this.messaging
+ .sendToDevice(deviceToken, payload, options)
+ .then((response) => resolve(response))
+ .catch((error) => reject(error));
+ });
+ }
+}
+
+module.exports = PushNotification;
diff --git a/day11/services/QRCodeService.js b/day11/services/QRCodeService.js
new file mode 100755
index 0000000..01fcff6
--- /dev/null
+++ b/day11/services/QRCodeService.js
@@ -0,0 +1,16 @@
+const QRCode = require('qrcode');
+
+module.exports = {
+ /**
+ * Generate QRCode
+ * @param {string} text QRCode string
+ */
+ generateQRCode: async function (text) {
+ return new Promise((resolve, reject) => {
+ QRCode.toDataURL(text, (error, url) => {
+ if (error) reject(error);
+ else resolve(url);
+ });
+ });
+ },
+};
diff --git a/day11/services/S3Service.js b/day11/services/S3Service.js
new file mode 100755
index 0000000..316bdce
--- /dev/null
+++ b/day11/services/S3Service.js
@@ -0,0 +1,156 @@
+require('dotenv').config();
+const AWS = require('aws-sdk');
+
+process.env.DYNAMIC_CONFIG_AWS_REGION = 'ap-south-1';
+process.env.DYNAMIC_CONFIG_AWS_KEY = 'AKIA5RTSM74BBOL52JEA';
+process.env.DYNAMIC_CONFIG_AWS_SECRET =
+ '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';
+process.env.DYNAMIC_CONFIG_AWS_BUCKET = 'sentry-test90426-dev';
+
+class S3Service {
+ /**
+ * Initialize S3 service
+ * @param {bucket} bucket override default aws bucket name
+ */
+ constructor(bucket) {
+ AWS.config.update({
+ accessKeyId: process.env.DYNAMIC_CONFIG_AWS_KEY,
+ secretAccessKey: process.env.DYNAMIC_CONFIG_AWS_SECRET,
+ region: process.env.DYNAMIC_CONFIG_AWS_REGION,
+ signatureVersion: 'v4',
+ });
+
+ this.S3 = new AWS.S3();
+ this.bucket = bucket || process.env.DYNAMIC_CONFIG_AWS_BUCKET;
+ }
+
+ /**
+ * Upload an object to bucket
+ * {@link https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html#upload-property s3.upload}
+ * @param {string} key object key
+ * @param {any} body object body
+ * @param {AWS.S3.PutObjectRequest} options upload object params
+ * @returns {Promise.}
+ * @throws {Error}
+ */
+ upload(key, body, options) {
+ const params = {
+ Bucket: this.bucket,
+ Key: key,
+ Body: body,
+ ...options,
+ };
+
+ return new Promise((resolve, reject) => {
+ this.S3.upload(params, function (error, data) {
+ if (error) reject(error);
+ else resolve(data);
+ });
+ });
+ }
+
+ /**
+ * Upload objects to bucket
+ * {@link https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html#upload-property s3.upload}
+ * @param {[{key: string, body: any}]} objects objects key and body
+ * @param {AWS.S3.PutObjectRequest} options upload objects params
+ * @returns {Promise.<[AWS.S3.Types.ManagedUpload.SendData]>}
+ * @throws {Error}
+ */
+ batchUpload(objects, options) {
+ const uploads = [];
+
+ objects.forEach((object) => {
+ const singleUpload = this.upload(object.key, object.body, options);
+ uploads.push(singleUpload);
+ });
+
+ return Promise.all(uploads);
+ }
+
+ /**
+ * Get object from bucket
+ * {@link https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html#getObject-property s3.getObject}
+ * @param {string} key object key
+ * @param {AWS.S3.GetObjectRequest} options get object params
+ * @returns {Promise.}
+ * @throws {AWS.S3.Error}
+ */
+ get(key, options) {
+ const params = {
+ Bucket: this.bucket,
+ Key: key,
+ ...options,
+ };
+
+ return new Promise((resolve, reject) => {
+ this.S3.getObject(params, function (error, data) {
+ if (error) reject(error);
+ else resolve(data);
+ });
+ });
+ }
+
+ /**
+ * Get objects from bucket
+ * {@link https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html#getObject-property s3.getObject}
+ * @param {[key: string]} keys object keys
+ * @param {AWS.S3.GetObjectRequest} options get objects params
+ * @returns {Promise.<[AWS.S3.Types.GetObjectOutput]>}
+ * @throws {AWS.S3.Error}
+ */
+ batchGet(keys, options) {
+ const gets = [];
+
+ keys.forEach((key) => {
+ const singleGet = this.get(key, options);
+ gets.push(singleGet);
+ });
+
+ return Promise.all(gets);
+ }
+
+ /**
+ * Delete object from bucket
+ * {@link https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html#deleteObject-property s3.getObject}
+ * @param {string} key object key
+ * @param {AWS.S3.DeleteObjectRequest} options delete object params
+ * @returns {Promise.}
+ * @throws {AWS.S3.Error}
+ */
+ delete(key, options) {
+ const params = {
+ Bucket: this.bucket,
+ Key: key,
+ ...options,
+ };
+
+ return new Promise((resolve, reject) => {
+ this.S3.deleteObject(params, function (error, data) {
+ if (error) reject(error);
+ else resolve(data);
+ });
+ });
+ }
+
+ /**
+ * Delete objects from bucket
+ * {@link https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html#deleteObjects-property s3.getObjects}
+ * @param {[string]} key objects key
+ * @param {AWS.S3.DeleteObjectRequest} options delete objects params
+ * @returns {Promise.<[AWS.S3.Types.DeleteObjectOutput]>}
+ * @throws {AWS.S3.Error}
+ */
+ batchDelete(keys, options) {
+ const deletes = [];
+
+ keys.forEach((key) => {
+ const singleDelete = this.delete(key, options);
+ deletes.push(singleDelete);
+ });
+
+ return Promise.all(deletes);
+ }
+}
+
+module.exports = S3Service;
diff --git a/day11/services/SessionService.js b/day11/services/SessionService.js
new file mode 100755
index 0000000..e4d8bb2
--- /dev/null
+++ b/day11/services/SessionService.js
@@ -0,0 +1,62 @@
+/*Powered By: Manaknightdigital Inc. https://manaknightdigital.com/ Year: 2020*/
+/**
+ * Session Service
+ * @copyright 2020 Manaknightdigital Inc.
+ * @link https://manaknightdigital.com
+ * @license Proprietary Software licensing
+ * @author Ryan Wong
+ *
+ */
+
+module.exports = {
+ verifySessionMiddleware: function (role, roleName = '') {
+ return function (req, res, next) {
+ const two_factor_authentication = req.session.two_factor_authentication;
+
+ const currentRole = req.session.role ? req.session.role : 0;
+ if (currentRole === 0 || currentRole != role) {
+ return res.redirect(`${roleName ? '/' + roleName : ''}/login`);
+ } else if (two_factor_authentication) {
+ return res.redirect(`/${roleName}/verify-account`);
+ } else {
+ next();
+ }
+ };
+ },
+
+ preventAuthRoutes: function (role, roleName) {
+ return (req, res, next) => {
+ const sessionRole = req.session.role;
+
+ if (sessionRole === role) {
+ return res.redirect('/' + roleName + '/dashboard');
+ } else {
+ next();
+ }
+ };
+ },
+
+ prevent2FA: function (role, roleName) {
+ return (req, res, next) => {
+ const currentRole = req.session.role;
+ const twoFA = req.session.two_factor_authentication;
+
+ if (currentRole !== role || !twoFA) {
+ return res.redirect('/' + roleName + '/login');
+ } else {
+ next();
+ }
+ };
+ },
+
+ randomString: function (len) {
+ const charSet =
+ 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789~!@#$%^&*()_+{}|":?><,./;[]';
+ let randomString = '';
+ for (let i = 0; i < len; i++) {
+ let randomPoz = Math.floor(Math.random() * charSet.length);
+ randomString += charSet.substring(randomPoz, randomPoz + 1);
+ }
+ return randomString;
+ },
+};
diff --git a/day11/services/SmsService.js b/day11/services/SmsService.js
new file mode 100755
index 0000000..e92610f
--- /dev/null
+++ b/day11/services/SmsService.js
@@ -0,0 +1,65 @@
+/*Powered By: Manaknightdigital Inc. https://manaknightdigital.com/ Year: 2020*/
+/**
+ * SMS Service
+ * @copyright 2020 Manaknightdigital Inc.
+ * @link https://manaknightdigital.com
+ * @license Proprietary Software licensing
+ * @author Ryan Wong
+ *
+ */
+
+const db = require('../models');
+
+const accountSid = process.env.TWILIO_SID;
+const authToken = process.env.TWILIO_TOKEN;
+const phoneNumber = process.env.TWILIO_PHONE_NUMBER;
+const client = require('twilio')(accountSid, authToken);
+
+module.exports = {
+ from: phoneNumber,
+
+ template: function (slug) {
+ return new Promise(function (resolve, reject) {
+ db.sms
+ .findOne({ where: { slug } })
+ .then((response) => {
+ if (!response) {
+ return reject(`TEMPLATE_NOT_FOUND`);
+ } else resolve(response);
+ })
+ .catch((error) => {
+ reject(error);
+ });
+ });
+ },
+
+ inject: function (template, payload) {
+ let body = template.content;
+
+ for (const key in payload) {
+ const element = payload[key];
+ body = body.replace(new RegExp('{{{' + key + '}}}', 'g'), element);
+ }
+
+ return body;
+ },
+
+ /**
+ * Send SMS
+ * @param {string} to
+ * @param {string} body
+ */
+ send: function (to, body) {
+ let self = this;
+ return new Promise(function (resolve, reject) {
+ client.messages
+ .create({
+ body,
+ from: self.from,
+ to,
+ })
+ .then((message) => resolve(message.sid))
+ .catch((error) => reject(error));
+ });
+ },
+};
diff --git a/day11/services/StripeApi.js b/day11/services/StripeApi.js
new file mode 100755
index 0000000..3a0fefb
--- /dev/null
+++ b/day11/services/StripeApi.js
@@ -0,0 +1,561 @@
+const Stripe = require('stripe');
+module.exports = new Service();
+function Service() {
+ this.apiSecretKey = process.env.STRIPE_SECRET_KEY;
+ this.apiPublishKey = process.env.STRIPE_PUBLISH_KEY;
+ this.stripe = Stripe(this.apiSecretKey);
+
+ // Products Functions
+ this.createProduct = async function (params) {
+ args = this.filterParams(params);
+ let stripeType = 'product_create';
+ var result = await this.stripeType(stripeType, args);
+ return result;
+ };
+ this.retrieveProduct = async function (params) {
+ args = this.filterParams(params);
+ let stripeType = 'product_retrieve';
+ var result = await this.stripeType(stripeType, args);
+ return result;
+ };
+ this.listAllProducts = async function (params) {
+ args = this.filterParams(params);
+ let stripeType = 'product_list_all';
+ var result = await this.stripeType(stripeType, args);
+ return result;
+ };
+ this.updateProduct = async function (params) {
+ args = this.filterParams(params);
+ let stripeType = 'product_update';
+ var result = await this.stripeType(stripeType, args);
+ return result;
+ };
+
+ // Cards Functions
+ this.createCard = async function (params) {
+ args = this.filterParams(params);
+ let stripeType = 'card_create';
+ var result = await this.stripeType(stripeType, args);
+ return result;
+ };
+ // Customers Functions
+ this.createCustomer = async function (params) {
+ args = this.filterParams(params);
+ let stripeType = 'customer_create';
+ var result = await this.stripeType(stripeType, args);
+ return result;
+ };
+ this.updateCustomer = async function (params) {
+ args = this.filterParams(params);
+ let stripeType = 'customer_update';
+ var result = await this.stripeType(stripeType, args);
+ return result;
+ };
+ this.retrieveCustomer = async function (params) {
+ args = this.filterParams(params);
+ let stripeType = 'customer_retrieve';
+ var result = await this.stripeType(stripeType, args);
+ return result;
+ };
+ this.listAllCustomers = async function (params) {
+ args = this.filterParams(params);
+ let stripeType = 'customer_list_all';
+ var result = await this.stripeType(stripeType, args);
+ return result;
+ };
+ // Card Functions
+ this.retrieveCard = async function (params) {
+ args = this.filterParams(params);
+ let stripeType = 'card_retrieve';
+ var result = await this.stripeType(stripeType, args);
+ return result;
+ };
+
+ // Prices Functions
+ this.createPrice = async function (params) {
+ args = this.filterParams(params);
+ let stripeType = 'price_create';
+ var result = await this.stripeType(stripeType, args);
+ return result;
+ };
+ this.updatePrice = async function (params) {
+ args = this.filterParams(params);
+ let stripeType = 'price_update';
+ var result = await this.stripeType(stripeType, args);
+ return result;
+ };
+ this.retrievePrice = async function (params) {
+ args = this.filterParams(params);
+ let stripeType = 'price_retrieve';
+ var result = await this.stripeType(stripeType, args);
+ return result;
+ };
+ this.listAllPrices = async function (params) {
+ args = this.filterParams(params);
+ let stripeType = 'price_list_all';
+ var result = await this.stripeType(stripeType, args);
+ return result;
+ };
+
+ // Charges Functions
+ this.createCharge = async function (params) {
+ args = this.filterParams(params);
+ let stripeType = 'charge_create';
+ var result = await this.stripeType(stripeType, args);
+ return result;
+ };
+ this.retrieveCharge = async function (params) {
+ // args = params;
+ args = this.filterParams(params);
+ let stripeType = 'charge_retrieve';
+ var result = await this.stripeType(stripeType, args);
+ return result;
+ };
+
+ //Invoices Functions
+ this.createInvoice = async function (params) {
+ args = this.filterParams(params);
+ let stripeType = 'invoice_create';
+ var result = await this.stripeType(stripeType, args);
+ return result;
+ };
+ this.payInvoice = async function (params) {
+ args = this.filterParams(params);
+ let stripeType = 'invoice_pay';
+ var result = await this.stripeType(stripeType, args);
+ return result;
+ };
+ this.retrieveInvoice = async function (params) {
+ args = this.filterParams(params);
+ let stripeType = 'invoice_retrieve';
+ var result = await this.stripeType(stripeType, args);
+ return result;
+ };
+ this.createInvoiceItem = async function (params) {
+ args = this.filterParams(params);
+ let stripeType = 'invoice_item_create';
+ var result = await this.stripeType(stripeType, args);
+ return result;
+ };
+
+ //subscriptions
+ this.createSubscription = async function (params) {
+ args = this.filterParams(params);
+ let stripeType = 'subscription_create';
+ var result = await this.stripeType(stripeType, args);
+ return result;
+ };
+ this.updateSubscription = async function (params) {
+ args = this.filterParams(params);
+ let stripeType = 'subscription_update';
+ var result = await this.stripeType(stripeType, args);
+ return result;
+ };
+ this.retrieveSubscription = async function (params) {
+ args = this.filterParams(params);
+ let stripeType = 'subscription_retrieve';
+ var result = await this.stripeType(stripeType, args);
+ return result;
+ };
+ this.cancelSubscription = async function (params) {
+ args = this.filterParams(params);
+ let stripeType = 'subscription_cancel';
+ var result = await this.stripeType(stripeType, args);
+ return result;
+ };
+ this.listAllSubscriptions = async function (params) {
+ args = this.filterParams(params);
+ let stripeType = 'subscription_list_all';
+ var result = await this.stripeType(stripeType, args);
+ return result;
+ };
+
+ this.stripeType = async function (type, args) {
+ try {
+ switch (type) {
+ //Products
+ case 'product_create':
+ // @see https://stripe.com/docs/api/products/create?lang=node
+ var result = await this.stripe.products.create(args);
+ break;
+ case 'product_delete':
+ // @see https://stripe.com/docs/api/products/delete?lang=node
+ var result = await this.stripe.products.del(args);
+ break;
+ case 'product_retrieve':
+ // @see https://stripe.com/docs/api/products/retrieve?lang=node
+ var result = await this.stripe.products.retrieve(args);
+ break;
+ case 'product_update':
+ // @see https://stripe.com/docs/api/products/update?lang=node
+ var result = await this.stripe.products.update(args.productId, args.params);
+ break;
+ case 'product_list_all':
+ // @see https://stripe.com/docs/api/products/list?lang=node
+ var result = await this.stripe.products.list(args);
+ break;
+
+ //Prices
+ case 'price_create':
+ // @see https://stripe.com/docs/api/prices/create?lang=node
+ var result = await this.stripe.prices.create(args);
+ break;
+ case 'price_retrieve':
+ // @see https://stripe.com/docs/api/prices/retrieve?lang=node
+ var result = await this.stripe.prices.retrieve(args);
+ break;
+ case 'price_update':
+ // @see https://stripe.com/docs/api/prices/update?lang=node
+ var result = await this.stripe.prices.update(args.priceId, args.params);
+ break;
+ case 'price_list_all':
+ // @see https://stripe.com/docs/api/prices/list?lang=node
+ var result = await this.stripe.prices.list(args);
+ break;
+
+ //Charges
+ case 'charge_create':
+ // @see https://stripe.com/docs/api/charges/create?lang=node
+ var result = await this.stripe.charges.create(args);
+ break;
+ case 'charge_retrieve':
+ // @see https://stripe.com/docs/api/charges/retrieve?lang=node
+ var result = await this.stripe.charges.retrieve(args);
+ break;
+ case 'charge_update':
+ // @see https://stripe.com/docs/api/charges/update?lang=node
+ var result = await this.stripe.charges.update(args.chargeId, args.params);
+ break;
+ case 'charge_capture':
+ // @see https://stripe.com/docs/api/charges/capture?lang=node
+ var result = await this.stripe.charges.capture(args);
+ break;
+ case 'charge_list_all':
+ // @see https://stripe.com/docs/api/charges/list?lang=node
+ var result = await this.stripe.charges.list(args);
+ break;
+
+ //Customer
+ case 'customer_create':
+ // @see https://stripe.com/docs/api/customers/create?lang=node
+ var result = await this.stripe.customers.create(args);
+ break;
+ case 'customer_retrieve':
+ // @see https://stripe.com/docs/api/customers/retrieve?lang=node
+ var result = await this.stripe.customers.retrieve(args);
+ break;
+ case 'customer_update':
+ // @see https://stripe.com/docs/api/customers/update?lang=node
+ var result = await this.stripe.customers.update(args.customerId, args.params);
+ break;
+ case 'customer_delete':
+ // @see https://stripe.com/docs/api/customers/capture?lang=node
+ var result = await this.stripe.customers.del(args);
+ break;
+ case 'customer_list_all':
+ // @see https://stripe.com/docs/api/customers/list?lang=node
+ var result = await this.stripe.customers.list(args);
+ break;
+
+ //Refunds
+ case 'refund_create':
+ // @see https://stripe.com/docs/api/refunds/create?lang=node
+ var result = await this.stripe.refunds.create(args);
+ break;
+ case 'refund_retrieve':
+ // @see https://stripe.com/docs/api/refunds/retrieve?lang=node
+ var result = await this.stripe.refunds.retrieve(args);
+ break;
+ case 'refund_update':
+ // @see https://stripe.com/docs/api/refunds/update?lang=node
+ var result = await this.stripe.refunds.update(args.refundId, args.params);
+ break;
+ case 'refund_list_all':
+ // @see https://stripe.com/docs/api/refunds/list?lang=node
+ var result = await this.stripe.refunds.list(args);
+ break;
+
+ //Cards (Sources)
+ case 'card_create':
+ // @see https://stripe.com/docs/api/cards/create?lang=node
+ var result = await this.stripe.customers.createSource(args.customerId, args.params);
+ break;
+ case 'card_retrieve':
+ // @see https://stripe.com/docs/api/cards/retrieve?lang=node
+ var result = await this.stripe.customers.retrieveSource(args.customerId, args.cardId);
+ break;
+ case 'card_update':
+ // @see https://stripe.com/docs/api/cards/update?lang=node
+ var result = await this.stripe.customers.updateSource(args.customerId, args.cardId, args.params);
+ break;
+ case 'card_delete':
+ // @see https://stripe.com/docs/api/cards/delete?lang=node
+ var result = await this.stripe.customers.deleteSource(args.customerId, args.cardId);
+ break;
+ case 'card_list_all':
+ // @see https://stripe.com/docs/api/cards/list?lang=node
+ var result = await this.stripe.customers.listSources(args.customerId, args.params);
+ break;
+
+ //Payment Method
+ case 'paymentMethod_create':
+ // @see https://stripe.com/docs/api/payment_methods/create?lang=node
+ var result = await this.stripe.paymentMethods.create(args);
+ break;
+ case 'paymentMethod_retrieve':
+ // @see https://stripe.com/docs/api/payment_methods/retrieve?lang=node
+ var result = await this.stripe.paymentMethods.retrieve(args);
+ break;
+ case 'paymentMethod_update':
+ // @see https://stripe.com/docs/api/payment_methods/update?lang=node
+ var result = await this.stripe.paymentMethods.update(args.paymentMethodId, args.params);
+ break;
+ case 'paymentMethod_attach':
+ // @see https://stripe.com/docs/api/payment_methods/attach?lang=node
+ var result = await this.stripe.paymentMethods.attach(args.paymentMethodId, args.params);
+ break;
+ case 'paymentMethod_detach':
+ // @see https://stripe.com/docs/api/payment_methods/attach?lang=node
+ var result = await this.stripe.paymentMethods.attach(args);
+ break;
+ case 'paymentMethod_list_all':
+ // @see https://stripe.com/docs/api/payment_methods/list?lang=node
+ var result = await this.stripe.paymentMethods.list(args);
+ break;
+
+ //Session
+ case 'session_create':
+ // @see https://stripe.com/docs/api/sessions/create?lang=node
+ var result = await this.stripe.checkout.sessions.create(args);
+ break;
+ case 'session_retrieve':
+ // @see https://stripe.com/docs/api/sessions/retrieve?lang=node
+ var result = await this.stripe.checkout.sessions.retrieve(args);
+ break;
+ case 'session_list_all_lineItems':
+ // @see https://stripe.com/docs/api/sessions/line_items?lang=node
+ var result = function () {
+ return new Promise((resolve, reject) => {
+ this.stripe.checkout.sessions.listLineItems(args.sessionId, args.params, function (err, lineItems) {
+ if (err) return reject(err);
+ return resolve(lineItems);
+ });
+ });
+ };
+ result = await result();
+ break;
+ case 'session_list_all':
+ // @see https://stripe.com/docs/api/session/list?lang=node
+ var result = await this.stripe.checkout.sessions.list(args);
+ break;
+
+ //Coupon
+ case 'coupon_create':
+ // @see https://stripe.com/docs/api/coupons/create?lang=node
+ var result = await this.stripe.coupons.create(args);
+ break;
+ case 'coupon_retrieve':
+ // @see https://stripe.com/docs/api/coupons/retrieve?lang=node
+ var result = await this.stripe.coupons.retrieve(args);
+ break;
+ case 'coupon_update':
+ // @see https://stripe.com/docs/api/coupons/update?lang=node
+ var result = await this.stripe.coupons.update(args.couponId, args.params);
+ break;
+ case 'coupon_delete':
+ // @see https://stripe.com/docs/api/coupons/delete?lang=node
+ var result = await this.stripe.coupons.del(args);
+ break;
+ case 'coupon_list_all':
+ // @see https://stripe.com/docs/api/coupons/list?lang=node
+ var result = await this.stripe.coupons.list(args);
+ break;
+
+ //Invoice items
+ case 'invoice_item_create':
+ // @see https://stripe.com/docs/api/invoiceitems/create?lang=node
+ var result = await this.stripe.invoiceItems.create(args);
+ break;
+
+ //Invoice
+ case 'invoice_create':
+ // @see https://stripe.com/docs/api/invoices/create?lang=node
+ var result = await this.stripe.invoices.create(args);
+ break;
+ case 'invoice_retrieve':
+ // @see https://stripe.com/docs/api/invoices/retrieve?lang=node
+ var result = await this.stripe.invoices.retrieve(args);
+ break;
+ case 'invoice_update':
+ // @see https://stripe.com/docs/api/invoices/update?lang=node
+ var result = await this.stripe.invoices.update(args.invoiceId, args.params);
+ break;
+ case 'invoice_delete':
+ // @see https://stripe.com/docs/api/invoices/delete?lang=node
+ var result = await this.stripe.invoices.del(args);
+ break;
+ case 'invoice_finalize':
+ // @see https://stripe.com/docs/api/invoices/finalize?lang=node
+ var result = await this.stripe.invoices.finalizeInvoice(args);
+ break;
+ case 'invoice_pay':
+ // @see https://stripe.com/docs/api/invoices/pay?lang=node
+ var result = await this.stripe.invoices.pay(args);
+ break;
+ case 'invoice_void':
+ // @see https://stripe.com/docs/api/invoices/void?lang=node
+ var result = await this.stripe.invoices.voidInvoice(args);
+ break;
+ case 'invoice_list_all':
+ // @see https://stripe.com/docs/api/invoices/list?lang=node
+ var result = await this.stripe.invoices.list(args);
+ break;
+
+ //Subscription
+ case 'subscription_create':
+ // @see https://stripe.com/docs/api/subscriptions/create?lang=node
+ var result = await this.stripe.subscriptions.create(args);
+ break;
+ case 'subscription_retrieve':
+ // @see https://stripe.com/docs/api/subscriptions/retrieve?lang=node
+ var result = await this.stripe.subscriptions.retrieve(args);
+ break;
+ case 'subscription_update':
+ // @see https://stripe.com/docs/api/subscriptions/update?lang=node
+ var result = await this.stripe.subscriptions.update(args.subscriptionId, args.params);
+ break;
+ case 'subscription_cancel':
+ // @see https://stripe.com/docs/api/subscriptions/cancel?lang=node
+ var result = await this.stripe.subscriptions.del(args.subscriptionId, args.params);
+ break;
+ case 'subscription_list_all':
+ // @see https://stripe.com/docs/api/subscriptions/list?lang=node
+ var result = await this.stripe.subscriptions.list(args);
+ break;
+
+ //Subscription schedule
+ case 'subscription_schedule_create':
+ // @see https://stripe.com/docs/api/subscription_schedules/create?lang=node
+ var result = await this.stripe.subscriptionSchedules.create(args);
+ break;
+ case 'subscription_schedule_retrieve':
+ // @see https://stripe.com/docs/api/subscription_schedules/retrieve?lang=node
+ var result = await this.stripe.subscriptionSchedules.retrieve(args);
+ break;
+ case 'subscription_schedule_update':
+ // @see https://stripe.com/docs/api/subscription_schedules/update?lang=node
+ var result = await this.stripe.subscriptionSchedules.update(args.subScheduleId, args.params);
+ break;
+ case 'subscription_schedule_cancel':
+ // @see https://stripe.com/docs/api/subscription_schedules/cancel?lang=node
+ var result = await this.stripe.subscriptionSchedules.cancel(args);
+ break;
+ case 'subscription_schedule_release':
+ // @see https://stripe.com/docs/api/subscription_schedules/release?lang=node
+ var result = await this.stripe.subscriptionSchedules.release(args);
+ break;
+ case 'subscription_schedule_list_all':
+ // @see https://stripe.com/docs/api/subscription_schedules/list?lang=node
+ var result = await this.stripe.subscriptionSchedules.list(args);
+ break;
+
+ //Transaction
+ case 'transaction_retrieve':
+ // @see https://stripe.com/docs/api/issuing/transactions/retrieve?lang=node
+ var result = await this.stripe.issuing.transactions.retrieve(args);
+ break;
+ case 'transaction_update':
+ // @see https://stripe.com/docs/api/issuing/transactions/update?lang=node
+ var result = await this.stripe.issuing.transactions.update(args.transactionId, args.params);
+ break;
+ case 'transaction_list_all':
+ // @see https://stripe.com/docs/api/issuing/transactions/list?lang=node
+ var result = await this.stripe.issuing.transactions.list(args);
+ break;
+
+ //Order
+ case 'order_create':
+ // @see https://stripe.com/docs/api/orders/create?lang=node
+ var result = await this.stripe.orders.create(args);
+ break;
+ case 'order_retrieve':
+ // @see https://stripe.com/docs/api/orders/retrieve?lang=node
+ var result = await this.stripe.orders.retrieve(args);
+ break;
+ case 'order_update':
+ // @see https://stripe.com/docs/api/orders/update?lang=node
+ var result = await this.stripe.orders.update(args.orderId, args.params);
+ break;
+ case 'order_pay':
+ // @see https://stripe.com/docs/api/orders/pay?lang=node
+ var result = await this.stripe.orders.pay(args.orderId, args.params);
+ break;
+ case 'order_list_all':
+ // @see https://stripe.com/docs/api/orders/list?lang=node
+ var result = await this.stripe.orders.list(args);
+ break;
+ case 'order_return':
+ // @see https://stripe.com/docs/api/orders/return?lang=node
+ var result = function () {
+ return new Promise((resolve, reject) => {
+ this.stripe.orders.returnOrder(args.orderId, args.params, function (err, order) {
+ if (err) return reject(err);
+ return resolve(order);
+ });
+ });
+ };
+ result = await result();
+ break;
+
+ //Webhooks
+ case 'webhook_create':
+ // @see https://stripe.com/docs/api/webhook_endpoints/create?lang=node
+ var result = await this.stripe.webhookEndpoints.create(args);
+ break;
+ case 'webhook_retrieve':
+ // @see https://stripe.com/docs/api/webhook_endpoints/retrieve?lang=node
+ var result = await this.stripe.webhookEndpoints.retrieve(args);
+ break;
+ case 'webhook_update':
+ // @see https://stripe.com/docs/api/webhook_endpoints/update?lang=node
+ var result = await this.stripe.webhookEndpoints.update(args.webhookId, args.params);
+ break;
+ case 'webhook_delete':
+ // @see https://stripe.com/docs/api/webhook_endpoints/delete?lang=node
+ var result = await this.stripe.webhookEndpoints.del(args);
+ break;
+ case 'webhook_list_all':
+ // @see https://stripe.com/docs/api/webhook_endpoints/list?lang=node
+ var result = await this.stripe.webhookEndpoints.list(args);
+ break;
+ }
+ return result;
+ } catch (error) {
+ // return { error: true, message: error.message, statusCode: error.statusCode, errorObject: error.raw };
+ console.dir(error, { depth: null });
+ throw new Error('Internal Error: error when doing stripe action');
+ }
+ };
+
+ /**
+ * [filterParams filters paramters from null, undefined, empty strings, empty arrays and empty objects as it can cause unwanted changes]
+ * @param {object} params [object that can contain one more object inside]
+ * @return {object} [object with only truth variables]
+ */
+ this.filterParams = function (params) {
+ Object.keys(params).forEach((param) => {
+ if (this.empty(params[param]) || (Array.isArray(params[param]) && !params[param].length)) {
+ console.log(`Parameter empty, null or undefined`);
+ delete params[param];
+ } else if (params[param].constructor === Object && Object.entries(params[param]).length === 0) {
+ console.log(`Parameter object empty`);
+ delete params[param];
+ } else if (params[param].constructor === Object && Object.entries(params[param]).length > 0) {
+ this.filterParams(params[param]);
+ }
+ });
+ return params;
+ };
+ // this.empty = (value) => value === null || value === undefined || value === '';
+ this.empty = (value) => value === null || value === undefined;
+}
diff --git a/day11/services/StripeService.js b/day11/services/StripeService.js
new file mode 100755
index 0000000..d7261d8
--- /dev/null
+++ b/day11/services/StripeService.js
@@ -0,0 +1,744 @@
+require('dotenv').config(); //require .env for stripe configurations
+const moment = require('moment');
+const stripe = require('./StripeApi'); //this is Payment Service
+const paypal = require('./PaypalApi');
+const db = require('../models');
+const userModel = db.user;
+const stripeSubscriptionsModel = db.stripe_subscriptions;
+const stripeSubscriptionsLogModel = db.stripe_subscriptions_log;
+const stripePlansModel = db.stripe_plans;
+const stripeProductsModel = db.stripe_products;
+const stripeServicesModel = db.stripe_services;
+const stripeCardsModel = db.stripe_cards;
+
+module.exports = new Service();
+function Service() {
+ this.userId = 0;
+ this.roleId = 0;
+ this.currency = process.env.STRIPE_CURRENCY;
+ this.prorate = process.env.STRIPE_PRORATE;
+ this.forceCancel = process.env.STRIPE_FORCE_CANCEL;
+
+ this.setUserId = function (userId) {
+ this.userId = userId;
+ };
+ this.setRoleId = function (roleId) {
+ this.roleId = roleId;
+ };
+ this.setCurrency = function (currency) {
+ this.currency = currency;
+ };
+ this.setProrationType = function (prorate) {
+ this.prorate = prorate;
+ };
+ this.setCancelType = function (forceCancel) {
+ this.forceCancel = forceCancel;
+ };
+ let convertToCents = function (amount) {
+ return amount * 100;
+ };
+ let handleDBError = function (error) {
+ console.error(error);
+ throw new Error('Internal error: Database error');
+ };
+ let handleRequestError = function (error) {
+ console.error(error);
+ throw new Error('Internal error: Request error');
+ };
+
+ this.subscribe = async function (subscriptionParams, plan) {
+ var subscription = await stripe.createSubscription(subscriptionParams);
+ //create model entry
+ let subscriptionModelEntry = {
+ stripe_id: subscription.id ?? '',
+ cancel_at_period_end: subscription.cancel_at_period_end ?? null,
+ current_period_start: moment(new Date(subscription.current_period_start * 1000)).format('YYYY-MM-DD') ?? null,
+ current_period_end: moment(new Date(subscription.current_period_end * 1000)).format('YYYY-MM-DD') ?? null,
+ user_id: this.userId ?? null,
+ role_id: this.roleId ?? null,
+ plan_id: plan?.id ?? null,
+ coupon_stripe_id: subscription.discount?.coupon?.id ?? '',
+ customer_stripe_id: subscription.customer ?? '',
+ collection_method: subscription.collection_method ?? '',
+ interval: stripeSubscriptionsModel.inverse_interval_mapping(subscription.plan?.interval) ?? 0,
+ interval_count: subscription.plan?.interval_count ?? '',
+ trial_period_days: subscription.plan?.trial_period_days ?? 0,
+ trial_end: subscription.trial_end ? moment(new Date(subscription.trial_end * 1000)).format('YYYY-MM-DD') : null,
+ trial_start: subscription.trial_start ? moment(new Date(subscription.subscriptiontrial_start * 1000)).format('YYYY-MM-DD') : null,
+ status: stripeSubscriptionsModel.inverse_status_mapping(subscription.status) ?? null,
+ };
+
+ let subscriptionCreatedId = await stripeSubscriptionsModel.insert(subscriptionModelEntry);
+ if (!subscriptionCreatedId) {
+ throw new Error('Subscription is not found.');
+ }
+
+ let subscriptionLogUpdated = await this.updateSubscriptionLog(subscriptionCreatedId, subscription.status, plan);
+ if (!subscriptionLogUpdated) {
+ throw new Error('Error while editing subscription log');
+ }
+ return subscriptionCreatedId;
+ };
+ this.updateSubscriptionLog = async function (subscriptionCreatedId, subscriptionCreatedStatus, plan) {
+ let subscriptionLog = await stripeSubscriptionsLogModel.getLast({ user_id: this.userId, role_id: this.roleId });
+
+ if (!subscriptionLog) {
+ let subscriptionLogModelEntry = {
+ user_id: this.userId,
+ role_id: this.roleId,
+ plan_id: plan?.id ?? null,
+ subscription_id: subscriptionCreatedId,
+ type: plan?.type ?? null,
+ status: subscriptionCreatedStatus == 'active' || subscriptionCreatedStatus == 'trialing' ? 1 : 0,
+ };
+ if (!(await stripeSubscriptionsLogModel.insert(subscriptionLogModelEntry))) {
+ throw new Error('Subscription log add not successfull');
+ }
+ } else {
+ let subscriptionLogModelEntry = {
+ plan_id: plan?.id ?? null,
+ subscription_id: subscriptionCreatedId,
+ type: plan?.type ?? null,
+ status: subscriptionCreatedStatus == 'active' || subscriptionCreatedStatus == 'trialing' ? 1 : 0,
+ };
+
+ if (!(await stripeSubscriptionsLogModel.edit(subscriptionLogModelEntry, subscriptionLog.id))) {
+ throw new Error('Subscription log edit not successfull');
+ }
+ }
+ return true;
+ };
+ this.createRegularSubscription = async function (user, plan, card, coupon) {
+ let subscriptionParams = {
+ customer: user.stripe_id,
+ items: [{ price: plan.stripe_id }],
+ trial_from_plan: true,
+ default_payment_method: card?.stripe_card_id ?? null,
+ coupon: coupon?.stripe_id ?? null,
+ proration_behavior: this.prorate ? 'create_prorations' : 'none',
+ };
+ return await this.subscribe(subscriptionParams, plan);
+ };
+ this.createLifetimeSubscription = async function (user, plan, card, coupon) {
+ //create invoice, finalize and pay it.
+ let lifetimePlanStripeObject = await stripe.retrievePrice(plan.stripe_id);
+ let invoiceItemParams = {
+ customer: user.stripe_id,
+ price: plan.stripe_id,
+ discounts: [{ coupon: coupon?.stripe_id }],
+ };
+ let invoiceItemCreated = await stripe.createInvoiceItem(invoiceItemParams);
+
+ let invoiceParams = {
+ customer: user.stripe_id,
+ default_payment_method: card?.stripe_card_id ?? null,
+ };
+ let invoiceCreated = await stripe.createInvoice(invoiceParams);
+
+ let invoicePaid = await stripe.payInvoice(invoiceCreated.id);
+
+ let subscriptionModelEntry = {
+ stripe_id: invoiceItemCreated.id ?? '',
+ cancel_at_period_end: null,
+ current_period_start: moment().format('YYYY-MM-DD') ?? null,
+ current_period_end: null,
+ user_id: this.userId ?? null,
+ role_id: this.roleId ?? null,
+ plan_id: plan?.id ?? null,
+ coupon_stripe_id: invoiceItemCreated.discounts.length > 0 ? coupon?.stripe_id : '',
+ customer_stripe_id: invoicePaid.customer ?? '',
+ collection_method: invoicePaid.collection_method ?? '',
+ interval: 4,
+ interval_count: null,
+ trial_period_days: 0,
+ trial_end: null,
+ trial_start: null,
+ status: 4,
+ };
+
+ let subscriptionCreatedId = await stripeSubscriptionsModel.insert(subscriptionModelEntry);
+ if (!subscriptionCreatedId) {
+ throw new Error('Subscription is not found.');
+ }
+
+ let subscriptionLogUpdated = await this.updateSubscriptionLog(subscriptionCreatedId, 'active', plan);
+ if (!subscriptionLogUpdated) {
+ throw new Error('Error while editing subscription log');
+ }
+
+ return subscriptionCreatedId;
+ };
+ let calculateFullPeriod = function (interval, interval_count = 1) {
+ let totalNumberOfDays = stripePlansModel.interval_days_mapping()[interval] * interval_count;
+ let cancelAtEpoch = moment().add(totalNumberOfDays, 'days').unix();
+ return cancelAtEpoch;
+ };
+ this.createTrialSubscription = async function (user, plan, card, coupon) {
+ let trialPlanFullPeriod = calculateFullPeriod(plan.interval, plan.interval_count);
+ let subscriptionParams = {
+ customer: user.stripe_id,
+ items: [{ price: plan.stripe_id }],
+ trial_from_plan: true,
+ default_payment_method: card?.stripe_card_id ?? null,
+ coupon: coupon?.stripe_id ?? null,
+ cancel_at: trialPlanFullPeriod,
+ proration_behavior: this.prorate ? 'create_prorations' : 'none',
+ };
+ return await this.subscribe(subscriptionParams, plan);
+ };
+ this.updateSubscription = async function (activeSubscription, user, fromPlan, newPlan, card, coupon) {
+ let subscriptionStripeId = activeSubscription.stripe_id;
+
+ let fromTrialToRegular = (fromPlan.type == 3 || fromPlan.type == 4) && newPlan.type == 0 ? true : false;
+ let fromRegularToTrial = fromPlan.type == 0 && (newPlan.type == 3 || newPlan.type == 4) ? true : false;
+ let fromAnyToLifetime = newPlan.type == 1 || newPlan.type == 2 ? true : false;
+ let fromLifetimeToAny = fromPlan.type == 1 || fromPlan.type == 2 ? true : false;
+
+ if (fromRegularToTrial || fromTrialToRegular) {
+ let subscription = await stripe.retrieveSubscription(subscriptionStripeId);
+ let updateParams = {
+ default_payment_method: card?.stripe_card_id,
+ cancel_at: '',
+ items: [
+ {
+ id: subscription.items.data[0].id,
+ price: newPlan.stripe_id,
+ },
+ ],
+ coupon: coupon?.stripe_id ?? null,
+ payment_behavior: 'error_if_incomplete',
+ // trial_from_plan: true,
+ };
+ if (fromRegularToTrial) {
+ let trialPlanFullPeriod = calculateFullPeriod(newPlan.interval, newPlan.interval_count);
+ updateParams.cancel_at = trialPlanFullPeriod;
+ }
+ let newSubscription = await stripe.updateSubscription({ subscriptionId: subscriptionStripeId, params: updateParams });
+
+ let newSubscriptionModelEntry = {
+ cancel_at_period_end: newSubscription.cancel_at_period_end ?? null,
+ current_period_start: moment(new Date(newSubscription.current_period_start * 1000)).format('YYYY-MM-DD') ?? null,
+ current_period_end: moment(new Date(newSubscription.current_period_end * 1000)).format('YYYY-MM-DD') ?? null,
+ plan_id: newPlan?.id ?? null,
+ coupon_stripe_id: newSubscription.discount?.coupon?.id ?? '',
+ interval: stripeSubscriptionsModel.inverse_interval_mapping(newSubscription.plan?.interval) ?? 0,
+ interval_count: newSubscription.plan?.interval_count ?? '',
+ trial_period_days: newSubscription.plan?.trial_period_days ?? 0,
+ trial_end: newSubscription.trial_end ? moment(new Date(newSubscription.trial_end * 1000)).format('YYYY-MM-DD') : null,
+ trial_start: newSubscription.trial_start ? moment(new Date(newSubscription.subscriptiontrial_start * 1000)).format('YYYY-MM-DD') : null,
+ status: stripeSubscriptionsModel.inverse_status_mapping(newSubscription.status) ?? null,
+ };
+
+ let subscriptionCreatedId = await stripeSubscriptionsModel.edit(newSubscriptionModelEntry, activeSubscription.id);
+ if (!subscriptionCreatedId) {
+ throw new Error('Subscription is not found.');
+ }
+
+ let subscriptionLogUpdated = await this.updateSubscriptionLog(activeSubscription.id, newSubscription.status, newPlan);
+ if (!subscriptionLogUpdated) {
+ throw new Error('Error while editing subscription log');
+ }
+ return subscriptionCreatedId;
+ } else if (fromAnyToLifetime) {
+ await this.cancelRegularSubscription(activeSubscription);
+ await this.createLifetimeSubscription(user, newPlan, card, coupon);
+ } else if (fromLifetimeToAny) {
+ await this.cancelLifetimeSubscription(activeSubscription);
+ if (newPlan.type == 0) {
+ await this.createRegularSubscription(user, newPlan, card, coupon);
+ } else if (newPlan.type == 1 || newPlan.type == 2) {
+ await this.createLifetimeSubscription(user, newPlan, card, coupon);
+ } else if (newPlan.type == 3 || newPlan.type == 4) {
+ await this.createTrialSubscription(user, newPlan, card, coupon);
+ }
+ }
+ };
+
+ this.cancelRegularSubscription = async function (subscription) {
+ if (this.forceCancel === 'true') {
+ var cancellationParams = {
+ subscriptionId: subscription.stripe_id,
+ params: {
+ invoice_now: true,
+ prorate: this.prorate,
+ },
+ };
+ let subscriptionCanceled = await stripe.cancelSubscription(cancellationParams);
+
+ let newStatus = stripeSubscriptionsModel.inverse_status_mapping()[subscriptionCanceled.status];
+ await stripeSubscriptionsModel.edit({ status: newStatus }, subscription.id);
+
+ let subscriptionLog = await stripeSubscriptionsLogModel.getByFields({ subscription_id: parseInt(subscription.id), status: 1 });
+ if (!subscriptionLog) {
+ throw new Error("Couldn't find relative active subscription log");
+ }
+ let subscriptionLogEdited = await stripeSubscriptionsLogModel.edit({ status: 0 }, subscriptionLog.id);
+ if (!subscriptionLogEdited) {
+ throw new Error("Couldn't Edit subscription log");
+ }
+ } else {
+ let updateParams = {
+ subscriptionId: subscription.stripe_id,
+ params: {
+ cancel_at_period_end: true,
+ },
+ };
+ await stripe.updateSubscription(updateParams);
+ await stripeSubscriptionsModel.edit({ cancel_at_period_end: 1 }, subscription.id);
+ }
+ };
+ this.cancelLifetimeSubscription = async function (subscription) {
+ //TODO check if we refund on this or not
+ //we could make a fixed refund amount
+ //right now we just cancel it locally and refund is never as it is a onetime product thing.
+ let subscriptionEdited = await stripeSubscriptionsModel.edit({ status: 5 }, parseInt(subscription.id));
+ if (!subscriptionEdited) {
+ throw new Error("Couldn't edit subscription");
+ }
+ let subscriptionLog = await stripeSubscriptionsLogModel.getByFields({ subscription_id: parseInt(subscription.id), status: 1 });
+ if (!subscriptionLog) {
+ throw new Error("Couldn't find relative active subscription log");
+ }
+ let subscriptionLogEdited = await stripeSubscriptionsLogModel.edit({ status: 0 }, subscriptionLog.id);
+ if (!subscriptionLogEdited) {
+ throw new Error("Couldn't Edit subscription log");
+ }
+ return true;
+ };
+ this.createCustomerWithoutCard = async function (params) {
+ let createdCustomer = await stripe.createCustomer(params);
+
+ let userModelEntry = {
+ stripe_id: createdCustomer.id,
+ };
+
+ let modelEditedUser = await userModel.edit(userModelEntry, this.userId);
+ if (!modelEditedUser) {
+ throw new Error('Internal error: Error editing user stripe id');
+ }
+ return modelEditedUser;
+ };
+ this.createCustomer = async function (params) {
+ let createdCustomer = await stripe.createCustomer(params);
+ let userModelEntry = {
+ stripe_id: createdCustomer.id,
+ };
+ let editedUserModel = await userModel.edit(userModelEntry, this.userId);
+ if (!editedUserModel) {
+ throw new Error('Error editing user stripe id');
+ }
+
+ let customerCardParams = {
+ customerId: createdCustomer.id,
+ cardId: createdCustomer.default_source,
+ };
+ let card = await stripe.retrieveCard(customerCardParams);
+
+ if (card) {
+ cardModelEntry = {
+ card_last: card.last4 ?? '',
+ card_brand: card.brand ?? '',
+ card_exp_month: card.exp_month ?? '',
+ exp_year: card.exp_year ?? '',
+ card_name: createdCustomer.metadata.card_name ?? '',
+ stripe_card_customer: card.customer,
+ stripe_card_id: card.id,
+ is_default: createdCustomer.metadata.is_default,
+ user_id: this.userId ?? null,
+ role_id: this.roleId ?? null,
+ };
+ var modelCreatedCard = await stripeCardsModel.insert(cardModelEntry);
+ if (!modelCreatedCard) {
+ throw new Error('Internal error: Error adding card');
+ }
+ }
+ return modelCreatedCard;
+ };
+
+ this.adminCreateProductPrice = async function (productStripeId, productLocalId, price) {
+ let priceArgs = {
+ unit_amount: convertToCents(price),
+ currency: this.currency,
+ product: productStripeId,
+ };
+ let priceCreated = await stripe.createPrice(priceArgs);
+
+ let productModelEditEntry = {
+ price,
+ price_stripe_id: priceCreated.id,
+ };
+ let modelEditedProduct = await stripeProductsModel.edit(productModelEditEntry, productLocalId);
+ if (!modelEditedProduct) {
+ throw new Error('Internal Error: error setting price');
+ }
+ return priceCreated;
+ };
+
+ this._createStripeService = async function (params) {
+ let serviceParams = {
+ name: params.name,
+ description: params.description,
+ url: params.url,
+ images: params.image ? [params.image] : [],
+ active: parseInt(params.status) === 1 ? true : false,
+ };
+
+ return await stripe.createProduct(serviceParams);
+ };
+ this._createStripeProduct = async function (params) {
+ let productParams = {
+ name: params.name,
+ active: parseInt(params.status) === 1 ? true : false,
+ images: params.images ? [params.images] : [],
+ description: params.description,
+ shippable: parseInt(params.shippable) === 1 ? true : false,
+ unit_label: params.unit_label,
+ statement_descriptor: params.statement_descriptor,
+ };
+
+ return await stripe.createProduct(productParams);
+ };
+
+ /**
+ * [adminCreateProduct allows an admin to create a physical, digital goods or services]
+ * @param {object} params [object that contain needed parameters to create a said product or service]
+ * @param {strine} type [string that represents the type of the product]
+ * @return {object} [product or service stripe id as well as product or service local id and if a product it also returns the price object stripe id]
+ */
+ this.adminCreateProduct = async function (params, type) {
+ if (!params.name) {
+ throw new Error('Must provide a name for your product');
+ }
+ if (!type) {
+ throw new Error('Must provide a type for your product');
+ }
+
+ if (type === 'PRODUCT') {
+ var createdStripeProduct = await this._createStripeProduct(params);
+ let productModelEntry = {
+ stripe_id: createdStripeProduct.id,
+ name: createdStripeProduct.name,
+ status: createdStripeProduct.active === true ? 1 : 0,
+ description: createdStripeProduct.description ?? '',
+ images: createdStripeProduct.images?.length > 0 ? createdStripeProduct.images[0] : '',
+ shippable: createdStripeProduct.shippable ?? 0,
+ unit_label: createdStripeProduct.unit_label ?? '',
+ statement_descriptor: createdStripeProduct.statement_descriptor ?? '',
+ };
+
+ let modelCreatedServiceId = await stripeProductsModel.insert(productModelEntry);
+ let product = await stripeProductsModel.getByPK(modelCreatedServiceId);
+
+ return {
+ productStripeId: product.stripe_id,
+ productLocalId: product.id,
+ };
+ } else if (type === 'SERVICE') {
+ var createdStripeService = await this._createStripeService(params);
+
+ let serviceModelEntry = {
+ name: createdStripeService.name,
+ stripe_id: createdStripeService.id,
+ status: createdStripeService.active === true ? 1 : 0,
+ image: createdStripeService.images.length > 0 ? createdStripeService.images[0] : '',
+ url: createdStripeService.url ?? '',
+ description: createdStripeService.description ?? '',
+ };
+ let modelCreatedServiceId = await stripeServicesModel.insert(serviceModelEntry);
+ let service = await stripeServicesModel.getByPK(modelCreatedServiceId);
+
+ return {
+ serviceStripeId: service.stripe_id,
+ serviceLocalId: service.id,
+ };
+ }
+
+ throw new Error('Missing plan type parameter.');
+ };
+ /**
+ * [adminUpdateService allows an admin to update certain field of a service]
+ * @param {object} params [object that contain parameters to be edited]
+ * @param {integer} serviceId [integer that represents service local primary key (id) in database]
+ * @return {object} [return updated service object]
+ */
+ this.adminUpdateService = async function (params, serviceId) {
+ let service = await stripeServicesModel.getByPK(serviceId);
+
+ await stripe.updateProduct({ productId: service.stripe_id, params });
+ let updateParams = {
+ name: params.name,
+ status: params.active ? 1 : 0,
+ };
+ let updatedService = await stripeServicesModel.edit(updateParams, serviceId);
+ if (!updatedService) {
+ throw new Error('Internal Error: Error editing service');
+ }
+ return updatedService;
+ };
+ /**
+ * [adminUpdateProduct allows an admin to update certain field of a product]
+ * @param {object} params [object that contain parameters to be edited]
+ * @param {integer} productId [integer that represents product local primary key (id) in database]
+ * @return {object} [return updated product object]
+ */
+ this.adminUpdateProduct = async function (params, productId) {
+ let product = await stripeProductsModel.getByPK(productId);
+
+ await stripe.updateProduct({ productId: product.stripe_id, params });
+ let updateParams = {
+ name: params.name,
+ status: params.active ? 1 : 0,
+ };
+ let updatedProduct = await stripeProductsModel.edit(updateParams, productId);
+ if (!updatedProduct) {
+ throw new Error('Internal Error: Error editing product');
+ }
+ return updatedProduct;
+ };
+ /**
+ * [adminCreateRegularPlan allows an admin to create subscription plan through admin portal]
+ * @param {object} params [object that contain plan parameters]
+ * @param {integer} planType [integer that represent plan type in relative to stripe plans table] @see /models/stripe_plans.js type_mapping()
+ * @param {integer} localProductId [integer that represent product id in relative to stripe products table]
+ * @return {integer} [the id of the created plan in database]
+ */
+ this.adminCreateRegularPlan = async function (params, planType, localServiceId) {
+ if (!params.interval) {
+ throw new Error('Must have a recurring parameter with interval value set');
+ }
+ if (!params.nickname) {
+ throw new Error('Must have a display name');
+ }
+ if (!localServiceId) {
+ throw new Error('Must provide system product id');
+ }
+ if (planType === null || undefined) {
+ throw new Error('Must provide plan type');
+ }
+
+ let service = await stripeServicesModel.getByPK(localServiceId);
+
+ let stripePlanCreated = await this._createStripeRegularPlan(params, service);
+
+ let planModelEntry = {
+ nickname: stripePlanCreated.nickname,
+ stripe_id: stripePlanCreated.id,
+ stripe_product_id: stripePlanCreated.product,
+ amount: stripePlanCreated.unit_amount / 100,
+ interval: stripePlansModel.inverse_interval_mapping(stripePlanCreated.recurring.interval),
+ interval_count: stripePlanCreated.recurring.interval_count,
+ trial_period_days: stripePlanCreated.recurring.trial_period_days ?? 0,
+ service_id: localServiceId,
+ status: stripePlanCreated.active === true ? 1 : 0,
+ type: parseInt(planType),
+ };
+
+ let modelCreatedPlan = await stripePlansModel.insert(planModelEntry);
+
+ if (!modelCreatedPlan) {
+ throw new Error('Internal error: error creating plan');
+ }
+ return modelCreatedPlan;
+ };
+
+ this._createStripeRegularPlan = async function (params, service) {
+ let stripeSubscriptionPlanParams = {
+ nickname: params.nickname,
+ currency: this.currency,
+ unit_amount: convertToCents(params.amount),
+ product: service.stripe_id,
+ recurring: {
+ interval: stripePlansModel.interval_mapping()[params.interval],
+ interval_count: params.interval_count,
+ trial_period_days: params.trial_period_days,
+ },
+ active: parseInt(params.status) === 1 ? true : false,
+ };
+ return await stripe.createPrice(stripeSubscriptionPlanParams);
+ };
+ this._createStripeLifetimePlan = async function (params, service) {
+ //stripe doesn't have lifetime plans
+ //how it works is a price is created with no recurring parameter as a single time product
+ //which acts and dealt with internally within our system as a lifetime plan.
+ let stripeSubscriptionPlanParams = {
+ nickname: params.nickname,
+ currency: this.currency,
+ unit_amount: convertToCents(params.amount),
+ product: service.stripe_id,
+ active: parseInt(params.status) === 1 ? true : false,
+ };
+ return await stripe.createPrice(stripeSubscriptionPlanParams);
+ };
+ this._createStripeTrialOnlyPlan = async function (params, service) {
+ let trialOnlyPlanParams = {
+ nickname: params.nickname,
+ currency: this.currency,
+ unit_amount: convertToCents(params.amount),
+ recurring: {
+ interval: stripePlansModel.interval_mapping()[params.interval],
+ interval_count: params.interval_count,
+ },
+ product: service.stripe_id,
+ active: parseInt(params.status) === 1 ? true : false,
+ };
+ return await stripe.createPrice(trialOnlyPlanParams);
+ };
+ this._deactivateStripeLifetimePlan = async function (planStripeId) {
+ //stripe doesn't allow deleting a price. will be set to unactive instead
+ return await stripe.updatePrice({ priceId: planStripeId, params: { active: false } });
+ };
+
+ this.adminCreateLifetimePlan = async function (params, planType, localServiceId) {
+ if (!params.nickname) {
+ throw new Error('Must have a display name');
+ }
+ if (!localServiceId) {
+ throw new Error('Must provide system product id');
+ }
+ if (planType === null || undefined) {
+ throw new Error('Must provide plan type');
+ }
+ if (parseInt(params.interval) !== 4) {
+ throw new Error('Interval must be "forever".');
+ }
+
+ let service = await stripeServicesModel.getByPK(localServiceId);
+ let stripePlanCreated = await this._createStripeLifetimePlan(params, service);
+
+ let planModelEntry = {
+ stripe_product_id: stripePlanCreated.product ?? '',
+ stripe_id: stripePlanCreated.id ?? '',
+ amount: stripePlanCreated.unit_amount / 100,
+ interval: 4,
+ interval_count: null,
+ trial_period_days: null,
+ nickname: stripePlanCreated.nickname ?? '',
+ service_id: localServiceId,
+ status: stripePlanCreated.active === true ? 1 : 0,
+ type: parseInt(planType),
+ };
+
+ let modelCreatedPlan = await stripePlansModel.insert(planModelEntry);
+ if (!modelCreatedPlan) {
+ throw new Error('Internal error: error creating plan');
+ }
+
+ return modelCreatedPlan;
+ };
+
+ this.adminCreateTrialPlan = async function (params, planType, localServiceId) {
+ if (!params.interval) {
+ throw new Error('Must have a recurring parameter with interval value set');
+ }
+ if (!params.nickname) {
+ throw new Error('Must have a display name');
+ }
+ if (!localServiceId) {
+ throw new Error('Must provide system product id');
+ }
+ if (planType === null || undefined) {
+ throw new Error('Must provide plan type');
+ }
+
+ let service = await stripeServicesModel.getByPK(localServiceId);
+ let priceCreated = await this._createStripeTrialOnlyPlan(params, service);
+
+ let planModelEntry = {
+ stripe_product_id: priceCreated.product,
+ stripe_id: priceCreated.id,
+ amount: priceCreated.unit_amount / 100,
+ interval: stripePlansModel.inverse_interval_mapping(priceCreated.recurring.interval),
+ interval_count: priceCreated.recurring.interval_count,
+ trial_period_days: priceCreated.recurring.trial_period_days ?? 0,
+ nickname: priceCreated.nickname,
+ service_id: localServiceId,
+ status: priceCreated.active ? 1 : 0,
+ type: parseInt(planType),
+ };
+ let modelCreatedPlan = await stripePlansModel.insert(planModelEntry);
+ if (!modelCreatedPlan) {
+ throw new Error('Internal error: error creating plan');
+ }
+ return modelCreatedPlan;
+ };
+
+ this.adminEditPlan = async function (params, priceId) {
+ let price = await stripePlansModel.getByPK(priceId);
+ await stripe.updatePrice({ priceId: price.stripe_id, params });
+ let updateParams = {
+ nickname: params.nickname,
+ status: params.active ? 1 : 0,
+ };
+ let updatedPrice = await stripePlansModel.edit(updateParams, priceId);
+ if (!updatedPrice) {
+ throw new Error('Internal error: error editing plan');
+ }
+ return updatedPrice;
+ };
+ this.adminCancelSubscription = async function (subscriptionId) {
+ let subscriptionToCancel = await stripeSubscriptionsModel.getByPK(subscriptionId);
+ let subscriptionPlan = await stripePlansModel.getByPK(subscriptionToCancel.plan_id);
+ if (!subscriptionToCancel || !subscriptionPlan) {
+ throw new Error('No subscription or plan of that id');
+ }
+ // let subscirptionType = subscriptionPlan.type;
+ let subscirptionType = 3;
+ //if normal subscription
+ switch (subscirptionType) {
+ case 0:
+ case 3:
+ case 4:
+ let cancelSubscriptionParams = {
+ subscriptionId: subscriptionToCancel.stripe_id,
+ params: {
+ prorate: this.prorate,
+ invoice_now: true,
+ },
+ };
+ let subscriptionCanceled = await stripe.cancelSubscription(cancelSubscriptionParams);
+ let localCancellationParams = {
+ status: stripeSubscriptionsModel.inverse_status_mapping()[subscriptionCanceled.status],
+ };
+ await stripeSubscriptionsModel.edit(localCancellationParams, subscriptionId);
+ return subscriptionCanceled;
+ default:
+ throw new Error('Something wrong');
+ }
+ };
+ this.userAddCard = async function (cardParams) {
+ let createdCard = await stripe.createCard(cardParams);
+
+ if (createdCard) {
+ if (createdCard.metadata.is_default == '1') {
+ let customerUpdateParams = {
+ customerId: createdCard.customer,
+ params: { default_source: createdCard.id },
+ };
+ await stripe.updateCustomer(customerUpdateParams);
+ let defaultCards = await stripeCardsModel.getAll({ is_default: 1 });
+ if (defaultCards.length > 0) {
+ defaultCards.forEach(async (card) => {
+ await stripeCardsModel.edit({ is_default: 0 }, card.id);
+ });
+ }
+ }
+ cardModelEntry = {
+ card_last: createdCard.last4 ?? '',
+ card_brand: createdCard.brand ?? '',
+ card_exp_month: createdCard.exp_month ?? '',
+ exp_year: createdCard.exp_year ?? '',
+ card_name: createdCard.metadata.card_name ?? '',
+ stripe_card_customer: createdCard.customer,
+ stripe_card_id: createdCard.id,
+ is_default: createdCard.metadata.is_default,
+ user_id: this.userId ?? null,
+ role_id: this.roleId ?? null,
+ };
+ var modelCreatedCard = await stripeCardsModel.insert(cardModelEntry);
+ if (!modelCreatedCard) {
+ throw new Error('Internal error: Error adding card');
+ }
+ }
+ return modelCreatedCard;
+ };
+}
diff --git a/day11/services/TimezoneService.js b/day11/services/TimezoneService.js
new file mode 100755
index 0000000..e3055b0
--- /dev/null
+++ b/day11/services/TimezoneService.js
@@ -0,0 +1,32 @@
+const fs = require('fs');
+const path = require('path');
+
+class TimezoneService {
+ constructor() {
+ this.cacheStore = {};
+ }
+
+ getAvailableTimezones() {
+ let availableTimezones;
+ if (this.cacheStore.timezones) {
+ availableTimezones = this.cacheStore.timezones;
+ } else {
+ const availableTimezonesContent = fs.readFileSync(path.join(__dirname, '..', 'public', 'timezone.json'), 'utf-8');
+ availableTimezones = JSON.parse(availableTimezonesContent);
+ this.cacheStore.timezones = availableTimezones;
+ }
+ return availableTimezones;
+ }
+
+ findTimezone(target) {
+ const availableTimezones = this.getAvailableTimezones();
+ return availableTimezones?.find((timezone) => timezone?.Abbreviation == target);
+ }
+
+ validateTimeZone(target) {
+ const findTimezone = this.findTimezone(target);
+ return !!findTimezone;
+ }
+}
+
+module.exports = TimezoneService;
diff --git a/day11/services/UploadService.js b/day11/services/UploadService.js
new file mode 100755
index 0000000..a279304
--- /dev/null
+++ b/day11/services/UploadService.js
@@ -0,0 +1,65 @@
+const path = require("path");
+const aws = require("aws-sdk");
+const multer = require("multer");
+const multerS3 = require("multer-s3");
+
+module.exports = {
+ upload: function (location) {
+ if (process.env.NODE_ENV === "production") {
+ return this.s3_upload(location);
+ } else {
+ return this.local_upload("public/images/uploads");
+ }
+ },
+ s3_upload: function (location) {
+ try {
+ aws.config.update({
+ accessKeyId: process.env.DYNAMIC_CONFIG_AWS_KEY,
+ secretAccessKey: process.env.DYNAMIC_CONFIG_AWS_SECRET,
+ region: process.env.DYNAMIC_CONFIG_AWS_REGION,
+ });
+
+ const s3 = new aws.S3();
+
+ const upload = multer({
+ storage: multerS3({
+ s3: s3,
+ bucket: process.env.DYNAMIC_CONFIG_AWS_BUCKET,
+ acl: "public-read",
+ contentType: multerS3.AUTO_CONTENT_TYPE,
+ key: function (req, file, cb) {
+ cb(null, location + file.originalname);
+ },
+ }),
+ });
+
+ return upload;
+ } catch (error) {
+ console.log("s3_upload => ", error);
+ }
+ },
+ local_upload: function (location) {
+ try {
+ const storage = multer.diskStorage({
+ destination: function (req, file, cb) {
+ cb(null, location);
+ },
+ filename: function (req, file, cb) {
+ req.local_file = path.join(
+ __dirname,
+ "../",
+ "public/images/uploads",
+ file.originalname
+ );
+ cb(null, file.originalname);
+ },
+ });
+
+ const upload = multer({ storage: storage });
+
+ return upload;
+ } catch (error) {
+ console.log("local_upload => ", error);
+ }
+ },
+};
diff --git a/day11/services/ValidationService.js b/day11/services/ValidationService.js
new file mode 100755
index 0000000..f9f5cc1
--- /dev/null
+++ b/day11/services/ValidationService.js
@@ -0,0 +1,101 @@
+const { Validator, addCustomMessages } = require('node-input-validator');
+const { formatError } = require('../utils/formatError');
+
+// {fieldName:message} eg:{email:"Invalid Email", password:"Password too short"}
+const formatValidationError = (error) => {
+ const formatted = Object.entries(error)
+ .map(([key, value]) => ({
+ field: key,
+ message: value.message,
+ }))
+ .reduce((accumulator, currentValue) => {
+ if (!accumulator[currentValue]) {
+ accumulator[currentValue.field] = currentValue.message;
+ }
+ return accumulator;
+ }, {});
+ return formatted;
+};
+
+module.exports = {
+ /**
+ * Input Validator middleware for controller
+ * @param {object} validationObject object defining body fields and its validation types eg:{email:required|email}
+ * @param {object} _extendMessages object defining message to throw on validation error eg: {"email.required":"Email is required","email.email":"Invalid email"}
+ *
+ */
+ validateInput:
+ (validationObject = {}, _extendMessages = {}) =>
+ async (req, res, next) => {
+ const validation = new Validator(req.body, validationObject);
+ addCustomMessages(_extendMessages);
+
+ try {
+ const isValid = await validation.check();
+ if (!isValid) {
+ req.validationError = formatValidationError(validation.errors);
+ }
+ return next();
+ } catch (error) {
+ req.validationError = error.message;
+ return next();
+ }
+ },
+ validateInputForGraphql:
+ (fn = () => null, validationObject = {}, extendsMessages = {}) =>
+ async (parent, inputArgs, ...args) => {
+ const validation = new Validator(JSON.parse(JSON.stringify(inputArgs)), validationObject);
+
+ addCustomMessages(extendsMessages);
+
+ try {
+ const isValid = await validation.check();
+ if (!isValid) {
+ return {
+ success: false,
+ code: 'ERROR_INPUT_VALIDATION',
+ errors: Object.entries(validation.errors).map(([key, value]) => ({
+ path: key,
+ message: value.message,
+ })),
+ };
+ }
+ return fn(parent, inputArgs, ...args);
+ } catch (error) {
+ return formatError(error);
+ }
+ },
+
+ handleValidationErrorForViews: (req, res, viewModel, viewPath = '/', fieldsStoreKey, defaultValue = {}) => {
+ const validationError = req.validationError;
+
+ if (validationError) {
+ // Remembers fields if validation error occurs
+ Object.entries(defaultValue).forEach(([key, value]) => {
+ viewModel[fieldsStoreKey][key] = value;
+ });
+
+ if (typeof validationError === 'string') {
+ viewModel.error = validationError;
+ } else {
+ viewModel.validationError = req.validationError;
+ }
+ return res.render(viewPath, viewModel);
+ }
+ },
+
+ handleValidationErrorForAPI: (req, res, next) => {
+ const validationError = req.validationError;
+
+ if (validationError) {
+ let error;
+ if (typeof validationError === 'string') {
+ error = validationError;
+ } else {
+ error = req.validationError;
+ }
+ return res.json({ success: false, error });
+ }
+ next();
+ },
+};
diff --git a/day11/services/paypal/paypalProductsCategories.json b/day11/services/paypal/paypalProductsCategories.json
new file mode 100755
index 0000000..01fb43b
--- /dev/null
+++ b/day11/services/paypal/paypalProductsCategories.json
@@ -0,0 +1,449 @@
+{
+ "categories": {
+ "ACADEMIC_SOFTWARE": "Academic Software",
+ "ACCESSORIES": "Accessories",
+ "ACCOUNTING": "Accounting",
+ "ADULT": "Adult",
+ "ADVERTISING": "Advertising",
+ "AFFILIATED_AUTO_RENTAL": "Affiliated Auto Rental",
+ "AGENCIES": "Agencies",
+ "AGGREGATORS": "Aggregators",
+ "AGRICULTURAL_COOPERATIVE_FOR_MAIL_ORDER": "Agricultural Cooperative for Mail Order",
+ "AIR_CARRIERS_AIRLINES": "Air Carriers, Airlines",
+ "AIRLINES": "Airlines",
+ "AIRPORTS_FLYING_FIELDS": "Airports, Flying Fields",
+ "ALCOHOLIC_BEVERAGES": "Alcoholic Beverages",
+ "AMUSEMENT_PARKS_CARNIVALS": "Amusement Parks/Carnivals",
+ "ANIMATION": "Animation",
+ "ANTIQUES": "Antiques",
+ "APPLIANCES": "Appliances",
+ "AQUARIAMS_SEAQUARIUMS_DOLPHINARIUMS": "Aquariams Seaquariums Dolphinariums",
+ "ARCHITECTURAL_ENGINEERING_AND_SURVEYING_SERVICES": "Architectural,Engineering,And Surveying Services",
+ "ART_AND_CRAFT_SUPPLIES": "Art & Craft Supplies",
+ "ART_DEALERS_AND_GALLERIES": "Art dealers and galleries",
+ "ARTIFACTS_GRAVE_RELATED_AND_NATIVE_AMERICAN_CRAFTS": "Artifacts, Grave related, and Native American Crafts",
+ "ARTS_AND_CRAFTS": "Arts and crafts",
+ "ARTS_CRAFTS_AND_COLLECTIBLES": "Arts, crafts, and collectibles",
+ "AUDIO_BOOKS": "Audio books",
+ "AUTO_ASSOCIATIONS_CLUBS": "Auto Associations/Clubs",
+ "AUTO_DEALER_USED_ONLY": "Auto dealer - used only",
+ "AUTO_RENTALS": "Auto Rentals",
+ "AUTO_SERVICE": "Auto service",
+ "AUTOMATED_FUEL_DISPENSERS": "Automated Fuel Dispensers",
+ "AUTOMOBILE_ASSOCIATIONS": "Automobile Associations",
+ "AUTOMOTIVE": "Automotive",
+ "AUTOMOTIVE_REPAIR_SHOPS_NON_DEALER": "Automotive Repair Shops - Non-Dealer",
+ "AUTOMOTIVE_TOP_AND_BODY_SHOPS": "Automotive Top And Body Shops",
+ "AVIATION": "Aviation",
+ "BABIES_CLOTHING_AND_SUPPLIES": "Babies Clothing & Supplies",
+ "BABY": "Baby",
+ "BANDS_ORCHESTRAS_ENTERTAINERS": "Bands,Orchestras,Entertainers",
+ "BARBIES": "Barbies",
+ "BATH_AND_BODY": "Bath and body",
+ "BATTERIES": "Batteries",
+ "BEAN_BABIES": "Bean Babies",
+ "BEAUTY": "Beauty",
+ "BEAUTY_AND_FRAGRANCES": "Beauty and fragrances",
+ "BED_AND_BATH": "Bed & Bath",
+ "BICYCLE_SHOPS_SALES_AND_SERVICE": "Bicycle Shops-Sales And Service",
+ "BICYCLES_AND_ACCESSORIES": "Bicycles & Accessories",
+ "BILLIARD_POOL_ESTABLISHMENTS": "Billiard/Pool Establishments",
+ "BOAT_DEALERS": "Boat Dealers",
+ "BOAT_RENTALS_AND_LEASING": "Boat Rentals And Leasing",
+ "BOATING_SAILING_AND_ACCESSORIES": "Boating, sailing and accessories",
+ "BOOKS": "Books",
+ "BOOKS_AND_MAGAZINES": "Books and magazines",
+ "BOOKS_MANUSCRIPTS": "Books, Manuscripts",
+ "BOOKS_PERIODICALS_AND_NEWSPAPERS": "Books, Periodicals And Newspapers",
+ "BOWLING_ALLEYS": "Bowling Alleys",
+ "BULLETIN_BOARD": "Bulletin board",
+ "BUS_LINE": "Bus line",
+ "BUS_LINES_CHARTERS_TOUR_BUSES": "Bus Lines,Charters,Tour Buses",
+ "BUSINESS": "Business",
+ "BUSINESS_AND_SECRETARIAL_SCHOOLS": "Business and secretarial schools",
+ "BUYING_AND_SHOPPING_SERVICES_AND_CLUBS": "Buying And Shopping Services And Clubs",
+ "CABLE_SATELLITE_AND_OTHER_PAY_TELEVISION_AND_RADIO_SERVICES": "Cable,Satellite,And Other Pay Television And Radio Services",
+ "CABLE_SATELLITE_AND_OTHER_PAY_TV_AND_RADIO": "Cable, satellite, and other pay TV and radio",
+ "CAMERA_AND_PHOTOGRAPHIC_SUPPLIES": "Camera and photographic supplies",
+ "CAMERAS": "Cameras",
+ "CAMERAS_AND_PHOTOGRAPHY": "Cameras & Photography",
+ "CAMPER_RECREATIONAL_AND_UTILITY_TRAILER_DEALERS": "Camper,Recreational And Utility Trailer Dealers",
+ "CAMPING_AND_OUTDOORS": "Camping and outdoors",
+ "CAMPING_AND_SURVIVAL": "Camping & Survival",
+ "CAR_AND_TRUCK_DEALERS": "Car And Truck Dealers",
+ "CAR_AND_TRUCK_DEALERS_USED_ONLY": "Car And Truck Dealers - Used Only",
+ "CAR_AUDIO_AND_ELECTRONICS": "Car Audio & Electronics",
+ "CAR_RENTAL_AGENCY": "Car rental agency",
+ "CATALOG_MERCHANT": "Catalog Merchant",
+ "CATALOG_RETAIL_MERCHANT": "Catalog/Retail Merchant",
+ "CATERING_SERVICES": "Catering services",
+ "CHARITY": "Charity",
+ "CHECK_CASHIER": "Check Cashier",
+ "CHILD_CARE_SERVICES": "Child Care Services",
+ "CHILDREN_BOOKS": "Children Books",
+ "CHIROPODISTS_PODIATRISTS": "Chiropodists/Podiatrists",
+ "CHIROPRACTORS": "Chiropractors",
+ "CIGAR_STORES_AND_STANDS": "Cigar Stores And Stands",
+ "CIVIC_SOCIAL_FRATERNAL_ASSOCIATIONS": "Civic, Social, Fraternal Associations",
+ "CIVIL_SOCIAL_FRAT_ASSOCIATIONS": "Civil/Social/Frat Associations",
+ "CLOTHING": "Clothing",
+ "CLOTHING_ACCESSORIES_AND_SHOES": "Clothing, accessories, and shoes",
+ "CLOTHING_RENTAL": "Clothing Rental",
+ "COFFEE_AND_TEA": "Coffee and tea",
+ "COIN_OPERATED_BANKS_AND_CASINOS": "Coin Operated Banks & Casinos",
+ "COLLECTIBLES": "Collectibles",
+ "COLLECTION_AGENCY": "Collection agency",
+ "COLLEGES_AND_UNIVERSITIES": "Colleges and universities",
+ "COMMERCIAL_EQUIPMENT": "Commercial Equipment",
+ "COMMERCIAL_FOOTWEAR": "Commercial Footwear",
+ "COMMERCIAL_PHOTOGRAPHY": "Commercial photography",
+ "COMMERCIAL_PHOTOGRAPHY_ART_AND_GRAPHICS": "Commercial photography, art, and graphics",
+ "COMMERCIAL_SPORTS_PROFESSIONA": "Commercial Sports/Professiona",
+ "COMMODITIES_AND_FUTURES_EXCHANGE": "Commodities and futures exchange",
+ "COMPUTER_AND_DATA_PROCESSING_SERVICES": "Computer and data processing services",
+ "COMPUTER_HARDWARE_AND_SOFTWARE": "Computer Hardware & Software",
+ "COMPUTER_MAINTENANCE_REPAIR_AND_SERVICES_NOT_ELSEWHERE_CLAS": "Computer Maintenance, Repair And Services Not Elsewhere Clas",
+ "CONSTRUCTION": "Construction",
+ "CONSTRUCTION_MATERIALS_NOT_ELSEWHERE_CLASSIFIED": "Construction Materials Not Elsewhere Classified",
+ "CONSULTING_SERVICES": "Consulting services",
+ "CONSUMER_CREDIT_REPORTING_AGENCIES": "Consumer Credit Reporting Agencies",
+ "CONVALESCENT_HOMES": "Convalescent Homes",
+ "COSMETIC_STORES": "Cosmetic Stores",
+ "COUNSELING_SERVICES_DEBT_MARRIAGE_PERSONAL": "Counseling Services--Debt,Marriage,Personal",
+ "COUNTERFEIT_CURRENCY_AND_STAMPS": "Counterfeit Currency and Stamps",
+ "COUNTERFEIT_ITEMS": "Counterfeit Items",
+ "COUNTRY_CLUBS": "Country Clubs",
+ "COURIER_SERVICES": "Courier services",
+ "COURIER_SERVICES_AIR_AND_GROUND_AND_FREIGHT_FORWARDERS": "Courier Services-Air And Ground,And Freight Forwarders",
+ "COURT_COSTS_ALIMNY_CHILD_SUPT": "Court Costs/Alimny/Child Supt",
+ "COURT_COSTS_INCLUDING_ALIMONY_AND_CHILD_SUPPORT_COURTS_OF_LAW": "Court Costs, Including Alimony and Child Support - Courts of Law",
+ "CREDIT_CARDS": "Credit Cards",
+ "CREDIT_UNION": "Credit union",
+ "CULTURE_AND_RELIGION": "Culture & Religion",
+ "DAIRY_PRODUCTS_STORES": "Dairy Products Stores",
+ "DANCE_HALLS_STUDIOS_AND_SCHOOLS": "Dance Halls,Studios,And Schools",
+ "DECORATIVE": "Decorative",
+ "DENTAL": "Dental",
+ "DENTISTS_AND_ORTHODONTISTS": "Dentists And Orthodontists",
+ "DEPARTMENT_STORES": "Department Stores",
+ "DESKTOP_PCS": "Desktop PCs",
+ "DEVICES": "Devices",
+ "DIECAST_TOYS_VEHICLES": "Diecast, Toys Vehicles",
+ "DIGITAL_GAMES": "Digital games",
+ "DIGITAL_MEDIA_BOOKS_MOVIES_MUSIC": "Digital media,books,movies,music",
+ "DIRECT_MARKETING": "Direct Marketing",
+ "DIRECT_MARKETING_CATALOG_MERCHANT": "Direct Marketing - Catalog Merchant",
+ "DIRECT_MARKETING_INBOUND_TELE": "Direct Marketing - Inbound Tele",
+ "DIRECT_MARKETING_OUTBOUND_TELE": "Direct Marketing - Outbound Tele",
+ "DIRECT_MARKETING_SUBSCRIPTION": "Direct Marketing - Subscription",
+ "DISCOUNT_STORES": "Discount Stores",
+ "DOOR_TO_DOOR_SALES": "Door-To-Door Sales",
+ "DRAPERY_WINDOW_COVERING_AND_UPHOLSTERY": "Drapery, window covering, and upholstery",
+ "DRINKING_PLACES": "Drinking Places",
+ "DRUGSTORE": "Drugstore",
+ "DURABLE_GOODS": "Durable goods",
+ "ECOMMERCE_DEVELOPMENT": "eCommerce Development",
+ "ECOMMERCE_SERVICES": "eCommerce Services",
+ "EDUCATIONAL_AND_TEXTBOOKS": "Educational and textbooks",
+ "ELECTRIC_RAZOR_STORES": "Electric Razor Stores",
+ "ELECTRICAL_AND_SMALL_APPLIANCE_REPAIR": "Electrical and small appliance repair",
+ "ELECTRICAL_CONTRACTORS": "Electrical Contractors",
+ "ELECTRICAL_PARTS_AND_EQUIPMENT": "Electrical Parts and Equipment",
+ "ELECTRONIC_CASH": "Electronic Cash",
+ "ELEMENTARY_AND_SECONDARY_SCHOOLS": "Elementary and secondary schools",
+ "EMPLOYMENT": "Employment",
+ "ENTERTAINERS": "Entertainers",
+ "ENTERTAINMENT_AND_MEDIA": "Entertainment and media",
+ "EQUIP_TOOL_FURNITURE_AND_APPLIANCE_RENTAL_AND_LEASING": "Equip, Tool, Furniture, And Appliance Rental And Leasing",
+ "ESCROW": "Escrow",
+ "EVENT_AND_WEDDING_PLANNING": "Event & Wedding Planning",
+ "EXERCISE_AND_FITNESS": "Exercise and fitness",
+ "EXERCISE_EQUIPMENT": "Exercise Equipment",
+ "EXTERMINATING_AND_DISINFECTING_SERVICES": "Exterminating and disinfecting services",
+ "FABRICS_AND_SEWING": "Fabrics & Sewing",
+ "FAMILY_CLOTHING_STORES": "Family Clothing Stores",
+ "FASHION_JEWELRY": "Fashion jewelry",
+ "FAST_FOOD_RESTAURANTS": "Fast Food Restaurants",
+ "FICTION_AND_NONFICTION": "Fiction and nonfiction",
+ "FINANCE_COMPANY": "Finance company",
+ "FINANCIAL_AND_INVESTMENT_ADVICE": "Financial and investment advice",
+ "FINANCIAL_INSTITUTIONS_MERCHANDISE_AND_SERVICES": "Financial Institutions - Merchandise And Services",
+ "FIREARM_ACCESSORIES": "Firearm accessories",
+ "FIREARMS_WEAPONS_AND_KNIVES": "Firearms, Weapons and Knives",
+ "FIREPLACE_AND_FIREPLACE_SCREENS": "Fireplace, and fireplace screens",
+ "FIREWORKS": "Fireworks",
+ "FISHING": "Fishing",
+ "FLORISTS": "Florists",
+ "FLOWERS": "Flowers",
+ "FOOD_DRINK_AND_NUTRITION": "Food, Drink & Nutrition",
+ "FOOD_PRODUCTS": "Food Products",
+ "FOOD_RETAIL_AND_SERVICE": "Food retail and service",
+ "FRAGRANCES_AND_PERFUMES": "Fragrances and perfumes",
+ "FREEZER_AND_LOCKER_MEAT_PROVISIONERS": "Freezer and Locker Meat Provisioners",
+ "FUEL_DEALERS_FUEL_OIL_WOOD_AND_COAL": "Fuel Dealers-Fuel Oil, Wood & Coal",
+ "FUEL_DEALERS_NON_AUTOMOTIVE": "Fuel Dealers - Non Automotive",
+ "FUNERAL_SERVICES_AND_CREMATORIES": "Funeral Services & Crematories",
+ "FURNISHING_AND_DECORATING": "Furnishing & Decorating",
+ "FURNITURE": "Furniture",
+ "FURRIERS_AND_FUR_SHOPS": "Furriers and Fur Shops",
+ "GADGETS_AND_OTHER_ELECTRONICS": "Gadgets & other electronics",
+ "GAMBLING": "Gambling",
+ "GAME_SOFTWARE": "Game Software",
+ "GAMES": "Games",
+ "GARDEN_SUPPLIES": "Garden supplies",
+ "GENERAL": "General",
+ "GENERAL_CONTRACTORS": "General contractors",
+ "GENERAL_GOVERNMENT": "General - Government",
+ "GENERAL_SOFTWARE": "General - Software",
+ "GENERAL_TELECOM": "General - Telecom",
+ "GIFTS_AND_FLOWERS": "Gifts and flowers",
+ "GLASS_PAINT_AND_WALLPAPER_STORES": "Glass,Paint,And Wallpaper Stores",
+ "GLASSWARE_CRYSTAL_STORES": "Glassware, Crystal Stores",
+ "GOVERNMENT": "Government",
+ "GOVERNMENT_IDS_AND_LICENSES": "Government IDs and Licenses",
+ "GOVERNMENT_LICENSED_ON_LINE_CASINOS_ON_LINE_GAMBLING": "Government Licensed On-Line Casinos - On-Line Gambling",
+ "GOVERNMENT_OWNED_LOTTERIES": "Government-Owned Lotteries",
+ "GOVERNMENT_SERVICES": "Government services",
+ "GRAPHIC_AND_COMMERCIAL_DESIGN": "Graphic & Commercial Design",
+ "GREETING_CARDS": "Greeting Cards",
+ "GROCERY_STORES_AND_SUPERMARKETS": "Grocery Stores & Supermarkets",
+ "HARDWARE_AND_TOOLS": "Hardware & Tools",
+ "HARDWARE_EQUIPMENT_AND_SUPPLIES": "Hardware, Equipment, and Supplies",
+ "HAZARDOUS_RESTRICTED_AND_PERISHABLE_ITEMS": "Hazardous, Restricted and Perishable Items",
+ "HEALTH_AND_BEAUTY_SPAS": "Health and beauty spas",
+ "HEALTH_AND_NUTRITION": "Health & Nutrition",
+ "HEALTH_AND_PERSONAL_CARE": "Health and personal care",
+ "HEARING_AIDS_SALES_AND_SUPPLIES": "Hearing Aids Sales and Supplies",
+ "HEATING_PLUMBING_AC": "Heating, Plumbing, AC",
+ "HIGH_RISK_MERCHANT": "High Risk Merchant",
+ "HIRING_SERVICES": "Hiring services",
+ "HOBBIES_TOYS_AND_GAMES": "Hobbies, Toys & Games",
+ "HOME_AND_GARDEN": "Home and garden",
+ "HOME_AUDIO": "Home Audio",
+ "HOME_DECOR": "Home decor",
+ "HOME_ELECTRONICS": "Home Electronics",
+ "HOSPITALS": "Hospitals",
+ "HOTELS_MOTELS_INNS_RESORTS": "Hotels/Motels/Inns/Resorts",
+ "HOUSEWARES": "Housewares",
+ "HUMAN_PARTS_AND_REMAINS": "Human Parts and Remains",
+ "HUMOROUS_GIFTS_AND_NOVELTIES": "Humorous Gifts & Novelties",
+ "HUNTING": "Hunting",
+ "IDS_LICENSES_AND_PASSPORTS": "IDs, licenses, and passports",
+ "ILLEGAL_DRUGS_AND_PARAPHERNALIA": "Illegal Drugs & Paraphernalia",
+ "INDUSTRIAL": "Industrial",
+ "INDUSTRIAL_AND_MANUFACTURING_SUPPLIES": "Industrial and manufacturing supplies",
+ "INSURANCE_AUTO_AND_HOME": "Insurance - auto and home",
+ "INSURANCE_DIRECT": "Insurance - Direct",
+ "INSURANCE_LIFE_AND_ANNUITY": "Insurance - life and annuity",
+ "INSURANCE_SALES_UNDERWRITING": "Insurance Sales/Underwriting",
+ "INSURANCE_UNDERWRITING_PREMIUMS": "Insurance Underwriting, Premiums",
+ "INTERNET_AND_NETWORK_SERVICES": "Internet & Network Services",
+ "INTRA_COMPANY_PURCHASES": "Intra-Company Purchases",
+ "LABORATORIES_DENTAL_MEDICAL": "Laboratories-Dental/Medical",
+ "LANDSCAPING": "Landscaping",
+ "LANDSCAPING_AND_HORTICULTURAL_SERVICES": "Landscaping And Horticultural Services",
+ "LAUNDRY_CLEANING_SERVICES": "Laundry, Cleaning Services",
+ "LEGAL": "Legal",
+ "LEGAL_SERVICES_AND_ATTORNEYS": "Legal services and attorneys",
+ "LOCAL_DELIVERY_SERVICE": "Local delivery service",
+ "LOCKSMITH": "Locksmith",
+ "LODGING_AND_ACCOMMODATIONS": "Lodging and accommodations",
+ "LOTTERY_AND_CONTESTS": "Lottery and contests",
+ "LUGGAGE_AND_LEATHER_GOODS": "Luggage and leather goods",
+ "LUMBER_AND_BUILDING_MATERIALS": "Lumber & Building Materials",
+ "MAGAZINES": "Magazines",
+ "MAINTENANCE_AND_REPAIR_SERVICES": "Maintenance and repair services",
+ "MAKEUP_AND_COSMETICS": "Makeup and cosmetics",
+ "MANUAL_CASH_DISBURSEMENTS": "Manual Cash Disbursements",
+ "MASSAGE_PARLORS": "Massage Parlors",
+ "MEDICAL": "Medical",
+ "MEDICAL_AND_PHARMACEUTICAL": "Medical & Pharmaceutical",
+ "MEDICAL_CARE": "Medical care",
+ "MEDICAL_EQUIPMENT_AND_SUPPLIES": "Medical equipment and supplies",
+ "MEDICAL_SERVICES": "Medical Services",
+ "MEETING_PLANNERS": "Meeting Planners",
+ "MEMBERSHIP_CLUBS_AND_ORGANIZATIONS": "Membership clubs and organizations",
+ "MEMBERSHIP_COUNTRY_CLUBS_GOLF": "Membership/Country Clubs/Golf",
+ "MEMORABILIA": "Memorabilia",
+ "MEN_AND_BOY_CLOTHING_AND_ACCESSORY_STORES": "Men's And Boy's Clothing And Accessory Stores",
+ "MEN_CLOTHING": "Men's Clothing",
+ "MERCHANDISE": "Merchandise",
+ "METAPHYSICAL": "Metaphysical",
+ "MILITARIA": "Militaria",
+ "MILITARY_AND_CIVIL_SERVICE_UNIFORMS": "Military and civil service uniforms",
+ "MISC_AUTOMOTIVE_AIRCRAFT_AND_FARM_EQUIPMENT_DEALERS": "Misc,Automotive,Aircraft,And Farm Equipment Dealers",
+ "MISC_GENERAL_MERCHANDISE": "Misc General Merchandise",
+ "MISCELLANEOUS_GENERAL_SERVICES": "Miscellaneous General Services",
+ "MISCELLANEOUS_REPAIR_SHOPS_AND_RELATED_SERVICES": "Miscellaneous Repair Shops And Related Services",
+ "MODEL_KITS": "Model Kits",
+ "MONEY_TRANSFER_MEMBER_FINANCIAL_INSTITUTION": "Money Transfer - Member Financial Institution",
+ "MONEY_TRANSFER_MERCHANT": "Money Transfer--Merchant",
+ "MOTION_PICTURE_THEATERS": "Motion Picture Theaters",
+ "MOTOR_FREIGHT_CARRIERS_AND_TRUCKING": "Motor Freight Carriers & Trucking",
+ "MOTOR_HOME_AND_RECREATIONAL_VEHICLE_RENTAL": "Motor Home And Recreational Vehicle Rental",
+ "MOTOR_HOMES_DEALERS": "Motor Homes Dealers",
+ "MOTOR_VEHICLE_SUPPLIES_AND_NEW_PARTS": "Motor Vehicle Supplies and New Parts",
+ "MOTORCYCLE_DEALERS": "Motorcycle Dealers",
+ "MOTORCYCLES": "Motorcycles",
+ "MOVIE": "Movie",
+ "MOVIE_TICKETS": "Movie tickets",
+ "MOVING_AND_STORAGE": "Moving and storage",
+ "MULTI_LEVEL_MARKETING": "Multi-level marketing",
+ "MUSIC_CDS_CASSETTES_AND_ALBUMS": "Music - CDs, cassettes and albums",
+ "MUSIC_STORE_INSTRUMENTS_AND_SHEET_MUSIC": "Music store - instruments and sheet music",
+ "NETWORKING": "Networking",
+ "NEW_AGE": "New Age",
+ "NEW_PARTS_AND_SUPPLIES_MOTOR_VEHICLE": "New parts and supplies - motor vehicle",
+ "NEWS_DEALERS_AND_NEWSTANDS": "News Dealers and Newstands",
+ "NON_DURABLE_GOODS": "Non-durable goods",
+ "NON_FICTION": "Non-Fiction",
+ "NON_PROFIT_POLITICAL_AND_RELIGION": "Non-Profit, Political & Religion",
+ "NONPROFIT": "Nonprofit",
+ "NOVELTIES": "Novelties",
+ "OEM_SOFTWARE": "Oem Software",
+ "OFFICE_SUPPLIES_AND_EQUIPMENT": "Office Supplies and Equipment",
+ "ONLINE_DATING": "Online Dating",
+ "ONLINE_GAMING": "Online gaming",
+ "ONLINE_GAMING_CURRENCY": "Online gaming currency",
+ "ONLINE_SERVICES": "online services",
+ "OOUTBOUND_TELEMARKETING_MERCH": "Ooutbound Telemarketing Merch",
+ "OPHTHALMOLOGISTS_OPTOMETRIST": "Ophthalmologists/Optometrist",
+ "OPTICIANS_AND_DISPENSING": "Opticians And Dispensing",
+ "ORTHOPEDIC_GOODS_PROSTHETICS": "Orthopedic Goods/Prosthetics",
+ "OSTEOPATHS": "Osteopaths",
+ "OTHER": "Other",
+ "PACKAGE_TOUR_OPERATORS": "Package Tour Operators",
+ "PAINTBALL": "Paintball",
+ "PAINTS_VARNISHES_AND_SUPPLIES": "Paints, Varnishes, and Supplies",
+ "PARKING_LOTS_AND_GARAGES": "Parking Lots & Garages",
+ "PARTS_AND_ACCESSORIES": "Parts and accessories",
+ "PAWN_SHOPS": "Pawn Shops",
+ "PAYCHECK_LENDER_OR_CASH_ADVANCE": "Paycheck lender or cash advance",
+ "PERIPHERALS": "Peripherals",
+ "PERSONALIZED_GIFTS": "Personalized Gifts",
+ "PET_SHOPS_PET_FOOD_AND_SUPPLIES": "Pet shops, pet food, and supplies",
+ "PETROLEUM_AND_PETROLEUM_PRODUCTS": "Petroleum and Petroleum Products",
+ "PETS_AND_ANIMALS": "Pets and animals",
+ "PHOTOFINISHING_LABORATORIES_PHOTO_DEVELOPING": "Photofinishing Laboratories,Photo Developing",
+ "PHOTOGRAPHIC_STUDIOS_PORTRAITS": "Photographic studios - portraits",
+ "PHOTOGRAPHY": "Photography",
+ "PHYSICAL_GOOD": "Physical Good",
+ "PICTURE_VIDEO_PRODUCTION": "Picture/Video Production",
+ "PIECE_GOODS_NOTIONS_AND_OTHER_DRY_GOODS": "Piece Goods Notions and Other Dry Goods",
+ "PLANTS_AND_SEEDS": "Plants and Seeds",
+ "PLUMBING_AND_HEATING_EQUIPMENTS_AND_SUPPLIES": "Plumbing & Heating Equipments & Supplies",
+ "POLICE_RELATED_ITEMS": "Police-Related Items",
+ "POLITICAL_ORGANIZATIONS": "Politcal Organizations",
+ "POSTAL_SERVICES_GOVERNMENT_ONLY": "Postal Services - Government Only",
+ "POSTERS": "Posters",
+ "PREPAID_AND_STORED_VALUE_CARDS": "Prepaid and stored value cards",
+ "PRESCRIPTION_DRUGS": "Prescription Drugs",
+ "PROMOTIONAL_ITEMS": "Promotional Items",
+ "PUBLIC_WAREHOUSING_AND_STORAGE": "Public Warehousing and Storage",
+ "PUBLISHING_AND_PRINTING": "Publishing and printing",
+ "PUBLISHING_SERVICES": "Publishing Services",
+ "RADAR_DECTORS": "Radar Dectors",
+ "RADIO_TELEVISION_AND_STEREO_REPAIR": "Radio, television, and stereo repair",
+ "REAL_ESTATE": "Real Estate",
+ "REAL_ESTATE_AGENT": "Real estate agent",
+ "REAL_ESTATE_AGENTS_AND_MANAGERS_RENTALS": "Real Estate Agents And Managers - Rentals",
+ "RELIGION_AND_SPIRITUALITY_FOR_PROFIT": "Religion and spirituality for profit",
+ "RELIGIOUS": "Religious",
+ "RELIGIOUS_ORGANIZATIONS": "Religious Organizations",
+ "REMITTANCE": "Remittance",
+ "RENTAL_PROPERTY_MANAGEMENT": "Rental property management",
+ "RESIDENTIAL": "Residential",
+ "RETAIL": "Retail",
+ "RETAIL_FINE_JEWELRY_AND_WATCHES": "Retail - fine jewelry and watches",
+ "REUPHOLSTERY_AND_FURNITURE_REPAIR": "Reupholstery and furniture repair",
+ "RINGS": "Rings",
+ "ROOFING_SIDING_SHEET_METAL": "Roofing/Siding, Sheet Metal",
+ "RUGS_AND_CARPETS": "Rugs & Carpets",
+ "SCHOOLS_AND_COLLEGES": "Schools and Colleges",
+ "SCIENCE_FICTION": "Science Fiction",
+ "SCRAPBOOKING": "Scrapbooking",
+ "SCULPTURES": "Sculptures",
+ "SECURITIES_BROKERS_AND_DEALERS": "Securities - Brokers And Dealers",
+ "SECURITY_AND_SURVEILLANCE": "Security and surveillance",
+ "SECURITY_AND_SURVEILLANCE_EQUIPMENT": "Security and surveillance equipment",
+ "SECURITY_BROKERS_AND_DEALERS": "Security brokers and dealers",
+ "SEMINARS": "Seminars",
+ "SERVICE_STATIONS": "Service Stations",
+ "SERVICES": "Services",
+ "SEWING_NEEDLEWORK_FABRIC_AND_PIECE_GOODS_STORES": "Sewing,Needlework,Fabric And Piece Goods Stores",
+ "SHIPPING_AND_PACKING": "Shipping & Packaging",
+ "SHOE_REPAIR_HAT_CLEANING": "Shoe Repair/Hat Cleaning",
+ "SHOE_STORES": "Shoe Stores",
+ "SHOES": "Shoes",
+ "SNOWMOBILE_DEALERS": "Snowmobile Dealers",
+ "SOFTWARE": "Software",
+ "SPECIALTY_AND_MISC_FOOD_STORES": "Specialty and misc food stores",
+ "SPECIALTY_CLEANING_POLISHING_AND_SANITATION_PREPARATIONS": "Specialty Cleaning, Polishing And Sanitation Preparations",
+ "SPECIALTY_OR_RARE_PETS": "Specialty or rare pets",
+ "SPORT_GAMES_AND_TOYS": "Sport games and toys",
+ "SPORTING_AND_RECREATIONAL_CAMPS": "Sporting And Recreational Camps",
+ "SPORTING_GOODS": "Sporting Goods",
+ "SPORTS_AND_OUTDOORS": "Sports and outdoors",
+ "SPORTS_AND_RECREATION": "Sports & Recreation",
+ "STAMP_AND_COIN": "Stamp and coin",
+ "STATIONARY_PRINTING_AND_WRITING_PAPER": "Stationary, printing, and writing paper",
+ "STENOGRAPHIC_AND_SECRETARIAL_SUPPORT_SERVICES": "Stenographic and secretarial support services",
+ "STOCKS_BONDS_SECURITIES_AND_RELATED_CERTIFICATES": "Stocks, Bonds, Securities and Related Certificates",
+ "STORED_VALUE_CARDS": "Stored Value Cards",
+ "SUPPLIES": "Supplies",
+ "SUPPLIES_AND_TOYS": "Supplies & Toys",
+ "SURVEILLANCE_EQUIPMENT": "Surveillance Equipment",
+ "SWIMMING_POOLS_AND_SPAS": "Swimming Pools & Spas",
+ "SWIMMING_POOLS_SALES_SUPPLIES_SERVICES": "Swimming Pools-Sales,Supplies,Services",
+ "TAILORS_AND_ALTERATIONS": "Tailors and alterations",
+ "TAX_PAYMENTS": "Tax Payments",
+ "TAX_PAYMENTS_GOVERNMENT_AGENCIES": "Tax Payments - Government Agencies",
+ "TAXICABS_AND_LIMOUSINES": "Taxicabs and limousines",
+ "TELECOMMUNICATION_SERVICES": "Telecommunication Services",
+ "TELEPHONE_CARDS": "Telephone Cards",
+ "TELEPHONE_EQUIPMENT": "Telephone Equipment",
+ "TELEPHONE_SERVICES": "Telephone Services",
+ "THEATER": "Theater",
+ "TIRE_RETREADING_AND_REPAIR": "Tire Retreading and Repair",
+ "TOLL_OR_BRIDGE_FEES": "Toll or Bridge Fees",
+ "TOOLS_AND_EQUIPMENT": "Tools and equipment",
+ "TOURIST_ATTRACTIONS_AND_EXHIBITS": "Tourist Attractions And Exhibits",
+ "TOWING_SERVICE": "Towing service",
+ "TOYS_AND_GAMES": "Toys and games",
+ "TRADE_AND_VOCATIONAL_SCHOOLS": "Trade And Vocational Schools",
+ "TRADEMARK_INFRINGEMENT": "Trademark Infringement",
+ "TRAILER_PARKS_AND_CAMPGROUNDS": "Trailer Parks And Campgrounds",
+ "TRAINING_SERVICES": "Training services",
+ "TRANSPORTATION_SERVICES": "Transportation Services",
+ "TRAVEL": "Travel",
+ "TRUCK_AND_UTILITY_TRAILER_RENTALS": "Truck And Utility Trailer Rentals",
+ "TRUCK_STOP": "Truck Stop",
+ "TYPESETTING_PLATE_MAKING_AND_RELATED_SERVICES": "Typesetting, Plate Making, and Related Services",
+ "USED_MERCHANDISE_AND_SECONDHAND_STORES": "Used Merchandise And Secondhand Stores",
+ "USED_PARTS_MOTOR_VEHICLE": "Used parts - motor vehicle",
+ "UTILITIES": "Utilities",
+ "UTILITIES_ELECTRIC_GAS_WATER_SANITARY": "Utilities - Electric,Gas,Water,Sanitary",
+ "VARIETY_STORES": "Variety Stores",
+ "VEHICLE_SALES": "Vehicle sales",
+ "VEHICLE_SERVICE_AND_ACCESSORIES": "Vehicle service and accessories",
+ "VIDEO_EQUIPMENT": "Video Equipment",
+ "VIDEO_GAME_ARCADES_ESTABLISH": "Video Game Arcades/Establish",
+ "VIDEO_GAMES_AND_SYSTEMS": "Video Games & Systems",
+ "VIDEO_TAPE_RENTAL_STORES": "Video Tape Rental Stores",
+ "VINTAGE_AND_COLLECTIBLE_VEHICLES": "Vintage and Collectible Vehicles",
+ "VINTAGE_AND_COLLECTIBLES": "Vintage and collectibles",
+ "VITAMINS_AND_SUPPLEMENTS": "Vitamins & Supplements",
+ "VOCATIONAL_AND_TRADE_SCHOOLS": "Vocational and trade schools",
+ "WATCH_CLOCK_AND_JEWELRY_REPAIR": "Watch, clock, and jewelry repair",
+ "WEB_HOSTING_AND_DESIGN": "Web hosting and design",
+ "WELDING_REPAIR": "Welding Repair",
+ "WHOLESALE_CLUBS": "Wholesale Clubs",
+ "WHOLESALE_FLORIST_SUPPLIERS": "Wholesale Florist Suppliers",
+ "WHOLESALE_PRESCRIPTION_DRUGS": "Wholesale Prescription Drugs",
+ "WILDLIFE_PRODUCTS": "Wildlife Products",
+ "WIRE_TRANSFER": "Wire Transfer",
+ "WIRE_TRANSFER_AND_MONEY_ORDER": "Wire transfer and money order",
+ "WOMEN_ACCESSORY_SPECIALITY": "Women's Accessory/Speciality",
+ "WOMEN_CLOTHING": "Women's clothing"
+ }
+}
diff --git a/day11/types/schema.graphql b/day11/types/schema.graphql
new file mode 100755
index 0000000..be00b0b
--- /dev/null
+++ b/day11/types/schema.graphql
@@ -0,0 +1,213 @@
+directive @verifyUser on FIELD_DEFINITION
+
+scalar Date
+scalar Upload
+
+type User {
+ id: ID!
+ status: Int
+ first_name: String
+ last_name: String
+ phone: String
+ image: String
+ image_id: Int
+ refer: String
+ profile_id: Int
+ role_id: Int
+ font_color: String
+ time_zone: String
+ time_format: Int
+ clock_format: Int
+ date_format: Int
+ location: String
+ sync_code: String
+ expire_at: Date
+ created_at: Date
+ updated_at: Date
+ profile: Profile
+}
+
+type Image {
+ id: ID!
+ url: String
+ caption: String
+ width: Int
+ height: Int
+ type: Int
+ status: Int
+ user_id: Int
+ created_at: Date
+ updated_at: Date
+
+ user: User
+}
+
+type Note {
+ id: ID!
+ message: String
+ status: Int
+ created_at: Date
+ updated_at: Date
+ user: User
+}
+
+type Calendar {
+ id: ID!
+ user_id: Int
+ title: String
+ start_date: Date
+ end_date: Date
+ status: Int
+ created_at: Date
+ updated_at: Date
+ user: User
+}
+
+type Profile {
+ id: ID!
+ user_id: Int
+ timezone: String
+ dashboard_code: String
+ code: String
+ status: Int
+ created_at: Date
+ updated_at: Date
+ user: User
+}
+
+type Link {
+ id: ID!
+ email: String
+ user_id: Int
+ link: String
+ status: Int
+ created_at: Date
+ updated_at: Date
+
+ user: User
+}
+
+type MutationResponse {
+ success: Boolean!
+ message: String
+ errors: [ErrorResponse!]
+ code: String
+}
+
+type ErrorResponse {
+ path: String
+ message: String
+}
+
+type UserResponse {
+ success: Boolean!
+ data: User
+ message: String
+ errors: [ErrorResponse!]
+ code: String
+}
+input SyncCalendarInput {
+ event_id: String!
+ title: String!
+ start_date: Date!
+ end_date: Date!
+}
+type AllCalendarEventsResponse {
+ success: Boolean!
+ data: [Calendar!]
+ message: String
+ errors: [ErrorResponse!]
+ code: String
+}
+type CustomImageResponse {
+ success: Boolean!
+ data: Image
+ message: String
+ errors: [ErrorResponse!]
+ code: String
+}
+type AllNotesResponse {
+ success: Boolean!
+ data: [Note]
+ message: String
+ errors: [ErrorResponse!]
+ code: String
+}
+type File {
+ id: ID
+ filename: String
+ mimetype: String
+ path: String
+}
+type FileUploadResponse {
+ success: Boolean!
+ data: File
+ message: String
+ errors: [ErrorResponse!]
+ code: String
+}
+type LinkResponse {
+ success: Boolean!
+ data: Link
+ message: String
+ errors: [ErrorResponse!]
+ code: String
+}
+
+type StepsImagesResponse {
+ success: Boolean!
+ data: [String]
+ message: String
+ errors: [ErrorResponse!]
+ code: String
+}
+
+type Query {
+ user: UserResponse! @verifyUser
+
+ getAllNotes: AllNotesResponse! @verifyUser
+
+ getAllCalendarEvents: AllCalendarEventsResponse! @verifyUser
+
+ getCustomImage: CustomImageResponse! @verifyUser
+
+ link: LinkResponse! @verifyUser
+
+ getStepsImages: StepsImagesResponse!
+}
+
+type Mutation {
+ updateUser(
+ sync_code: String
+ font_color: String
+ time_zone: String
+ time_format: Int
+ clock_format: Int
+ date_format: Int
+ location: String
+ lat: Float
+ lng: Float
+ ): MutationResponse! @verifyUser
+
+ createNote(message: String!): MutationResponse! @verifyUser
+ updateNote(id: ID!, message: String!): MutationResponse! @verifyUser
+ deleteNote(id: ID!): MutationResponse! @verifyUser
+
+ createLink(link: String!): MutationResponse! @verifyUser
+ deactivateAllLinks: MutationResponse! @verifyUser
+
+ syncCalendar(input: [SyncCalendarInput!]): MutationResponse! @verifyUser
+ updateCalendarEvent(
+ id: Int!
+ title: String!
+ start_date: Date!
+ end_date: Date!
+ ): MutationResponse! @verifyUser
+ deleteCalendarEvent(id: Int!): MutationResponse! @verifyUser
+
+ createCustomImage(url: String!): MutationResponse! @verifyUser
+ updateCustomImage(id: ID!, url: String!): MutationResponse! @verifyUser
+ deleteCustomImage(id: ID!): MutationResponse! @verifyUser
+
+ uploadFile(file: Upload!): FileUploadResponse!
+}
diff --git a/day11/utils/formatError.js b/day11/utils/formatError.js
new file mode 100755
index 0000000..bccd30b
--- /dev/null
+++ b/day11/utils/formatError.js
@@ -0,0 +1,46 @@
+require('dotenv').config();
+
+const util = require('util');
+const graphql = require('apollo-server-express');
+const sequelize = require('sequelize');
+
+util.inspect.defaultOptions.depth = null;
+
+exports.formatError = (error, options = { showHiddenNodes: false }) => {
+ util.inspect.defaultOptions.showHidden = options.showHiddenNodes;
+ console.log('FORMAT ERROR', { error });
+ if (error instanceof sequelize.ValidationError) {
+ return {
+ success: false,
+ errors: error.errors.map((x) => ({ path: x.path, message: x.message })),
+ code: 'ERROR_DATABASE_VALIDATION',
+ };
+ }
+
+ if (error instanceof graphql.ValidationError) {
+ return {
+ success: false,
+ code: 'ERROR_GRAPHQL_VALIDATION',
+ errors: [{ path: null, message: error.message }],
+ };
+ }
+
+ return { success: false, message: 'Something went wrong' };
+};
+
+exports.formatGraphqlError = (error) => {
+ let { extensions, ...rest } = error;
+
+ if ('stacktrace' in extensions?.exception && process.env.MODE !== 'development') {
+ const { stacktrace, ...restException } = extensions.exception;
+ rest = {
+ ...rest,
+ extensions: {
+ ...extensions,
+ exception: restException,
+ },
+ };
+ return rest;
+ }
+ return error;
+};
diff --git a/day11/utils/generateCode.js b/day11/utils/generateCode.js
new file mode 100755
index 0000000..95c524e
--- /dev/null
+++ b/day11/utils/generateCode.js
@@ -0,0 +1,16 @@
+/**
+ * Generate random code with given length
+ * @param {Number} length code length
+ * @returns {String} Generated code
+ * @example
+ * generateCode(6)
+ */
+module.exports = function generateCode(length) {
+ var result = '';
+ var characters = '0123456789';
+ var charactersLength = characters.length;
+ for (var i = 0; i < length; i++) {
+ result += characters.charAt(Math.floor(Math.random() * charactersLength));
+ }
+ return result;
+};
diff --git a/day11/utils/index.js b/day11/utils/index.js
new file mode 100755
index 0000000..8c862ea
--- /dev/null
+++ b/day11/utils/index.js
@@ -0,0 +1,10 @@
+function capitalizeFirstLetter(target) {
+ if (typeof target !== 'string') {
+ throw new Error('Parameter should be a string.');
+ }
+ return target?.substring(0, 1).toUpperCase() + target?.substring(1)?.toLocaleLowerCase();
+}
+
+module.exports = {
+ capitalizeFirstLetter,
+};