├── CONTRIBUTING.md ├── src ├── framework │ ├── security │ │ ├── isAccessTokenValid.ts │ │ ├── validateAccessToken.ts │ │ ├── createJwtToken.ts │ │ ├── getUserWithAccessToken.ts │ │ ├── isAuthQuery.ts │ │ ├── getUserRoles.ts │ │ └── getUserAllPermissions.ts │ ├── initialization │ │ ├── initializeGraphql.ts │ │ ├── initializeRestApi.ts │ │ ├── checkModules.ts │ │ ├── checkInstalledPackages.ts │ │ └── startServers.ts │ ├── database │ │ ├── helpers │ │ │ ├── convertedFiltersIntoMongooseQuery.ts │ │ │ ├── convertFiltersIntoSequalizeObject.ts │ │ │ ├── index.ts │ │ │ ├── paginate.ts │ │ │ └── stats.ts │ │ ├── models.ts │ │ ├── connect.ts │ │ └── loadTables.ts │ ├── mailer │ │ ├── send.ts │ │ ├── email-templates │ │ │ ├── twoFactorLogin.ts │ │ │ ├── accountActivated.ts │ │ │ ├── changePassword.ts │ │ │ ├── welcome.ts │ │ │ └── requestPasswordReset.ts │ │ ├── emailTemplates.ts │ │ └── index.ts │ ├── types │ │ ├── rbac.ts │ │ ├── servers.ts │ │ ├── models.ts │ │ ├── override.ts │ │ └── configuration.ts │ ├── helpers │ │ ├── internalServerError.ts │ │ ├── getRequestedFieldsFromResolverInfo.ts │ │ ├── auth.ts │ │ ├── convertConfigurationIntoEnvVariables.ts │ │ ├── validateConfigurationObject.ts │ │ ├── replaceFilterOperators.ts │ │ ├── index.ts │ │ └── statusCodes.ts │ ├── storage │ │ └── generateMulterInstancesForStorages.ts │ ├── events │ │ └── runEvent.ts │ ├── restApi │ │ ├── restApiSuccessResponse.ts │ │ ├── restApiErrorResponse.ts │ │ ├── versions │ │ │ └── v1 │ │ │ │ └── loadAllModules.ts │ │ ├── customApi.ts │ │ └── index.ts │ ├── defaults │ │ ├── defaultConfigurations │ │ │ ├── postgresConfiguration.ts │ │ │ └── defaultConfiguration.ts │ │ ├── loadDefaults.ts │ │ └── options │ │ │ └── index.ts │ ├── logger │ │ ├── consoleMessages.ts │ │ └── index.ts │ ├── graphql │ │ ├── schemaMap.ts │ │ ├── voyager │ │ │ └── index.ts │ │ ├── generalSchema.ts │ │ ├── index.ts │ │ └── loadAllModules.ts │ ├── apiDocs │ │ ├── docs │ │ │ └── index.ts │ │ └── index.ts │ ├── builtinModules │ │ ├── me │ │ │ └── index.ts │ │ ├── mail │ │ │ └── index.ts │ │ ├── role │ │ │ └── index.ts │ │ ├── userRole │ │ │ └── index.ts │ │ ├── rolePermission │ │ │ └── index.ts │ │ ├── userPermission │ │ │ └── index.ts │ │ ├── permission │ │ │ └── index.ts │ │ ├── forgetPassword │ │ │ ├── handlers │ │ │ │ └── index.ts │ │ │ └── index.ts │ │ ├── user │ │ │ └── index.ts │ │ ├── storage │ │ │ └── index.ts │ │ ├── backup │ │ │ ├── index.ts │ │ │ └── helpers.ts │ │ └── auth │ │ │ └── handlers │ │ │ └── index.ts │ ├── cron │ │ └── index.ts │ ├── seeds │ │ └── index.ts │ ├── socket │ │ └── index.ts │ ├── moduleRelationships │ │ ├── database.ts │ │ └── graphql.ts │ └── reporting │ │ └── index.ts ├── devServer.ts └── main.ts ├── index.js ├── nodemon.json ├── apiDoc.json ├── examples ├── socket-test │ ├── README.md │ └── index.html └── demo │ └── index.js ├── docs ├── introduction │ ├── upcoming-features.md │ └── motivation.md ├── v2 │ ├── storage.md │ ├── database.md │ ├── graphql.md │ ├── sockets.md │ ├── mailer.md │ ├── restApi.md │ ├── cron-jobs.md │ ├── custom-modules.md │ └── configuration.md ├── getting-started │ ├── built-in-modules.md │ ├── installation.md │ └── configuration.md ├── seeds │ └── introduction.md ├── graphql │ └── introduction.md ├── rbac │ └── introduction.md ├── restApi │ └── introduction.md ├── storage │ └── introduction.md ├── database │ └── introduction.md ├── sockets │ └── introduction.md ├── email │ └── introduction.md ├── context │ └── introduction.md ├── events │ └── introduction.md └── custom-modules │ └── introduction.md ├── .npmignore ├── .gitignore ├── .github └── ISSUE_TEMPLATE │ ├── something_missing.md │ ├── request_help.md │ ├── feature_request.md │ └── bug_report.md ├── tsconfig.json ├── .eslintrc.json ├── LICENSE ├── package.json ├── README.md └── CODE_OF_CONDUCT.md /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ### Contributing 2 | -------------------------------------------------------------------------------- /src/framework/security/isAccessTokenValid.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/framework/initialization/initializeGraphql.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/framework/initialization/initializeRestApi.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | let wertik = require("./lib/main.js"); 2 | module.exports = wertik; 3 | -------------------------------------------------------------------------------- /nodemon.json: -------------------------------------------------------------------------------- 1 | { 2 | "ignore": ["lib/framework/apiDocs/docs.js","doc/*","lib/framework/apiDocs/docs/*"] 3 | } -------------------------------------------------------------------------------- /apiDoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Wertik-js", 3 | "version": "1.4.7", 4 | "description": "Wertik Api Documentation" 5 | } -------------------------------------------------------------------------------- /src/framework/database/helpers/convertedFiltersIntoMongooseQuery.ts: -------------------------------------------------------------------------------- 1 | export default function (filters) { 2 | return filters; 3 | } 4 | -------------------------------------------------------------------------------- /examples/socket-test/README.md: -------------------------------------------------------------------------------- 1 | ## Socket Demo 2 | 3 | Run below command to test socket.io on Wertik 4 | 5 | ```terminal 6 | npx serve 7 | ``` -------------------------------------------------------------------------------- /src/framework/mailer/send.ts: -------------------------------------------------------------------------------- 1 | let handlebars = require("handlebars"); 2 | let nodemailer = require("nodemailer"); 3 | import mailerInstance from "./index"; 4 | -------------------------------------------------------------------------------- /docs/introduction/upcoming-features.md: -------------------------------------------------------------------------------- 1 | ### Upcoming Features 2 | 3 | These are the upcoming features for Wertik-js: 4 | 5 | - Logs with database 6 | - Cron Jobs -------------------------------------------------------------------------------- /src/framework/types/rbac.ts: -------------------------------------------------------------------------------- 1 | import { IPermission, IRole } from "./models"; 2 | 3 | export interface IConfigurationRbacAccessControl {} 4 | 5 | export interface IConfigurationRbac {} 6 | -------------------------------------------------------------------------------- /src/framework/helpers/internalServerError.ts: -------------------------------------------------------------------------------- 1 | let { ApolloError } = require("apollo-server"); 2 | export default function(e: any, code = 500) { 3 | return new ApolloError(e.message, code, {}); 4 | } 5 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | package-lock.json 3 | logs 4 | dist 5 | src 6 | yarn.lock 7 | .idea 8 | 9 | crit.log 10 | debug.log 11 | emerg.log 12 | error.log 13 | notice.log 14 | info.log 15 | warning.log 16 | .env 17 | uploads 18 | storage -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | package-lock.json 3 | logs 4 | dist 5 | lib 6 | yarn.lock 7 | .idea 8 | 9 | crit.log 10 | debug.log 11 | emerg.log 12 | error.log 13 | notice.log 14 | info.log 15 | warning.log 16 | .env 17 | storage 18 | /backups 19 | .DS_STORE -------------------------------------------------------------------------------- /src/framework/storage/generateMulterInstancesForStorages.ts: -------------------------------------------------------------------------------- 1 | import { IConfiguration } from "src/framework/types/configuration"; 2 | 3 | export default async (configuration: IConfiguration) => { 4 | let storages = configuration.storage.storageDirectory; 5 | }; 6 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/something_missing.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Something Missing 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: 'missing' 6 | assignees: 'ilyaskarim' 7 | --- 8 | 9 | 10 | **Please explain whats missing** 11 | 12 | Write here 13 | -------------------------------------------------------------------------------- /src/framework/database/helpers/convertFiltersIntoSequalizeObject.ts: -------------------------------------------------------------------------------- 1 | import replaceFilterOperators from "../../helpers/replaceFilterOperators" 2 | export default async function (filters: any) { 3 | let output = replaceFilterOperators(filters); 4 | return output; 5 | } 6 | -------------------------------------------------------------------------------- /src/framework/mailer/email-templates/twoFactorLogin.ts: -------------------------------------------------------------------------------- 1 | export default ` 2 |

3 | Hi {{userName}}, 4 |

5 |

6 | Use {{twoFactorCode}} to login as {{userName}}. 7 |

8 | 9 |

10 | Thanks
11 | {{ siteName }} Team. 12 |

13 | `; 14 | -------------------------------------------------------------------------------- /src/framework/mailer/email-templates/accountActivated.ts: -------------------------------------------------------------------------------- 1 | export default ` 2 |

3 | Hi {{userName}}, 4 |

5 |

6 | Your account is successfully activated on {{siteName}} 7 |

8 | 9 |

10 | Thanks
11 | {{ siteName }} Team. 12 |

13 | `; 14 | -------------------------------------------------------------------------------- /src/framework/helpers/getRequestedFieldsFromResolverInfo.ts: -------------------------------------------------------------------------------- 1 | import graphqlFields from "graphql-fields"; 2 | export default function (info: any) { 3 | let fields = graphqlFields(info, null, 2); 4 | delete fields["pagination"]; 5 | delete fields["filters"]; 6 | return fields; 7 | } 8 | -------------------------------------------------------------------------------- /src/framework/events/runEvent.ts: -------------------------------------------------------------------------------- 1 | import {IConfigurationEvents} from "../types/configuration" 2 | export default function (events: IConfigurationEvents) { 3 | 4 | return function (name: string, args: any) { 5 | if (events.hasOwnProperty(name)) { 6 | events[name](args); 7 | } 8 | } 9 | } -------------------------------------------------------------------------------- /src/framework/mailer/email-templates/changePassword.ts: -------------------------------------------------------------------------------- 1 | export default ` 2 |

3 | Hi {{userName}}, 4 |

5 |

6 | Your password was recently changed on {{siteName}}. 7 | Your email {{email}} 8 |

9 | 10 |

11 | Thanks
12 | {{ siteName }} Team. 13 |

14 | `; 15 | -------------------------------------------------------------------------------- /src/framework/restApi/restApiSuccessResponse.ts: -------------------------------------------------------------------------------- 1 | export default function(obj: any) { 2 | let { res, err, message, data } = obj; 3 | res.status(200); 4 | res.send({ 5 | result: { 6 | status: 200, 7 | success: true, 8 | message: message || "Ok", 9 | data: data 10 | } 11 | }); 12 | } 13 | -------------------------------------------------------------------------------- /src/framework/restApi/restApiErrorResponse.ts: -------------------------------------------------------------------------------- 1 | export default function(obj: any) { 2 | let { res, err, data, code } = obj; 3 | if (!code) { 4 | code = 500; 5 | } 6 | res.status(code); 7 | res.send({ 8 | result: { 9 | status: code, 10 | success: false, 11 | message: err.message, 12 | data: data 13 | } 14 | }); 15 | } 16 | -------------------------------------------------------------------------------- /docs/v2/storage.md: -------------------------------------------------------------------------------- 1 | #### storage - beta 2 | 3 | **Type:** Object 4 | **Default Value:** 5 | **Description:** Its a module for storage functionality in your app. 6 | 7 | ```javascript 8 | { 9 | storageDirectory: "./uploads/", 10 | }, 11 | ``` 12 | 13 | For please visit: https://github.com/Uconnect-Technologies/wertik-js/blob/master/src/framework/builtinModules/storage/index.ts. 14 | -------------------------------------------------------------------------------- /src/framework/helpers/auth.ts: -------------------------------------------------------------------------------- 1 | import bcrypt from "bcryptjs"; 2 | 3 | export const generateHashPassword = (password: string) => { 4 | var salt = bcrypt.genSaltSync(10); 5 | var hash = bcrypt.hashSync(password, salt); 6 | return hash; 7 | }; 8 | 9 | export const verifyPassword = async (tryingPassword, storedPassword) => { 10 | return await bcrypt.compareSync(tryingPassword, storedPassword); 11 | }; 12 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "esModuleInterop": true, 5 | "target": "es2015", 6 | "noImplicitAny": false, 7 | "moduleResolution": "node", 8 | "sourceMap": true, 9 | "outDir": "lib", 10 | "baseUrl": "./", 11 | "lib": ["es6", "esnext.asynciterable"], 12 | "types": [ 13 | "node" 14 | ] 15 | }, 16 | "include": [ 17 | "src/**/*" 18 | ] 19 | } -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es6": true, 5 | "node": true 6 | }, 7 | "extends": "airbnb-base", 8 | "globals": { 9 | "Atomics": "readonly", 10 | "SharedArrayBuffer": "readonly" 11 | }, 12 | "parserOptions": { 13 | "ecmaVersion": 2018, 14 | "sourceType": "module" 15 | }, 16 | "rules": { 17 | } 18 | } -------------------------------------------------------------------------------- /src/framework/security/validateAccessToken.ts: -------------------------------------------------------------------------------- 1 | import getUserWithAccessToken from "./getUserWithAccessToken"; 2 | 3 | export default async function(userModel, accessToken) { 4 | const token = accessToken.replace("bearer ", ""); 5 | const user = await getUserWithAccessToken(userModel, token); 6 | if (user) { 7 | return user; 8 | } else { 9 | throw new Error("Access token expired"); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /docs/getting-started/built-in-modules.md: -------------------------------------------------------------------------------- 1 | ### Built-in Modules 2 | 3 | Wertik-js provides following built-in Modules: 4 | 5 | - user 6 | - auth 7 | - permissiom 8 | - role 9 | - rolePermission 10 | - userPermission 11 | - userRole 12 | - me 13 | 14 | You can check all modules at [https://github.com/Uconnect-Technologies/wertik-js/tree/master/src/framework/builtinModules](https://github.com/Uconnect-Technologies/wertik-js/tree/master/src/framework/builtinModules). 15 | -------------------------------------------------------------------------------- /src/framework/mailer/email-templates/welcome.ts: -------------------------------------------------------------------------------- 1 | export default ` 2 |

3 | Hi {{username}}, Welcome to {{siteName}} 4 |

5 | 6 |

7 | Thanks for joining {{siteName}}, This email is to inform you that you have joined {{siteName}} on {{ date }}, Click Here to verify your account. 8 |

9 | 10 |

11 | Thanks
12 | {{ siteName }} Team. 13 |

