From a07577bffacb4557b2f82e168cc26e855e6104fa Mon Sep 17 00:00:00 2001 From: ryanwong Date: Sun, 6 Feb 2022 22:15:10 -0500 Subject: [PATCH] day 11 --- .DS_Store | Bin 14340 -> 14340 bytes day11/.DS_Store | Bin 0 -> 8196 bytes day11/README.md | 55 ++ day11/app.js | 158 ++++ day11/core/helpers.js | 67 ++ day11/core/models.js | 259 ++++++ day11/core/strings.js | 45 ++ day11/core/translation.json | 384 +++++++++ day11/core/utils.js | 11 + day11/directives/VerifyUser.js | 48 ++ day11/directives/index.js | 5 + day11/models/activity_log.js | 143 ++++ day11/models/admin_operation.js | 156 ++++ day11/models/calendar.js | 164 ++++ day11/models/code.js | 152 ++++ day11/models/credential.js | 198 +++++ day11/models/email.js | 149 ++++ day11/models/image.js | 173 ++++ day11/models/index.js | 69 ++ day11/models/link.js | 152 ++++ day11/models/member_operation.js | 153 ++++ day11/models/note.js | 152 ++++ day11/models/profile.js | 158 ++++ day11/models/refer_log.js | 163 ++++ day11/models/role.js | 137 ++++ day11/models/setting.js | 165 ++++ day11/models/sms.js | 135 ++++ day11/models/token.js | 174 ++++ day11/models/user.js | 482 ++++++++++++ day11/package.json | 41 + day11/resolvers/.gitkeep | 0 day11/resolvers/all/.gitkeep | 0 day11/resolvers/all/allActivityLog.js | 56 ++ day11/resolvers/all/allCalendar.js | 56 ++ day11/resolvers/all/allCode.js | 56 ++ day11/resolvers/all/allCredential.js | 56 ++ day11/resolvers/all/allImage.js | 56 ++ day11/resolvers/all/allLink.js | 56 ++ day11/resolvers/all/allLinks.js | 56 ++ day11/resolvers/all/allNote.js | 56 ++ day11/resolvers/all/allNotes.js | 57 ++ day11/resolvers/all/allProfile.js | 56 ++ day11/resolvers/all/allReferLog.js | 56 ++ day11/resolvers/all/allUser.js | 56 ++ day11/resolvers/create/.gitkeep | 0 day11/resolvers/create/createActivityLog.js | 36 + day11/resolvers/create/createCalendar.js | 36 + day11/resolvers/create/createCode.js | 36 + day11/resolvers/create/createCredential.js | 40 + day11/resolvers/create/createImage.js | 38 + day11/resolvers/create/createLink.js | 73 ++ day11/resolvers/create/createNote.js | 36 + day11/resolvers/create/createProfile.js | 40 + day11/resolvers/create/createReferLog.js | 40 + day11/resolvers/create/createUser.js | 39 + day11/resolvers/custom/.gitkeep | 0 day11/resolvers/delete/.gitkeep | 0 day11/resolvers/delete/deactivateAllLinks.js | 32 + day11/resolvers/delete/deleteActivityLog.js | 23 + day11/resolvers/delete/deleteCalendar.js | 23 + day11/resolvers/delete/deleteCode.js | 23 + day11/resolvers/delete/deleteCredential.js | 23 + day11/resolvers/delete/deleteImage.js | 23 + day11/resolvers/delete/deleteLink.js | 23 + day11/resolvers/delete/deleteNote.js | 23 + day11/resolvers/delete/deleteProfile.js | 23 + day11/resolvers/delete/deleteReferLog.js | 23 + day11/resolvers/delete/deleteUser.js | 23 + day11/resolvers/index.js | 54 ++ day11/resolvers/relation/.gitkeep | 0 day11/resolvers/relation/relationEvent.js | 8 + day11/resolvers/relation/relationImage.js | 8 + day11/resolvers/relation/relationProfile.js | 8 + .../relation/relationReferrerUser.js | 8 + day11/resolvers/relation/relationRole.js | 8 + day11/resolvers/single/.gitkeep | 0 day11/resolvers/single/singleActivityLog.js | 25 + day11/resolvers/single/singleCalendar.js | 25 + day11/resolvers/single/singleCode.js | 25 + day11/resolvers/single/singleCredential.js | 25 + day11/resolvers/single/singleImage.js | 25 + day11/resolvers/single/singleLink.js | 27 + day11/resolvers/single/singleNote.js | 25 + day11/resolvers/single/singleProfile.js | 25 + day11/resolvers/single/singleReferLog.js | 25 + day11/resolvers/single/singleUser.js | 17 + day11/resolvers/type/typeActivityLog.js | 6 + day11/resolvers/type/typeCalendar.js | 15 + day11/resolvers/type/typeCode.js | 6 + day11/resolvers/type/typeCredential.js | 6 + day11/resolvers/type/typeImage.js | 6 + day11/resolvers/type/typeLink.js | 6 + day11/resolvers/type/typeNote.js | 6 + day11/resolvers/type/typeProfile.js | 6 + day11/resolvers/type/typeReferLog.js | 6 + day11/resolvers/type/typeUser.js | 12 + day11/resolvers/update/.gitkeep | 0 day11/resolvers/update/updateActivityLog.js | 32 + day11/resolvers/update/updateCalendar.js | 32 + day11/resolvers/update/updateCode.js | 32 + day11/resolvers/update/updateCredential.js | 35 + day11/resolvers/update/updateImage.js | 34 + day11/resolvers/update/updateLink.js | 36 + day11/resolvers/update/updateNote.js | 32 + day11/resolvers/update/updateProfile.js | 36 + day11/resolvers/update/updateReferLog.js | 35 + day11/resolvers/update/updateUser.js | 127 +++ day11/server.js | 18 + day11/services/.gitkeep | 0 day11/services/AclService.js | 0 day11/services/AuthService.js | 377 +++++++++ day11/services/BarcodeService.js | 24 + day11/services/CsvService.js | 84 ++ day11/services/EncryptDecryptService.js | 15 + day11/services/ErrorService.js | 36 + day11/services/FirebaseService.js | 166 ++++ day11/services/HtmlToPdf.js | 30 + day11/services/JwtService.js | 101 +++ day11/services/LoggingService.js | 37 + day11/services/MailService.js | 99 +++ day11/services/MaintenanceService.js | 0 day11/services/OAuthService.js | 210 +++++ day11/services/PaginationService.js | 64 ++ day11/services/PasswordService.js | 22 + day11/services/PaymentService.js | 680 ++++++++++++++++ day11/services/PaypalApi.js | 300 +++++++ day11/services/PaypalService.js | 705 +++++++++++++++++ day11/services/PermissionService.js | 17 + day11/services/PowerByService.js | 4 + day11/services/ProfileService.js | 52 ++ day11/services/PushNotification.js | 90 +++ day11/services/QRCodeService.js | 16 + day11/services/S3Service.js | 156 ++++ day11/services/SessionService.js | 62 ++ day11/services/SmsService.js | 65 ++ day11/services/StripeApi.js | 561 +++++++++++++ day11/services/StripeService.js | 744 ++++++++++++++++++ day11/services/TimezoneService.js | 32 + day11/services/UploadService.js | 65 ++ day11/services/ValidationService.js | 101 +++ .../paypal/paypalProductsCategories.json | 449 +++++++++++ day11/types/schema.graphql | 213 +++++ day11/utils/formatError.js | 46 ++ day11/utils/generateCode.js | 16 + day11/utils/index.js | 10 + 145 files changed, 12008 insertions(+) create mode 100644 day11/.DS_Store mode change 100644 => 100755 day11/README.md create mode 100755 day11/app.js create mode 100755 day11/core/helpers.js create mode 100755 day11/core/models.js create mode 100755 day11/core/strings.js create mode 100644 day11/core/translation.json create mode 100755 day11/core/utils.js create mode 100755 day11/directives/VerifyUser.js create mode 100755 day11/directives/index.js create mode 100755 day11/models/activity_log.js create mode 100755 day11/models/admin_operation.js create mode 100755 day11/models/calendar.js create mode 100755 day11/models/code.js create mode 100755 day11/models/credential.js create mode 100755 day11/models/email.js create mode 100755 day11/models/image.js create mode 100755 day11/models/index.js create mode 100755 day11/models/link.js create mode 100755 day11/models/member_operation.js create mode 100755 day11/models/note.js create mode 100755 day11/models/profile.js create mode 100755 day11/models/refer_log.js create mode 100755 day11/models/role.js create mode 100755 day11/models/setting.js create mode 100755 day11/models/sms.js create mode 100755 day11/models/token.js create mode 100755 day11/models/user.js create mode 100755 day11/package.json create mode 100755 day11/resolvers/.gitkeep create mode 100755 day11/resolvers/all/.gitkeep create mode 100755 day11/resolvers/all/allActivityLog.js create mode 100755 day11/resolvers/all/allCalendar.js create mode 100755 day11/resolvers/all/allCode.js create mode 100755 day11/resolvers/all/allCredential.js create mode 100755 day11/resolvers/all/allImage.js create mode 100755 day11/resolvers/all/allLink.js create mode 100755 day11/resolvers/all/allLinks.js create mode 100755 day11/resolvers/all/allNote.js create mode 100755 day11/resolvers/all/allNotes.js create mode 100755 day11/resolvers/all/allProfile.js create mode 100755 day11/resolvers/all/allReferLog.js create mode 100755 day11/resolvers/all/allUser.js create mode 100755 day11/resolvers/create/.gitkeep create mode 100755 day11/resolvers/create/createActivityLog.js create mode 100755 day11/resolvers/create/createCalendar.js create mode 100755 day11/resolvers/create/createCode.js create mode 100755 day11/resolvers/create/createCredential.js create mode 100755 day11/resolvers/create/createImage.js create mode 100755 day11/resolvers/create/createLink.js create mode 100755 day11/resolvers/create/createNote.js create mode 100755 day11/resolvers/create/createProfile.js create mode 100755 day11/resolvers/create/createReferLog.js create mode 100755 day11/resolvers/create/createUser.js create mode 100755 day11/resolvers/custom/.gitkeep create mode 100755 day11/resolvers/delete/.gitkeep create mode 100755 day11/resolvers/delete/deactivateAllLinks.js create mode 100755 day11/resolvers/delete/deleteActivityLog.js create mode 100755 day11/resolvers/delete/deleteCalendar.js create mode 100755 day11/resolvers/delete/deleteCode.js create mode 100755 day11/resolvers/delete/deleteCredential.js create mode 100755 day11/resolvers/delete/deleteImage.js create mode 100755 day11/resolvers/delete/deleteLink.js create mode 100755 day11/resolvers/delete/deleteNote.js create mode 100755 day11/resolvers/delete/deleteProfile.js create mode 100755 day11/resolvers/delete/deleteReferLog.js create mode 100755 day11/resolvers/delete/deleteUser.js create mode 100755 day11/resolvers/index.js create mode 100755 day11/resolvers/relation/.gitkeep create mode 100755 day11/resolvers/relation/relationEvent.js create mode 100755 day11/resolvers/relation/relationImage.js create mode 100755 day11/resolvers/relation/relationProfile.js create mode 100755 day11/resolvers/relation/relationReferrerUser.js create mode 100755 day11/resolvers/relation/relationRole.js create mode 100755 day11/resolvers/single/.gitkeep create mode 100755 day11/resolvers/single/singleActivityLog.js create mode 100755 day11/resolvers/single/singleCalendar.js create mode 100755 day11/resolvers/single/singleCode.js create mode 100755 day11/resolvers/single/singleCredential.js create mode 100755 day11/resolvers/single/singleImage.js create mode 100755 day11/resolvers/single/singleLink.js create mode 100755 day11/resolvers/single/singleNote.js create mode 100755 day11/resolvers/single/singleProfile.js create mode 100755 day11/resolvers/single/singleReferLog.js create mode 100755 day11/resolvers/single/singleUser.js create mode 100755 day11/resolvers/type/typeActivityLog.js create mode 100755 day11/resolvers/type/typeCalendar.js create mode 100755 day11/resolvers/type/typeCode.js create mode 100755 day11/resolvers/type/typeCredential.js create mode 100755 day11/resolvers/type/typeImage.js create mode 100755 day11/resolvers/type/typeLink.js create mode 100755 day11/resolvers/type/typeNote.js create mode 100755 day11/resolvers/type/typeProfile.js create mode 100755 day11/resolvers/type/typeReferLog.js create mode 100755 day11/resolvers/type/typeUser.js create mode 100755 day11/resolvers/update/.gitkeep create mode 100755 day11/resolvers/update/updateActivityLog.js create mode 100755 day11/resolvers/update/updateCalendar.js create mode 100755 day11/resolvers/update/updateCode.js create mode 100755 day11/resolvers/update/updateCredential.js create mode 100755 day11/resolvers/update/updateImage.js create mode 100755 day11/resolvers/update/updateLink.js create mode 100755 day11/resolvers/update/updateNote.js create mode 100755 day11/resolvers/update/updateProfile.js create mode 100755 day11/resolvers/update/updateReferLog.js create mode 100755 day11/resolvers/update/updateUser.js create mode 100755 day11/server.js create mode 100755 day11/services/.gitkeep create mode 100755 day11/services/AclService.js create mode 100755 day11/services/AuthService.js create mode 100755 day11/services/BarcodeService.js create mode 100755 day11/services/CsvService.js create mode 100755 day11/services/EncryptDecryptService.js create mode 100755 day11/services/ErrorService.js create mode 100755 day11/services/FirebaseService.js create mode 100755 day11/services/HtmlToPdf.js create mode 100755 day11/services/JwtService.js create mode 100755 day11/services/LoggingService.js create mode 100755 day11/services/MailService.js create mode 100755 day11/services/MaintenanceService.js create mode 100755 day11/services/OAuthService.js create mode 100755 day11/services/PaginationService.js create mode 100755 day11/services/PasswordService.js create mode 100755 day11/services/PaymentService.js create mode 100755 day11/services/PaypalApi.js create mode 100755 day11/services/PaypalService.js create mode 100755 day11/services/PermissionService.js create mode 100755 day11/services/PowerByService.js create mode 100755 day11/services/ProfileService.js create mode 100755 day11/services/PushNotification.js create mode 100755 day11/services/QRCodeService.js create mode 100755 day11/services/S3Service.js create mode 100755 day11/services/SessionService.js create mode 100755 day11/services/SmsService.js create mode 100755 day11/services/StripeApi.js create mode 100755 day11/services/StripeService.js create mode 100755 day11/services/TimezoneService.js create mode 100755 day11/services/UploadService.js create mode 100755 day11/services/ValidationService.js create mode 100755 day11/services/paypal/paypalProductsCategories.json create mode 100755 day11/types/schema.graphql create mode 100755 day11/utils/formatError.js create mode 100755 day11/utils/generateCode.js create mode 100755 day11/utils/index.js diff --git a/.DS_Store b/.DS_Store index 08953d29a6d0e66845ea4b3efa72505333aac45c..6c2932a2f5d2064a1e40e0e793c0967ba038e9ce 100644 GIT binary patch delta 513 zcmZoEXepTBFPgx>z`)GFAi%&-$zaG}SQcEAmyhBT1IBZy5E~qL~U7}Z@WdXq8VtM zn~yLMn>@0C3`_x&pGX!hWjtaH*bOiTik-$+klW=wRO+$bZ#n)1C{Ab#>Z8EGhM zpA1OJ<~uSInHa@4^C-+^pFBa~=Hz+mnwtwmmoag(LL&hfr<>&^a+xOYQIFr;C3~N7 LGrPeeklJVf?hc5D delta 622 zcmZoEXepTBFB-$Zz`)GFAi%&-$zaG}oRe-CoSZ-LqVi+|6PAhHLX&3-2yI*+!_Vlv zd6hsF^JH#0erKcHd>5Cboctu9R*s}tKKfU?lLs2aC?`3&Pg)IRjYtmzADuCBw`5GU<&&@MIquX(%g4Mh0Zl4(WXm-@rS^Q%~=Tvq5xe*o&P|KNEf9< zLKOK2Nu3{oM8%uk_1&!R&Po*pF)QuPYUl0FySH~<&6+nKow90Q~^~$6;K6kivl>axwLA|edpCy6;K8KO9lA% zAx34Jc$+zEe>%|E6aY5Dur27XaR)Z`Ht{xd79JGiroy_Z#-|v@O~-ibaf!E?vu-*W zpL`e}+4u~_*yvb4ws10uv)ZZxsz6x*&fO#GQ%d_Z4DjT4#2wI(#&i(GHurze`(^wGOB=KJ!5Wja;82GNco=(C z(WEIIQV%Rf)DL*PNS|6B!Y_I3-b)G|0jA(lhY5JR++aQK@*dCuG#S$odi(RLJbGS@ zlAMk&b50fONre_81AfsD=GO$H6iU5kmVr+6>)pIxa=L167dnYGB%3-+z{7NzM++LU zI!C-;B`%FW=T#WV=i8^8PiZa5X%Q~q71{k;hB6=VE|t_-Jp&`*m0TWv$+-lG;ti@s z1o7RWxor1lRQj6e&UYK1cg}ic{H?S0Y;VMTsX{d(jPIYHcVtK(=H8CR-pIz!y)ypE z>9ZofKorkkjR@jL-PsMLp8t?qiz;vp1(qCUo%4U3EOqz)*H}p*s(>o+uPR`o&Q7Nd z_CB{RJx2p0F=`i;3*$0p?G!W&;c=Ue!{&b&q91!-;%(+EJSch*U}exo6}V9a{sIH- BG1>qC literal 0 HcmV?d00001 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, +};