14 | `; 15 | -------------------------------------------------------------------------------- /src/framework/security/createJwtToken.ts: -------------------------------------------------------------------------------- 1 | let moment = require("moment"); 2 | let jwt = require("jsonwebtoken"); 3 | import { get, has } from "lodash"; 4 | 5 | export default async function createJwtToken(data: any) { 6 | if (typeof data !== "object") { 7 | throw "Data must be object"; 8 | } 9 | let firstArgument = data; 10 | let secret = process.env.jwtSecret || "asdasdasd"; 11 | return await jwt.sign(firstArgument, secret, {expiresIn: data.expiresIn }); 12 | } 13 | -------------------------------------------------------------------------------- /docs/getting-started/installation.md: -------------------------------------------------------------------------------- 1 | # Installation 2 | 3 | You can Install wertik with npm or yarn 4 | 5 | npm install wertik-js --save 6 | 7 | or with yarn 8 | 9 | yarn add wertik-js --dev 10 | 11 | After installation you can import Wertik by: 12 | 13 | import wertik from "wertik-js/lib/main"; 14 | 15 | or 16 | 17 | const wertik = require("wertik-js/lib/main"); 18 | 19 | Great, you have installed wertik, now lets setup the basic app. Please see Configuration part. 20 | -------------------------------------------------------------------------------- /docs/v2/database.md: -------------------------------------------------------------------------------- 1 | ### Database 2 | 3 | Wertik-js uses Sequelize and supports the database that sequelize support. Database connection can be pass by: 4 | 5 | ```javascript 6 | export default { 7 | name: "Wertik", 8 | // ... Rest of the configuration 9 | database: { 10 | dbDialect: "mysql", 11 | dbUsername: "root", 12 | dbPassword: "pass", 13 | dbName: "graphql", 14 | dbHost: "localhost", 15 | dbPort: "3306", 16 | }, 17 | // ... Rest of the configuration 18 | }; 19 | ``` 20 | -------------------------------------------------------------------------------- /docs/v2/graphql.md: -------------------------------------------------------------------------------- 1 | #### GraphQL 2 | 3 | **Type:** Object 4 | **Default Value:** 5 | **Description:** Options for graphql. We have used Apollo Graphql. 6 | 7 | ```javascript 8 | { 9 | disable: false, 10 | apolloGraphqlServerOptions: defaultApolloGraphqlOptions 11 | } 12 | ``` 13 | 14 | apolloGraphqlServerOptions are the options for apollo graphql. For more details please see: https://www.apollographql.com/docs/apollo-server/api/apollo-server/#options. 15 | 16 | Note: Please donot add these options context, resolvers and typeDefs. -------------------------------------------------------------------------------- /src/framework/defaults/defaultConfigurations/postgresConfiguration.ts: -------------------------------------------------------------------------------- 1 | import defaultConfiguration from "./defaultConfiguration"; 2 | 3 | let configuration = { ...defaultConfiguration }; 4 | 5 | configuration.database = { 6 | dbDialect: "postgres", 7 | dbUsername: "wmysixugpufzba", 8 | dbPassword: "7255d55e83deafe26d093306e958b7e50dad5ac02f687249d02c9a53590c120f", 9 | dbName: "d5rsarnju68s4f", 10 | dbHost: "ec2-54-246-89-234.eu-west-1.compute.amazonaws.com", 11 | dbPort: "5432" 12 | }; 13 | 14 | export default configuration; 15 | -------------------------------------------------------------------------------- /src/framework/mailer/email-templates/requestPasswordReset.ts: -------------------------------------------------------------------------------- 1 | export default ` 2 |

3 | Hi, 4 |

5 | 6 |

7 | Action taken for resetting {{ email }} password. 8 |

9 | 10 |

11 | Your token is: {{ token }} 12 |

13 | 14 |

15 | You can reset your password Here. This 16 | email is valid for next 30 minutes, The link will expire on {{ nextMinutes}}. 17 |

18 | 19 |

20 | Thanks,
21 | {{siteName}} Team 22 |

23 | `; 24 | -------------------------------------------------------------------------------- /src/framework/security/getUserWithAccessToken.ts: -------------------------------------------------------------------------------- 1 | let { get } = require("lodash"); 2 | export default async function (UserModel: any, token: string) { 3 | try { 4 | let find = await UserModel.findOneByArgs({ access_token: token }, "*"); 5 | return get(find, "instance", null); 6 | } catch (errorInstance) { 7 | let modules: any = process.env.builtinModules; 8 | modules = modules.split(","); 9 | modules = modules.filter((c) => c); 10 | if (modules.length == 0) { 11 | return null; 12 | } else { 13 | throw errorInstance; 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/framework/security/isAuthQuery.ts: -------------------------------------------------------------------------------- 1 | let { get } = require("lodash"); 2 | let gql = require("graphql-tag"); 3 | export default function(query: string) { 4 | let parsed = gql` 5 | ${query} 6 | `; 7 | let queryName = get(parsed, "definitions[0].selectionSet.selections[0].name.value", ""); 8 | let auth = [ 9 | "signup", 10 | "resetPassword", 11 | "requestPasswordResetToken", 12 | "login", 13 | "activateAccount", 14 | "generalAccessToken", 15 | "twoFactorLogin", 16 | "twoFactorLoginValidate" 17 | ]; 18 | return auth.indexOf(queryName) > -1; 19 | } 20 | -------------------------------------------------------------------------------- /docs/v2/sockets.md: -------------------------------------------------------------------------------- 1 | #### sockets - beta 2 | 3 | **Type:** Object 4 | **Default Value:** 5 | **Description:** For sockets we use Socket.IO. 6 | 7 | ```javascript 8 | { 9 | disable: false, 10 | onClientConnected: function () { 11 | console.log("onClientConnected"); 12 | }, 13 | onMessageReceived: function () { 14 | console.log("onMessageReceived"); 15 | }, 16 | onClientDisconnect: function () { 17 | console.log("onClientDisconnect"); 18 | }, 19 | }; 20 | ``` 21 | 22 | For please visit https://github.com/Uconnect-Technologies/wertik-js/blob/master/src/framework/socket/index.ts. -------------------------------------------------------------------------------- /examples/socket-test/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Document 7 | 8 | 9 | 10 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/framework/logger/consoleMessages.ts: -------------------------------------------------------------------------------- 1 | import logSymbols from "log-symbols"; 2 | import chalk from "chalk"; 3 | const log = console.log; 4 | 5 | export const successMessage = function(message, secondMessage?: string) { 6 | log( 7 | logSymbols.success, 8 | ` [Wertik-js]: `, 9 | chalk.green(message), 10 | secondMessage ? chalk.blue.underline.bold(secondMessage) : "" 11 | ); 12 | }; 13 | 14 | export const errorMessage = function(message) { 15 | log(logSymbols.error, ` [Wertik-js]:`, chalk.red(message)); 16 | }; 17 | 18 | export const warningMessage = function() {}; 19 | 20 | export const infoMessage = function() {}; 21 | -------------------------------------------------------------------------------- /src/framework/database/helpers/index.ts: -------------------------------------------------------------------------------- 1 | const sequelize = require("sequelize"); 2 | 3 | export const convertFieldsIntoSequelizeFields = (fields) => { 4 | let k = Object.keys(fields); 5 | k.forEach((element) => { 6 | let t = fields[element].type; 7 | if (t.toLowerCase() === "number") { 8 | t = "integer"; 9 | } 10 | fields[element].type = sequelize[t.toUpperCase()]; 11 | fields[element].oldType = t; 12 | }); 13 | return fields; 14 | }; 15 | 16 | export const deleteModel = () => {}; 17 | 18 | export const paginateModel = () => {}; 19 | 20 | export const updateModel = () => {}; 21 | 22 | export const viewModel = () => {}; 23 | -------------------------------------------------------------------------------- /src/framework/mailer/emailTemplates.ts: -------------------------------------------------------------------------------- 1 | import { get } from "lodash"; 2 | import fs from "fs"; 3 | import { join } from "path"; 4 | import { filesInAFolder } from "./../helpers/index"; 5 | export default function(configuration, rootPath) { 6 | let allEmailTemplates = filesInAFolder(`${__dirname}/email-templates`); 7 | let templatesObject = {}; 8 | allEmailTemplates.forEach(element => { 9 | let name = element.split(".")[0]; 10 | let template = require(`./email-templates/${name}`).default; 11 | templatesObject[name] = template; 12 | }); 13 | 14 | return { 15 | ...templatesObject, 16 | ...get(configuration, "email.templates", {}) 17 | }; 18 | } 19 | -------------------------------------------------------------------------------- /src/framework/security/getUserRoles.ts: -------------------------------------------------------------------------------- 1 | export default async function(userId, database) { 2 | let sqlQuery = `SELECT r.*, ur.id as ur_id, 3 | ur.name as ur_name, 4 | ur.created_at as ur_created_at, 5 | ur.updated_at as ur_updated_at, 6 | ur.deleted_at as ur_deleted_at, 7 | ur.role as ur_role, 8 | ur.user as ur_user 9 | 10 | from user_role as ur 11 | left JOIN role as r on r.id = ur.role 12 | where ur.user = _________user_ID`; 13 | sqlQuery = sqlQuery.replace(/_________user_ID/g, userId + ""); 14 | const roles = await database.query(sqlQuery, { type: database.QueryTypes.SELECT }); 15 | return roles; 16 | } 17 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/request_help.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Request Help 3 | about: Any issues while using wertik-js you can use this template for use case of wertik-js 4 | title: '' 5 | labels: 'help support' 6 | assignees: 'ilyaskarim' 7 | --- 8 | 9 | #### Issue having onboarding Wertik? We are here to help. 10 | 11 | **What isue you are having?** 12 | 13 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 14 | 15 | **What you actually you want to do?** 16 | 17 | A clear and concise description of what you want to happen. 18 | 19 | 20 | **Other Remarks** 21 | 22 | Add any other context or screenshots about the feature request here. 23 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /docs/seeds/introduction.md: -------------------------------------------------------------------------------- 1 | ### Seeds 2 | 3 | Seeds allow you to insert some random data to your app, So lets say you want to insert some default Roles into your app, so you can pass seeds by 4 | 5 | ```javascript 6 | let configuration = { 7 | // rest of the configuration 8 | seeds: { 9 | Role: [{ name: "My Role" }, { name: "My Role #2" }], 10 | }, 11 | }; 12 | ``` 13 | 14 | So, you have added, After initializetion of wertik-js you can run seeds by: 15 | 16 | ```javascript 17 | import wertik from "./main"; 18 | wertik({ expressApp: app }, defaultConfiguration).then((wertikApp: any) => { 19 | wertikApp.seeds(["Role"]); // Same name goes for module.name 20 | }); 21 | ``` 22 | 23 | This will insert module seeds. 24 | -------------------------------------------------------------------------------- /docs/graphql/introduction.md: -------------------------------------------------------------------------------- 1 | ### GraphQL 2 | 3 | Under the hood Wertik-js uses Apollo GraphQL server for Graphql Implementation, we selected Apollo Graphql server due to its popularity and features. Please see this file https://github.com/Uconnect-Technologies/wertik-js/tree/master/src/framework/graphql/index.ts where the GraphQL is added. 4 | 5 | Currently we donot have direct implementation for GraphQL, You can only use GraphQL by using with custom modules, Please see custom modules page for more details. 6 | 7 | If you have any suggestions, bugs or feature request you can create a new issue 8 | 9 | https://github.com/Uconnect-Technologies/wertik-js/issues/new/choose 10 | 11 | We will be happy to assist you with your issue. -------------------------------------------------------------------------------- /docs/rbac/introduction.md: -------------------------------------------------------------------------------- 1 | ### Role Based Access Control 2 | 3 | Wertik-js provides Role Based Access Control feature. While this can be done through context. Roles and Permissions for a user are passed to context, In GraphQL you can access Roles and Permissions by: 4 | 5 | ```javascript 6 | function (_, args, context,info) { 7 | console.log(context.userRoles); // User Roles 8 | console.log(context.userPermissions) // User Permissions 9 | } 10 | ``` 11 | 12 | In RestAPI you can access Roles and Permissions by: 13 | 14 | ```javascript 15 | function (req,res) { 16 | console.log(req.userRoles) // User Roles 17 | console.log(req.userPermissions) // User Permissions 18 | } 19 | ``` 20 | 21 | Based on Roles and Permisions, You can decide to continue request. -------------------------------------------------------------------------------- /src/framework/database/models.ts: -------------------------------------------------------------------------------- 1 | import Model from "./../model/model"; 2 | import { IConfiguration } from "../types/configuration"; 3 | import { loadModulesFromConfiguration } from "../helpers"; 4 | import { get } from "lodash"; 5 | 6 | export default function (dbTables, configuration: IConfiguration) { 7 | let modules = loadModulesFromConfiguration(configuration); 8 | let allTables = Object.keys(dbTables); 9 | let models = {}; 10 | allTables.forEach((element) => { 11 | let module = modules.filter((c) => c.name == element); 12 | models[element] = Model({ 13 | dbTables: dbTables, 14 | tableName: element, 15 | configuration: configuration, 16 | module: get(module, "[0]", {}), 17 | }); 18 | }); 19 | return models; 20 | } 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - Version [e.g. 22] 28 | 29 | **Additional context** 30 | Add any other context about the problem here. 31 | -------------------------------------------------------------------------------- /src/framework/helpers/convertConfigurationIntoEnvVariables.ts: -------------------------------------------------------------------------------- 1 | declare var process: any; 2 | import { IConfiguration } from "./../types/configuration"; 3 | 4 | export default function(configuration: IConfiguration) { 5 | return new Promise((resolve, reject) => { 6 | try { 7 | let keys = Object.keys(configuration); 8 | // Important ones 9 | process.env.dbDialect = configuration.database.dbDialect; 10 | // Important ones 11 | keys.forEach((key, index) => { 12 | let value = configuration[key]; 13 | 14 | process.env[key] = value; 15 | if (index + 1 == keys.length) { 16 | resolve("Added to env."); 17 | } 18 | }); 19 | } catch (e) { 20 | reject(e.message); 21 | } 22 | }); 23 | } 24 | -------------------------------------------------------------------------------- /src/framework/helpers/validateConfigurationObject.ts: -------------------------------------------------------------------------------- 1 | import checkInstalledPackages from "../initialization/checkInstalledPackages"; 2 | import checkModules from "../initialization/checkModules"; 3 | 4 | export const requiredFields = { 5 | name: "required", 6 | dialect: "mysql", 7 | db_username: "required", 8 | db_password: "required", 9 | db_name: "required", 10 | db_host: "required", 11 | db_port: "required" 12 | }; 13 | 14 | export default function(configuration) { 15 | return new Promise(async (resolve, reject) => { 16 | try { 17 | let responseCheckInstalledPackages = await checkInstalledPackages(configuration); 18 | let responseCheckModules = await checkModules(configuration); 19 | resolve(); 20 | } catch (errr) { 21 | reject(errr); 22 | console.log(errr); 23 | } 24 | }); 25 | } 26 | -------------------------------------------------------------------------------- /docs/v2/mailer.md: -------------------------------------------------------------------------------- 1 | #### email 2 | 3 | **Type:** Object 4 | **Default Value:** 5 | **Description:** Wertik uses NodeMailer for emails. The configuration for email handling in Wertik 6 | 7 | ```javascript 8 | { 9 | disable: true; 10 | configuration: NodeMailerConfigurationObject; 11 | } 12 | ``` 13 | 14 | By default emailing is disabled in wertik, Assigning false to disable activates the emailing in wertik. You have to provide second arugment which is configuration of Node mailer. 15 | 16 | When pasing mailer. Wertik sends a method called sendEmail to context to send emails. Please see this function for more details: https://github.com/Uconnect-Technologies/wertik-js/blob/master/src/framework/mailer/index.ts#L22. Since its a closure, Main function starts from here: https://github.com/Uconnect-Technologies/wertik-js/blob/master/src/framework/mailer/index.ts#L22. 17 | -------------------------------------------------------------------------------- /src/framework/graphql/schemaMap.ts: -------------------------------------------------------------------------------- 1 | export default ` 2 | type Response { 3 | message: String 4 | version: String 5 | } 6 | type SuccessResponse { 7 | message: String 8 | } 9 | input IDDeleteInput { 10 | id: [Int] 11 | } 12 | [generalSchema__replace] 13 | [modulesSchema__replace] 14 | type Mutation { 15 | response: Response 16 | [mutation__replace] 17 | } 18 | type Query { 19 | response: Response 20 | [query__replace] 21 | } 22 | type Subscription { 23 | response: Response 24 | [subscription__replace] 25 | } 26 | input EmailInput { 27 | email: String! 28 | } 29 | input SignupInput { 30 | email: String! 31 | password: String! 32 | confirmPassword: String! 33 | } 34 | schema { 35 | query: Query 36 | mutation: Mutation 37 | subscription: Subscription 38 | } 39 | `; 40 | -------------------------------------------------------------------------------- /examples/demo/index.js: -------------------------------------------------------------------------------- 1 | // Import files for database connection and serving wertik. 2 | const { serve, connectDatabase } = require("./../../index"); 3 | // import default configuration, remember you have to add .env setup. 4 | const configuration = require("./../../lib/framework/defaults/defaultConfigurations/defaultConfiguration").default; 5 | 6 | // Connect to database. 7 | connectDatabase(configuration.database) 8 | .then((databaseInstance) => { 9 | // Assign database instance to configuration 10 | configuration.databaseInstance = databaseInstance; 11 | // serve app! 12 | serve(configuration).then((wertikApp) => { 13 | if (configuration.database.dbDialect.includes("sql")) { 14 | wertikApp.database.sync(); 15 | } 16 | }); 17 | }) 18 | .catch((e) => { 19 | console.log(`Error connecting with database`); 20 | console.log(e); 21 | }); 22 | -------------------------------------------------------------------------------- /src/devServer.ts: -------------------------------------------------------------------------------- 1 | import { connectDatabase, serve } from "./main"; 2 | import { IConfiguration } from "./framework/types/configuration"; 3 | const defaultConfiguration: IConfiguration = require("./framework/defaults/defaultConfigurations/defaultConfiguration") 4 | .default; 5 | const postgresConfiguration: IConfiguration = require("./framework/defaults/defaultConfigurations/postgresConfiguration") 6 | .default; 7 | 8 | let configuration = defaultConfiguration; 9 | 10 | connectDatabase(configuration.database) 11 | .then((databaseInstance) => { 12 | configuration.databaseInstance = databaseInstance; 13 | serve(configuration).then((wertikApp: any) => { 14 | if (configuration.database.dbDialect.includes("sql")) { 15 | wertikApp.database.sync(); 16 | } 17 | }); 18 | }) 19 | .catch((e) => { 20 | console.log(`Error connecting with database`); 21 | console.log(e); 22 | }); 23 | -------------------------------------------------------------------------------- /src/framework/initialization/checkModules.ts: -------------------------------------------------------------------------------- 1 | import { IConfiguration } from "../types/configuration"; 2 | 3 | export default function(configuration: IConfiguration) { 4 | const { modules } = configuration; 5 | return new Promise((resolve, reject) => { 6 | if (modules.length == 0) { 7 | console.log("[Wertik-js] Starting with no custom modules.") 8 | resolve(true); 9 | } 10 | modules.forEach((element, index) => { 11 | let isLast = index == modules.length - 1; 12 | if (!element || element.constructor !== Object) { 13 | console.error( 14 | `You have passed unsupported modules at index: ${index}, received: ${element.constructor} \n Closing process. Please see: http://www.wapgee.com/wertik-js/getting-started/custom-modules` 15 | ); 16 | process.exit(); 17 | } 18 | if (isLast) { 19 | resolve(true); 20 | } 21 | }); 22 | }); 23 | } 24 | -------------------------------------------------------------------------------- /docs/restApi/introduction.md: -------------------------------------------------------------------------------- 1 | 2 | ### Rest API 3 | 4 | For Rest API wertik-js uses Express-js for rest api feature. Due to its popularity and key features. The rest api is registered in this file: [https://github.com/Uconnect-Technologies/wertik-js/blob/master/src/framework/restApi/index.ts](https://github.com/Uconnect-Technologies/wertik-js/blob/master/src/framework/restApi/index.ts) 5 | 6 | #### Changing Port 7 | 8 | You can change the port by 9 | 10 | ```javascript 11 | let configuration = { 12 | /// Rest of the configuration 13 | restApi: { 14 | port: 7070 // Define your port her 15 | } 16 | } 17 | ``` 18 | 19 | #### Disable Rest API 20 | 21 | 22 | You can disable Rest API by: 23 | 24 | ```javascript 25 | let configuration = { 26 | /// Rest of the configuration 27 | restApi: { 28 | disable: true // passing true will disable the Rest API 29 | } 30 | } 31 | ``` 32 | 33 | 34 | For adding Rest Api endpoints, Please see custom modules section. -------------------------------------------------------------------------------- /src/framework/apiDocs/docs/index.ts: -------------------------------------------------------------------------------- 1 | import { IDocServerConfiguration } from "./../../types/configuration"; 2 | import express from "express"; 3 | import path from "path"; 4 | const port = 5200; 5 | const app = express(); 6 | import { get } from "lodash"; 7 | import { successMessage } from "../../logger/consoleMessages"; 8 | 9 | export default function(options: IDocServerConfiguration, cb: Function) { 10 | const { configuration } = options; 11 | const port = get(options, "port", 5200); 12 | app.use(express.static("index")); 13 | app.use(express.static(path.join(__dirname, "/content"))); 14 | app.use(async function(req, res, next) { 15 | const ip = req.connection.remoteAddress; 16 | next(); 17 | }); 18 | app.get("/", function(req, res) { 19 | res.sendFile("./index.html", { root: __dirname }); 20 | }); 21 | app.listen(port, function() { 22 | successMessage(`Rest API docs running at`,`http://localhost:${port}/`) 23 | cb(); 24 | }); 25 | } 26 | -------------------------------------------------------------------------------- /src/framework/builtinModules/me/index.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | name: "Me", 3 | useDatabase: false, 4 | graphql: { 5 | schema: ` 6 | type Me { 7 | user: User 8 | name: String 9 | permissions: [Permission] 10 | } 11 | `, 12 | mutation: { 13 | schema: ``, 14 | resolvers: {} 15 | }, 16 | query: { 17 | schema: ` 18 | me: Me 19 | `, 20 | resolvers: { 21 | me: (_: any, args: any, context: any, info: any) => { 22 | return { 23 | name: "Ilyas", 24 | user: context.user, 25 | permissions: context.permissions.map(element => { 26 | return { 27 | cant: element.permission_cant, 28 | can: element.permission_can, 29 | id: element.permission_id 30 | }; 31 | }) 32 | }; 33 | } 34 | } 35 | } 36 | }, 37 | restApi: {} 38 | }; 39 | -------------------------------------------------------------------------------- /docs/storage/introduction.md: -------------------------------------------------------------------------------- 1 | ## Storage - Beta 2 | 3 | Wertik-js allows storing files on your server by using Multer package, Currently storage is only available on Rest Api side. You can find initialization of multer on main.ts file, Please see https://github.com/Uconnect-Technologies/wertik-js/blob/master/src/main.ts#L41. For implementation of storage, You can find storage module here: https://github.com/Uconnect-Technologies/wertik-js/blob/master/src/framework/builtinModules/storage/index.ts. 4 | 5 | After running server you send files to upload on this address: http://localhost:7000/api/v1/storage/upload, 6 | 7 | Method Type: Post 8 | 9 | 10 | Paramter: file 11 | 12 | Depending on status the API will send a response. The Storage feature is in beta, If you can any idea to implement you can write it in a feature request [here][1]. 13 | 14 | 15 | [1]: https://github.com/Uconnect-Technologies/wertik-js/issues/new?assignees=&labels=&template=feature_request.md&title=Storage%20feature 16 | -------------------------------------------------------------------------------- /src/framework/apiDocs/index.ts: -------------------------------------------------------------------------------- 1 | import fs from "fs"; 2 | declare var process: any; 3 | import { exists, deleteFile, createEmptyFile, appendToFileSync } from "./../helpers/index"; 4 | import { IDocServerConfiguration } from "./../types/configuration"; 5 | 6 | const docFileSource = process.env['generateDocumentationPath']; 7 | // const docFileSource = "asd"; 8 | 9 | export const addContentsToDoc = async function(doc: string) { 10 | setTimeout(() => { 11 | doc = doc.replace("first________", "/**"); 12 | doc = doc.replace("last________", "*/"); 13 | appendToFileSync(docFileSource, doc); 14 | }, 600); 15 | }; 16 | 17 | export const resetDocFile = async function() { 18 | deleteDocFile(function() { 19 | createEmptyFile(docFileSource, function() { 20 | addContentsToDoc("//empty file"); 21 | }); 22 | }); 23 | }; 24 | 25 | export const deleteDocFile = async function(cb: Function) { 26 | if (exists(docFileSource)) { 27 | deleteFile(docFileSource, cb); 28 | } else { 29 | cb(); 30 | } 31 | }; -------------------------------------------------------------------------------- /src/framework/types/servers.ts: -------------------------------------------------------------------------------- 1 | import { IConfiguration } from "./configuration"; 2 | 3 | export interface IGraphQLInitialize { 4 | sendEmail: Function; 5 | expressApp: any; 6 | configuration: IConfiguration; 7 | dbTables: Array; 8 | models: any; 9 | emailTemplates: Object; 10 | database: any; 11 | WertikEventEmitter: any; 12 | mailerInstance: any; 13 | socketio: any; 14 | logger: any; 15 | } 16 | 17 | export interface IRestApiInitialize { 18 | sendEmail: Function; 19 | configuration: IConfiguration; 20 | mailerInstance: any; 21 | expressApp: any; 22 | dbTables: Array; 23 | emailTemplates: any; 24 | models: any; 25 | database: any; 26 | WertikEventEmitter: any; 27 | multerInstance: any; 28 | socketio: any; 29 | logger: any; 30 | } 31 | 32 | export interface ISocketConfiguration { 33 | onMessageReceived: Function; 34 | onClientConnected: Function; 35 | onClientDisconnect: Function; 36 | disable: Boolean; 37 | options: { 38 | [key: string]: any 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/framework/defaults/loadDefaults.ts: -------------------------------------------------------------------------------- 1 | import defaultConfiguration from "./defaultConfigurations/defaultConfiguration"; 2 | import { IConfiguration } from "./../types/configuration"; 3 | 4 | export default function(configuration: IConfiguration) { 5 | return new Promise((resolve, reject) => { 6 | try { 7 | let configurationPassesInKeys = Object.keys(configuration); 8 | if (configurationPassesInKeys.length == 0) { 9 | console.warn("[WERTIK-JS] Configuration not passed, using default configuration."); 10 | resolve(defaultConfiguration); 11 | } 12 | let newConfiguration = new Object({ ...defaultConfiguration }); 13 | configurationPassesInKeys.forEach((element, index) => { 14 | let isLast = index + 1 == configurationPassesInKeys.length; 15 | newConfiguration[element] = configuration[element]; 16 | if (isLast) { 17 | resolve(newConfiguration); 18 | } 19 | }); 20 | } catch (err) { 21 | reject(err); 22 | } 23 | }); 24 | } 25 | -------------------------------------------------------------------------------- /src/framework/helpers/replaceFilterOperators.ts: -------------------------------------------------------------------------------- 1 | import { isPlainObject } from "lodash"; 2 | import sequelize from "sequelize"; 3 | const Op = sequelize.Op; 4 | 5 | const wrap = (operator) => { 6 | return Op[operator.replace("_", "")]; 7 | }; 8 | 9 | const iterate = (obj) => { 10 | const isObject = isPlainObject(obj); 11 | const isArray = Array.isArray(obj); 12 | if (isObject) { 13 | const keys = Object.keys(obj); 14 | keys.forEach((element) => { 15 | const value = obj[element]; 16 | const isArray = Array.isArray(value); 17 | const isObject = isPlainObject(value); 18 | if (element.indexOf("_") === 0) { 19 | const newWrapValue = wrap(element); 20 | obj[newWrapValue] = obj[element]; 21 | delete obj[element]; 22 | } 23 | if (isArray === true || isObject === true) { 24 | iterate(value); 25 | } 26 | }); 27 | return obj; 28 | } else { 29 | obj.forEach((element) => { 30 | iterate(element); 31 | }); 32 | } 33 | }; 34 | 35 | export default iterate; 36 | -------------------------------------------------------------------------------- /src/framework/types/models.ts: -------------------------------------------------------------------------------- 1 | export interface IPermission { 2 | id: Number; 3 | name: String; 4 | can: String; 5 | cant: String; 6 | } 7 | 8 | export interface IRole { 9 | name: String; 10 | defaultPermissions: String; 11 | } 12 | 13 | export interface IUser { 14 | id: Number; 15 | name: String; 16 | username: String; 17 | refreshToken: String; 18 | accessToken: String; 19 | isActivated: Boolean; 20 | activatedOn: String; 21 | twoFactorCode: String; 22 | isSuperUser: Boolean; 23 | activationToken: String; 24 | email: String; 25 | gender: String; 26 | referer: String; 27 | } 28 | 29 | export interface IForgetPassword { 30 | id: Number; 31 | name: String; 32 | email: String; 33 | user: any; 34 | token: String; 35 | } 36 | 37 | export interface IRolePermission { 38 | id: Number; 39 | role: any; 40 | permission: any; 41 | } 42 | 43 | export interface IUserPermission { 44 | id: Number; 45 | user: any; 46 | permission: any; 47 | } 48 | 49 | export interface IModelHandlerFunctionProps { 50 | mode: string; 51 | req: any; 52 | } 53 | -------------------------------------------------------------------------------- /src/framework/cron/index.ts: -------------------------------------------------------------------------------- 1 | import { IConfiguration } from "../types/configuration"; 2 | import cron from "node-cron"; 3 | import { get } from "lodash"; 4 | 5 | export default function (configuration: IConfiguration, context: any) { 6 | return new Promise(async (resolve, reject) => { 7 | let configurationCron = configuration.cron; 8 | configurationCron.cronList.forEach((cronItem) => { 9 | let initializedEvent = get(cronItem, "events.initialized", () => {}); 10 | let cronItemOptions = get(cronItem, "options", {}); 11 | let cronItemExpression = get(cronItem, "expression", {}); 12 | let handler = get(cronItem, "function", () => {}); 13 | if (cron.validate(cronItem.expression)) { 14 | let cronScheduleItem = cron.schedule( 15 | cronItemExpression, 16 | () => { 17 | handler(context); 18 | }, 19 | cronItemOptions 20 | ); 21 | initializedEvent(cronScheduleItem); 22 | } else { 23 | console.log(`Wrong expression: ${cronItem.expression}`); 24 | } 25 | }); 26 | }); 27 | } 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Ilyas Karim 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /docs/v2/restApi.md: -------------------------------------------------------------------------------- 1 | ### Rest API 2 | 3 | For Rest API wertik-js uses Express-js for rest api feature. Due to its popularity and key features. The rest api is registered in this file: [https://github.com/Uconnect-Technologies/wertik-js/blob/master/src/framework/restApi/index.ts](https://github.com/Uconnect-Technologies/wertik-js/blob/master/src/framework/restApi/index.ts) 4 | 5 | #### Configuration 6 | 7 | ```javascript 8 | let configuration = { 9 | /// Rest of the configuration 10 | restApi: { 11 | showWertik404Page: true, // Show wertik 404 page. If set to false wertik js will not show 404 message. 12 | restApi404Handler: function (req, res) { 13 | res.status(404).json({ 14 | message: "Not found", 15 | data: { 16 | message: "Request page didn't found", 17 | }, 18 | }); 19 | }, // Showing 404 page. 20 | onCustomApiFailure: function ({ path, res, err }) { 21 | res.send("failed at " + path); 22 | }, // On api request fail. 23 | }, 24 | }; 25 | ``` 26 | 27 | For using rest api in modules. Please see [custom modules](http://wapgee.com/wertik-js/custom-modules/introduction) section. -------------------------------------------------------------------------------- /docs/introduction/motivation.md: -------------------------------------------------------------------------------- 1 | 2 | ### Motivation 3 | 4 | Building SaaS applications backend is not an easy thing, this requires a lot of effort. So wertik-js comes to play here, Wertik-js provides all required features to build a backend. Following things needed to be cared of when building backend api 5 | 6 | 7 | - Database 8 | 9 | - Security 10 | 11 | - Development 12 | 13 | - Seeding 14 | 15 | - Realtime Development 16 | 17 | - Api 18 | 19 | - Email system 20 | 21 | - Events 22 | 23 | - Role based access control 24 | 25 | - Storage 26 | 27 | 28 | #### Why Wertik-js 29 | 30 | Wertik-js comes to play here to build a backend that is powered with all features mentioned above. Wertik-js allows GraphQL and RestAPI in single backend application. The library also provides database that Seqeulize supports. Wertik-js allows sockets integrations. For more you can check this folder to see all Wertik-js features: https://github.com/Uconnect-Technologies/wertik-js/tree/master/src/framework. 31 | 32 | #### Upcoming Features 33 | 34 | These are the upcoming features for Wertik-js: 35 | 36 | - MongoDB Implentation 37 | - Logs with database 38 | - Cron Jobs 39 | 40 | -------------------------------------------------------------------------------- /src/framework/seeds/index.ts: -------------------------------------------------------------------------------- 1 | export default function(configuration, models) { 2 | return function(modules: Array) { 3 | return new Promise((resolve, reject) => { 4 | if (modules && modules.constructor == Array) { 5 | const seeds = configuration.seeds; 6 | if (modules.length == 0) { 7 | resolve("No Seeds provided."); 8 | return; 9 | } 10 | modules.forEach((currentModule, index) => { 11 | let isEnd = modules.length - 1 == index; 12 | let moduleData = seeds[currentModule]; 13 | let model = models[currentModule]; 14 | moduleData.forEach((element, indexOfData) => { 15 | model.create(element); 16 | if (isEnd && indexOfData == moduleData.length - 1) { 17 | resolve(`Seeds added for modules: ${modules.join(", ")}`); 18 | } 19 | }); 20 | }); 21 | 22 | }else { 23 | let message = `[Wertik Seeds]: modules expected in this format: Array, but received: ${modules}`; 24 | console.error(message); 25 | reject(message); 26 | } 27 | }); 28 | }; 29 | } -------------------------------------------------------------------------------- /src/framework/logger/index.ts: -------------------------------------------------------------------------------- 1 | export default function () { 2 | return new Promise((resolve, reject) => { 3 | const { createLogger, format, transports } = require("winston"); 4 | const { printf, combine, timestamp, label } = format; 5 | 6 | const myFormat = printf(({ level, message, label, timestamp }) => { 7 | // return `${timestamp} [${label}] ${level}: ${message}`; 8 | return JSON.stringify({ 9 | timestamp: timestamp, 10 | label: label, 11 | level: level, 12 | message: message && message.constructor == String ? message : message, 13 | }); 14 | }); 15 | 16 | const logger = createLogger({ 17 | format: combine(label({ label: "right meow!" }), timestamp(), myFormat), 18 | transports: [ 19 | new transports.Console(), 20 | new transports.File({ 21 | filename: "info.log", 22 | level: "info", 23 | }), 24 | new transports.File({ 25 | filename: "error.log", 26 | level: "error", 27 | }), 28 | new transports.File({ 29 | filename: "warning.log", 30 | level: "warning", 31 | }), 32 | ], 33 | }); 34 | 35 | resolve(logger); 36 | }); 37 | } 38 | -------------------------------------------------------------------------------- /src/framework/initialization/checkInstalledPackages.ts: -------------------------------------------------------------------------------- 1 | import { IConfiguration } from "./../types/configuration"; 2 | import { errorMessage } from "../logger/consoleMessages"; 3 | export function checkIfPackageIsInstalled(packageName: String) { 4 | try { 5 | let version = require(`${packageName}/package.json`).version; 6 | return version; 7 | } catch (e) { 8 | return false; 9 | } 10 | } 11 | 12 | export function check(name: String) { 13 | const isInstalled = checkIfPackageIsInstalled(name); 14 | if (isInstalled) { 15 | return true; 16 | } else { 17 | errorMessage(name + " is not installed, Exiting wertik-js process."); 18 | process.exit(); 19 | } 20 | } 21 | 22 | export default function (configuration: IConfiguration) { 23 | return new Promise((resolve, reject) => { 24 | try { 25 | const { dbDialect } = configuration.database; 26 | check("apollo-server"); 27 | if (dbDialect == "mysql") { 28 | check("sequelize"); 29 | check("mysql2"); 30 | } 31 | if (dbDialect == "postgres") { 32 | check("pg"); 33 | check("pg-hstore"); 34 | check("pg-native"); 35 | } 36 | resolve(); 37 | } catch (e) { 38 | reject(e); 39 | } 40 | }); 41 | } 42 | -------------------------------------------------------------------------------- /src/framework/socket/index.ts: -------------------------------------------------------------------------------- 1 | import { ISocketConfiguration } from "../types/servers"; 2 | import { get } from "lodash"; 3 | import { IConfiguration } from "../types/configuration"; 4 | import SocketIO from "socket.io"; 5 | 6 | export const defaultSocketInstance = (sockets: ISocketConfiguration, context: any) => { 7 | const {disable,options, onMessageReceived, onClientConnected, onClientDisconnect} = sockets; 8 | if (disable === true) { 9 | return null; 10 | } 11 | const { httpServer } = context; 12 | const io = SocketIO(httpServer, options); 13 | io.on("connection", (socket) => { 14 | onClientConnected({ 15 | socket, 16 | }); 17 | socket.on("message", onMessageReceived); 18 | socket.on("disconnect", onClientDisconnect); 19 | }); 20 | 21 | return io; 22 | }; 23 | 24 | export default function (options: IConfiguration, context: any) { 25 | let ws = defaultSocketInstance( 26 | { 27 | onClientConnected: get(options,'sockets.onClientConnected', function () {}), 28 | onMessageReceived: get(options,'sockets.onMessageReceived', function () {}), 29 | onClientDisconnect: get(options,'sockets.onClientDisconnect', function () {}), 30 | disable: get(options,'sockets.disable', false), 31 | options: get(options,'sockets.options',{}), 32 | }, 33 | context 34 | ); 35 | return ws; 36 | } 37 | -------------------------------------------------------------------------------- /docs/database/introduction.md: -------------------------------------------------------------------------------- 1 | ### Database 2 | 3 | Wertik-js currently provides following database features 4 | 5 | - Mysql 6 | - PostgreSQL 7 | 8 | and other database that sequelize support. 9 | 10 | #### MySQL 11 | 12 | To connect with your MySQL database, You need to share your `Database Name`, `Database Password`, `Database Username` and `Database Host`. Here is how to connect with your MySQL database: 13 | 14 | ```javascript 15 | export default { 16 | name: "Wertik", 17 | // ... Rest of the configuration 18 | database: { 19 | dbDialect: "mysql", 20 | dbUsername: "root", 21 | dbPassword: "pass", 22 | dbName: "graphql", 23 | dbHost: "localhost", 24 | dbPort: "3306", 25 | }, 26 | // ... Rest of the configuration 27 | }; 28 | ``` 29 | 30 | #### PostgreSQL 31 | 32 | To connect with your PostgreSQL database, You need to share your `Database Name`, `Database Password`, `Database Username` and `Database Host` same as MySQL but `dbDialect` must be postgres. Here is how to connect with your PostgreSQL database: 33 | 34 | ```javascript 35 | export default { 36 | name: "Wertik", 37 | // ... Rest of the configuration 38 | database: { 39 | dbDialect: "postgres", 40 | dbUsername: "root", 41 | dbPassword: "pass", 42 | dbName: "graphql", 43 | dbHost: "localhost", 44 | dbPort: "3306", 45 | }, 46 | // ... Rest of the configuration 47 | }; 48 | ```` 49 | 50 | -------------------------------------------------------------------------------- /docs/v2/cron-jobs.md: -------------------------------------------------------------------------------- 1 | #### sockets - beta 2 | 3 | **Type:** Object 4 | **Default Value:** 5 | **Description:** For sockets we use Socket.IO. 6 | 7 | ```javascript 8 | { 9 | disable: false, 10 | onClientConnected: function () { 11 | console.log("onClientConnected"); 12 | }, 13 | onMessageReceived: function () { 14 | console.log("onMessageReceived"); 15 | }, 16 | onClientDisconnect: function () { 17 | console.log("onClientDisconnect"); 18 | }, 19 | }; 20 | ``` 21 | 22 | For please visit https://github.com/Uconnect-Technologies/wertik-js/blob/master/src/framework/socket/index.ts. 23 | 24 | #### storage - beta 25 | 26 | **Type:** Object 27 | **Default Value:** 28 | 29 | ```javascript 30 | { 31 | storageDirectory: "./uploads/", 32 | }, 33 | ``` 34 | 35 | **Description:** Its a module for storage functionality in your app. 36 | 37 | For please visit: https://github.com/Uconnect-Technologies/wertik-js/blob/master/src/framework/builtinModules/storage/index.ts. 38 | 39 | ### Cron - beta 40 | 41 | **Type:** Object 42 | **Default Value:** {} 43 | **Description:** Under the we use node-cron library for cron jobs. Please see https://www.npmjs.com/package/node-cron. 44 | 45 | For more please check [Default Configuration](https://github.com/Uconnect-Technologies/wertik-js/blob/master/src/framework/defaults/defaultConfigurations/defaultConfiguration.ts) to get an idea about cron jobs. 46 | -------------------------------------------------------------------------------- /docs/v2/custom-modules.md: -------------------------------------------------------------------------------- 1 | ### Custom Modules 2 | 3 | **Type:** Array 4 | 5 | With this guide, you can extend your app with extra modules and functionality. Please refer to this module for getting familiar with modules: https://github.com/Uconnect-Technologies/wertik-js/blob/master/src/framework/builtinModules/forgetPassword/index.ts. 6 | 7 | ### Configuration explained 8 | 9 | ### name 10 | 11 | **Type:** String 12 | **Default Value:** null 13 | **Description:** The name of module. 14 | 15 | ### useDatabase 16 | 17 | **Type:** Boolean 18 | **Default Value:** true 19 | **Description:** Define whether the module uses database or not. If false, database property is not required. 20 | 21 | ### graphql 22 | 23 | **Type:** Object 24 | **Default Value:** {} 25 | **Description:** The graphql will add graphql features to module. 26 | 27 | ### restApi 28 | 29 | **Type:** Object 30 | **Default Value:** {} 31 | **Description:** With this option you can add endpoints to your modules. 32 | 33 | ### database 34 | 35 | **Type:** Object 36 | **Default Value:** {} 37 | **Description:** Defines current module database table. This includes, fields, relationships, which fields to ignore while executing graphql schema. Please visit database section of above shared module: https://github.com/Uconnect-Technologies/wertik-js/blob/master/src/framework/builtinModules/forgetPassword/index.ts#L118. 38 | -------------------------------------------------------------------------------- /src/framework/restApi/versions/v1/loadAllModules.ts: -------------------------------------------------------------------------------- 1 | import { get, isFunction } from "lodash"; 2 | import { IConfiguration } from "src/framework/types/configuration"; 3 | 4 | export default async function (expressApp, configuration: IConfiguration, customApi) { 5 | let modules = configuration.builtinModules.split(","); 6 | modules = modules.filter((c) => c); 7 | modules = [...modules, ...get(configuration, "modules", [])]; 8 | 9 | const processModule = (module) => { 10 | if (module && module.hasOwnProperty("restApi")) { 11 | const restApi = get(module, "restApi", {}); 12 | const restApiEndpoints = get(restApi, "endpoints", []); 13 | restApiEndpoints.forEach((restApiEndpointsElement) => { 14 | customApi(expressApp, restApiEndpointsElement, module, configuration); 15 | }); 16 | 17 | const expressAccess = get(module,'restApi.expressAccess', function () {}); 18 | 19 | expressAccess(expressApp); 20 | } 21 | }; 22 | 23 | modules.forEach(async (element: any) => { 24 | let module; 25 | if (element.constructor === String) { 26 | module = require(`./../../../builtinModules/${element}/index`).default; 27 | } else if (element.constructor === Object || isFunction(element)) { 28 | if (element.constructor == Function) { 29 | module = await element(configuration); 30 | } else { 31 | module = element; 32 | } 33 | } 34 | processModule(module); 35 | }); 36 | } 37 | -------------------------------------------------------------------------------- /docs/sockets/introduction.md: -------------------------------------------------------------------------------- 1 | ### Sockets - Beta 2 | 3 | Wertik-js uses `ws` for SocketIO integration, Please see https://github.com/socketio/socket.io. To see how Sockets is enabled please see https://github.com/Uconnect-Technologies/wertik-js/blob/master/src/framework/socket/index.ts. This file enables SocketIO with Wertik-js. 4 | 5 | By default the socket server runs at port `ws://localhost:2000`. You can detect events for connected, message received and client disconnected by: 6 | 7 | ```javascript 8 | let object = { 9 | // Rest of the configuration 10 | sockets: { 11 | disable: false, 12 | onClientConnected: function () { 13 | console.log("onClientConnected"); 14 | }, 15 | onMessageReceived: function () { 16 | console.log("onMessageReceived"); 17 | }, 18 | onClientDisconnect: function () { 19 | console.log("onClientDisconnect"); 20 | }, 21 | }, 22 | }; 23 | ``` 24 | 25 | #### Sending a message in Rest API handler 26 | 27 | Consider this handler in your rest api handler, SocketIO comes with `req.socketio`, So you can send data, 28 | 29 | ```javascript 30 | async function(req, res) { 31 | req.socketio.send("Test Message"); // This will send test message to all clients 32 | } 33 | ``` 34 | 35 | In GraphQL, SocketIO can be accessed by `context.socketio`, 36 | 37 | ```javascript 38 | function (_, args, context,info) { 39 | context.socketio.send("Test Message"); // Tis will send test message to all clients from GraphQL 40 | } 41 | ``` 42 | 43 | Currently Web Sockets in wertik-js are in beta, So more features are coming. 44 | -------------------------------------------------------------------------------- /docs/email/introduction.md: -------------------------------------------------------------------------------- 1 | ### Email - Beta 2 | 3 | Wertik-js also provides email system that allows you to send emails from your own applications. Under the hood, Wertik-js uses nodemailer for sending emails. Please see here how node mailer is initialized https://github.com/Uconnect-Technologies/wertik-js/blob/master/src/framework/mailer/index.ts. 4 | 5 | Wertik-js js allows you to send define your own custom Mailer instance and Email send method. You can check this function that provides mailer instance https://github.com/Uconnect-Technologies/wertik-js/blob/master/src/framework/mailer/index.ts#L52. You have two ways, You can send mailer instance through `configuration.email.defaultMailerInstance` or to extend node mailer you can configuration object for node mailer at `configuration.email.configuration`, The default value for `configuration.email.configuration` is: 6 | 7 | ```javascript 8 | const wertiknodemailerDefaultConfiguration = { 9 | host: "smtp.ethereal.email", 10 | port: 587, 11 | secure: false, // true for 465, false for other ports 12 | auth: { 13 | user: testAccount.user, // generated ethereal user 14 | pass: testAccount.pass, // generated ethereal password 15 | }, 16 | }; 17 | ``` 18 | 19 | **Send Email** 20 | 21 | Wertik-js uses this method to send email by using mailer instance returned from https://github.com/Uconnect-Technologies/wertik-js/blob/master/src/framework/mailer/index.ts#L52. You can define your own custom send email method at `configuration.email.sendEmail`. For more please see https://github.com/Uconnect-Technologies/wertik-js/blob/master/src/main.ts#L33. 22 | -------------------------------------------------------------------------------- /src/framework/builtinModules/mail/index.ts: -------------------------------------------------------------------------------- 1 | import { get } from "lodash"; 2 | export default { 3 | name: "Mail", 4 | useDatabase: false, 5 | graphql: { 6 | schema: ` 7 | type EmailResponse { 8 | message: String 9 | } 10 | input SendEmailInput { 11 | template: String! 12 | templateVariablesJSONStringify: String 13 | cc: String 14 | subject: String! 15 | to: String! 16 | bcc: String 17 | } 18 | `, 19 | mutation: { 20 | schema: ` 21 | sendEmail(input: SendEmailInput): EmailResponse 22 | `, 23 | resolvers: { 24 | sendEmail: async (_: any, args: any, context: any, info: any) => { 25 | try { 26 | const sendEmail = context.wertik.sendEmail; 27 | const template = args.input.template; 28 | const variables = JSON.stringify( 29 | get(args, "input.templateVariablesJSONStringify", "{}") 30 | ); 31 | const transportVariables = { 32 | cc: args.input.cc, 33 | subject: args.input.subject, 34 | bcc: args.input.bcc, 35 | to: args.input.to, 36 | }; 37 | await sendEmail(template, variables, transportVariables); 38 | return { 39 | message: "Message Successfully", 40 | }; 41 | } catch (e) { 42 | throw new Error(e); 43 | } 44 | }, 45 | }, 46 | }, 47 | query: { 48 | schema: ``, 49 | resolvers: {}, 50 | }, 51 | }, 52 | restApi: { 53 | endpoints: [], 54 | }, 55 | }; 56 | -------------------------------------------------------------------------------- /src/framework/graphql/voyager/index.ts: -------------------------------------------------------------------------------- 1 | import { get } from "lodash"; 2 | import { defaultPort } from "../../helpers/index"; 3 | import { IConfiguration } from "src/framework/types/configuration"; 4 | import { successMessage } from "./../../logger/consoleMessages"; 5 | 6 | export default function (configuration: IConfiguration) { 7 | const port = get(configuration,'port',defaultPort) 8 | let html = ` 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | Wertik-js GraphQL voyager 18 | 19 | 20 |
Loading...
21 | 35 | 36 | 37 | `; 38 | return html; 39 | } 40 | -------------------------------------------------------------------------------- /src/framework/restApi/customApi.ts: -------------------------------------------------------------------------------- 1 | import { get, kebabCase } from "lodash"; 2 | import restApiErrorResponse from "./restApiErrorResponse"; 3 | import restApiSuccessResponse from "./restApiSuccessResponse"; 4 | 5 | export default (expressApp, restApiEndpointsElement, module, configuration) => { 6 | const type = get(restApiEndpointsElement, "methodType", "get"); 7 | const handler = get(restApiEndpointsElement, "handler", null); 8 | const onCustomApiFailure = get(configuration, "restApi.onCustomApiFailure", null); 9 | const path = get(restApiEndpointsElement, "path", ""); 10 | const types = ["get", "post", "put", "delete", "copy", "head", "options", "link", "unlink", "purge", "lock", "unlock", "view"]; 11 | 12 | if (types.indexOf(type) > -1 && path.length > 0) { 13 | let apiPath = `${path}`; 14 | let find = "//"; 15 | let re = new RegExp(find, "g"); 16 | apiPath = apiPath.replace(re, "/"); 17 | expressApp[type](apiPath, async function (req, res) { 18 | try { 19 | await handler(req, res, restApiSuccessResponse, restApiErrorResponse); 20 | } catch (e) { 21 | if (onCustomApiFailure) { 22 | onCustomApiFailure({ 23 | path: apiPath, 24 | code: 500, 25 | err: e, 26 | res: res, 27 | data: {}, 28 | }); 29 | } else { 30 | restApiErrorResponse({ 31 | path: apiPath, 32 | code: 500, 33 | err: e, 34 | res: res, 35 | data: {}, 36 | }); 37 | } 38 | } 39 | }); 40 | } else { 41 | console.warn(`On module ${module.name}, Api endpoint ${path}, has undefined method type ${type}`); 42 | } 43 | }; 44 | -------------------------------------------------------------------------------- /src/framework/database/connect.ts: -------------------------------------------------------------------------------- 1 | import { get } from "lodash"; 2 | import { Sequelize } from 'sequelize' 3 | 4 | import { successMessage } from "../logger/consoleMessages"; 5 | import { IConfigurationDatabase } from "../types/configuration"; 6 | import { databaseDefaultOptions } from "../defaults/options/index"; 7 | 8 | export default async function (database: IConfigurationDatabase) { 9 | return new Promise(async (resolve, reject) => { 10 | try { 11 | let DATABASE_INSTANCE; 12 | let dialect = database.dbDialect; 13 | let options; 14 | if (dialect == "postgres") { 15 | options = get(database, "dbInitializeOptions", databaseDefaultOptions.postgres.dbInitializeOptions); 16 | DATABASE_INSTANCE = new Sequelize(`${database.dbName}`, database.dbUsername, database.dbPassword, { 17 | dialect: "postgres", 18 | host: database.dbHost, 19 | port: database.dbPort, 20 | ...options, 21 | }); 22 | } else if (dialect === "mysql") { 23 | options = get(database, "dbInitializeOptions", databaseDefaultOptions.sql.dbInitializeOptions); 24 | DATABASE_INSTANCE = new Sequelize(`${database.dbName}`, database.dbUsername, database.dbPassword, { 25 | dialect: "mysql", 26 | host: database.dbHost, 27 | port: database.dbPort, 28 | ...options, 29 | }); 30 | } 31 | DATABASE_INSTANCE.authenticate() 32 | .then(() => { 33 | successMessage(`Database: Successfully Connected!`); 34 | }) 35 | .catch((e) => { 36 | console.log(e); 37 | process.exit(); 38 | }); 39 | resolve(DATABASE_INSTANCE); 40 | } catch (e) { 41 | reject(e); 42 | } 43 | }); 44 | } 45 | -------------------------------------------------------------------------------- /src/framework/builtinModules/role/index.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | name: "Role", 3 | graphql: { 4 | schema: ` 5 | type Role { 6 | id: Int 7 | name: String 8 | default_permissions: String 9 | created_by: User 10 | created_by_id: Int 11 | is_deleted: Boolean 12 | created_at: String 13 | updated_at: String 14 | user_roles: UserRoleList 15 | role_permissions: RolePermissionList 16 | } 17 | input RoleInput { 18 | id: Int 19 | default_permissions: String 20 | created_by_id: Int 21 | name: String 22 | } 23 | `, 24 | mutation: { 25 | schema: ``, 26 | resolvers: {}, 27 | }, 28 | query: { 29 | schema: ``, 30 | resolvers: {}, 31 | }, 32 | }, 33 | restApi: {}, 34 | database: { 35 | selectIgnoreFields: ["user_roles", "role_permissions", "created_by"], 36 | relationships: { 37 | oneToMany: { 38 | UserRole: { 39 | graphqlName: "user_roles", 40 | foreignKey: "role_id", 41 | }, 42 | RolePermission: { 43 | graphqlName: "role_permissions", 44 | foreignKey: "role_id", 45 | }, 46 | }, 47 | oneToOne: { 48 | User: { 49 | graphqlName: "created_by", 50 | foreignKey: "id", 51 | relationColumn: "created_by_id", 52 | }, 53 | }, 54 | }, 55 | sql: { 56 | tableName: "role", 57 | fields: { 58 | name: { 59 | type: "STRING", 60 | unique: true, 61 | }, 62 | default_permissions: { 63 | type: "STRING", 64 | }, 65 | is_deleted: { 66 | type: "INTEGER", 67 | }, 68 | created_by_id: { 69 | type: "INTEGER", 70 | }, 71 | }, 72 | }, 73 | }, 74 | }; 75 | -------------------------------------------------------------------------------- /src/framework/builtinModules/userRole/index.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | name: "UserRole", 3 | graphql: { 4 | schema: ` 5 | type UserRole { 6 | id: Int 7 | name: String 8 | user: User 9 | user_id: Int 10 | role: Role 11 | role_id: Int 12 | created_by: User 13 | created_by_id: Int 14 | created_at: String 15 | updated_at: String 16 | } 17 | input UserRoleInput { 18 | id: Int 19 | name: String 20 | user_id: Int 21 | role_id: Int 22 | created_by_id: Int 23 | } 24 | `, 25 | customResolvers: {}, 26 | mutation: { 27 | schema: ``, 28 | resolvers: {}, 29 | }, 30 | query: { 31 | schema: ``, 32 | resolvers: {}, 33 | }, 34 | }, 35 | 36 | restApi: {}, 37 | database: { 38 | selectIgnoreFields: ["user", "role", "created_by"], 39 | relationships: { 40 | oneToOne: { 41 | User: [ 42 | { 43 | relationColumn: "user_id", 44 | graphqlName: "user", 45 | foreignKey: "id", 46 | }, 47 | { 48 | relationColumn: "created_by_id", 49 | graphqlName: "created_by", 50 | foreignKey: "id", 51 | }, 52 | ], 53 | Role: { 54 | relationColumn: "role_id", 55 | graphqlName: "role", 56 | foreignKey: "id", 57 | }, 58 | }, 59 | }, 60 | sql: { 61 | tableName: "userRole", 62 | fields: { 63 | name: { 64 | type: "STRING", 65 | }, 66 | user_id: { 67 | type: "INTEGER", 68 | }, 69 | role_id: { 70 | type: "INTEGER", 71 | }, 72 | is_deleted: { 73 | type: "INTEGER", 74 | }, 75 | created_by_id: { 76 | type: "INTEGER", 77 | }, 78 | }, 79 | }, 80 | }, 81 | }; 82 | -------------------------------------------------------------------------------- /src/framework/defaults/options/index.ts: -------------------------------------------------------------------------------- 1 | import Sequelize from "sequelize"; 2 | export const databaseDefaultOptions = { 3 | postgres: { 4 | dbInitializeOptions: { 5 | logging: false, 6 | operatorsAliases: "0", 7 | native: true 8 | }, 9 | }, 10 | sql: { 11 | dbInitializeOptions: { 12 | logging: false, 13 | operatorsAliases: "0", 14 | underscored: false, 15 | freetableName: true, 16 | }, 17 | defaultTableOptions: { 18 | timestamps: false, 19 | paranoid: false, 20 | underscored: false, 21 | freezeTableName: true, 22 | }, 23 | timestamps: { 24 | created_at: { 25 | type: "TIMESTAMP", 26 | defaultValue: Sequelize.literal("CURRENT_TIMESTAMP"), 27 | allowNull: false, 28 | }, 29 | updated_at: { 30 | type: "TIMESTAMP", 31 | defaultValue: Sequelize.literal("CURRENT_TIMESTAMP"), 32 | allowNull: false, 33 | }, 34 | }, 35 | }, 36 | }; 37 | 38 | export const defaultSocketOptions = { 39 | perMessageDeflate: { 40 | zlibDeflateOptions: { 41 | // See zlib defaults. 42 | chunkSize: 1024, 43 | memLevel: 7, 44 | level: 3, 45 | }, 46 | zlibInflateOptions: { 47 | chunkSize: 10 * 1024, 48 | }, 49 | // Other options settable: 50 | clientNoContextTakeover: true, // Defaults to negotiated value. 51 | serverNoContextTakeover: true, // Defaults to negotiated value. 52 | serverMaxWindowBits: 10, // Defaults to negotiated value. 53 | // Below options specified as default values. 54 | concurrencyLimit: 10, // Limits zlib concurrency for perf. 55 | threshold: 1024, // Size (in bytes) below which messages 56 | }, 57 | }; 58 | 59 | export const defaultApolloGraphqlOptions = { 60 | cacheControl: { 61 | defaultMaxAge: 0, 62 | }, 63 | tracing: true, 64 | subscriptions: { 65 | path: "/subscriptions", 66 | }, 67 | }; 68 | -------------------------------------------------------------------------------- /src/framework/graphql/generalSchema.ts: -------------------------------------------------------------------------------- 1 | export default ` 2 | input StringFilterInput { 3 | _eq: String 4 | _ne: String 5 | _like: String 6 | _notLike: String 7 | _iLike: String 8 | _notILike: String 9 | _startsWith: String 10 | _endsWith: String 11 | _substring: String 12 | _regexp: String 13 | _notRegexp: String 14 | _iRegexp: String 15 | _notIRegexp: String 16 | _or: StringFilterInput 17 | _and: StringFilterInput 18 | } 19 | input IntFilterInput { 20 | _eq: Int 21 | _gt: Int 22 | _gte: Int 23 | _lt: Int 24 | _lte: Int 25 | _ne: Int 26 | _between: [Int] 27 | _notBetween: [Int] 28 | _in: [Int] 29 | _notIn: [Int] 30 | _or: IntFilterInput 31 | _and: IntFilterInput 32 | } 33 | input DateFilterInput { 34 | _eq: String 35 | _gt: String 36 | _gte: String 37 | _in: [String!] 38 | _lt: String 39 | _lte: String 40 | _neq: String 41 | _nin: [String!] 42 | } 43 | input BooleanFilterInput { 44 | _eq: Boolean 45 | _ne: Boolean 46 | } 47 | 48 | type ModuleStats { 49 | total_count: Int 50 | total_created_this_month: Int 51 | total_created_this_week: Int 52 | total_created_last_7_days: Int 53 | total_created_today: Int 54 | total_created_last_month: Int 55 | total_created_last_90_days: Int 56 | total_created_last_year: Int 57 | total_created_this_year: Int 58 | } 59 | type Sorting { 60 | column: String 61 | type: String 62 | } 63 | input SortingInput { 64 | column: String 65 | type: String 66 | } 67 | input PaginationInput { 68 | page: Int 69 | limit: Int 70 | } 71 | input FilterInput { 72 | column: String! 73 | operator: String! 74 | value: String! 75 | } 76 | type Pagination { 77 | page: Int 78 | limit: Int 79 | } 80 | type Filter { 81 | column: String 82 | operator: String 83 | value: String 84 | } 85 | type PaginationProperties { 86 | total: Int 87 | pages: Int 88 | page: Int 89 | nextPage: Int 90 | previousPage: Int 91 | } 92 | `; 93 | -------------------------------------------------------------------------------- /src/framework/builtinModules/rolePermission/index.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | name: "RolePermission", 3 | graphql: { 4 | schema: ` 5 | type RolePermission { 6 | id: Int 7 | name: String 8 | role: Role 9 | role_id: Int 10 | permission: Permission 11 | permission_id: Int 12 | created_by: User 13 | created_by_id: Int 14 | created_at: String 15 | updated_at: String 16 | } 17 | input RolePermissionInput { 18 | id: Int 19 | name: String 20 | role_id: Int 21 | permission_id: Int 22 | created_by_id: Int 23 | } 24 | `, 25 | customResolvers: {}, 26 | mutation: { 27 | schema: ``, 28 | resolvers: {}, 29 | }, 30 | query: { 31 | schema: ``, 32 | resolvers: {}, 33 | }, 34 | }, 35 | restApi: {}, 36 | database: { 37 | selectIgnoreFields: ["permission", "role","created_by"], 38 | relationships: { 39 | oneToOne: { 40 | Permission: { 41 | relationColumn: "permission_id", 42 | graphqlName: "permission", 43 | foreignKey: "id", 44 | }, 45 | Role: { 46 | relationColumn: "role_id", 47 | graphqlName: "role", 48 | foreignKey: "id", 49 | }, 50 | User: { 51 | graphqlName: "created_by", 52 | foreignKey: "id", 53 | relationColumn: "created_by_id", 54 | }, 55 | }, 56 | }, 57 | sql: { 58 | tableName: "rolePermission", 59 | fields: { 60 | name: { 61 | type: "STRING", 62 | }, 63 | role_id: { 64 | type: "INTEGER", 65 | }, 66 | permission_id: { 67 | type: "INTEGER", 68 | }, 69 | is_deleted: { 70 | type: "INTEGER", 71 | }, 72 | created_by_id: { 73 | type: "INTEGER", 74 | }, 75 | }, 76 | }, 77 | }, 78 | }; 79 | -------------------------------------------------------------------------------- /src/framework/builtinModules/userPermission/index.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | name: "UserPermission", 3 | graphql: { 4 | schema: ` 5 | type UserPermission { 6 | id: Int 7 | name: String 8 | user: User 9 | user_id: Int 10 | permission: Permission 11 | permission_id: Int 12 | created_by: User 13 | created_by_id: Int 14 | created_at: String 15 | updated_at: String 16 | } 17 | input UserPermissionInput { 18 | id: Int 19 | name: String 20 | user_id: Int 21 | permission_id: Int 22 | } 23 | `, 24 | customResolvers: {}, 25 | mutation: { 26 | schema: ``, 27 | resolvers: {}, 28 | }, 29 | query: { 30 | schema: ``, 31 | resolvers: {}, 32 | }, 33 | }, 34 | restApi: {}, 35 | database: { 36 | selectIgnoreFields: ["user", "permission", "created_by"], 37 | relationships: { 38 | oneToOne: { 39 | User: [ 40 | { 41 | relationColumn: "created_by_id", 42 | graphqlName: "created_by", 43 | foreignKey: "id", 44 | }, 45 | { 46 | relationColumn: "user_id", 47 | graphqlName: "user", 48 | foreignKey: "id", 49 | }, 50 | ], 51 | Permission: { 52 | relationColumn: "permission_id", 53 | graphqlName: "permission", 54 | foreignKey: "id" 55 | }, 56 | }, 57 | }, 58 | sql: { 59 | tableName: "userPermission", 60 | fields: { 61 | name: { 62 | type: "STRING", 63 | }, 64 | user_id: { 65 | type: "INTEGER", 66 | }, 67 | permission_id: { 68 | type: "INTEGER", 69 | }, 70 | is_deleted: { 71 | type: "INTEGER", 72 | }, 73 | created_by_id: { 74 | type: "INTEGER", 75 | }, 76 | }, 77 | }, 78 | }, 79 | }; 80 | -------------------------------------------------------------------------------- /src/framework/builtinModules/permission/index.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | name: "Permission", 3 | graphql: { 4 | schema: ` 5 | type Permission { 6 | id: Int 7 | name: String 8 | cant: String 9 | can: String 10 | created_by: User 11 | created_by_id: Int 12 | created_at: String 13 | updated_at: String 14 | user_permissions: UserPermissionList 15 | role_permissions: RolePermissionList 16 | } 17 | input PermissionInput { 18 | id: Int 19 | name: String 20 | cant: String 21 | can: String 22 | created_by_id: Int 23 | } 24 | 25 | `, 26 | mutation: { 27 | schema: ``, 28 | resolvers: {}, 29 | }, 30 | query: { 31 | schema: ``, 32 | resolvers: {}, 33 | }, 34 | }, 35 | restApi: {}, 36 | database: { 37 | selectIgnoreFields: ["user_permissions", "role_permissions", "created_by"], 38 | relationships: { 39 | oneToMany: { 40 | UserPermission: { 41 | graphqlName: "user_permissions", 42 | foreignKey: "permission_id", 43 | }, 44 | RolePermission: { 45 | graphqlName: "role_permissions", 46 | foreignKey: "permission_id" 47 | }, 48 | }, 49 | oneToOne: { 50 | User: { 51 | graphqlName: "created_by", 52 | foreignKey: "id", 53 | relationColumn: "created_by_id", 54 | }, 55 | }, 56 | }, 57 | sql: { 58 | tableName: "permission", 59 | fields: { 60 | name: { 61 | type: "STRING", 62 | unique: true, 63 | }, 64 | cant: { 65 | type: "STRING", 66 | }, 67 | can: { 68 | type: "STRING", 69 | }, 70 | is_deleted: { 71 | type: "INTEGER", 72 | }, 73 | created_by_id: { 74 | type: "INTEGER", 75 | }, 76 | }, 77 | }, 78 | }, 79 | }; 80 | -------------------------------------------------------------------------------- /src/framework/types/override.ts: -------------------------------------------------------------------------------- 1 | export interface IBuiltinModuleOverride { 2 | graphql: { 3 | mutation: { 4 | create: Function; 5 | update: Function; 6 | delete: Function; 7 | softDelete: Function; 8 | bulkCreate: Function; 9 | bulkDelete: Function; 10 | bulkUpdate: Function; 11 | save: Function; 12 | }; 13 | query: { 14 | list: Function; 15 | view: Function; 16 | byId: Function; 17 | }; 18 | }; 19 | restApi: { 20 | create: Function; 21 | update: Function; 22 | delete: Function; 23 | bulkCreate: Function; 24 | bulkDelete: Function; 25 | bulkUpdate: Function; 26 | list: Function; 27 | view: Function; 28 | save: Function; 29 | }; 30 | } 31 | 32 | export interface IBuiltinModuleOverrideAuth { 33 | graphql: { 34 | mutation: { 35 | login: Function; 36 | signup: Function; 37 | twoFactorLoginValidate: Function; 38 | loginWithAccessToken: Function; 39 | activateAccount: Function; 40 | refreshToken: Function; 41 | }; 42 | }; 43 | restApi: { 44 | login: Function; 45 | signup: Function; 46 | twoFactorLoginValidate: Function; 47 | loginWithAccessToken: Function; 48 | activateAccount: Function; 49 | refreshToken: Function; 50 | }; 51 | } 52 | export interface IBuiltinModuleOverrideForgetPassword { 53 | graphql: { 54 | mutation: { 55 | requestPasswordReset: Function; 56 | resetPassword: Function; 57 | }; 58 | }; 59 | restApi: { 60 | requestPasswordReset: Function; 61 | resetPassword: Function; 62 | }; 63 | } 64 | 65 | export interface IConfigurationOverride { 66 | ForgetPassword: IBuiltinModuleOverrideForgetPassword; 67 | Role: IBuiltinModuleOverride; 68 | UserPermission: IBuiltinModuleOverride; 69 | Permission: IBuiltinModuleOverride; 70 | UserRole: IBuiltinModuleOverride; 71 | RolePermission: IBuiltinModuleOverride; 72 | User: IBuiltinModuleOverride; 73 | Auth: IBuiltinModuleOverrideAuth; 74 | } 75 | -------------------------------------------------------------------------------- /src/framework/security/getUserAllPermissions.ts: -------------------------------------------------------------------------------- 1 | export default async function(userId, database) { 2 | let sqlQuery = ` 3 | SELECT permissionTable.id AS permission_id, 4 | permissionTable.NAME AS permission_name, 5 | permissionTable.can AS permission_can, 6 | permissionTable.cant AS permission_cant, 7 | role_permissionTable.id AS rolepermission_id, 8 | role_permissionTable.role AS rolepermission_role, 9 | role_permissionTable.permission AS rolepermission_permission, 10 | role_permissionTable.NAME AS rolepermission_name, 11 | userpermissionTable.id AS userpermission_id, 12 | userpermissionTable.USER AS userpermission_user, 13 | userpermissionTable.permission AS userpermission_permission, 14 | roleTable.id AS role_id, 15 | roleTable.NAME AS role_name, 16 | userroleTable.id AS userrole_id, 17 | userroleTable.NAME AS userrole_name, 18 | userroleTable.USER AS userrole_user, 19 | userroleTable.role AS userrole_role 20 | FROM permission AS permissionTable 21 | LEFT JOIN role_permission AS role_permissionTable 22 | ON permissionTable.id = role_permissionTable.permission 23 | LEFT JOIN user_permission AS userpermissionTable 24 | ON permissionTable.id = userpermissionTable.permission 25 | LEFT JOIN role AS roleTable 26 | ON role_permissionTable.role = roleTable.id 27 | LEFT JOIN user_role AS userroleTable 28 | ON userroleTable.id = role_permissionTable.role 29 | WHERE userroleTable.USER = _________user_ID 30 | OR userpermissionTable.USER = _________user_ID 31 | `; 32 | sqlQuery = sqlQuery.replace(/_________user_ID/g, userId + ""); 33 | const permissions = await database.query(sqlQuery, { type: database.QueryTypes.SELECT }); 34 | return permissions; 35 | } 36 | -------------------------------------------------------------------------------- /src/framework/graphql/index.ts: -------------------------------------------------------------------------------- 1 | // let { ApolloServer } = require("apollo-server"); 2 | import loadAllModules from "./loadAllModules" 3 | import { IGraphQLInitialize } from "./../types/servers"; 4 | import { get } from "lodash"; 5 | import voyager from "./voyager/index"; 6 | import { defaultApolloGraphqlOptions } from "../defaults/options/index"; 7 | const { ApolloServer } = require("apollo-server-express"); 8 | import * as auth from "./../helpers/auth" 9 | 10 | //expressApp,configuration,models,emailTemplates,sendEmail,database,WertikEventEmitter 11 | 12 | export default async function (options: IGraphQLInitialize) { 13 | const { mailerInstance, configuration, models, sendEmail, emailTemplates, database, socketio, logger } = options; 14 | const apolloGraphqlOptions = get(configuration, "graphql.apolloGraphqlServerOptions", defaultApolloGraphqlOptions); 15 | let initializeContext = get(configuration, "context.initializeContext", async function () {}); 16 | initializeContext = await initializeContext("graphql",{ 17 | models, 18 | database, 19 | }); 20 | const modules = await loadAllModules(configuration); 21 | const graphqlVoyager = voyager(configuration); 22 | const apollo = new ApolloServer({ 23 | typeDefs: modules.schema, 24 | resolvers: modules.resolvers, 25 | context: async ({ req, res, connection }) => { 26 | let cxt = { 27 | wertik: { 28 | database: database, 29 | auth: { 30 | helpers: auth, 31 | }, 32 | models, 33 | sendEmail: sendEmail, 34 | emailTemplates: emailTemplates, 35 | mailerInstance: mailerInstance, 36 | req, 37 | res, 38 | socketio, 39 | logger, 40 | initializeContext: initializeContext, 41 | configuration: configuration 42 | } 43 | }; 44 | let requestContext = await get(configuration.context, "requestContext", () => {})("graphql", cxt); 45 | cxt["requestContext"] = requestContext; 46 | return cxt; 47 | }, 48 | ...apolloGraphqlOptions, 49 | }); 50 | 51 | return { 52 | graphql: apollo, 53 | graphqlVoyager: graphqlVoyager, 54 | }; 55 | } 56 | -------------------------------------------------------------------------------- /src/framework/database/helpers/paginate.ts: -------------------------------------------------------------------------------- 1 | import {get} from "lodash" 2 | import convertFiltersIntoSequalizeObject from "./convertFiltersIntoSequalizeObject"; 3 | import { request } from "express"; 4 | import { removeColumnsFromAccordingToSelectIgnoreFields } from "../../helpers/index"; 5 | export default async function (model) { 6 | return function (args, requestedFields: any = []) { 7 | return new Promise(async (resolve, reject) => { 8 | try { 9 | if (!Array.isArray(requestedFields)) { 10 | console.error("Requested fields must be an array"); 11 | process.exit(); 12 | } 13 | let page = get(args, "pagination.page", 1); 14 | let limit = get(args, "pagination.limit", 10); 15 | let filters = get(args, "filters", {}); 16 | let sorting = get(args, "sorting", []); 17 | let offset = limit * (page - 1); 18 | let convertedFilters = await convertFiltersIntoSequalizeObject(filters); 19 | 20 | requestedFields = removeColumnsFromAccordingToSelectIgnoreFields( 21 | requestedFields, 22 | model.selectIgnoreFields 23 | ); 24 | 25 | let mainObject = { 26 | order: sorting.map((c) => { 27 | return [c.column, c.type]; 28 | }), 29 | attributes: requestedFields, 30 | }; 31 | if (mainObject.attributes.length === 0) { 32 | delete mainObject["attributes"]; 33 | } 34 | if (mainObject.order.length == 0) { 35 | delete mainObject["order"]; 36 | } 37 | const list = await model.findAndCountAll({ 38 | offset: offset, 39 | limit: limit, 40 | where: convertedFilters, 41 | ...mainObject, 42 | }); 43 | resolve({ 44 | filters, 45 | pagination: { page, limit }, 46 | list: list.rows, 47 | paginationProperties: { 48 | total: list.count, 49 | nextPage: page + 1, 50 | page: page, 51 | previousPage: page == 1 ? 1 : page - 1, 52 | pages: Math.ceil(list.count / limit), 53 | }, 54 | }); 55 | } catch (error) { 56 | reject(error); 57 | } 58 | }); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/framework/moduleRelationships/database.ts: -------------------------------------------------------------------------------- 1 | import { get } from "lodash"; 2 | import { IConfigurationCustomModule } from "../types/configuration"; 3 | 4 | export const applyRelationshipSql = ( 5 | module: IConfigurationCustomModule, 6 | tables: { 7 | [key: string]: any; 8 | } 9 | ) => { 10 | let relationships = get(module, "database.relationships", null); 11 | if (relationships) { 12 | const oneToOne = get(relationships, "oneToOne", {}); 13 | const oneToMany = get(relationships, "oneToMany", {}); 14 | const currentModel = tables[module.name]; 15 | Object.keys(oneToMany).forEach((key) => { 16 | const foreignModel = tables[key] 17 | const relationshipInfo = oneToMany[key]; 18 | if (relationshipInfo.constructor === Array) { 19 | relationshipInfo.forEach(relationshipInfoItem => { 20 | currentModel.hasMany(foreignModel, { 21 | as: relationshipInfoItem.graphqlName, 22 | foreignKey: relationshipInfoItem.foreignKey, 23 | sourceKey: relationshipInfoItem.relationColumn, 24 | ...get(relationshipInfoItem,'options',{}) 25 | }); 26 | }); 27 | } else { 28 | currentModel.hasMany(foreignModel, { 29 | as: relationshipInfo.graphqlName, 30 | foreignKey: relationshipInfo.foreignKey, 31 | sourceKey: relationshipInfo.relationColumn, 32 | ...get(relationshipInfo,'options',{}) 33 | }); 34 | } 35 | }); 36 | Object.keys(oneToOne).forEach((key) => { 37 | const foreignModel = tables[key] 38 | const relationshipInfo = oneToOne[key]; 39 | if (relationshipInfo.constructor === Array) { 40 | relationshipInfo.forEach(relationshipInfoItem => { 41 | currentModel.belongsTo(foreignModel, { 42 | as: relationshipInfoItem.graphqlName, 43 | foreignKey: relationshipInfoItem.foreignKey, 44 | sourceKey: relationshipInfoItem.relationColumn, 45 | ...get(relationshipInfoItem,'options',{}) 46 | }); 47 | }); 48 | } else { 49 | currentModel.belongsTo(foreignModel, { 50 | as: relationshipInfo.graphqlName, 51 | foreignKey: relationshipInfo.foreignKey, 52 | sourceKey: relationshipInfo.relationColumn, 53 | ...get(relationshipInfo,'options',{}) 54 | }); 55 | } 56 | }); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/framework/mailer/index.ts: -------------------------------------------------------------------------------- 1 | import handlebars from "handlebars"; 2 | import nodemailer from "nodemailer"; 3 | import { IConfiguration } from "../types/configuration"; 4 | import { get } from "lodash"; 5 | 6 | export const defaultMailerInstance = async function(configuration: IConfiguration) { 7 | let testAccount = await nodemailer.createTestAccount(); 8 | const wertiknodemailerDefaultConfiguration = { 9 | host: "smtp.ethereal.email", 10 | port: 587, 11 | secure: false, 12 | auth: { 13 | user: testAccount.user, 14 | pass: testAccount.pass 15 | } 16 | }; 17 | let transporterConfiguration = get(configuration, "email.configuration", wertiknodemailerDefaultConfiguration); 18 | let transporter = nodemailer.createTransport(transporterConfiguration); 19 | return transporter; 20 | }; 21 | 22 | export const sendEmail = function(configuration: IConfiguration, mailerInstance: any) { 23 | let userPassedSendEmail = get(configuration, "email.sendEmail", null); 24 | if (userPassedSendEmail !== null) { 25 | return userPassedSendEmail; 26 | } else { 27 | return async function(template: string, variables: any, credentials: any) { 28 | let transporter = mailerInstance; 29 | let compiled = handlebars.compile(template); 30 | let resultTemplate = compiled(variables); 31 | try { 32 | let send = await transporter.sendMail({ 33 | from: credentials.from, 34 | to: credentials.to, 35 | html: resultTemplate, 36 | subject: credentials.subject 37 | }); 38 | if (send && send.messageId) { 39 | console.log("Message sent: %s", send.messageId); 40 | } 41 | if (nodemailer && nodemailer.getTestMessageUrl) { 42 | console.log("Preview URL: %s", nodemailer.getTestMessageUrl(send)); 43 | } 44 | return send; 45 | } catch (e) { 46 | console.log(`Failed sending email: ${e.message}`); 47 | } 48 | }; 49 | } 50 | }; 51 | 52 | export default async function mailerInstance(configuration: IConfiguration) { 53 | let isDisabled = get(configuration,'email.disable', false); 54 | if (isDisabled === true) { 55 | return null; 56 | } 57 | let userPassedInstance = get(configuration, "email.defaultMailerInstance", null); 58 | if (userPassedInstance !== null) { 59 | return userPassedInstance; 60 | } else { 61 | return await defaultMailerInstance(configuration); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/framework/builtinModules/forgetPassword/handlers/index.ts: -------------------------------------------------------------------------------- 1 | import { ApolloError } from "apollo-server"; 2 | import createJwtToken from "./../../../security/createJwtToken"; 3 | import { randomString } from "./../../../helpers/index"; 4 | import moment from "moment"; 5 | import { generateHashPassword } from "../../../helpers/auth"; 6 | 7 | export const requestPasswordResetHandler = async function(obj) { 8 | const { userModel, forgetPasswordModel, data, emailTemplates, sendEmail } = obj; 9 | let user = await userModel.findOneByArgs({ 10 | email: data.email 11 | }); 12 | if (!user.instance) throw new ApolloError("No User found with such email."); 13 | let forgetPassword = await forgetPasswordModel.findOneByArgs({ 14 | email: data.email 15 | }); 16 | if (forgetPassword.instance) { 17 | await forgetPassword.delete(); 18 | } 19 | let token = randomString(24, "MYNAMEISILYASKARIMANDIVETHESENUMBERS123456789"); 20 | await forgetPasswordModel.create({ 21 | token: token, 22 | email: user.instance.email, 23 | user: user.instance.id, 24 | expiresIn: moment() 25 | .add(30, "m") 26 | .unix() 27 | }); 28 | await sendEmail( 29 | emailTemplates.requestPasswordReset, 30 | { 31 | email: user.instance.email, 32 | nextMinutes: moment() 33 | .add(30, "m") 34 | .format("LLLL"), 35 | token: token, 36 | siteName: process.env.name, 37 | frontendAppPasswordResetUrl: process.env.frontendAppPasswordResetUrl 38 | }, 39 | { 40 | from: process.env.mailerServiceUsername, 41 | to: user.instance.email, 42 | subject: `Reset Password` 43 | } 44 | ); 45 | return { 46 | message: "Please check your email" 47 | }; 48 | }; 49 | 50 | export const resetPasswordHandler = async function(obj) { 51 | const { userModel, forgetPasswordModel, data } = obj; 52 | const { token, password, confirmPassword } = data; 53 | let forgetPassword = await forgetPasswordModel.findOneByArgs({ 54 | token: token 55 | }); 56 | if (!forgetPassword.instance) throw new ApolloError("Token mismatch or already used."); 57 | let user = await userModel.findOneByArgs({ 58 | email: forgetPassword.instance.email 59 | }); 60 | if (!user.instance) throw new ApolloError("User not found"); 61 | const hash = generateHashPassword(password); 62 | await user.update({ 63 | password: hash 64 | }); 65 | await forgetPassword.delete(); 66 | return { 67 | message: "Password changed" 68 | }; 69 | }; 70 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wertik-js", 3 | "version": "1.99.99", 4 | "main": "lib/main.js", 5 | "repository": "https://github.com/Uconnect-Technologies/wertik-js.git", 6 | "keywords": [ 7 | "mysql", 8 | "graphql", 9 | "nodejs", 10 | "boilerplate", 11 | "javascript", 12 | "starter-kit", 13 | "api", 14 | "saas", 15 | "saas-boilerplate", 16 | "concepts", 17 | "wertik-js", 18 | "database", 19 | "express-js", 20 | "express-graphql", 21 | "express-graphql-boilerplate" 22 | ], 23 | "homepage": "http://www.wapgee.com/wertik-js", 24 | "author": "Ilyas Karim ", 25 | "license": "MIT", 26 | "scripts": { 27 | "dev": "concurrently -n TS,DevServer \"npm run watch-ts\" \"nodemon lib/devServer.js\" ", 28 | "dev-without-nodemon": "concurrently \"npm run watch-ts\" \"node lib/devServer.js\" ", 29 | "build": "yarn tsc", 30 | "production": "node lib/devServer.js", 31 | "watch-ts": "tsc -w" 32 | }, 33 | "dependencies": { 34 | "apollo-server": "^2.8.2", 35 | "apollo-server-express": "^2.9.4", 36 | "aws-sdk": "^2.820.0", 37 | "bcryptjs": "^2.4.3", 38 | "body-parser": "^1.18.3", 39 | "chalk": "^3.0.0", 40 | "cors": "^2.8.5", 41 | "dropbox": "^8.2.0", 42 | "express": "^4.16.4", 43 | "graphql": "^14.1.1", 44 | "graphql-fields": "^2.0.3", 45 | "handlebars": "^4.5.3", 46 | "jsonwebtoken": "^8.4.0", 47 | "lodash": "^4.17.15", 48 | "log-symbols": "^3.0.0", 49 | "moment": "^2.23.0", 50 | "morgan": "^1.9.1", 51 | "multer": "^1.4.2", 52 | "mysql2": "^1.6.4", 53 | "mysqldump": "^3.2.0", 54 | "node-cron": "^2.0.3", 55 | "nodemailer": "^5.0.0", 56 | "pg": "^7.18.2", 57 | "pg-hstore": "^2.3.3", 58 | "request-ip": "^2.1.3", 59 | "sequelize": "^6.3.5", 60 | "shelljs": "^0.8.3", 61 | "socket.io": "^2.3.0", 62 | "winston": "^3.1.0" 63 | }, 64 | "devDependencies": { 65 | "@types/body-parser": "^1.16.8", 66 | "@types/cors": "^2.8.4", 67 | "@types/dotenv": "^4.0.3", 68 | "@types/express": "^4.16.0", 69 | "@types/http-codes": "^1.0.1", 70 | "@types/jsonwebtoken": "^7.2.8", 71 | "@types/knex": "^0.14.19", 72 | "@types/morgan": "^1.7.35", 73 | "@types/node": "^9.6.55", 74 | "@types/nodemailer": "^4.3.4", 75 | "@types/winston": "^2.4.4", 76 | "concurrently": "^3.6.1", 77 | "dotenv": "^6.2.0", 78 | "module-alias": "^2.1.0", 79 | "nodemon": "^1.18.9", 80 | "sequelize-cli": "^5.4.0", 81 | "tslint": "^5.17.0", 82 | "typescript": "^3.5.1" 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/framework/restApi/index.ts: -------------------------------------------------------------------------------- 1 | import { get } from "lodash"; 2 | import cors from "cors"; 3 | import bodyParser from "body-parser"; 4 | import morgan from "morgan"; 5 | 6 | import { IRestApiInitialize } from "./../types/servers"; 7 | import customApi from "./customApi"; 8 | import * as auth from "./../helpers/auth"; 9 | 10 | export default async function (options: IRestApiInitialize) { 11 | const { 12 | configuration, 13 | models, 14 | sendEmail, 15 | emailTemplates, 16 | expressApp, 17 | database, 18 | multerInstance, 19 | mailerInstance, 20 | socketio, 21 | logger, 22 | } = options; 23 | let initializeContext = get( 24 | configuration, 25 | "context.initializeContext", 26 | async function () {} 27 | ); 28 | const useCors = get(configuration, "restApi.useCors", true); 29 | const useBodyParser = get(configuration, "restApi.useBodyParser", true); 30 | const useMorgan = get(configuration, "restApi.useMorgan", true); 31 | initializeContext = await initializeContext("restApi", { 32 | models, 33 | expressApp, 34 | database, 35 | }); 36 | if (useCors) { 37 | expressApp.use(cors()); 38 | } 39 | if (useBodyParser) { 40 | expressApp.use(bodyParser.urlencoded({ extended: false })); 41 | expressApp.use(bodyParser.json()); 42 | } 43 | if (useMorgan) { 44 | expressApp.use(morgan("combined")); 45 | } 46 | expressApp.use(async function (req, res, next) { 47 | req.wertik = { 48 | database: database, 49 | auth: { 50 | helpers: auth, 51 | }, 52 | mailerInstance: mailerInstance, 53 | models: models, 54 | socketio: socketio, 55 | sendEmail: sendEmail, 56 | emailTemplates: emailTemplates, 57 | multerInstance: multerInstance, 58 | logger: logger, 59 | // requestContext: requestContext, 60 | initializeContext: initializeContext, 61 | configuration: configuration, 62 | }; 63 | 64 | let requestContext = await get( 65 | configuration.context, 66 | "requestContext", 67 | () => {} 68 | )("restApi", req); 69 | 70 | req.wertik.requestContext = requestContext; 71 | 72 | next(); 73 | }); 74 | 75 | require("./versions/v1/loadAllModules").default( 76 | expressApp, 77 | configuration, 78 | customApi 79 | ); 80 | 81 | expressApp.get("/w/info", (req, res) => { 82 | res.status(200).json({ 83 | message: "Welcome to wertik, You are successfully running Wertik.", 84 | version: require("./../../../package.json").version, 85 | }); 86 | }); 87 | 88 | return expressApp; 89 | } 90 | -------------------------------------------------------------------------------- /docs/getting-started/configuration.md: -------------------------------------------------------------------------------- 1 | # Wertik Configuration 2 | 3 | This is the default configuration for Wertik used in source code. 4 | 5 | ```javascript 6 | export default { 7 | name: "Wertik", 8 | builtinModules: "user,auth,forgetPassword,permission,role,rolePermission,userPermission,userRole,me,storage", 9 | database: { 10 | dbDialect: "mysql", 11 | dbUsername: "root", 12 | dbPassword: "pass", 13 | dbName: "graphql", 14 | dbHost: "localhost", 15 | dbPort: "3306", 16 | }, 17 | frontendAppUrl: "http://localhost:8080/", 18 | frontendAppActivationUrl: "http://localhost:8080/activate-account", 19 | frontendAppPasswordResetUrl: "http://localhost:8080/reset-password", 20 | context: { 21 | data: { 22 | myName: "My powerful app", 23 | }, 24 | requestContext: async function (mode, context) { 25 | return { 26 | value: "Value 1", 27 | }; 28 | }, 29 | }, 30 | graphql: { 31 | disable: false, 32 | }, 33 | restApi: { 34 | onCustomApiFailure: function ({ path, res }) { 35 | res.send("failed at " + path); 36 | }, 37 | }, 38 | modules: [], // For modules please see http://wapgee.com/wertik-js/getting-started/custom-modules, 39 | events: { 40 | beforeGraphqlStart: function () { 41 | console.log("beforeGraphqlStart"); 42 | }, 43 | beforeRestApiStart: function () { 44 | console.log("beforeRestApiStart"); 45 | }, 46 | database: { 47 | Permission: { 48 | afterCreate() { 49 | console.log("permision created"); 50 | }, 51 | }, 52 | }, 53 | }, 54 | seeds: { 55 | Role: [{ name: "Admin" }, { name: "Kako" }], 56 | Permission: [ 57 | { name: "ca", cant: "true", can: "true" }, 58 | { name: "ca1", cant: "true1", can: "true1" }, 59 | { name: "ca2", cant: "true2", can: "true2" }, 60 | ], 61 | }, 62 | sockets: { 63 | disable: false, 64 | onClientConnected: function () { 65 | console.log("onClientConnected"); 66 | }, 67 | onMessageReceived: function () { 68 | console.log("onMessageReceived"); 69 | }, 70 | onClientDisconnect: function () { 71 | console.log("onClientDisconnect"); 72 | }, 73 | }, 74 | storage: { 75 | storageDirectory: "./storage/", 76 | }, 77 | }; 78 | ``` 79 | 80 | Now run your app and you see logs something like this if everything went fine 81 | 82 | √ [Wertik-js]: Socket.IO server running at http://localhost:5000 83 | √ [Wertik-js]: Rest API server started at http://localhost:5000 84 | √ [Wertik-js]: GraphQL Voyager running at http://localhost:5000/graphql-voyager 85 | √ [Wertik-js]: GraphQL Server started at http://localhost:5000/graphql 86 | √ [Wertik-js]: GraphQL Subscriptions are running at ws://localhost:5000/subscriptions 87 | √ [Wertik-js]: SQL: Database Connected 88 | 89 | For Dependencies you can check wertik-js package.json file. 90 | -------------------------------------------------------------------------------- /docs/context/introduction.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ### Context 5 | 6 | 7 | 8 | Context is an object that is passed to all resolvers while executing an operation in GraphQL, and same goes for Rest API, Context may contain data or functions. Like the current User data, Database Instance, Current Data Permissions and stuff like these. 9 | 10 | 11 | 12 | Wertik-js provides these built-in context properties 13 | 14 | 15 | 16 | - user: The current user 17 | 18 | - dbTables: All database tables, See Sequelize Model 19 | 20 | - models: All Wertik-js Models. 21 | 22 | - sendEmail: Send email method 23 | 24 | - emailTemplates: All email templates to be used in resolvers and http handlers. 25 | 26 | - userPermissions: All user permissions if user exists 27 | 28 | - userRoles: All user roles if user exists 29 | 30 | - mailerInstance: Mailer instance 31 | 32 | - req: Request Object 33 | 34 | - res: Res object to send custom response, Varies with GraphQL and RestAPI 35 | 36 | #### Create Context Handler 37 | If you want to pass a function that executes while context creation, You can pass a function in your configuration, This function supports async await. `configuration.context.requestContext`, When this function will be called, two paramters will be passed, 38 | 39 | - mode: This will be restApi or graphql, When this function runs in graphql then value will be graphql and for restApi it becomes restApi. 40 | - context: For GraphQL this value is context object and for restApi it is req object so that you can access current context 41 | 42 | At the end you have to return something from this function and that data will be available in your GraphQL resolver and RestAPI handler. 43 | 44 | For GraphQL resolver: 45 | 46 | ```javascript 47 | function (_, args, context,info) { 48 | console.log(context.requestContext) 49 | } 50 | ``` 51 | 52 | For RestAPI handler: 53 | 54 | ```javascript 55 | function (req,res) { 56 | console.log(req.requestContext) 57 | } 58 | ``` 59 | 60 | #### Using Context in Rest API 61 | Consider this rest api handler: 62 | ```javascript 63 | function (req,res) { 64 | // do something 65 | } 66 | ``` 67 | context is available in req.[context_name]. 68 | #### Using Context in GraphQL 69 | 70 | Consider this resolver in GraphQL: 71 | ```javascript 72 | function (_, args, context,info) { 73 | // do something 74 | } 75 | ``` 76 | You can access wertik context through context.[context_name]. 77 | 78 | #### Sending Custom Context 79 | 80 | If you want to send custom context in an object, Then you can pass context by `configuration.context.data`, This property should return an object. 81 | 82 | In GraphQL resolver you can access it by 83 | 84 | ```javascript 85 | context.context 86 | ``` 87 | 88 | In RestAPI you can access it by 89 | 90 | ```javascript 91 | req.context 92 | ``` 93 | 94 | If you have any questions regarding context, Please open an issue on our repo [https://github.com/Uconnect-Technologies/wertik-js/](https://github.com/Uconnect-Technologies/wertik-js/) -------------------------------------------------------------------------------- /src/framework/builtinModules/user/index.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | name: "User", 3 | graphql: { 4 | schema: ` 5 | type User { 6 | id: Int 7 | name: String 8 | username: String 9 | refresh_token: String 10 | access_token: String 11 | is_activated: Boolean 12 | activated_on: String 13 | two_factor_code: String 14 | is_super_user: Boolean 15 | activation_token: String 16 | email: String 17 | gender: String 18 | referer: String 19 | created_at: String 20 | updated_at: String 21 | user_roles: UserRoleList 22 | user_permissions: UserPermissionList 23 | } 24 | input UserInput { 25 | id: Int 26 | name: String 27 | username: String 28 | refresh_token: String 29 | access_token: String 30 | is_activated: Boolean 31 | activated_on: String 32 | two_factor_code: String 33 | is_super_user: Boolean 34 | activation_token: String 35 | email: String 36 | password: String 37 | gender: String 38 | referer: String 39 | } 40 | input UserSignupInput { 41 | email: String 42 | password: String 43 | confirmPassword: String 44 | } 45 | `, 46 | mutation: { 47 | schema: ``, 48 | resolvers: {}, 49 | }, 50 | query: { 51 | schema: ``, 52 | resolvers: {}, 53 | }, 54 | }, 55 | restApi: {}, 56 | database: { 57 | selectIgnoreFields: ["user_permissions", "user_roles"], 58 | relationships: { 59 | oneToMany: { 60 | UserRole: { 61 | graphqlName: "user_roles", 62 | foreignKey: "user_id", 63 | }, 64 | UserPermission: { 65 | graphqlName: "user_permissions", 66 | foreignKey: "user_id", 67 | }, 68 | }, 69 | }, 70 | sql: { 71 | tableName: "user", 72 | fields: { 73 | name: { 74 | type: "STRING", 75 | }, 76 | username: { 77 | type: "String", 78 | }, 79 | refresh_token: { 80 | type: "String", 81 | }, 82 | access_token: { 83 | type: "String", 84 | }, 85 | is_activated: { 86 | type: "BOOLEAN", 87 | }, 88 | activated_on: { 89 | type: "String", 90 | }, 91 | two_factor_code: { 92 | type: "String", 93 | }, 94 | is_super_user: { 95 | type: "BOOLEAN", 96 | }, 97 | activation_token: { 98 | type: "String", 99 | }, 100 | email: { 101 | type: "String", 102 | unique: true, 103 | }, 104 | password: { 105 | type: "String", 106 | }, 107 | gender: { 108 | type: "String", 109 | }, 110 | referer: { 111 | type: "String", 112 | }, 113 | is_deleted: { 114 | type: "INTEGER", 115 | }, 116 | }, 117 | }, 118 | }, 119 | }; 120 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Wertik JS 2 | 3 | 4 | Wertik is a **💪 GraphQL + Rest API** framework to build servers that gives you support for GraphQL, Rest Api, Emailing, Storage, Sockets and Cron Jobs feature. 5 | 6 | 7 |
8 | 9 | 10 | 11 |
12 | 13 | ### Installation 14 | 15 | To install you can use npm or yarn, To install with npm: 16 | 17 | ``` 18 | 19 | npm install --save wertik-js 20 | 21 | ``` 22 | 23 | Install with yarn 24 | 25 | ``` 26 | 27 | yarn add wertik-js --dev 28 | 29 | ``` 30 | 31 | ### Setting up server 32 | 33 | Lets setup an app: 34 | 35 | ```javascript 36 | import { connectDatabase, serve } from "wertik-js"; 37 | 38 | import configuration from "./path/to/your/configuration"; 39 | 40 | connectDatabase(configuration.database) 41 | .then((databaseInstance) => { 42 | configuration.databaseInstance = databaseInstance; 43 | serve(configuration).then((wertikApp: any) => { 44 | wertikApp.database.sync(); 45 | }); 46 | }) 47 | .catch((e) => { 48 | console.log(`Error connecting with database`); 49 | console.log(e); 50 | }); 51 | ``` 52 | 53 | If everything goes fine, you will see: 54 | 55 | ``` 56 | ✔ [Wertik-js]: Socket.IO server running at http://localhost:5000 57 | ✔ [Wertik-js]: Rest API server started at http://localhost:5000 58 | ✔ [Wertik-js]: GraphQL Voyager running at http://localhost:5000/graphql-voyager 59 | ✔ [Wertik-js]: GraphQL Server started at http://localhost:5000/graphql 60 | ✔ [Wertik-js]: GraphQL Subscriptions are running at ws://localhost:5000/subscriptions 61 | ✔ [Wertik-js]: Database: Successfully Connected! 62 | ``` 63 | 64 | **Note: 5000 is the default port** 65 | 66 | The above code example is taken from: [Dev Server Example](https://github.com/Uconnect-Technologies/wertik-js/blob/master/src/devServer.ts) 67 | 68 | ### Further Steps 69 | 70 | Now you have installed weritk-js in your app, now next step is to get familiar with the configuration to use. Let's go with the documentation flow: 71 | 72 | 1. Configuration 73 | 2. Database 74 | 3. Rest Api 75 | 4. GraphQL 76 | 5. Mailer 77 | 6. Sockets 78 | 7. Storage 79 | 8. Custom Modules 80 | 9. Cron Jobs 81 | 82 | The documentation is hosted at [http://wapgee.com/wertik-js](http://wapgee.com/wertik-js). 83 | 84 | ### Versioning 85 | 86 | Wertik-js follows semantic versioning (semver) principles. The version can be look like this, X.Y.Z, 87 | 88 | - **Z** When fixing bug we relase with chaning Z. For example: 1.2.1 to 1.2.2 89 | - **Y** When adding feature we release with changing Y, For example: 1.2.1 to 1.3.1 90 | - **Z** When adding breaking changes we made this release. For example: 1.2.1 to 2.2.1 91 | 92 | ### Contributing 93 | 94 | Pull Requests are welcome. If you think something is missing or needs to changed. Please open a pull request or new issue. 95 | 96 | ## License 97 | 98 | [MIT](https://choosealicense.com/licenses/mit/) 99 | -------------------------------------------------------------------------------- /src/framework/moduleRelationships/graphql.ts: -------------------------------------------------------------------------------- 1 | import { IConfigurationCustomModule } from "../types/configuration"; 2 | import { get } from "lodash"; 3 | import { identityColumn, removeColumnsFromAccordingToSelectIgnoreFields } from "../helpers/index"; 4 | import getRequestedFieldsFromResolverInfo from "./../helpers/getRequestedFieldsFromResolverInfo"; 5 | 6 | const processManyToManyRelationship = (relationshipInfo, key) => { 7 | return async (parentRow: any, args: any, context: any, info: any) => { 8 | let model = context.wertik.models[key] 9 | let parentRowValue = parentRow[identityColumn()].toString(); 10 | let requestedFields = getRequestedFieldsFromResolverInfo(info); 11 | if (!parentRowValue) { 12 | return null; 13 | } 14 | return await model.paginate( 15 | { 16 | filters: [ 17 | { 18 | column: relationshipInfo.foreignKey, 19 | operator: "=", 20 | value: parentRow[identityColumn()].toString(), 21 | }, 22 | ], 23 | }, 24 | Object.keys(requestedFields.list) 25 | ); 26 | }; 27 | }; 28 | 29 | const processOneToOneRelationship = (relationshipInfo, key) => { 30 | return async (parentRow: any, args: any, context: any, info: any) => { 31 | let requestedFields = getRequestedFieldsFromResolverInfo(info); 32 | let model = context.wertik.models[key]; 33 | let parentRowValue = parentRow[relationshipInfo.relationColumn]; 34 | if (!parentRowValue) { 35 | return null; 36 | } 37 | let a = await model.findOneByArgs( 38 | { 39 | [relationshipInfo.foreignKey]: parentRowValue, 40 | }, 41 | removeColumnsFromAccordingToSelectIgnoreFields( 42 | Object.keys(requestedFields), 43 | model.selectIgnoreFields 44 | ) 45 | ); 46 | return a.instance; 47 | }; 48 | }; 49 | 50 | export const GraphQLModuleRelationMapper = (module: IConfigurationCustomModule) => { 51 | let returnObject = {}; 52 | 53 | let relationships = get(module, "database.relationships", null); 54 | 55 | if (relationships) { 56 | const oneToOne = get(relationships, "oneToOne", {}); 57 | const oneToMany = get(relationships, "oneToMany", {}); 58 | Object.keys(oneToMany).forEach((key) => { 59 | const relationshipInfo = oneToMany[key]; 60 | if (relationshipInfo.constructor === Array) { 61 | relationshipInfo.forEach((relationshipInfoItem) => { 62 | returnObject[relationshipInfoItem.graphqlName] = processManyToManyRelationship(relationshipInfoItem, key); 63 | }); 64 | } else { 65 | returnObject[relationshipInfo.graphqlName] = processManyToManyRelationship(relationshipInfo, key); 66 | } 67 | }); 68 | Object.keys(oneToOne).forEach((key) => { 69 | const relationshipInfo = oneToOne[key]; 70 | if (relationshipInfo.constructor === Array) { 71 | relationshipInfo.forEach((relationshipInfoItem) => { 72 | returnObject[relationshipInfoItem.graphqlName] = processOneToOneRelationship(relationshipInfoItem, key); 73 | }); 74 | } else { 75 | returnObject[relationshipInfo.graphqlName] = processOneToOneRelationship(relationshipInfo, key); 76 | } 77 | }); 78 | } 79 | return returnObject; 80 | }; 81 | -------------------------------------------------------------------------------- /src/framework/builtinModules/storage/index.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | name: "Storage", 3 | graphql: { 4 | schema: ` 5 | type Storage { 6 | id: Int 7 | name: String 8 | filename: String 9 | size: String 10 | type: String 11 | folder: String 12 | created_by: User 13 | created_by_id: Int 14 | deleted: Boolean 15 | created_at: String 16 | updated_at: String 17 | } 18 | input StorageInput { 19 | id: Int 20 | name: String 21 | filename: String 22 | size: String 23 | type: String 24 | folder: String 25 | } 26 | `, 27 | mutation: { 28 | schema: ``, 29 | resolvers: {}, 30 | }, 31 | query: { 32 | schema: ``, 33 | resolvers: {}, 34 | }, 35 | }, 36 | 37 | restApi: { 38 | endpoints: [ 39 | { 40 | path: "/upload", 41 | methodType: "post", 42 | handler: async function (req, res, restApiSuccessResponse, restApiErrorResponse) { 43 | const upload = req.multerInstance.single("file"); 44 | upload(req, res, async function (err) { 45 | if (err) { 46 | restApiErrorResponse({ 47 | err: err, 48 | res: res, 49 | data: {}, 50 | }); 51 | return; 52 | } 53 | let object = { 54 | filename: req.file.filename, 55 | ...req.body, 56 | size: req.file.size, 57 | type: req.file.mimetype, 58 | }; 59 | let response = await req.wertik.models.Storage.create(object); 60 | response = response.instance; 61 | restApiSuccessResponse({ 62 | res: res, 63 | data: { 64 | storageInstance: response, 65 | info: { 66 | sizeUploaded: `${new String(req.file.size / 1024 / 1024)}`.substring(0, 6) + `MB`, 67 | disk: "default", 68 | }, 69 | directory: req.file.path, 70 | }, 71 | message: `File successfully uploaded`, 72 | }); 73 | }); 74 | }, 75 | }, 76 | ], 77 | }, 78 | database: { 79 | selectIgnoreFields: ["created_by"], 80 | relationships: { 81 | oneToOne: { 82 | User: { 83 | relationColumn: "created_by_id", 84 | graphqlName: "created_by", 85 | foreignKey: "id", 86 | }, 87 | }, 88 | }, 89 | sql: { 90 | tableName: "storage", 91 | fields: { 92 | name: { 93 | type: "STRING", 94 | }, 95 | filename: { 96 | type: "STRING", 97 | unique: true, 98 | }, 99 | size: { 100 | type: "STRING", 101 | }, 102 | folder: { 103 | type: "STRING", 104 | }, 105 | type: { 106 | type: "STRING", 107 | }, 108 | is_deleted: { 109 | type: "INTEGER", 110 | }, 111 | created_by_id: { 112 | type: "INTEGER", 113 | }, 114 | }, 115 | }, 116 | }, 117 | }; -------------------------------------------------------------------------------- /src/framework/initialization/startServers.ts: -------------------------------------------------------------------------------- 1 | import { successMessage } from "./../logger/consoleMessages"; 2 | import { get } from "lodash"; 3 | import { defaultPort } from "../helpers/index"; 4 | import { IConfiguration } from "../types/configuration"; 5 | import { isFunction } from "lodash"; 6 | 7 | export default function (configuration: IConfiguration, servers: any) { 8 | const startServers = get(configuration, "startServers", null); 9 | 10 | if (isFunction(startServers)) { 11 | startServers(configuration, servers); 12 | } else { 13 | const { graphql, restApi, graphqlVoyager, httpServer } = servers; 14 | 15 | const expressAppPort = get(configuration, "port", defaultPort); 16 | const showWertik404Page = get( 17 | configuration, 18 | "restApi.showWertik404Page", 19 | true 20 | ); 21 | const restApi404Handler = get( 22 | configuration, 23 | "restApi.restApi404Handler", 24 | function (req, res) { 25 | res.status(404).json({ 26 | message: "Not found", 27 | data: { 28 | message: "Request page didn't found", 29 | }, 30 | }); 31 | } 32 | ); 33 | const restApiBeforeStart = get( 34 | configuration, 35 | "restApi.beforeStart", 36 | function () {} 37 | ); 38 | 39 | const graphqlPath = get(configuration, "graphql.path", "/graphql"); 40 | const graphqlVoyagerPath = get( 41 | configuration, 42 | "graphql.graphqlVoyagerPath", 43 | "/graphql-voyager" 44 | ); 45 | const disableGraphqlVoyager = get( 46 | configuration, 47 | "graphql.disableGraphqlVoyager", 48 | false 49 | ); 50 | 51 | const disableGraphql = get(configuration, "graphql.disable", false); 52 | const disableSockets = get(configuration, "sockets.disable", false); 53 | 54 | if (disableGraphqlVoyager === false) { 55 | restApi.get(graphqlVoyagerPath, (req, res) => res.send(graphqlVoyager)); 56 | } 57 | if (disableGraphql === false) { 58 | graphql.applyMiddleware({ app: restApi, path: graphqlPath }); 59 | graphql.installSubscriptionHandlers(httpServer); 60 | } 61 | 62 | restApiBeforeStart({ express: restApi }); 63 | 64 | if (showWertik404Page) { 65 | restApi.get("*", restApi404Handler); 66 | } 67 | httpServer.listen(expressAppPort, () => { 68 | if (disableSockets === false) { 69 | successMessage( 70 | `Socket.IO server running at`, 71 | `http://localhost:${expressAppPort}` 72 | ); 73 | } 74 | successMessage( 75 | `Rest API server started at`, 76 | `http://localhost:${expressAppPort}` 77 | ); 78 | if (disableGraphqlVoyager === false) { 79 | successMessage( 80 | `GraphQL Voyager running at`, 81 | `http://localhost:${expressAppPort}${graphqlVoyagerPath}` 82 | ); 83 | } 84 | if (disableGraphql === false) { 85 | successMessage( 86 | "GraphQL Server started at", 87 | `http://localhost:${expressAppPort}${graphql.graphqlPath}` 88 | ); 89 | successMessage( 90 | "GraphQL Subscriptions are running at", 91 | `ws://localhost:${expressAppPort}${graphql.subscriptionsPath}` 92 | ); 93 | } 94 | }); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/framework/database/loadTables.ts: -------------------------------------------------------------------------------- 1 | import { get, snakeCase } from "lodash"; 2 | import Sequelize from "sequelize"; 3 | import moment from "moment"; 4 | import { convertFieldsIntoSequelizeFields } from "./helpers/index"; 5 | import { errorMessage } from "../logger/consoleMessages"; 6 | import { databaseDefaultOptions } from "../defaults/options/index"; 7 | import { IConfigurationCustomModule, IConfiguration } from "../types/configuration"; 8 | import { applyRelationshipSql } from "../moduleRelationships/database"; 9 | import stats from "./helpers/stats"; 10 | import paginate from "./helpers/paginate"; 11 | 12 | const checkDatabaseOptions = (moduleName, tableName) => { 13 | if (moduleName && !tableName) { 14 | errorMessage(`Module ${moduleName} didn't provided table name. Exiting process.`); 15 | process.exit(); 16 | } 17 | }; 18 | 19 | export default function (connection, configuration: IConfiguration) { 20 | let dialect = process.env.dbDialect; 21 | let modules = process.env.builtinModules.split(","); 22 | modules = modules.filter((c) => c); 23 | modules = [...modules, ...get(configuration, "modules", [])]; 24 | let tables = {}; 25 | const processModule = (module: IConfigurationCustomModule) => { 26 | let moduleName = get(module, "name", ""); 27 | let useDatabase = get(module, "useDatabase", true); 28 | 29 | if (useDatabase) { 30 | if (dialect == "mysql" || dialect == "postgres") { 31 | let tableName = get(module, "database.sql.tableName", ""); 32 | checkDatabaseOptions(moduleName, tableName); 33 | let tableFields = convertFieldsIntoSequelizeFields(module.database.sql.fields); 34 | let tableOptions = get(module, "database.sql.tableOptions", databaseDefaultOptions.sql.defaultTableOptions); 35 | tables[moduleName] = connection.define( 36 | tableName, 37 | { 38 | ...tableFields, 39 | ...databaseDefaultOptions.sql.timestamps, 40 | }, 41 | { 42 | ...tableOptions, 43 | getterMethods: { 44 | created_at: function () { 45 | return moment(this.getDataValue("created_at")).format(); 46 | }, 47 | updated_at: function () { 48 | return moment(this.getDataValue("updated_at")).format(); 49 | }, 50 | }, 51 | } 52 | ); 53 | tables[moduleName].selectIgnoreFields = get(module,'database.selectIgnoreFields', []) 54 | } 55 | } 56 | }; 57 | modules.forEach((element) => { 58 | let module; 59 | if (element.constructor === String) { 60 | module = require(`./../builtinModules/${element}/index`).default; 61 | } else if (element.constructor === Object) { 62 | module = element; 63 | } 64 | processModule(module); 65 | }); 66 | 67 | // Apply relationships 68 | 69 | modules.forEach((element) => { 70 | let module; 71 | if (element.constructor === String) { 72 | module = require(`./../builtinModules/${element}/index`).default; 73 | } else if (element.constructor === Object) { 74 | module = element; 75 | } 76 | applyRelationshipSql(module, tables); 77 | }); 78 | 79 | Object.keys(tables).forEach( async table => { 80 | tables[table].stats = await stats(connection, tables[table]); 81 | tables[table].paginate = await paginate(tables[table]); 82 | }); 83 | 84 | return tables; 85 | } 86 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at ilyas.datoo@gmail.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /docs/events/introduction.md: -------------------------------------------------------------------------------- 1 | ### Events - Beta 2 | 3 | Events are so useful on backend server when you want do a follow. For example, Send email after blog is published to subscribers. In these case events are so useful. Let's start with an example to get more familiar with Wertik-js events. Consider you are using events for module Role, See module at https://github.com/Uconnect-Technologies/wertik-js/blob/master/src/framework/builtinModules/role/index.ts 4 | 5 | 6 | ### Database Events 7 | 8 | #### Configuration for Role events 9 | 10 | Lets see how you can setup event handlers for module Role. To see list of events please check this typescript file: https://github.com/Uconnect-Technologies/wertik-js/blob/master/src/framework/types/configuration.ts#L98. 11 | 12 | If you checked that file, Those are the list of all events that can be used with Role CRUD. So you can adjust Role module according to your needs without changing changing any thing. An example: 13 | 14 | ```javascript 15 | let configuration = { 16 | // rest of your configuration 17 | events: { 18 | database: { 19 | Role: { 20 | beforeCreate: function (mode, params) { 21 | // mode can be restApi or graphql 22 | // if mode == graphql params will contain graphql resolver arguments {parent, args, context, info} 23 | // if mode == graphql params will contain rest Api handler arguments {req, res} 24 | // While sugin beforeCreate event you to reutrn the arguments that is going to be used, like 25 | if (mode == "graphql") { 26 | return params.args.input; 27 | }else { 28 | return req.body.input 29 | } 30 | }, 31 | afterCreate: function (mode, params) { 32 | // same mode params goes as above 33 | // params will be same but another property will be added which will be instance that is created. 34 | console.log("Role created"); 35 | } 36 | } 37 | } 38 | } 39 | } 40 | ``` 41 | 42 | Let's see how to use events with Rest API and GraphQL 43 | 44 | #### Events with Rest API 45 | 46 | To use events, You can do something like this 47 | 48 | ```javascript 49 | let configuration = { 50 | //rest of your configuration 51 | events: { 52 | database: { 53 | Role: { 54 | beforeCreate: function (mode, params) { 55 | // mode == "restApi" 56 | // params == {req,res} 57 | if (mode == "graphql") { 58 | return params.args.input; 59 | }else { 60 | return req.body.input 61 | } 62 | }, 63 | afterCreate: function (mode, params) { 64 | // params == {req,res,instance} 65 | console.log("Created with restApi",); 66 | } 67 | } 68 | } 69 | } 70 | } 71 | ``` 72 | 73 | #### Events with GraphQL 74 | 75 | 76 | ```javascript 77 | let configuration = { 78 | //rest of your configuration 79 | events: { 80 | database: { 81 | Role: { 82 | beforeCreate: function (mode, params) { 83 | // mode == "graphql" 84 | // params == {_, args, context,info} 85 | if (mode == "graphql") { 86 | return params.args.input; 87 | }else { 88 | return req.body.input 89 | } 90 | }, 91 | afterCreate: function (mode, params) { 92 | // params == {_, args, context,info,instance} 93 | console.log("Created with graphql",); 94 | } 95 | } 96 | } 97 | } 98 | } 99 | ``` 100 | 101 | 102 | ### General Events 103 | 104 | **Coming Soon** -------------------------------------------------------------------------------- /src/framework/builtinModules/backup/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | dumpDatabase, 3 | dumpDatabaseToDigitalOcean, 4 | dumpDatabaseToDropbox, 5 | loadAllLocalBackups, 6 | } from "./helpers"; 7 | export default { 8 | name: "Backup", 9 | graphql: { 10 | schema: ` 11 | type Backup { 12 | id: Int 13 | name: String 14 | uploaded_to: String 15 | uploaded_filename: String 16 | created_at: String 17 | updated_at: String 18 | } 19 | input BackupInput { 20 | id: Int 21 | name: String 22 | uploaded_to: String 23 | uploaded_filename: String 24 | } 25 | type BackupSuccessResponse { 26 | message: String 27 | filename: String 28 | backup: Backup 29 | } 30 | `, 31 | mutation: { 32 | schema: ` 33 | backupLocal: BackupSuccessResponse 34 | backupDigitalOceanSpaces: BackupSuccessResponse 35 | backupDropbox: BackupSuccessResponse 36 | `, 37 | resolvers: { 38 | // fixme: continue progress 39 | backupLocal: async (_, args, context, info) => { 40 | let op = await dumpDatabase({ 41 | database: context.wertik.configuration.database, 42 | models: context.wertik.models, 43 | }); 44 | return { 45 | message: "Backup to Local Drive has been completed.", 46 | filename: op.filename, 47 | backup: op.backupInstance, 48 | }; 49 | }, 50 | backupDigitalOceanSpaces: async (_, args, context, info) => { 51 | let op = await dumpDatabase({ 52 | database: context.wertik.configuration.database, 53 | models: context.wertik.models, 54 | }); 55 | 56 | let opDigitalOcean = await dumpDatabaseToDigitalOcean({ 57 | localDump: op, 58 | configuration: context.wertik.configuration, 59 | }); 60 | 61 | return { 62 | message: 63 | "Backup to DigitalOcean and Local Drive has been completed.", 64 | filename: opDigitalOcean.filename, 65 | backup: opDigitalOcean.backupInstance, 66 | }; 67 | }, 68 | backupDropbox: async (_, args, context, info) => { 69 | let op = await dumpDatabase({ 70 | database: context.wertik.configuration.database, 71 | models: context.wertik.models, 72 | }); 73 | let opDropbox = await dumpDatabaseToDropbox({ 74 | localDump: op, 75 | configuration: context.wertik.configuration, 76 | }); 77 | return { 78 | message: "Backup to Dropbox has been completed.", 79 | filename: opDropbox.filename, 80 | backup: opDropbox.backupInstance, 81 | }; 82 | }, 83 | }, 84 | }, 85 | query: { 86 | schema: ` 87 | localBackupsList: [String] 88 | `, 89 | resolvers: { 90 | localBackupsList: async () => { 91 | const list = await loadAllLocalBackups(); 92 | return list; 93 | }, 94 | }, 95 | }, 96 | }, 97 | restApi: {}, 98 | database: { 99 | selectIgnoreFields: [], 100 | relationships: {}, 101 | sql: { 102 | tableName: "Backup", 103 | fields: { 104 | name: { 105 | type: "STRING", 106 | unique: true, 107 | }, 108 | uploaded_to: { 109 | type: "STRING", 110 | }, 111 | uploaded_filename: { 112 | type: "STRING", 113 | }, 114 | }, 115 | }, 116 | }, 117 | }; 118 | -------------------------------------------------------------------------------- /src/framework/helpers/index.ts: -------------------------------------------------------------------------------- 1 | import { ApolloError } from "apollo-server"; 2 | import fs from "fs"; 3 | import { IConfiguration } from "../types/configuration"; 4 | import { get, isFunction } from "lodash"; 5 | 6 | export const generateError = (e: any, statusCode: Number = 404) => { 7 | return new ApolloError(e.message); 8 | }; 9 | 10 | export const getDirectoriesInFolder = (path: string) => { 11 | return fs.readdirSync(path).filter(function (file: any) { 12 | return fs.statSync(path + "/" + file).isDirectory(); 13 | }); 14 | }; 15 | 16 | export const randomString = ( 17 | len, 18 | charSet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" 19 | ) => { 20 | charSet = charSet; 21 | var randomString = ""; 22 | for (var i = 0; i < len; i++) { 23 | var randomPoz = Math.floor(Math.random() * charSet.length); 24 | randomString += charSet.substring(randomPoz, randomPoz + 1); 25 | } 26 | return randomString; 27 | }; 28 | 29 | export const filesInAFolder = (path: string) => { 30 | return fs.readdirSync(path); 31 | }; 32 | 33 | export const exists = (path: any) => { 34 | try { 35 | fs.accessSync(path); 36 | } catch (err) { 37 | return false; 38 | } 39 | return true; 40 | }; 41 | 42 | export const appendToFileSync = function (path: string, content: string) { 43 | // try { 44 | fs.appendFileSync(path, content); 45 | // return true; 46 | // } catch (e) { 47 | // return false; 48 | // } 49 | }; 50 | 51 | export const createEmptyFile = function (path: string, cb: Function) { 52 | fs.writeFile(path, "", function (err) { 53 | if (err) throw err; 54 | cb(); 55 | }); 56 | }; 57 | 58 | export const checkIfModuleIsValid = function (object: IConfiguration) { 59 | if (!module) { 60 | console.log("Module must be object"); 61 | return false; 62 | } 63 | if (module && module.constructor !== Object) { 64 | console.log("Module must be object"); 65 | return false; 66 | } 67 | return true; 68 | }; 69 | 70 | export const deleteFile = async (path: string, cb: Function) => { 71 | if (exists(path)) { 72 | try { 73 | fs.unlink(path, function (err) { 74 | cb(); 75 | }); 76 | return true; 77 | } catch (e) { 78 | return false; 79 | } 80 | } else { 81 | return true; 82 | } 83 | }; 84 | 85 | export const firstLetterLowerCase = (s) => { 86 | if (typeof s !== "string") return ""; 87 | return s.charAt(0).toLowerCase() + s.slice(1); 88 | }; 89 | 90 | export const identityColumn = () => { 91 | return "id" 92 | }; 93 | 94 | export const loadModulesFromConfiguration = (configuration: IConfiguration) => { 95 | let list = []; 96 | let modules = [ 97 | ...configuration.builtinModules.split(","), 98 | ...get(configuration, "modules", []), 99 | ]; 100 | modules = modules.filter((c) => c); 101 | modules.forEach(async (element) => { 102 | let module; 103 | if (element.constructor === String) { 104 | module = require(`./../builtinModules/${element}/index`).default; 105 | } else if (element.constructor === Object || isFunction(element)) { 106 | if (element.constructor == Function) { 107 | module = await element(configuration); 108 | } else { 109 | module = element; 110 | } 111 | } 112 | list.push(module); 113 | }); 114 | return list; 115 | }; 116 | 117 | export const removeColumnsFromAccordingToSelectIgnoreFields = ( 118 | requestedFields, 119 | ignoreFields 120 | ) => { 121 | requestedFields.forEach((element, index) => { 122 | if (ignoreFields.includes(element)) { 123 | requestedFields.splice(index, 1); 124 | } 125 | }); 126 | return requestedFields; 127 | }; 128 | 129 | export const defaultPort = 7000; 130 | export const defaultGraphqlPath = "/graphql"; 131 | -------------------------------------------------------------------------------- /src/framework/reporting/index.ts: -------------------------------------------------------------------------------- 1 | import moment from "moment"; 2 | import { resolve } from "url"; 3 | 4 | export const getYear = (mm = null) => { 5 | return mm ? mm.year() : moment().year(); 6 | }; 7 | 8 | export const getMonth = (mm = null) => { 9 | return mm ? mm.month() + 1 : moment().month() + 1; 10 | }; 11 | 12 | export const getDate = (mm = null) => { 13 | return mm ? mm.date() : moment().date(); 14 | }; 15 | 16 | export const substractDays = (num) => { 17 | return moment().subtract(num, "d"); 18 | }; 19 | 20 | export const getQueryForLast7Days = function (tableName: String) { 21 | return ` 22 | SELECT count(*) as total_created_last_7_days FROM ${tableName} 23 | WHERE DATE(created_at) 24 | BETWEEN 25 | '${getYear(substractDays(7))}-${substractDays(7).month() + 1}-${substractDays(7).date()}' 26 | AND 27 | '${getYear(substractDays(7))}-${moment().month() + 1}-${moment().date()}' 28 | `; 29 | }; 30 | 31 | export const getQueryForToday = function (tableName: String) { 32 | return ` 33 | SELECT count(*) as total_created_today FROM ${tableName} 34 | WHERE DATE(created_at) 35 | BETWEEN 36 | '${getYear()}-${getMonth()}-${getDate()} 00:00:00' 37 | AND 38 | '${getYear()}-${getMonth()}-${getDate()} 23:59:59' 39 | `; 40 | }; 41 | 42 | export const getQueryForThisYear = function (tableName: String) { 43 | return ` 44 | SELECT count(*) as total_created_this_year FROM ${tableName} 45 | WHERE DATE(created_at) 46 | BETWEEN 47 | '${getYear()}-1-1' 48 | AND 49 | '${getYear()}-12-31' 50 | `; 51 | }; 52 | 53 | export const getQueryForThisMonth = function (tableName: String) { 54 | return ` 55 | SELECT count(*) as total_created_this_month FROM ${tableName} 56 | WHERE DATE(created_at) 57 | BETWEEN 58 | '${getYear()}-${moment().month() + 1}-${moment().startOf("month").date()}' 59 | AND 60 | '${getYear()}-${moment().month() + 1}-${moment().endOf("month").date()}' 61 | `; 62 | }; 63 | 64 | export const getQueryForThisWeek = function (tableName: String) { 65 | return ` 66 | SELECT count(*) as total_created_this_week FROM ${tableName} 67 | WHERE DATE(created_at) 68 | BETWEEN 69 | '${getYear(moment().startOf("isoWeek"))}-${moment().startOf("isoWeek").month() + 1}-${moment().startOf("isoWeek").date()}' 70 | AND 71 | '${getYear(moment().endOf("isoWeek"))}-${moment().endOf("isoWeek").month() + 1}-${moment().endOf("isoWeek").date()}' 72 | `; 73 | }; 74 | 75 | export const getQueryForLastMonth = function (tableName: String) { 76 | return ` 77 | SELECT count(*) as total_created_last_month FROM ${tableName} 78 | WHERE DATE(created_at) 79 | BETWEEN 80 | '${moment().subtract(1, "months").year()}-${moment().subtract(1, "months").month() + 1}-${moment() 81 | .subtract(1, "months") 82 | .startOf("month") 83 | .date()}' 84 | AND 85 | '${moment().subtract(1, "months").year()}-${moment().subtract(1, "months").month() + 1}-${moment() 86 | .subtract(1, "months") 87 | .endOf("month") 88 | .date()}' 89 | `; 90 | }; 91 | 92 | export const getQueryForLast90Days = function (tableName: String) { 93 | return ` 94 | SELECT count(*) as total_created_last_90_days FROM ${tableName} 95 | WHERE DATE(created_at) 96 | BETWEEN 97 | '${moment().subtract(90, "days").year()}-${moment().subtract(90, "days").month() + 1}-${moment() 98 | .subtract(90, "days") 99 | .startOf("month") 100 | .date()}' 101 | AND 102 | '${moment().year()}-${moment().month() + 1}-${moment().endOf("month").date()}' 103 | `; 104 | }; 105 | 106 | export const getQueryForLastYear = function (tableName: String) { 107 | return ` 108 | SELECT count(*) as total_created_last_year FROM ${tableName} 109 | WHERE DATE(created_at) 110 | BETWEEN 111 | '${getYear() - 1}-1-1' 112 | AND 113 | '${getYear() - 1}-12-31' 114 | `; 115 | }; -------------------------------------------------------------------------------- /src/framework/builtinModules/backup/helpers.ts: -------------------------------------------------------------------------------- 1 | import path from "path"; 2 | import moment from "moment"; 3 | import mysqldump from "mysqldump"; 4 | import * as aws from "aws-sdk"; 5 | import { get } from "lodash"; 6 | import fs from "fs"; 7 | import { Dropbox, Error, files } from "dropbox"; // eslint-disable-line no-unused-vars 8 | 9 | export const dumpDatabase = async ({ database, models }) => { 10 | return new Promise(async (resolve, reject) => { 11 | try { 12 | const filename = `backups/${moment().format("MMMM-DD-YYYY-h-mm-ss-a")}-database-${database.dbName}.sql`; 13 | 14 | const backupInstance = await models.Backup.create({ 15 | uploaded_filename: filename, 16 | uploaded_to: "local", 17 | }); 18 | 19 | setTimeout(async () => { 20 | await mysqldump({ 21 | connection: { 22 | host: database.dbHost, 23 | user: database.dbUsername, 24 | password: database.dbPassword, 25 | database: database.dbName, 26 | }, 27 | dumpToFile: filename, 28 | // compressFile: true, 29 | }); 30 | 31 | resolve({ 32 | filename, 33 | backupInstance, 34 | }); 35 | }, 1500); 36 | } catch (e) { 37 | reject(e); 38 | } 39 | }); 40 | }; 41 | 42 | export const dumpDatabaseToDigitalOcean = async ({ localDump, configuration }) => { 43 | try { 44 | const filename = localDump.filename; 45 | const digitalOceanSpacesDetails = get(configuration, "backup.digitalOceanSpaces", null); 46 | 47 | if ( 48 | digitalOceanSpacesDetails && 49 | digitalOceanSpacesDetails.accessKeyId && 50 | digitalOceanSpacesDetails.secretAccessKey && 51 | digitalOceanSpacesDetails.spacesEndpoint && 52 | digitalOceanSpacesDetails.uploadParams 53 | ) { 54 | aws.config.update({ 55 | accessKeyId: digitalOceanSpacesDetails.accessKeyId, 56 | secretAccessKey: digitalOceanSpacesDetails.secretAccessKey, 57 | }); 58 | 59 | const spacesEndpoint: any = new aws.Endpoint(digitalOceanSpacesDetails.spacesEndpoint); 60 | 61 | const s3 = new aws.S3({ 62 | endpoint: spacesEndpoint, 63 | }); 64 | 65 | const data = await fs.readFileSync(filename); 66 | 67 | const params = { 68 | Bucket: digitalOceanSpacesDetails.uploadParams.Bucket, 69 | Key: `${filename}`, 70 | Body: data, 71 | ACL: digitalOceanSpacesDetails.uploadParams.ACL, 72 | }; 73 | 74 | await s3.upload(params).promise(); 75 | 76 | localDump.backupInstance = await localDump.backupInstance.update({ 77 | uploaded_to: localDump.backupInstance.uploaded_to + ", digital_ocean", 78 | }); 79 | } else { 80 | throw new Error("Please provide your digital ocean details to backup database."); 81 | } 82 | } catch (e) { 83 | throw new Error(e); 84 | } 85 | 86 | return localDump; 87 | }; 88 | 89 | export const dumpDatabaseToDropbox = async ({ localDump, configuration }) => { 90 | try { 91 | const filename = localDump.filename; 92 | const dropdboxDetails = get(configuration, "backup.dropbox", null); 93 | 94 | if (dropdboxDetails && dropdboxDetails.accessToken) { 95 | const dbx = new Dropbox({ accessToken: dropdboxDetails.accessToken }); 96 | 97 | const data: Buffer = await fs.readFileSync(filename); 98 | 99 | await dbx.filesUpload({ strict_conflict: false, path: `/${filename}`, contents: data }); 100 | 101 | localDump.backupInstance = await localDump.backupInstance.update({ 102 | uploaded_to: localDump.backupInstance.uploaded_to + ", dropbox", 103 | }); 104 | 105 | return localDump; 106 | } else { 107 | throw new Error("Please provide your dropbox details to backup database."); 108 | } 109 | } catch (e) { 110 | console.log(e); 111 | throw new Error(e.message); 112 | } 113 | }; 114 | 115 | export const loadAllLocalBackups = async () => { 116 | try { 117 | const backupsPath = path.join(__dirname, "../../../../backups"); 118 | let list = await fs.readdirSync(backupsPath); 119 | return list; 120 | } catch (e) { 121 | throw new Error(e); 122 | } 123 | }; 124 | -------------------------------------------------------------------------------- /src/framework/builtinModules/forgetPassword/index.ts: -------------------------------------------------------------------------------- 1 | import { requestPasswordResetHandler, resetPasswordHandler } from "./handlers"; 2 | import getRequestedFieldsFromResolverInfo from "./../../helpers/getRequestedFieldsFromResolverInfo"; 3 | 4 | export default { 5 | name: "ForgetPassword", 6 | graphql: { 7 | schema: ` 8 | type ForgetPassword { 9 | id: Int 10 | name: String 11 | email: String 12 | user: User 13 | user_id: Int 14 | token: String 15 | created_at: String 16 | updated_at: String 17 | } 18 | input requestPasswordResetInput { 19 | email: String! 20 | } 21 | input resetPasswordInput { 22 | token: String! 23 | password: String! 24 | confirmPassword: String! 25 | } 26 | input ForgetPasswordInput { 27 | id: Int 28 | name: String 29 | email: String 30 | user_id: Int 31 | token: String 32 | } 33 | `, 34 | customResolvers: {}, 35 | mutation: { 36 | schema: ` 37 | requestPasswordReset(input: requestPasswordResetInput): SuccessResponse 38 | resetPassword(input: resetPasswordInput): SuccessResponse 39 | `, 40 | resolvers: { 41 | requestPasswordReset: async (_: any, args: any, context: any, info: any) => { 42 | return await requestPasswordResetHandler({ 43 | userModel: context.wertik.models.User, 44 | forgetPasswordModel: context.wertik.models.ForgetPassword, 45 | data: args.input, 46 | emailTemplates: context.wertik.emailTemplates, 47 | sendEmail: context.wertik.sendEmail, 48 | }); 49 | }, 50 | resetPassword: async (_: any, args: any, context: any, info: any) => { 51 | return await resetPasswordHandler({ 52 | userModel: context.wertik.models.User, 53 | forgetPasswordModel: context.wertik.models.ForgetPassword, 54 | data: args.input, 55 | }); 56 | }, 57 | }, 58 | }, 59 | query: { 60 | schema: ``, 61 | resolvers: {}, 62 | }, 63 | }, 64 | restApi: { 65 | endpoints: [ 66 | { 67 | path: "/request-password-reset", 68 | methodType: "post", 69 | handler: async function (req, res) { 70 | try { 71 | let response = await requestPasswordResetHandler({ 72 | userModel: req.wertik.models.User, 73 | forgetPasswordModel: req.wertik.models.ForgetPassword, 74 | data: req.body.input, 75 | emailTemplates: req.wertik.emailTemplates, 76 | sendEmail: req.wertik.sendEmail, 77 | }); 78 | res.json({ 79 | message: response, 80 | }); 81 | } catch (e) { 82 | res.json({ 83 | success: false, 84 | message: e.message, 85 | result: {}, 86 | }); 87 | } 88 | }, 89 | }, 90 | { 91 | path: "/reset-password", 92 | methodType: "post", 93 | handler: async function (req, res) { 94 | try { 95 | let response = await resetPasswordHandler({ 96 | userModel: req.wertik.models.User, 97 | forgetPasswordModel: req.wertik.models.ForgetPassword, 98 | data: req.body.input, 99 | }); 100 | res.json({ 101 | message: response, 102 | }); 103 | } catch (e) { 104 | res.json({ 105 | success: false, 106 | message: e.message, 107 | result: {}, 108 | }); 109 | } 110 | }, 111 | }, 112 | ], 113 | }, 114 | database: { 115 | selectIgnoreFields: ["user"], 116 | relationships: { 117 | oneToOne: { 118 | User: { 119 | relationColumn: "user_id", 120 | graphqlName: "user", 121 | foreignKey: "id", 122 | }, 123 | }, 124 | }, 125 | sql: { 126 | tableName: "forgetPassword", 127 | fields: { 128 | name: { 129 | type: "String", 130 | }, 131 | email: { 132 | type: "String", 133 | }, 134 | user_id: { 135 | type: "Integer", 136 | }, 137 | token: { 138 | type: "String", 139 | }, 140 | is_deleted: { 141 | type: "INTEGER", 142 | }, 143 | }, 144 | }, 145 | }, 146 | }; 147 | -------------------------------------------------------------------------------- /src/framework/defaults/defaultConfigurations/defaultConfiguration.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | name: "Wertik", 3 | builtinModules: "user,auth,forgetPassword,permission,role,rolePermission,userPermission,userRole,me,storage,mail,backup", 4 | database: { 5 | dbDialect: process.env.dbDialect, 6 | dbUsername: process.env.dbUsername, 7 | dbPassword: process.env.dbPassword, 8 | dbName: process.env.dbName, 9 | dbHost: process.env.dbHost, 10 | dbPort: process.env.dbPort, 11 | }, 12 | port: 5000, 13 | frontendAppUrl: "http://localhost:8080/", 14 | frontendAppActivationUrl: "http://localhost:8080/activate-account", 15 | frontendAppPasswordResetUrl: "http://localhost:8080/reset-password", 16 | context: { 17 | initializeContext: function (mode, context) { 18 | return { 19 | someKey: "somekeyvalue", 20 | }; 21 | }, 22 | requestContext: async function (mode, context) { 23 | return { 24 | value: "Value 1", 25 | }; 26 | }, 27 | }, 28 | email: { 29 | disable: true, 30 | }, 31 | graphql: { 32 | disable: false, 33 | }, 34 | restApi: { 35 | useCors: true, 36 | useBodyParser: true, 37 | useMorgan: true, 38 | showWertik404Page: true, 39 | onCustomApiFailure: function ({ path, res, err }) { 40 | res.send("failed at " + path); 41 | }, 42 | // Below function for custom 404 page or response. 43 | // restApi404Handler: function () {} 44 | }, 45 | backup: { 46 | digitalOceanSpaces: { 47 | accessKeyId: "", 48 | secretAccessKey: "", 49 | spacesEndpoint: "", 50 | uploadParams: { 51 | Bucket: "", 52 | ACL: "", 53 | }, 54 | }, 55 | dropbox: { 56 | accessToken: "", 57 | }, 58 | }, 59 | modules: [ 60 | { 61 | name: "Article", 62 | graphql: { 63 | crud: { 64 | query: { 65 | generate: true, 66 | operations: "*", 67 | }, 68 | mutation: { 69 | generate: true, 70 | operations: "*", 71 | }, 72 | }, 73 | schema: ` 74 | type Article { 75 | id: Int 76 | title: String 77 | description: String 78 | created_at: String 79 | updated_at: String 80 | } 81 | input ArticleInput { 82 | title: String 83 | description: String 84 | } 85 | `, 86 | mutation: { 87 | schema: ``, 88 | resolvers: {}, 89 | }, 90 | query: { 91 | schema: ``, 92 | resolvers: {}, 93 | }, 94 | }, 95 | restApi: { 96 | endpoints: [ 97 | { 98 | path: "/apple/response", 99 | methodType: "get", 100 | handler: function (req, res) { 101 | res.json({ 102 | message: true, 103 | }); 104 | }, 105 | }, 106 | ], 107 | }, 108 | database: { 109 | sql: { 110 | tableName: "article", 111 | fields: { 112 | title: { 113 | type: "STRING", 114 | }, 115 | description: { 116 | type: "STRING", 117 | }, 118 | }, 119 | }, 120 | }, 121 | }, 122 | ], 123 | events: { 124 | beforeGraphqlStart: function () { 125 | console.log("beforeGraphqlStart"); 126 | }, 127 | beforeRestApiStart: function () { 128 | console.log("beforeRestApiStart"); 129 | }, 130 | database: { 131 | Permission: {}, 132 | }, 133 | }, 134 | seeds: { 135 | Role: [{ name: "Admin" }, { name: "Kako" }], 136 | Permission: [ 137 | { name: "ca", cant: "true", can: "true" }, 138 | { name: "ca1", cant: "true1", can: "true1" }, 139 | { name: "ca2", cant: "true2", can: "true2" }, 140 | ], 141 | }, 142 | sockets: { 143 | disable: false, 144 | onClientConnected: function () { 145 | console.log("onClientConnected"); 146 | }, 147 | onMessageReceived: function () { 148 | console.log("onMessageReceived"); 149 | }, 150 | onClientDisconnect: function () { 151 | console.log("onClientDisconnect"); 152 | }, 153 | }, 154 | storage: { 155 | storageDirectory: "./uploads/", 156 | }, 157 | cron: { 158 | cronList: [ 159 | { 160 | expression: "* * * * *", 161 | function: (context) => { 162 | // app context is available in context variable. 163 | }, 164 | options: {}, 165 | events: { 166 | initialized(i) {}, 167 | }, 168 | }, 169 | ], 170 | }, 171 | }; 172 | -------------------------------------------------------------------------------- /src/framework/database/helpers/stats.ts: -------------------------------------------------------------------------------- 1 | import { 2 | getQueryForLast7Days, 3 | getQueryForLastYear, 4 | getQueryForThisYear, 5 | getQueryForThisMonth, 6 | getQueryForLastMonth, 7 | getQueryForThisWeek, 8 | getQueryForToday, 9 | getQueryForLast90Days, 10 | } from "../../reporting/index"; 11 | import { get } from "lodash"; 12 | 13 | export default function (database, model) { 14 | return function (requestedReports) { 15 | return new Promise(async (resolve, reject) => { 16 | try { 17 | let statsInfo = { 18 | total_count: null, 19 | total_created_today: null, 20 | total_created_this_week: null, 21 | total_created_last_7_days: null, 22 | total_created_this_month: null, 23 | total_created_last_month: null, 24 | total_created_last_90_days: null, 25 | total_created_last_year: null, 26 | total_created_this_year: null, 27 | }; 28 | let count, 29 | countLast7Days, 30 | countToday, 31 | countLastYear, 32 | countThisYear, 33 | countThisMonth, 34 | countThisweek, 35 | countLastMonth, 36 | countLast90Days; 37 | let selectOptions = { 38 | type: database.QueryTypes.SELECT, 39 | }; 40 | if (requestedReports.includes("total_count")) { 41 | count = await database.query( 42 | `select count(*) as total_count from ${model.getTableName()}`, 43 | selectOptions 44 | ); 45 | } 46 | if (requestedReports.includes("total_created_last_7_days")) { 47 | countLast7Days = await database.query( 48 | getQueryForLast7Days(model.getTableName()), 49 | selectOptions 50 | ); 51 | } 52 | if (requestedReports.includes("total_created_today")) { 53 | countToday = await database.query( 54 | getQueryForToday(model.getTableName()), 55 | selectOptions 56 | ); 57 | } 58 | if (requestedReports.includes("total_created_last_year")) { 59 | countLastYear = await database.query( 60 | getQueryForLastYear(model.getTableName()), 61 | selectOptions 62 | ); 63 | } 64 | if (requestedReports.includes("total_created_this_year")) { 65 | countThisYear = await database.query( 66 | getQueryForThisYear(model.getTableName()), 67 | selectOptions 68 | ); 69 | } 70 | if (requestedReports.includes("total_created_this_month")) { 71 | countThisMonth = await database.query( 72 | getQueryForThisMonth(model.getTableName()), 73 | selectOptions 74 | ); 75 | } 76 | if (requestedReports.includes("total_created_this_week")) { 77 | countThisweek = await database.query( 78 | getQueryForThisWeek(model.getTableName()), 79 | selectOptions 80 | ); 81 | } 82 | if (requestedReports.includes("total_created_last_month")) { 83 | countLastMonth = await database.query( 84 | getQueryForLastMonth(model.getTableName()), 85 | selectOptions 86 | ); 87 | } 88 | if (requestedReports.includes("total_created_last_90_days")) { 89 | countLast90Days = await database.query( 90 | getQueryForLast90Days(model.getTableName()), 91 | selectOptions 92 | ); 93 | } 94 | 95 | statsInfo.total_count = get(count, "[0].total_count", 0); 96 | statsInfo.total_created_this_month = get( 97 | countThisMonth, 98 | "[0].total_created_this_month", 99 | 0 100 | ); 101 | statsInfo.total_created_this_week = get( 102 | countThisweek, 103 | "[0].total_created_this_week", 104 | 0 105 | ); 106 | statsInfo.total_created_last_7_days = get( 107 | countLast7Days, 108 | "[0].total_created_last_7_days", 109 | 0 110 | ); 111 | statsInfo.total_created_today = get( 112 | countToday, 113 | "[0].total_created_today", 114 | 0 115 | ); 116 | statsInfo.total_created_last_month = get( 117 | countLastMonth, 118 | "[0].total_created_last_month", 119 | 0 120 | ); 121 | statsInfo.total_created_last_90_days = get( 122 | countLast90Days, 123 | "[0].total_created_last_90_days", 124 | 0 125 | ); 126 | statsInfo.total_created_last_year = get( 127 | countLastYear, 128 | "[0].total_created_last_year", 129 | 0 130 | ); 131 | statsInfo.total_created_this_year = get( 132 | countThisYear, 133 | "[0].total_created_this_year", 134 | 0 135 | ); 136 | 137 | resolve(statsInfo); 138 | } catch (error) { 139 | reject(error) 140 | } 141 | }) 142 | 143 | } 144 | } -------------------------------------------------------------------------------- /src/framework/graphql/loadAllModules.ts: -------------------------------------------------------------------------------- 1 | /* 2 | This is all where GraphQL thing happens. This file loads all graphql schemas from the app. 3 | */ 4 | import { get, isFunction } from "lodash"; 5 | import generalSchema from "./generalSchema"; 6 | import { 7 | generateSubscriptionsCrudResolvers, 8 | generateQueriesCrudSchema, 9 | generateListTypeForModule, 10 | generateMutationsCrudSubscriptionSchema, 11 | generateMutationsCrudSchema, 12 | generateCrudResolvers, 13 | } from "./crudGenerator"; 14 | import { PubSub } from "apollo-server"; 15 | import { IConfiguration } from "../types/configuration"; 16 | import { GraphQLModuleRelationMapper } from "../moduleRelationships/graphql"; 17 | const pubsub = new PubSub(); 18 | 19 | export default async function (configuration: IConfiguration) { 20 | let modulesSchema = ``; 21 | 22 | let modulesQuerySchema = ``; 23 | let modulesMutationSchema = ``; 24 | let modulesSubscriptionSchema = ``; 25 | let modules = process.env.builtinModules.split(","); 26 | modules = modules.filter((c) => c); 27 | modules = [...modules, ...get(configuration, "modules", [])]; 28 | let response = () => { 29 | return { 30 | message: "Welcome to wertik, You are successfully running Wertik.", 31 | version: require("../../../package.json").version, 32 | }; 33 | }; 34 | let schemaMap = require("./schemaMap").default; 35 | 36 | let appMutations = {}; 37 | let appQueries = {}; 38 | let appSubscriptions = {}; 39 | let appCustomResolvers = {}; 40 | 41 | const processModule = function (currentModule) { 42 | if (currentModule && currentModule.hasOwnProperty("graphql")) { 43 | // Process Module Graphql 44 | const moduleGraphql = currentModule.graphql; 45 | const useDatabase = get(currentModule, "useDatabase", true); 46 | const currentModuleName = currentModule.name; 47 | 48 | const currentModuleGraphqlSchema = get(moduleGraphql, "schema", ""); 49 | 50 | const currentModuleGraphqlQuerySchema = get( 51 | moduleGraphql, 52 | "query.schema", 53 | "" 54 | ); 55 | const currentModuleGraphqlQueryResolvers = get( 56 | moduleGraphql, 57 | "query.resolvers", 58 | {} 59 | ); 60 | 61 | const currentModuleGraphqlMutationSchema = get( 62 | moduleGraphql, 63 | "mutation.schema", 64 | "" 65 | ); 66 | const currentModuleGraphqlMutationResolvers = get( 67 | moduleGraphql, 68 | "mutation.resolvers", 69 | {} 70 | ); 71 | 72 | const customResolvers = get(moduleGraphql, "customResolvers", {}); 73 | 74 | modulesSchema = `${modulesSchema} 75 | ${currentModuleGraphqlSchema}`; 76 | 77 | modulesQuerySchema = `${modulesQuerySchema} 78 | ${currentModuleGraphqlQuerySchema}`; 79 | 80 | appQueries = { 81 | ...appQueries, 82 | ...currentModuleGraphqlQueryResolvers, 83 | }; 84 | 85 | modulesMutationSchema = `${modulesMutationSchema} 86 | ${currentModuleGraphqlMutationSchema}`; 87 | 88 | appMutations = { 89 | ...appMutations, 90 | ...currentModuleGraphqlMutationResolvers, 91 | }; 92 | 93 | appCustomResolvers[currentModuleName] = { 94 | ...customResolvers, 95 | ...GraphQLModuleRelationMapper(currentModule), 96 | }; 97 | 98 | // Process Module Graphql 99 | 100 | // Process Crud for Module: 101 | 102 | if (useDatabase === true) { 103 | let currentModuleListSchema = generateListTypeForModule(currentModule); 104 | 105 | modulesSchema = `${modulesSchema} 106 | ${currentModuleListSchema}`; 107 | 108 | const crudResolvers = generateCrudResolvers( 109 | currentModule, 110 | pubsub, 111 | configuration 112 | ); 113 | 114 | const crudMutationSchema = generateMutationsCrudSchema( 115 | currentModuleName 116 | ); 117 | const crudMutationResolvers = crudResolvers.mutations; 118 | 119 | const crudQuerySchema = generateQueriesCrudSchema(currentModuleName); 120 | const crudQueryResolvers = crudResolvers.queries; 121 | 122 | const crudSubscriptionSchema = generateMutationsCrudSubscriptionSchema( 123 | currentModuleName 124 | ); 125 | const crudSubscriptionResolvers = generateSubscriptionsCrudResolvers( 126 | currentModuleName, 127 | pubsub 128 | ); 129 | 130 | modulesQuerySchema = `${modulesQuerySchema} 131 | ${crudQuerySchema}`; 132 | 133 | appQueries = { 134 | ...appQueries, 135 | ...crudQueryResolvers, 136 | }; 137 | 138 | modulesMutationSchema = `${modulesMutationSchema} 139 | ${crudMutationSchema}`; 140 | 141 | appMutations = { 142 | ...appMutations, 143 | ...crudMutationResolvers, 144 | }; 145 | 146 | modulesSubscriptionSchema = 147 | modulesSubscriptionSchema + crudSubscriptionSchema; 148 | 149 | appSubscriptions = { 150 | ...appSubscriptions, 151 | ...crudSubscriptionResolvers, 152 | }; 153 | } 154 | 155 | // Check for empty resolvers and remove them 156 | 157 | const totalResolvers = Object.keys(get(appCustomResolvers,currentModuleName,{})).length; 158 | // cosnoel 159 | if (totalResolvers === 0) { 160 | delete appCustomResolvers[currentModuleName]; 161 | } 162 | 163 | // Process Crud for module: 164 | } 165 | }; 166 | 167 | modules.forEach(async (element: any) => { 168 | let module; 169 | if (element.constructor === String) { 170 | module = require(`./../builtinModules/${element}/index`).default; 171 | } else if (element.constructor === Object || isFunction(element)) { 172 | if (element.constructor == Function) { 173 | module = await element(configuration); 174 | } else { 175 | module = element; 176 | } 177 | } 178 | processModule(module); 179 | }); 180 | 181 | schemaMap = schemaMap.replace("[generalSchema__replace]", generalSchema); 182 | schemaMap = schemaMap.replace("[modulesSchema__replace]", modulesSchema); 183 | schemaMap = schemaMap.replace("[mutation__replace]", modulesMutationSchema); 184 | schemaMap = schemaMap.replace("[query__replace]", modulesQuerySchema); 185 | schemaMap = schemaMap.replace( 186 | "[subscription__replace]", 187 | modulesSubscriptionSchema 188 | ); 189 | 190 | return { 191 | schema: schemaMap, 192 | resolvers: { 193 | Mutation: { 194 | ...appMutations, 195 | response: response, 196 | }, 197 | Query: { 198 | ...appQueries, 199 | response: response, 200 | }, 201 | Subscription: { 202 | ...appSubscriptions, 203 | }, 204 | ...appCustomResolvers, 205 | }, 206 | }; 207 | } 208 | -------------------------------------------------------------------------------- /src/framework/helpers/statusCodes.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | ACCEPTED: { 3 | type: "ACCEPTED", 4 | number: 202, 5 | value: "Accepted" 6 | }, 7 | BAD_GATEWAY: { 8 | type: "BAD_GATEWAY", 9 | number: 502, 10 | value: "Bad Gateway" 11 | }, 12 | BAD_REQUEST: { 13 | type: "BAD_REQUEST", 14 | number: 400, 15 | value: "Bad Request" 16 | }, 17 | CONFLICT: { 18 | type: "CONFLICT", 19 | number: 409, 20 | value: "Conflict" 21 | }, 22 | CONTINUE: { 23 | type: "CONTINUE", 24 | number: 100, 25 | value: "Continue" 26 | }, 27 | CREATED: { 28 | type: "CREATED", 29 | number: 201, 30 | value: "Created" 31 | }, 32 | EXPECTATION_FAILED: { 33 | type: "EXPECTATION_FAILED", 34 | number: 417, 35 | value: "Expectation Failed" 36 | }, 37 | FAILED_DEPENDENCY: { 38 | type: "FAILED_DEPENDENCY", 39 | number: 424, 40 | value: "Failed Dependency" 41 | }, 42 | FORBIDDEN: { 43 | type: "FORBIDDEN", 44 | number: 403, 45 | value: "Forbidden" 46 | }, 47 | GATEWAY_TIMEOUT: { 48 | type: "GATEWAY_TIMEOUT", 49 | number: 504, 50 | value: "Gateway Timeout" 51 | }, 52 | GONE: { 53 | type: "GONE", 54 | number: 410, 55 | value: "Gone" 56 | }, 57 | HTTP_VERSION_NOT_SUPPORTED: { 58 | type: "HTTP_VERSION_NOT_SUPPORTED", 59 | number: 505, 60 | value: "HTTP Version Not Supported" 61 | }, 62 | IM_A_TEAPOT: { 63 | type: "IM_A_TEAPOT", 64 | number: 418, 65 | value: "I'm a teapot" 66 | }, 67 | INSUFFICIENT_SPACE_ON_RESOURCE: { 68 | type: "INSUFFICIENT_SPACE_ON_RESOURCE", 69 | number: 419, 70 | value: "Insufficient Space on Resource" 71 | }, 72 | INSUFFICIENT_STORAGE: { 73 | type: "INSUFFICIENT_STORAGE", 74 | number: 507, 75 | value: "Insufficient Storage" 76 | }, 77 | INTERNAL_SERVER_ERROR: { 78 | type: "INTERNAL_SERVER_ERROR", 79 | number: 500, 80 | value: "Server Error" 81 | }, 82 | LENGTH_REQUIRED: { 83 | type: "LENGTH_REQUIRED", 84 | number: 411, 85 | value: "Length Required" 86 | }, 87 | LOCKED: { 88 | type: "LOCKED", 89 | number: 423, 90 | value: "Locked" 91 | }, 92 | METHOD_FAILURE: { 93 | type: "METHOD_FAILURE", 94 | number: 420, 95 | value: "Method Failure" 96 | }, 97 | METHOD_NOT_ALLOWED: { 98 | type: "METHOD_NOT_ALLOWED", 99 | number: 405, 100 | value: "Method Not Allowed" 101 | }, 102 | MOVED_PERMANENTLY: { 103 | type: "MOVED_PERMANENTLY", 104 | number: 301, 105 | value: "Moved Permanently" 106 | }, 107 | MOVED_TEMPORARILY: { 108 | type: "MOVED_TEMPORARILY", 109 | number: 302, 110 | value: "Moved Temporarily" 111 | }, 112 | MULTI_STATUS: { 113 | type: "MULTI_STATUS", 114 | number: 207, 115 | value: "Multi-Status" 116 | }, 117 | MULTIPLE_CHOICES: { 118 | type: "MULTIPLE_CHOICES", 119 | number: 300, 120 | value: "Multiple Choices" 121 | }, 122 | NETWORK_AUTHENTICATION_REQUIRED: { 123 | type: "NETWORK_AUTHENTICATION_REQUIRED", 124 | number: 511, 125 | value: "Network Authentication Required" 126 | }, 127 | NO_CONTENT: { 128 | type: "NO_CONTENT", 129 | number: 204, 130 | value: "No Content" 131 | }, 132 | NON_AUTHORITATIVE_INFORMATION: { 133 | type: "NON_AUTHORITATIVE_INFORMATION", 134 | number: 203, 135 | value: "Non Authoritative Information" 136 | }, 137 | NOT_ACCEPTABLE: { 138 | type: "NOT_ACCEPTABLE", 139 | number: 406, 140 | value: "Not Acceptable" 141 | }, 142 | NOT_FOUND: { 143 | type: "NOT_FOUND", 144 | number: 404, 145 | value: "Not Found" 146 | }, 147 | NOT_IMPLEMENTED: { 148 | type: "NOT_IMPLEMENTED", 149 | number: 501, 150 | value: "Not Implemented" 151 | }, 152 | NOT_MODIFIED: { 153 | type: "NOT_MODIFIED", 154 | number: 304, 155 | value: "Not Modified" 156 | }, 157 | OK: { 158 | type: "OK", 159 | number: 200, 160 | value: "OK" 161 | }, 162 | PARTIAL_CONTENT: { 163 | type: "PARTIAL_CONTENT", 164 | number: 206, 165 | value: "Partial Content" 166 | }, 167 | PAYMENT_REQUIRED: { 168 | type: "PAYMENT_REQUIRED", 169 | number: 402, 170 | value: "Payment Required" 171 | }, 172 | PERMANENT_REDIRECT: { 173 | type: "PERMANENT_REDIRECT", 174 | number: 308, 175 | value: "Permanent Redirect" 176 | }, 177 | PRECONDITION_FAILED: { 178 | type: "PRECONDITION_FAILED", 179 | number: 412, 180 | value: "Precondition Failed" 181 | }, 182 | PRECONDITION_REQUIRED: { 183 | type: "PRECONDITION_REQUIRED", 184 | number: 428, 185 | value: "Precondition Required" 186 | }, 187 | PROCESSING: { 188 | type: "PROCESSING", 189 | number: 102, 190 | value: "Processing" 191 | }, 192 | PROXY_AUTHENTICATION_REQUIRED: { 193 | type: "PROXY_AUTHENTICATION_REQUIRED", 194 | number: 407, 195 | value: "Proxy Authentication Required" 196 | }, 197 | REQUEST_HEADER_FIELDS_TOO_LARGE: { 198 | type: "REQUEST_HEADER_FIELDS_TOO_LARGE", 199 | number: 431, 200 | value: "Request Header Fields Too Large" 201 | }, 202 | REQUEST_TIMEOUT: { 203 | type: "REQUEST_TIMEOUT", 204 | number: 408, 205 | value: "Request Timeout" 206 | }, 207 | REQUEST_TOO_LONG: { 208 | type: "REQUEST_TOO_LONG", 209 | number: 413, 210 | value: "Request Entity Too Large" 211 | }, 212 | REQUEST_URI_TOO_LONG: { 213 | type: "REQUEST_URI_TOO_LONG", 214 | number: 414, 215 | value: "Request-URI Too Long" 216 | }, 217 | REQUESTED_RANGE_NOT_SATISFIABLE: { 218 | type: "REQUESTED_RANGE_NOT_SATISFIABLE", 219 | number: 416, 220 | value: "Requested Range Not Satisfiable" 221 | }, 222 | RESET_CONTENT: { 223 | type: "RESET_CONTENT", 224 | number: 205, 225 | value: "Reset Content" 226 | }, 227 | SEE_OTHER: { 228 | type: "SEE_OTHER", 229 | number: 303, 230 | value: "See Other" 231 | }, 232 | SERVICE_UNAVAILABLE: { 233 | type: "SERVICE_UNAVAILABLE", 234 | number: 503, 235 | value: "Service Unavailable" 236 | }, 237 | SWITCHING_PROTOCOLS: { 238 | type: "SWITCHING_PROTOCOLS", 239 | number: 101, 240 | value: "Switching Protocols" 241 | }, 242 | TEMPORARY_REDIRECT: { 243 | type: "TEMPORARY_REDIRECT", 244 | number: 307, 245 | value: "Temporary Redirect" 246 | }, 247 | TOO_MANY_REQUESTS: { 248 | type: "TOO_MANY_REQUESTS", 249 | number: 429, 250 | value: "Too Many Requests" 251 | }, 252 | UNAUTHORIZED: { 253 | type: "UNAUTHORIZED", 254 | number: 401, 255 | value: "Unauthorized" 256 | }, 257 | UNPROCESSABLE_ENTITY: { 258 | type: "UNPROCESSABLE_ENTITY", 259 | number: 422, 260 | value: "Unprocessable Entity" 261 | }, 262 | UNSUPPORTED_MEDIA_value: { 263 | type: "UNSUPPORTED_MEDIA_value", 264 | number: 415, 265 | value: "Unsupported Media Type" 266 | }, 267 | USE_PROXY: { 268 | type: "USE_PROXY", 269 | number: 305, 270 | value: "Use Proxy" 271 | } 272 | }; 273 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | require('dotenv').config() 2 | import { get } from "lodash"; 3 | import multer from "multer"; 4 | import http from "http"; 5 | 6 | import convertConfigurationIntoEnvVariables from "./framework/helpers/convertConfigurationIntoEnvVariables"; 7 | import validateConfigurationObject from "./framework/helpers/validateConfigurationObject"; 8 | import { IConfiguration } from "./framework/types/configuration"; 9 | import { errorMessage } from "./framework/logger/consoleMessages"; 10 | import loadDefaults from "./framework/defaults/loadDefaults"; 11 | import initiateLogger from "./framework/logger/index"; 12 | import initiateMailer from "./framework/mailer/index"; 13 | import { randomString } from "./framework/helpers"; 14 | import startServers from "./framework/initialization/startServers"; 15 | let connectDatabaseFn = require("./framework/database/connect").default; 16 | 17 | export const connectDatabase = connectDatabaseFn 18 | 19 | export const serve = function (configurationOriginal: IConfiguration) { 20 | let expressApp = get(configurationOriginal, "expressApp", null); 21 | if (!expressApp) { 22 | expressApp = require("express")(); 23 | } 24 | return new Promise((resolve, reject) => { 25 | loadDefaults(configurationOriginal) 26 | .then((configuration: IConfiguration) => { 27 | validateConfigurationObject(configuration) 28 | .then(() => { 29 | convertConfigurationIntoEnvVariables(configuration) 30 | .then(() => { 31 | initiateLogger().then((logger) => { 32 | initiateMailer(configuration) 33 | .then(async (mailerInstance) => { 34 | const database = configuration.databaseInstance 35 | let graphql = require("./framework/graphql/index").default; 36 | let restApi = require("./framework/restApi/index").default; 37 | let cron = require("./framework/cron/index").default; 38 | 39 | let models = require("./framework/database/loadTables").default(database, configuration); 40 | // let models = require("./framework/database/models").default(dbTables, configuration); 41 | let sendEmail = 42 | get(configuration, "email.disable", false) === false 43 | ? require("./framework/mailer/index").sendEmail(configuration, mailerInstance) 44 | : null; 45 | let seeds = require("./framework/seeds/index").default(configuration, models); 46 | let emailTemplates = require("./framework/mailer/emailTemplates").default(configuration, __dirname); 47 | /* Storage */ 48 | let storage = multer.diskStorage({ 49 | destination: configuration.storage.storageDirectory, 50 | filename: function (req, file, cb) { 51 | cb(null, randomString(20) + "_" + file.originalname); 52 | }, 53 | }); 54 | /* Storage */ 55 | const httpServer = http.createServer(expressApp); 56 | let multerInstance = multer({ storage: storage }); 57 | 58 | let socketio = require("./framework/socket/index").default(configuration, { 59 | expressApp: expressApp, 60 | httpServer: httpServer, 61 | configuration: configuration, 62 | models: models, 63 | sendEmail: sendEmail, 64 | emailTemplates: emailTemplates, 65 | database: database, 66 | mailerInstance: mailerInstance, 67 | }); 68 | 69 | let { graphql: graphqlAppInstance, graphqlVoyager } = await graphql({ 70 | expressApp: expressApp, 71 | configuration: configuration, 72 | models: models, 73 | sendEmail: sendEmail, 74 | emailTemplates: emailTemplates, 75 | database: database, 76 | mailerInstance: mailerInstance, 77 | socketio: socketio, 78 | }); 79 | let restApiInstance = await restApi({ 80 | expressApp: expressApp, 81 | configuration: configuration, 82 | models: models, 83 | emailTemplates: emailTemplates, 84 | sendEmail: sendEmail, 85 | database: database, 86 | multerInstance: multerInstance, 87 | mailerInstance: mailerInstance, 88 | socketio: socketio, 89 | }); 90 | 91 | cron(configuration, { 92 | graphql: graphqlAppInstance, 93 | restApi: restApiInstance, 94 | socketio: socketio, 95 | models: models, 96 | emailTemplates: emailTemplates, 97 | sendEmail: sendEmail, 98 | database: database, 99 | seeds: seeds, 100 | logger: logger, 101 | multerInstance: multerInstance, 102 | mailerInstance: mailerInstance, 103 | httpServer: httpServer, 104 | }); 105 | await startServers(configuration, { 106 | graphql: graphqlAppInstance, 107 | restApi: restApiInstance, 108 | graphqlVoyager: graphqlVoyager, 109 | httpServer: httpServer, 110 | }); 111 | resolve({ 112 | socketio: socketio, 113 | models: models, 114 | emailTemplates: emailTemplates, 115 | sendEmail: sendEmail, 116 | database: database, 117 | seeds: seeds, 118 | logger: logger, 119 | multerInstance: multerInstance, 120 | mailerInstance: mailerInstance, 121 | // 122 | express: expressApp, 123 | graphql: graphqlAppInstance, 124 | httpServer: httpServer, 125 | }); 126 | 127 | }) 128 | .catch((e) => { 129 | errorMessage(e); 130 | }); 131 | }); 132 | }) 133 | .catch((err2) => { 134 | errorMessage( 135 | `Something went wrong while initializing Wertik js, Please check docs, and make sure you that you pass correct configuration.` 136 | ); 137 | errorMessage(err2); 138 | reject(err2); 139 | }); 140 | }) 141 | .catch((err) => { 142 | reject(err); 143 | }); 144 | }) 145 | .catch((err: any) => { 146 | errorMessage("Something went wrong while verifying default configuration \n Received: " + err.message); 147 | }); 148 | }); 149 | } -------------------------------------------------------------------------------- /src/framework/builtinModules/auth/handlers/index.ts: -------------------------------------------------------------------------------- 1 | import moment from "moment"; 2 | import createJwtToken from "./../../../../framework/security/createJwtToken"; 3 | import { ApolloError } from "apollo-server"; 4 | import { verifyPassword, generateHashPassword } from "./../../../../framework/helpers/auth"; 5 | import { get } from "lodash"; 6 | 7 | export const signup = async function(obj) { 8 | 9 | const { userModel, data, emailTemplates, sendEmail, configuration } = obj; 10 | const {sendEmailOnSignup} = get(configuration,'email.sendEmailOnSignup',true) 11 | let { email, password, confirmPassword, ...restData } = data; 12 | if (password !== confirmPassword) throw new ApolloError("Passwords doesn't match."); 13 | let user = await userModel.findOneByArgs({ 14 | email: email 15 | }); 16 | if (user.instance) throw new ApolloError("Email is already used"); 17 | var hash = generateHashPassword(password); 18 | let newUser = await userModel.create({ 19 | email: email, 20 | referer: get(data, "referer", ""), 21 | superUser: false, 22 | name: get(data, "name", ""), 23 | accessToken: await createJwtToken({ 24 | email: email, 25 | for: "authentication", 26 | expiresIn: moment() 27 | .add(5, "days") 28 | .unix() 29 | }), 30 | refreshToken: await createJwtToken({ 31 | email: email, 32 | for: "refreshToken", 33 | expiresIn: moment() 34 | .add(5, "days") 35 | .unix() 36 | }), 37 | isActivated: false, 38 | isSuperUser: get(data, "isSuperUser", false), 39 | activationToken: 40 | Math.random() 41 | .toString(36) 42 | .substring(2) + 43 | Math.random() 44 | .toString(36) 45 | .substring(2) + 46 | Math.random() 47 | .toString(36) 48 | .substring(2), 49 | password: hash, 50 | ...restData 51 | }); 52 | let userInstance = newUser.instance; 53 | if (sendEmailOnSignup){ 54 | await sendEmail( 55 | emailTemplates.welcome, 56 | { 57 | email: newUser.instance.email, 58 | username: newUser.instance.email, 59 | date: moment().format("dddd, MMMM Do YYYY, h:mm:ss a"), 60 | siteName: process.env.name, 61 | activationUrl: `${process.env.frontendAppUrl}/activate-account/`, 62 | activationToken: newUser.instance.activationToken 63 | }, 64 | { 65 | from: process.env.mailerServiceUsername, 66 | to: newUser.instance.email, 67 | subject: `Welcome to ${process.env.name}` 68 | } 69 | ); 70 | } 71 | return { 72 | message: "Signup Completed", 73 | returning: userInstance 74 | }; 75 | }; 76 | export const login = async function(obj, NoUserFoundMessage: string = '"No User found with such email"') { 77 | const { userModel, data } = obj; 78 | const { email, password } = data; 79 | const restArgs = get(data, "restArgs", {}); 80 | let user = await userModel.findOneByArgs({ 81 | email: email, 82 | ...restArgs 83 | }); 84 | if (!user.instance) { 85 | throw new ApolloError(NoUserFoundMessage); 86 | } 87 | let comparePassword = await verifyPassword(password, user.instance.password); 88 | if (!comparePassword) { 89 | throw new ApolloError("Incorrect Password"); 90 | } 91 | let token = await createJwtToken({ 92 | email: email, 93 | for: "authentication", 94 | expiresIn: moment() 95 | .add(5, "days") 96 | .unix() 97 | }); 98 | user = await user.update({ 99 | accessToken: token 100 | }); 101 | return { 102 | message: "Login Completed", 103 | returning: user.instance 104 | }; 105 | }; 106 | export const twoFactorLogin = async function(obj) { 107 | const { userModel, emailTemplates, sendEmail, data } = obj; 108 | const { email } = data; 109 | let user = await userModel.findOneByArgs({ email: email }); 110 | if (!user.instance) { 111 | throw new ApolloError("Incorrect email."); 112 | } 113 | const twoFactorCode = `Code-` + Math.floor(Math.random() * 60000 + 5000); 114 | user = await user.update({ 115 | twoFactorCode: twoFactorCode 116 | }); 117 | let userInstance = user.instance; 118 | await sendEmail( 119 | emailTemplates.twoFactorLogin, 120 | { 121 | username: user.instance.email, 122 | siteName: process.env.name, 123 | twoFactorCode: twoFactorCode 124 | }, 125 | { 126 | from: process.env.mailerServiceUsername, 127 | to: user.instance.email, 128 | subject: `${twoFactorCode} is your authentication number - ${process.env.name}` 129 | } 130 | ); 131 | return { 132 | message: `A code has been sent to your email which is ${userInstance.email}` 133 | }; 134 | }; 135 | export const twoFactorLoginValidate = async function(obj) { 136 | const { userModel, data } = obj; 137 | const { twoFactorCode } = data; 138 | let user = await userModel.findOneByArgs({ twoFactorCode: twoFactorCode }); 139 | if (!user.instance) { 140 | throw new ApolloError("Incorrect twoFactorCode or already used."); 141 | } 142 | user = await user.update({ 143 | twoFactorCode: "", 144 | accessToken: await createJwtToken({ 145 | email: user.instance.email, 146 | for: "authentication" 147 | }), 148 | refreshToken: await createJwtToken({ 149 | email: user.instance.email, 150 | for: "authentication" 151 | }) 152 | }); 153 | return user.instance; 154 | }; 155 | export const loginWithAccessToken = async function(obj) { 156 | const { userModel, data } = obj; 157 | const { accessToken } = data; 158 | let user = await userModel.findOneByArgs({ accessToken: accessToken }); 159 | if (!user.instance) { 160 | throw new ApolloError("Access token is missing."); 161 | } 162 | user = await user.update({ 163 | accessToken: await createJwtToken({ 164 | email: user.instance.email, 165 | for: "authentication" 166 | }), 167 | refreshToken: await createJwtToken({ 168 | email: user.instance.email, 169 | for: "authentication" 170 | }) 171 | }); 172 | return user.instance; 173 | }; 174 | export const activateAccount = async function(obj) { 175 | const { userModel, emailTemplates, sendEmail, data } = obj; 176 | const { activationToken } = data; 177 | let user = await userModel.findOneByArgs({ activationToken: activationToken }); 178 | if (!user.instance) { 179 | throw new ApolloError("No User found or account is already is activated."); 180 | } 181 | user = await user.update({ 182 | isActivated: true, 183 | activationToken: "" 184 | }); 185 | let userInstance = user.instance; 186 | await sendEmail( 187 | emailTemplates.accountActivated, 188 | { 189 | username: user.instance.email, 190 | siteName: process.env.name 191 | }, 192 | { 193 | from: process.env.mailerServiceUsername, 194 | to: user.instance.email, 195 | subject: `Account activated ${process.env.name}` 196 | } 197 | ); 198 | return userInstance; 199 | }; 200 | export const refreshTokenHandler = async function(obj) { 201 | const { userModel, data } = obj; 202 | const { refreshToken } = data; 203 | let user = await userModel.findOneByArgs({ refreshToken: refreshToken }); 204 | if (!user.instance) { 205 | throw new ApolloError("Unauthorized, Missing refresh token."); 206 | } 207 | user = await user.update({ 208 | accessToken: await createJwtToken({ 209 | email: user.instance.email, 210 | for: "authentication" 211 | }), 212 | refreshToken: await createJwtToken({ 213 | email: user.instance.email, 214 | for: "authentication" 215 | }) 216 | }); 217 | return user.instance; 218 | }; 219 | -------------------------------------------------------------------------------- /src/framework/types/configuration.ts: -------------------------------------------------------------------------------- 1 | import { ISocketConfiguration } from "./servers"; 2 | import { IConfigurationOverride } from "./override"; 3 | import { IConfigurationRbac } from "./rbac"; 4 | 5 | 6 | export interface IConfigurationCustomModuleGraphqlMutation { 7 | schema: string; 8 | resolvers: Object; 9 | } 10 | export interface IConfigurationCustomModuleGraphqlQuery { 11 | schema: string; 12 | resolvers: Object; 13 | } 14 | 15 | export interface IConfigurationCustomModuleGraphql { 16 | schema: string; 17 | customResolvers: { 18 | [Key: string]: Function; 19 | }; 20 | mutation: IConfigurationCustomModuleGraphqlMutation; 21 | query: IConfigurationCustomModuleGraphqlQuery; 22 | } 23 | 24 | export interface IConfigurationCustomModuleRestApiEndpoint { 25 | path: string; 26 | methodType: string; 27 | handler: Function; 28 | } 29 | 30 | export interface IConfigurationCustomModuleRestApi { 31 | expressAccess: Function; 32 | endpoints: Array; 33 | } 34 | 35 | export interface IConfigurationCustomModuleDatabaseSql { 36 | fields: Object; 37 | tableName: string; 38 | tableOptions: Object; 39 | } 40 | 41 | /* 42 | 43 | Defines the type of relationship with another table. If options is passed foreignKey will be ignored. You can also add foreignKey in options. 44 | 45 | SQL/PostgreSQL(Sequelize): https://sequelize.org/master/manual/assocs.html 46 | 47 | */ 48 | 49 | export interface IConfigurationCustomModuleDatabaseRelationshipOneToOne { 50 | relationColumn: string; 51 | foreignKey: string; 52 | graphqlName: string; 53 | options: { 54 | [Key: string]: any; 55 | }; 56 | } 57 | export interface IConfigurationCustomModuleDatabaseRelationshipOneToMany { 58 | foreignKey: string; 59 | graphqlName: string; 60 | options: { 61 | [Key: string]: any; 62 | }; 63 | } 64 | 65 | /* 66 | 67 | Defines relationship for modoules. This relationship feature will map through: 68 | 69 | 1. Custom Module 70 | 2. Rest API 71 | 3. GraphQL && GraphQL Relation 72 | 73 | */ 74 | 75 | export interface IConfigurationCustomModuleDatabaseRelationship { 76 | oneToOne: { 77 | [key: string]: IConfigurationCustomModuleDatabaseRelationshipOneToOne; 78 | }; 79 | oneToMany: { 80 | [key: string]: IConfigurationCustomModuleDatabaseRelationshipOneToMany; 81 | }; 82 | // hasOne: { 83 | // [key: string]: IConfigurationCustomModuleDatabaseRelationshipType; 84 | // }, 85 | // hasMany: { 86 | // [key: string]: IConfigurationCustomModuleDatabaseRelationshipType; 87 | // }, 88 | // belongsTo: { 89 | // [key: string]: IConfigurationCustomModuleDatabaseRelationshipType; 90 | // } 91 | } 92 | 93 | export interface IConfigurationCustomModuleDatabase { 94 | sql: IConfigurationCustomModuleDatabaseSql; 95 | relationships: IConfigurationCustomModuleDatabaseRelationship; 96 | selectIgnoreFields: Array; 97 | } 98 | 99 | export interface IConfigurationCustomModule { 100 | name: string; 101 | graphql: IConfigurationCustomModuleGraphql; 102 | restApi: IConfigurationCustomModuleRestApi; 103 | database: IConfigurationCustomModuleDatabase; 104 | } 105 | 106 | export interface IConfigurationDatabase { 107 | dbDialect: string; 108 | dbConnectionString?: string; 109 | dbUsername?: string; 110 | dbPassword?: string; 111 | dbName?: string; 112 | dbHost?: string; 113 | dbPort?: string; 114 | dbInitializeOptions: { 115 | [Key: string]: any; 116 | }; 117 | } 118 | 119 | export interface IDocServerConfiguration { 120 | configuration: IConfiguration; 121 | } 122 | 123 | export interface IConfigurationEvents { 124 | beforeRestApiStart?: Function; 125 | beforeGraphqlStart?: Function; 126 | database?: { 127 | [Key: string]: { 128 | // Cud 129 | beforeSoftDelete: Function; 130 | afterSoftDelete: Function; 131 | beforeBulkDelete: Function; 132 | afterBulkDelete: Function; 133 | beforeBulkCreate: Function; 134 | afterBulkCreate: Function; 135 | beforeBulkSoftCreate: Function; 136 | afterBulkSoftCreate: Function; 137 | beforeBulkUpdate: Function; 138 | afterBulkUpdate: Function; 139 | beforeBulkSoftUpdate: Function; 140 | afterBulkSoftUpdate: Function; 141 | // R 142 | beforeList: Function; 143 | afterList: Function; 144 | beforeView: Function; 145 | afterView: Function; 146 | beforeById: Function; 147 | afterById: Function; 148 | beforeByModule: Function; 149 | afterByModule: Function; 150 | }; 151 | }; 152 | } 153 | 154 | export interface IConfigurationContext { 155 | initializeContext: Function; 156 | requestContext: Function; 157 | } 158 | 159 | export interface IConfigurationRestApi { 160 | onCustomApiFailure: Function; 161 | showWertik404Page: boolean; 162 | beforeStart: Function; 163 | restApi404Handler: Function; 164 | useCors: Boolean; 165 | useBodyParser: Boolean; 166 | useMorgan: Boolean; 167 | } 168 | 169 | export interface IConfigurationGraphql { 170 | disable: Boolean; 171 | path: string; 172 | graphqlVoyagerPath: string; 173 | disableGraphqlVoyager: Boolean; 174 | apolloGraphqlServerOptions: any; 175 | } 176 | 177 | export interface IConfigurationStorage { 178 | disable: Boolean; 179 | storageDirectory: string; 180 | } 181 | 182 | export interface IConfigurationEmail { 183 | disable: boolean; 184 | defaultMailerInstance: any; // This can be the mailer instance a user is using, Just like node mailer 185 | sendEmail: Function; // A function that uses IConfigurationEmail.defaultEmailInstance and sends an email 186 | configuration: any; // This can be string or object, A string of connection string for smtp mailer or configuration for node mailer 187 | templates: { 188 | [Key: string]: string; 189 | }; 190 | sendEmailOnSignup: Boolean; 191 | } 192 | 193 | export interface IConfigurationCron { 194 | disable: Boolean; 195 | cronList: Array<{ 196 | expression: string; 197 | function: Function; 198 | options: Object; 199 | events: { 200 | initialized: Function; 201 | }; 202 | }>; 203 | } 204 | 205 | export interface IConfigurationBackup { 206 | local: { 207 | output_directory: String; 208 | }; 209 | dropbox: { 210 | key: String 211 | }; 212 | digitalOceanSpaces: { 213 | accessKeyId: String; 214 | secretAccessKey: String; 215 | spacesEndpoint: String; 216 | uploadParams: { 217 | Bucket: String; 218 | ACL: String; 219 | } 220 | }; 221 | } 222 | 223 | export interface IConfiguration { 224 | dbDialect: string; 225 | name: string; 226 | builtinModules: string; 227 | expressApp: any; 228 | port: number; 229 | startServers: boolean; 230 | extendBuiltinModules: { 231 | [Key: string]: { 232 | database: { 233 | tableFieds: any; 234 | }; 235 | graphql: { 236 | mainSchemaExtend: string; 237 | inputSchemaExtend: string; 238 | }; 239 | }; 240 | }; 241 | database: IConfigurationDatabase; 242 | databaseInstance: any; 243 | frontendAppUrl: string; 244 | frontendAppActivationUrl: string; 245 | frontendAppPasswordResetUrl: string; 246 | context: { 247 | [Key: string]: any; 248 | }; 249 | backup: IConfigurationBackup; 250 | email: IConfigurationEmail; 251 | override: IConfigurationOverride; 252 | restApi: IConfigurationRestApi; 253 | graphql: IConfigurationGraphql; 254 | modules: Array; 255 | events: IConfigurationEvents; 256 | seeds: any; 257 | sockets: ISocketConfiguration; 258 | rbac: IConfigurationRbac; 259 | storage: IConfigurationStorage; 260 | cron: IConfigurationCron; 261 | } -------------------------------------------------------------------------------- /docs/custom-modules/introduction.md: -------------------------------------------------------------------------------- 1 | ### Custom Modules 2 | 3 | With this guide, you can extend your app with extra modules and functionality. Let's create a Articles module in this example. 4 | 5 | So, This is how your complete module will look like. 6 | 7 | ```javascript 8 | let otherConfiguration = {}; // Here other wertik configuration 9 | let configuration = { 10 | ...otherConfiguration, 11 | modules: [ 12 | { 13 | name: "Article", 14 | useDatabase: true, 15 | fields: { 16 | sql: { 17 | title: { 18 | type: "String", 19 | }, 20 | }, 21 | }, 22 | graphql: { 23 | // Main schema for Article 24 | schema: ` 25 | type Article { 26 | title: String 27 | } 28 | type ArticleInput { 29 | title: String 30 | } 31 | `, 32 | mutation: { 33 | schema: ``, // Write mutation schema for your module Article 34 | resolvers: {}, // Resolvers for article mutations 35 | }, 36 | query: { 37 | schema: ``, // Write query schema for your module Article 38 | resolvers: {}, // Resolvers for article query 39 | }, 40 | }, 41 | restApi: { 42 | endpoints: [ 43 | { 44 | path: "/people/", // Will be available under http://localhost:port/api/v1/article/people/ 45 | methodType: "get", // get,post,put,delete,copy,head,options,link,unlink,purge,lock,unlock,view 46 | handler: async function (req, res, restApiSuccessResponse, restApiErrorResponse) { 47 | let somethinWentFine = true; 48 | if (somethinWentFine) { 49 | restApiSuccessResponse({ 50 | res: res, 51 | data: { 52 | success: true, 53 | }, 54 | message: `Went all fine`, 55 | }); 56 | } else { 57 | restApiErrorResponse({ 58 | err: Error, 59 | res: res, 60 | data: { 61 | message: "Something went wrong", 62 | message: "Detail", 63 | }, 64 | }); 65 | } 66 | }, 67 | }, 68 | ], 69 | }, 70 | }, 71 | ], 72 | }; 73 | export default configuration; 74 | ``` 75 | 76 | #### Database options: 77 | 78 | To allow database you have to configure your module in this way: 79 | 80 | ```javascript 81 | let configuration = { 82 | modules: [ 83 | { 84 | useDatabase: true, // Set false if you are not storing data for this module. 85 | database: { 86 | sql: { 87 | tableName: "YOUR_TABLE_NAME", 88 | tableOptions: { 89 | // Use Sequelize table options here, Please see https://sequelize.org/v5/manual/models-definition.html 90 | // See section: Apart from datatypes, there are plenty of options that you can set on each column. 91 | }, 92 | fields: { 93 | // Use Sequelize table fields here, Please see https://sequelize.org/v5/manual/models-definition.html 94 | }, 95 | }, 96 | }, 97 | }, 98 | ], 99 | }; 100 | ``` 101 | 102 | #### GraphQL: 103 | 104 | To Enable to GraphQL, You have to configure your module in this way: 105 | 106 | ```javascript 107 | let configuration = { 108 | modules: [ 109 | { 110 | // other module configuration 111 | graphql: { 112 | // In schema you have to Provide same type as you provided the moduleName, Consider you have provided Person you have to set it as Person with attributes 113 | // PersonInput is an input that will be used for mutations. If you set your module to Person then input name should be PersonInput 114 | schema: ` 115 | type Person { 116 | id: Int 117 | name: String 118 | } 119 | input PersonInput { 120 | id: Int 121 | name: String 122 | } 123 | `, 124 | // You can add more custom mutations to this module, See below 125 | mutation: { 126 | schema: ` 127 | UpdatePersonWithUser(input: PersonInput): Person 128 | `, 129 | resolvers: { 130 | // Here as you have set mutation name as UpdatePersonWithUser, you have provide a method with same name: 131 | UpdatePersonWithUser: async (_, args, context, info) => { 132 | // _ is the fields parent, as Apollo states: The return value of the resolver for this field's parent (i.e., the previous resolver in the resolver chain). 133 | // args is the object passed from oustide 134 | // For please see context part 135 | // info is the information about Query 136 | // Read more about resolver arguments here: https://www.apollographql.com/docs/apollo-server/data/resolvers/#resolver-arguments 137 | return {}; // Something 138 | }, 139 | }, 140 | }, 141 | query: { 142 | // You can add custom Query to your module 143 | schema: ` 144 | ReturnbasicPerson: Person 145 | `, 146 | resolvers: { 147 | ReturnbasicPerson: async (_, args, context, info) => { 148 | return { 149 | id: 1, 150 | name: "John", 151 | }; 152 | }, 153 | }, 154 | }, 155 | }, 156 | }, 157 | ], 158 | }; 159 | ``` 160 | 161 | #### GraphQL Subscription: 162 | 163 | The above module will generate these GraphQL subscriptions: 164 | 165 | UserRoleCreated: UserRole 166 | UserRoleSaved: UserRole 167 | UserRoleDeleted: SuccessResponse 168 | UserRoleUpdated: UserRole 169 | UserRoleSoftDeleted: UserRole 170 | 171 | #### Rest API: 172 | 173 | So when you enable useDatabase feature, If you have enabled RestAPI these API's will be generated by Wertik for the module named Person: 174 | 175 | [SAVE] http://localhost:7000/api/v1/person/save - {input: args } 176 | [POST] http://localhost:7000/api/v1/person/create - {input: args } 177 | [PUT] http://localhost:7000/api/v1/person/update - {input: args } 178 | [GET] http://localhost:7000/api/v1/person/view/:id 179 | [DELETE] http://localhost:7000/api/v1/person/:id/delete 180 | [POST] http://localhost:7000/api/v1/person/list - { filters: [], pagination: [], sorting: [] } 181 | [POST] http://localhost:7000/api/v1/person/bulk-create - {input: args } 182 | [PUT] http://localhost:7000/api/v1/person/bulk-update - {input: args } 183 | [DELETE] http://localhost:7000/api/v1/person/bulk-delete {input: [12,13,15]} 184 | [DELETE] http://localhost:7000/api/v1/person/soft-delete - {input: {id: 12} } 185 | [DELETE] http://localhost:7000/api/v1/person/bulk-soft-delete - {input: [12,13,15]} 186 | 187 | 7000 is your Wertik JS default port. You can change it. To create custom Rest API endpoints, You can do something like this: 188 | 189 | ```javascript 190 | let configuration = { 191 | // other configuration 192 | modules: [ 193 | { 194 | name: "Person", 195 | restApi: { 196 | endpoints: [ 197 | { 198 | path: "/path", 199 | methodType: "post", 200 | handler: (req, res, restApiSuccessResponse, restApiErrorResponse) => { 201 | let isAllRight = true; 202 | if (isAllRight) { 203 | restApiSuccessResponse({ 204 | res: res, 205 | message: "String", 206 | data: {}, 207 | }); 208 | } else { 209 | restApiErrorResponse({ 210 | code: 500, 211 | err: e, 212 | res: res, 213 | data: {}, 214 | }); 215 | } 216 | }, 217 | }, 218 | ], 219 | }, 220 | }, 221 | ], 222 | }; 223 | ``` 224 | -------------------------------------------------------------------------------- /docs/v2/configuration.md: -------------------------------------------------------------------------------- 1 | ### Configuration 2 | 3 | Wertik runs on a single object, It is like options or configuration. Configuration everything about mailer, garphql, restapi, sockets and custom modules. 4 | 5 | Before getting started, Have a look at our [Default Configuration](https://github.com/Uconnect-Technologies/wertik-js/blob/master/src/framework/defaults/defaultConfigurations/defaultConfiguration.ts) to get an idea about configuration. Let's go with the configuration: 6 | 7 | #### name 8 | 9 | **Type:** String 10 | **Default Value:** Wertik 11 | **Description:** The name of your application 12 | 13 | #### builtinModules 14 | 15 | **Type:** String 16 | **Default Value:** user,auth,forgetPassword,permission,role,rolePermission,userPermission,userRole,me,storage,mail 17 | **Description:** Wertik-js provide some builtin module. To enable them you have to provide their names by comma separate. 18 | 19 | #### database 20 | 21 | **Type:** Object 22 | **Default Value:** 23 | **Description:** Your database credentials. 24 | 25 | ```javascript 26 | { 27 | dbDialect: process.env.dbDialect, 28 | dbUsername: process.env.dbUsername, 29 | dbPassword: process.env.dbPassword, 30 | dbName: process.env.dbName, 31 | dbHost: process.env.dbHost, 32 | dbPort: process.env.dbPort, 33 | } 34 | ``` 35 | 36 | #### port 37 | 38 | **Type:** Number | Integer 39 | **Default Value:** 5000 40 | **Description:** The port on which your application will run 41 | 42 | #### startServers 43 | 44 | **Type:** Boolean 45 | **Default Value:** true 46 | **Description:** If assign true, Wertik will start server. If assign false, Wertik will not start servers. You have to start servers. Wertik gives you following properties to start servers: https://github.com/Uconnect-Technologies/wertik-js/blob/master/src/main.ts#L114. 47 | 48 | #### frontendAppUrl 49 | 50 | (obselete donot use this option) 51 | 52 | #### frontendAppActivationUrl 53 | 54 | (obselete donot use this option) 55 | 56 | #### frontendAppPasswordResetUrl 57 | 58 | (obselete donot use this option) 59 | 60 | #### context 61 | 62 | **Type:** Object `{initializeContext: Function, requestContext: Function}` 63 | 64 | **Description:** The is same as the graphql context argument and in express passing properties through req object to rest api handlers. So here are two things, initializeContext that will run one time when application starts and passes to each context of graphql and express. And requestContext will every time you pass a request to server. 65 | For rest api please see initializeContext: https://github.com/Uconnect-Technologies/wertik-js/blob/master/src/framework/restApi/index.ts#L28 66 | For rest api please see requestContext: https://github.com/Uconnect-Technologies/wertik-js/blob/master/src/framework/restApi/index.ts#L46 67 | 68 | For graphql please see initializeContext: https://github.com/Uconnect-Technologies/wertik-js/blob/master/src/framework/graphql/index.ts#L20 69 | For graphql please see requestContext: https://github.com/Uconnect-Technologies/wertik-js/blob/master/src/framework/graphql/index.ts#L60 70 | 71 | **Default Value:** 72 | 73 | ```javascript 74 | { 75 | { 76 | initializeContext: function (mode, context) { 77 | return { 78 | someKey: "somekeyvalue", 79 | }; 80 | }, 81 | requestContext: async function (mode, context) { 82 | return { 83 | value: "Value 1", 84 | }; 85 | } 86 | } 87 | ``` 88 | 89 | #### email 90 | 91 | **Type:** Object 92 | **Default Value:** 93 | **Description:** Wertik uses NodeMailer for emails. The configuration for email handling in Wertik 94 | 95 | ```javascript 96 | { 97 | disable: true; 98 | configuration: NodeMailerConfigurationObject; 99 | } 100 | ``` 101 | 102 | By default emailing is disabled in wertik, Assigning false to disable activates the emailing in wertik. You have to provide second arugment which is configuration of Node mailer. 103 | 104 | When pasing mailer. Wertik sends a method called sendEmail to context to send emails. Please see this function for more details: https://github.com/Uconnect-Technologies/wertik-js/blob/master/src/framework/mailer/index.ts#L22. Since its a closure, Main function starts from here: https://github.com/Uconnect-Technologies/wertik-js/blob/master/src/framework/mailer/index.ts#L22. 105 | 106 | #### graphql 107 | 108 | **Type:** Object 109 | **Default Value:** 110 | **Description:** Options for graphql. We have used Apollo Graphql. 111 | 112 | ```javascript 113 | { 114 | disable: false, 115 | apolloGraphqlServerOptions: defaultApolloGraphqlOptions 116 | } 117 | ``` 118 | 119 | apolloGraphqlServerOptions are the options for apollo graphql. For more details please see: https://www.apollographql.com/docs/apollo-server/api/apollo-server/#options. 120 | 121 | Note: Please donot add these options context, resolvers and typeDefs. 122 | 123 | #### restApi 124 | 125 | **Type:** Object 126 | **Default Value:** 127 | **Description:** This is the options for rest api. Which means the express. 128 | 129 | ```javascript 130 | const a = { 131 | showWertik404Page: true 132 | restApi404Handler: function (req, res) { 133 | res.status(404).json({ 134 | message: "Not found", 135 | data: { 136 | message: "Request page didn't found", 137 | }, 138 | }); 139 | }, 140 | beforeStart: Void 141 | } 142 | ``` 143 | 144 | If you don't want wertik to show 404 page, you can assign it as 404. You can also customize 404 response with restApi404Handler helper. beforeStarts run before starting server. 145 | 146 | #### modules 147 | 148 | **Type:** Array 149 | **Default Value:** [] 150 | **Description:** To add your custom functionality, you can use modules, Like User module. Companies Module. User Role module. To get an idea please visit: https://github.com/Uconnect-Technologies/wertik-js/tree/master/src/framework/builtinModules. 151 | 152 | Please see an example module from here: https://github.com/Uconnect-Technologies/wertik-js/blob/master/src/framework/builtinModules/userRole/index.ts. 153 | 154 | For details about custom modules please visit: http://wapgee.com/wertik-js/custom-modules/introduction. 155 | 156 | #### events - beta 157 | 158 | **Type:** Object 159 | **Default Value:** `{}` 160 | **Description:** So events run when something happens. Right now wertik offer events on database model insert and update. 161 | 162 | For more please visit this page for configuration idea: https://github.com/Uconnect-Technologies/wertik-js/blob/master/src/framework/types/configuration.ts#L137 163 | 164 | For graphql: https://github.com/Uconnect-Technologies/wertik-js/blob/master/src/framework/graphql/crudGenerator.ts#L112 165 | For rest api: https://github.com/Uconnect-Technologies/wertik-js/blob/master/src/framework/restApi/versions/v1/loadAllModules.ts#L40 166 | 167 | #### seeds 168 | 169 | (We recommend to use some other seed helpers like knex seeds or sequelize seeds). 170 | 171 | #### sockets - beta 172 | 173 | **Type:** Object 174 | **Default Value:** 175 | **Description:** For sockets we use Socket.IO. 176 | 177 | ```javascript 178 | { 179 | disable: false, 180 | onClientConnected: function () { 181 | console.log("onClientConnected"); 182 | }, 183 | onMessageReceived: function () { 184 | console.log("onMessageReceived"); 185 | }, 186 | onClientDisconnect: function () { 187 | console.log("onClientDisconnect"); 188 | }, 189 | }; 190 | ``` 191 | 192 | For please visit https://github.com/Uconnect-Technologies/wertik-js/blob/master/src/framework/socket/index.ts. 193 | 194 | #### storage - beta 195 | 196 | **Type:** Object 197 | **Default Value:** 198 | 199 | ```javascript 200 | { 201 | storageDirectory: "./uploads/", 202 | }, 203 | ``` 204 | 205 | **Description:** Its a module for storage functionality in your app. 206 | 207 | For please visit: https://github.com/Uconnect-Technologies/wertik-js/blob/master/src/framework/builtinModules/storage/index.ts. 208 | 209 | ### Cron - beta 210 | 211 | **Type:** Object 212 | **Default Value:** {} 213 | **Description:** Under the we use node-cron library for cron jobs. Please see https://www.npmjs.com/package/node-cron. You can 214 | 215 | For more please [Default Configuration](https://github.com/Uconnect-Technologies/wertik-js/blob/master/src/framework/defaults/defaultConfigurations/defaultConfiguration.ts) to get an idea about cron jobs. 216 | --------------------------------------------------------------------------------