├── .DS_Store ├── .editorconfig ├── .env.sample ├── .eslintignore ├── .eslintrc.json ├── .gitattributes ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.yml │ ├── config.yml │ └── feature_request.yml └── pull_request_template.md ├── .gitignore ├── .idea ├── .gitignore ├── codeStyles │ ├── Project.xml │ └── codeStyleConfig.xml ├── copywriter-api.iml ├── copywriterproai-backend.iml ├── inspectionProfiles │ ├── Project_Default.xml │ └── profiles_settings.xml ├── misc.xml ├── modules.xml └── vcs.xml ├── .lintstagedrc.json ├── .prettierignore ├── .prettierrc.json ├── CONTRIBUTING.md ├── Dockerfile ├── LICENSE ├── README.md ├── docker-compose.yml ├── ecosystem.config.json ├── package.json ├── pnpm-lock.yaml ├── src ├── .DS_Store ├── app.js ├── config │ ├── config.js │ ├── corsoptions.js │ ├── inputLimit.js │ ├── logger.js │ ├── mailtype.js │ ├── morgan.js │ ├── passport.js │ ├── plan.js │ ├── roles.js │ └── tokens.js ├── controllers │ ├── auth.controller.js │ ├── blog.controller.js │ ├── content.controller.js │ ├── demo.controller.js │ ├── extension.controller.js │ ├── index.js │ ├── interest.controller.js │ ├── notice.controller.js │ ├── payment.controller.js │ ├── plagiarismChecker.controller.js │ ├── subscriber.controller.js │ ├── support.controller.js │ ├── tool.controller.js │ └── user.controller.js ├── data │ └── notice.json ├── index.js ├── middlewares │ ├── auth.js │ ├── content.validate.js │ ├── error.js │ ├── passportAuth.js │ ├── rateLimiter.js │ ├── validate.js │ └── verifyEmail.js ├── models │ ├── blog.model.js │ ├── content.model.js │ ├── demo.model.js │ ├── extension.model.js │ ├── index.js │ ├── interest.model.js │ ├── payment.model.js │ ├── plugins │ │ ├── index.js │ │ ├── paginate.plugin.js │ │ └── toJSON.plugin.js │ ├── subscriber.model.js │ ├── token.model.js │ ├── tool.model.js │ └── user.model.js ├── routes │ └── v1 │ │ ├── auth.route.js │ │ ├── blog.route.js │ │ ├── content.route.js │ │ ├── demo.route.js │ │ ├── extension.route.js │ │ ├── index.js │ │ ├── interest.route.js │ │ ├── notice.route.js │ │ ├── onboarding.js │ │ ├── payment.route.js │ │ ├── plagiarismChecker.routes.js │ │ ├── subscriber.route.js │ │ ├── support.route.js │ │ ├── tool.route.js │ │ └── user.route.js ├── services │ ├── auth.service.js │ ├── blog.service.js │ ├── content.service.js │ ├── contents │ │ ├── amazon.contents.js │ │ ├── blog.contents.js │ │ ├── business.contents.js │ │ ├── common.contents.js │ │ ├── cooking.contents.js │ │ ├── cv.contents.js │ │ ├── demo.contents.js │ │ ├── email.contents.js │ │ ├── extension.contents.js │ │ ├── facebook.contents.js │ │ ├── features.contents.js │ │ ├── fiverr.contents.js │ │ ├── google.contents.js │ │ ├── headline.contents.js │ │ ├── index.js │ │ ├── instagram.contents.js │ │ ├── linkedIn.contents.js │ │ ├── product.contents.js │ │ ├── sales.contents.js │ │ ├── website.contents.js │ │ ├── writing.contents.js │ │ └── youtube.contents.js │ ├── email.service.js │ ├── extension.service.js │ ├── index.js │ ├── interest.service.js │ ├── payment.service.js │ ├── plagiarismChecker.services.js │ ├── subscriber.service.js │ ├── token.service.js │ ├── tool.service.js │ ├── user.service.js │ └── utils.service.js ├── utils │ ├── ApiError.js │ ├── catchAsync.js │ └── pick.js └── validations │ ├── auth.validation.js │ ├── blog.validation.js │ ├── contents │ ├── amazon.validation.js │ ├── blog.validation.js │ ├── business.validation.js │ ├── common.validation.js │ ├── cooking.validation.js │ ├── cv.validation.js │ ├── demo.validation.js │ ├── email.validation.js │ ├── extension.validation.js │ ├── facebook.validation.js │ ├── features.validation.js │ ├── fiverr.validation.js │ ├── google.validation.js │ ├── headline.validation.js │ ├── helper.validation.js │ ├── index.js │ ├── instagram.validation.js │ ├── linkedIn.validation.js │ ├── product.validation.js │ ├── sales.validation.js │ ├── validationData │ │ ├── amazon.data.js │ │ ├── blog.data.js │ │ ├── business.data.js │ │ ├── common.data.js │ │ ├── cooking.data.js │ │ ├── cv.data.js │ │ ├── demo.data.js │ │ ├── email.data.js │ │ ├── extension.data.js │ │ ├── facebook.data.js │ │ ├── features.data.js │ │ ├── fiverr.data.js │ │ ├── google.data.js │ │ ├── headline.data.js │ │ ├── index.js │ │ ├── instagram.data.js │ │ ├── linkedIn.data.js │ │ ├── product.data.js │ │ ├── sales.data.js │ │ ├── website.data.js │ │ ├── writing.data.js │ │ └── youtube.data.js │ ├── website.validation.js │ ├── writing.validation.js │ └── youtube.validation.js │ ├── custom.validation.js │ ├── index.js │ ├── interest.validation.js │ ├── notice.validation.js │ ├── payment.validation.js │ ├── plagiarismChecker.validation.js │ ├── services.txt │ ├── subscriber.validation.js │ ├── support.validation.js │ ├── tool.validation.js │ └── user.validation.js ├── stripe.json ├── yarn 2.lock └── yarn.lock /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CopywriterPro-ai/copywriterproai-backend/048cc8359a98f9dd575182b8c47b80f271846a98/.DS_Store -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | -------------------------------------------------------------------------------- /.env.sample: -------------------------------------------------------------------------------- 1 | PORT=8080 2 | 3 | # MONGODB Database configuration 4 | MONGODB_URL=mongodb://127.0.0.1:27017/copywriterpro 5 | 6 | # JWT secret key 7 | JWT_SECRET=23uF$%gdfh43@kDj#6Yf8sV4kL@Z9m#N7bS^Yhd9 8 | # Number of minutes after which an access token expires 9 | JWT_ACCESS_EXPIRATION_MINUTES=15 10 | # Number of days after which a refresh token expires 11 | JWT_REFRESH_EXPIRATION_DAYS=1000 12 | 13 | # WORD LIMIT 14 | PACKAGES=FREEMIUM,BASIC_1MONTH,BASIC_6MONTH,STANDARD_1MONTH,STANDARD_6MONTH,PROFESSIONAL_1MONTH,PROFESSIONAL_6MONTH 15 | INPUT_CHARACTER_RATE=1,1,1,2,2,4,4 16 | 17 | # COPYSCAPE 18 | COPYSCAPE_USERNAME=randomUser123 19 | COPYSCAPE_API_KEY=randomAPIKey12345 20 | PLAGIARISM_CHECKER_ALLOWED_PACKAGES=FREEMIUM,BASIC_1MONTH 21 | 22 | # Google oauth2 client id 23 | GOOGLE_OAUTH2_CLIENT_ID=352363168566-random-client-id-12345.apps.googleusercontent.com 24 | 25 | # Google oauth2 secret id 26 | GOOGLE_OAUTH2_SECRET_ID=randomSecretId12345 27 | 28 | # Passport secret jwt key 29 | PASSPORT_SECRET_JWT_KEY=randomPassportSecretKey12345 30 | 31 | # Passport auth expires time 32 | PASSPORT_AUTH_EXPIRES_TIME=1h 33 | 34 | # Facebook app id 35 | FACEBOOK_APP_ID=1234567890123456 36 | 37 | # Facebook app secret 38 | FACEBOOK_APP_SECRET=randomFacebookAppSecret12345 39 | 40 | # STRIPE 41 | STRIPE_SECRET_KEY=sk_test_randomStripeSecretKey12345 42 | STRIPE_WEBHOOK_SECRET_KEY=whsec_randomStripeWebhookKey12345 43 | 44 | # SMTP configuration options for the email service 45 | SMTP_HOST=email-smtp.us-east-1.amazonaws.com 46 | SMTP_PORT=465 47 | SMTP_USERNAME=randomSMTPUsername12345 48 | SMTP_PASSWORD=randomSMTPPassword12345 49 | EMAIL_FROM=noreply@copywriterpro.ai 50 | 51 | # OpenApi 52 | OPENAI_API_KEY=sk-proj-randomOpenApiKey12345 53 | 54 | # Web Client URL 55 | WEB_CLIENT_URL=http://localhost:3000 56 | 57 | # Mail token verify 58 | MAIL_VERIFY_TOKEN_SECRET=randomMailVerifyTokenSecret12345 59 | MAIL_VERIFY_TOKEN_EXPIRE=10m 60 | 61 | # Cors Whitelist 62 | CORS_WHITELIST=https://example.com,https://example2.com,http://localhost:3000,http://localhost:5000 63 | 64 | # Sentry dns URL 65 | SENTRY_DNS_URL=https://randomSentryDnsUrl@o737236.ingest.sentry.io/5791435 66 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | bin 3 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "node": true 4 | }, 5 | "extends": ["airbnb-base", "plugin:prettier/recommended"], 6 | "plugins": ["prettier"], 7 | "parserOptions": { 8 | "ecmaVersion": 2018 9 | }, 10 | "rules": { 11 | "no-console": "off", 12 | "func-names": "off", 13 | "no-underscore-dangle": "off", 14 | "consistent-return": "off", 15 | "prettier/prettier": "error" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Convert text file line endings to lf 2 | * text eol=lf 3 | *.js text 4 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: 🐛 Bug Report 2 | description: Create a report to help us improve 3 | # title: "Bug Report" 4 | labels: [bug, triage] 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | Thank you for submitting a Bug Report! 10 | 11 | - type: checkboxes 12 | attributes: 13 | label: Search before asking 14 | description: > 15 | Please search the CopywriterPro [issues](https://github.com/CopywriterPro-ai/copywriterproai-backend/issues) to see if a similar bug report already exists. 16 | options: 17 | - label: > 18 | I have searched the CopywriterPro [issues](https://github.com/CopywriterPro-ai/copywriterproai-backend/issues) and found no similar bug report. 19 | required: true 20 | 21 | - type: textarea 22 | attributes: 23 | label: Bug 24 | description: Provide console output with error messages and/or screenshots of the bug. 25 | placeholder: | 26 | Provide a brief description of the problem here. 27 | validations: 28 | required: true 29 | 30 | - type: textarea 31 | attributes: 32 | label: Steps To Reproduce 33 | description: Provide clearly ordered steps to reproduce the issue 34 | placeholder: | 35 | Include as much information as possible (screenshots, logs, tracebacks etc.) to receive the most helpful response. 36 | validations: 37 | required: true 38 | 39 | - type: textarea 40 | attributes: 41 | label: Current Behavior 42 | description: Clearly describe the current behavior 43 | placeholder: | 44 | Include as much information as possible (screenshots, logs, tracebacks etc.) to receive the most helpful response. 45 | validations: 46 | required: true 47 | 48 | - type: textarea 49 | attributes: 50 | label: Expected Behavior 51 | description: Clearly describe the expected behavior 52 | placeholder: | 53 | Include as much information as possible (screenshots, logs, tracebacks etc.) to receive the most helpful response. 54 | validations: 55 | required: true 56 | 57 | - type: textarea 58 | attributes: 59 | label: Additional 60 | description: Anything else you would like to share? 61 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yml: -------------------------------------------------------------------------------- 1 | name: 🚀 Feature Request 2 | description: Suggest an idea for this project 3 | # title: "Feature Request" 4 | labels: [enhancement] 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | Thank you for submitting a Feature Request! 10 | 11 | - type: checkboxes 12 | attributes: 13 | label: Search before asking 14 | description: > 15 | Please search the CopywriterPro [issues](https://github.com/CopywriterPro-ai/copywriterproai-backend/issues) to see if a similar feature request already exists. 16 | options: 17 | - label: > 18 | I have searched the CopywriterPro [issues](https://github.com/CopywriterPro-ai/copywriterproai-backend/issues) and found no similar feature request. 19 | required: true 20 | 21 | - type: textarea 22 | attributes: 23 | label: Description 24 | description: A short description of your feature. 25 | placeholder: | 26 | What new feature would you like to see in CopywriterPro? 27 | validations: 28 | 29 | - type: textarea 30 | attributes: 31 | label: Use case 32 | description: | 33 | Describe the use case of your feature request. 34 | placeholder: | 35 | How would this feature be used, and who would use it? 36 | 37 | - type: textarea 38 | attributes: 39 | label: Additional 40 | description: Anything else you would like to share? 41 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## Please answer the following queries before submitting a PR 2 | 3 | 4 | * **Issue ticket number and link** 5 | 6 | 7 | * **What kind of change does this PR introduce?** (Bug fix, feature, docs update, ...) 8 | 9 | 10 | * **What is the current behavior?** (You can also link to an open issue here) 11 | 12 | 13 | * **What is the new behavior (if this is a feature change)?** 14 | 15 | 16 | * **Other information**: 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | node_modules 3 | 4 | # yarn error logs 5 | yarn-error.log 6 | 7 | # Environment varibales 8 | .env* 9 | !.env*.example 10 | 11 | # Code coverage 12 | coverage 13 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Editor-based HTTP Client requests 5 | /httpRequests/ 6 | # Datasource local storage ignored files 7 | /dataSources/ 8 | /dataSources.local.xml 9 | # GitHub Copilot persisted chat sessions 10 | /copilot/chatSessions 11 | -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 15 | 16 | 24 | 25 | 28 | 29 | 36 | 37 | 44 | 45 | 52 | 53 | 58 | 59 | -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/copywriter-api.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/copywriterproai-backend.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 15 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.lintstagedrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "*.js": "eslint" 3 | } 4 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | coverage 3 | 4 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "printWidth": 125 4 | } 5 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Thanks for your interest in contributing to CopywriterProAI! We welcome and appreciate your contributions. To report bugs, create a [GitHub issue](https://github.com/CopywriterPro-ai/copywriterproai-backend/issues/new/choose). 4 | 5 | ## Contribution Guide 6 | 7 | ### 1. Fork the Official Repository 8 | 9 | Fork the [CopywriterProAI backend repository](https://github.com/CopywriterPro-ai/copywriterproai-backend) into your own account. Clone your forked repository into your local environment. 10 | 11 | ```shell 12 | git clone git@github.com:/copywriterproai-backend.git 13 | ``` 14 | 2. Configure Git 15 | Set the official repository as your upstream to synchronize with the latest updates in the official repository. Add the original repository as upstream. 16 | 17 | ```shell 18 | cd copywriterproai-backend 19 | git remote add upstream git@github.com:CopywriterPro-ai/copywriterproai-backend.git 20 | ``` 21 | Verify that the remote is set. 22 | 23 | ```shell 24 | 25 | git remote -v 26 | ``` 27 | You should see both origin and upstream in the output. 28 | 29 | 3. Synchronize with Official Repository 30 | Synchronize the latest commit with the official repository before coding. 31 | 32 | ```shell 33 | 34 | git fetch upstream 35 | git checkout main 36 | git merge upstream/main 37 | git push origin main 38 | ``` 39 | ### 4. Create a New Branch and Open a Pull Request 40 | 41 | After you finish your implementation, open the forked repository. The source branch is your new branch, and the target branch is the `CopywriterPro-ai/copywriterproai-backend` `main` branch. Then a PR should appear in [CopywriterProAI Backend PRs](https://github.com/CopywriterPro-ai/copywriterproai-backend/pulls). 42 | 43 | The CopywriterProAI team will review your code. 44 | 45 | ## PR Rules 46 | 47 | ### 1. Pull Request Title 48 | 49 | As described [here](https://github.com/commitizen/conventional-commit-types/blob/master/index.json), a valid PR title should begin with one of the following prefixes: 50 | 51 | - `feat`: A new feature 52 | - `fix`: A bug fix 53 | - `doc`: Documentation only changes 54 | - `refactor`: A code change that neither fixes a bug nor adds a feature 55 | - `style`: A refactoring that improves code style 56 | - `perf`: A code change that improves performance 57 | - `test`: Adding missing tests or correcting existing tests 58 | - `ci`: Changes to CI configuration files and scripts (example scopes: `.github`, `ci` (Buildkite)) 59 | - `chore`: Other changes that don't modify src or test files 60 | - `revert`: Reverts a previous commit 61 | 62 | For example, a PR title could be: 63 | - `refactor: modify package path` 64 | - `feat(backend): add new API endpoint`, where `(backend)` means that this PR mainly focuses on the backend component. 65 | 66 | You may also check out previous PRs in the [PR list](https://github.com/CopywriterPro-ai/copywriterproai-backend/pulls). 67 | 68 | As described [here](https://github.com/CopywriterPro-ai/copywriterproai-backend/labels), we create several labels. Every PR should be tagged with the corresponding labels. 69 | 70 | ### 2. Pull Request Description 71 | 72 | - If your PR is small (such as a typo fix), you can keep it brief. 73 | - If it is large and you have changed a lot, it's better to write more details. 74 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Use the official Node.js image as the base image 2 | FROM node:12 3 | 4 | # Set the working directory 5 | WORKDIR /usr/src/app 6 | 7 | # Copy the package.json and yarn.lock files 8 | COPY package*.json yarn.lock ./ 9 | 10 | # Install dependencies 11 | RUN yarn install 12 | 13 | # Copy the rest of the application code 14 | COPY . . 15 | 16 | # Expose the port the app runs on 17 | EXPOSE 8080 18 | 19 | # Command to start the application 20 | CMD ["yarn", "start"] 21 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.8' 2 | 3 | services: 4 | app: 5 | build: . 6 | ports: 7 | - "8080:8080" 8 | env_file: 9 | - .env 10 | environment: 11 | - NODE_ENV=development 12 | - MONGODB_URL=mongodb://mongo:27017/copywriterpro 13 | depends_on: 14 | - mongo 15 | 16 | mongo: 17 | image: mongo:latest 18 | ports: 19 | - "27017:27017" 20 | volumes: 21 | - mongo-data:/data/db 22 | 23 | volumes: 24 | mongo-data: 25 | -------------------------------------------------------------------------------- /ecosystem.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "apps": [ 3 | { 4 | "name": "app", 5 | "script": "src/index.js", 6 | "instances": 1, 7 | "autorestart": true, 8 | "watch": false, 9 | "time": true, 10 | "env": { 11 | "NODE_ENV": "production" 12 | } 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "copywriter-api", 3 | "version": "1.0.0", 4 | "description": "REST API for copywriter", 5 | "main": "src/index.js", 6 | "license": "MIT", 7 | "engines": { 8 | "node": ">=12.0.0" 9 | }, 10 | "scripts": { 11 | "start": "pm2 start ecosystem.config.json --no-daemon", 12 | "dev": "cross-env NODE_ENV=development nodemon src/index.js", 13 | "trace": "node --trace-warnings src/index.js", 14 | "lint": "eslint .", 15 | "lint:fix": "eslint . --fix", 16 | "prettier": "prettier --check **/*.js", 17 | "prettier:fix": "prettier --write **/*.js" 18 | }, 19 | "keywords": [ 20 | "copywriter-api" 21 | ], 22 | "dependencies": { 23 | "@sentry/node": "^6.4.1", 24 | "@sentry/tracing": "^6.4.1", 25 | "bcryptjs": "^2.4.3", 26 | "chalk": "^4.1.2", 27 | "compression": "^1.7.4", 28 | "connect-timeout": "^1.9.0", 29 | "copyscape": "^0.0.4", 30 | "cors": "^2.8.5", 31 | "cross-env": "^7.0.3", 32 | "dotenv": "^8.2.0", 33 | "express": "^4.17.1", 34 | "express-mongo-sanitize": "^2.0.2", 35 | "express-rate-limit": "^5.2.6", 36 | "helmet": "^4.4.1", 37 | "http-status": "^1.5.0", 38 | "joi": "^17.4.0", 39 | "joi-phone-number": "^5.0.1", 40 | "jsonwebtoken": "^8.5.1", 41 | "moment-timezone": "^0.5.33", 42 | "mongoose": "^5.12.0", 43 | "morgan": "^1.10.0", 44 | "multer": "^1.4.2", 45 | "node-cache": "^5.1.2", 46 | "nodemailer": "^6.5.0", 47 | "openai": "^4.47.1", 48 | "passport": "^0.4.1", 49 | "passport-facebook": "^3.0.0", 50 | "passport-google-oauth20": "^2.0.0", 51 | "passport-jwt": "^4.0.0", 52 | "pm2": "^4.5.5", 53 | "stripe": "^8.138.0", 54 | "uuid": "^8.3.2", 55 | "validator": "^13.5.2", 56 | "winston": "^3.3.3", 57 | "xss-clean": "^0.1.1" 58 | }, 59 | "devDependencies": { 60 | "eslint": "^7.21.0", 61 | "eslint-config-airbnb-base": "^14.2.1", 62 | "eslint-config-prettier": "^8.1.0", 63 | "eslint-plugin-import": "^2.22.1", 64 | "eslint-plugin-prettier": "^3.3.1", 65 | "lint-staged": "^10.5.4", 66 | "nodemon": "^3.1.1", 67 | "prettier": "^2.2.1" 68 | }, 69 | "nodemonConfig": { 70 | "ignore": [ 71 | "src/data/*" 72 | ] 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CopywriterPro-ai/copywriterproai-backend/048cc8359a98f9dd575182b8c47b80f271846a98/src/.DS_Store -------------------------------------------------------------------------------- /src/app.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const helmet = require('helmet'); 3 | const xss = require('xss-clean'); 4 | const mongoSanitize = require('express-mongo-sanitize'); 5 | const compression = require('compression'); 6 | const cors = require('cors'); 7 | const timeout = require('connect-timeout'); 8 | const passport = require('passport'); 9 | const httpStatus = require('http-status'); 10 | const Sentry = require('@sentry/node'); 11 | const Tracing = require('@sentry/tracing'); 12 | const config = require('./config/config'); 13 | const morgan = require('./config/morgan'); 14 | const corsOptions = require('./config/corsoptions'); 15 | const { jwtStrategy, googleStrategy, facebookStrategy } = require('./config/passport'); 16 | const { authLimiter } = require('./middlewares/rateLimiter'); 17 | const routes = require('./routes/v1'); 18 | const { errorConverter, errorHandler } = require('./middlewares/error'); 19 | const ApiError = require('./utils/ApiError'); 20 | 21 | const app = express(); 22 | 23 | // Sentry initial 24 | Sentry.init({ 25 | dsn: config.sentry.dns, 26 | integrations: [ 27 | // enable HTTP calls tracing 28 | new Sentry.Integrations.Http({ tracing: true }), 29 | // enable Express.js middleware tracing 30 | new Tracing.Integrations.Express({ 31 | // to trace all requests to the default router 32 | app, 33 | // alternatively, you can specify the routes you want to trace: 34 | // router: someRouter, 35 | }), 36 | ], 37 | 38 | // We recommend adjusting this value in production, or using tracesSampler 39 | // for finer control 40 | tracesSampleRate: 1.0, 41 | }); 42 | 43 | if (config.env !== 'test') { 44 | app.use(morgan.successHandler); 45 | app.use(morgan.errorHandler); 46 | } 47 | 48 | // set security HTTP headers 49 | app.use(helmet()); 50 | 51 | // Use JSON parser for parsing payloads as JSON on all non-webhook routes. 52 | app.use((req, res, next) => { 53 | if (req.originalUrl === '/v1/payments/payment-webhook') { 54 | next(); 55 | } else { 56 | express.json()(req, res, next); 57 | } 58 | }); 59 | 60 | // request timeout 61 | app.use(timeout('60s')); 62 | 63 | // parse urlencoded request body 64 | app.use(express.urlencoded({ extended: true })); 65 | 66 | // sanitize request data 67 | app.use(xss()); 68 | app.use(mongoSanitize()); 69 | 70 | // gzip compression 71 | app.use(compression()); 72 | 73 | // enable cors 74 | app.use(cors(corsOptions)); 75 | app.options('*', cors(corsOptions)); 76 | 77 | // passportjs authentication 78 | app.use(passport.initialize()); 79 | passport.use('jwt', jwtStrategy); 80 | passport.use('google', googleStrategy); 81 | passport.use('facebook', facebookStrategy); 82 | 83 | // Sentry error request in production 84 | if (config.env === 'production') { 85 | app.use(Sentry.Handlers.requestHandler()); 86 | app.use(Sentry.Handlers.tracingHandler()); 87 | } 88 | 89 | // limit repeated failed requests to auth endpoints 90 | if (config.env === 'production') { 91 | app.use('/v1/auth', authLimiter); 92 | } 93 | 94 | // v1 api routes 95 | app.use('/v1', routes); 96 | 97 | // Sentry error handle in production 98 | if (config.env === 'production') { 99 | app.use(Sentry.Handlers.errorHandler()); 100 | } 101 | 102 | // send back a 404 error for any unknown api request 103 | app.use((req, res, next) => { 104 | next(new ApiError(httpStatus.NOT_FOUND, 'Not found')); 105 | }); 106 | 107 | // convert error to ApiError, if needed 108 | app.use(errorConverter); 109 | 110 | // handle error 111 | app.use(errorHandler); 112 | 113 | module.exports = app; 114 | -------------------------------------------------------------------------------- /src/config/corsoptions.js: -------------------------------------------------------------------------------- 1 | const { cors } = require('./config'); 2 | 3 | /** 4 | * Function to dynamically configure CORS options based on the request origin. 5 | * This function checks if the request's origin is in the whitelist, and sets 6 | * the appropriate CORS options. 7 | * 8 | * @param {Object} req - The HTTP request object. 9 | * @param {Function} callback - A callback function to set the CORS options. 10 | */ 11 | const corsOptionsDelegate = function (req, callback) { 12 | let corsOptions; 13 | // Extract the origin header from the request 14 | const { origin } = req.headers; 15 | 16 | // Check if the origin is in the whitelist 17 | if (cors.whitelist.includes(origin)) { 18 | // If the origin is whitelisted, allow the request 19 | corsOptions = { origin }; 20 | } else { 21 | // If the origin is not whitelisted, block the request 22 | corsOptions = { origin: false }; 23 | } 24 | 25 | // Pass the CORS options to the callback 26 | callback(null, corsOptions); 27 | }; 28 | 29 | module.exports = corsOptionsDelegate; 30 | -------------------------------------------------------------------------------- /src/config/inputLimit.js: -------------------------------------------------------------------------------- 1 | const { inputLimit: { packages, inputCharacterRate } } = require('./config'); 2 | 3 | const inputLimit = (function () { 4 | const perPackageInputCharacterLimit = {}; 5 | packages.forEach((pkg, index) => { 6 | perPackageInputCharacterLimit[pkg] = inputCharacterRate[index]; 7 | }); 8 | return perPackageInputCharacterLimit; 9 | })(); 10 | 11 | console.log(inputLimit); // Add this line to log the mapping and verify it 12 | 13 | module.exports = inputLimit; 14 | -------------------------------------------------------------------------------- /src/config/logger.js: -------------------------------------------------------------------------------- 1 | const winston = require('winston'); 2 | const config = require('./config'); 3 | 4 | /** 5 | * Custom format to enumerate error stack traces in the log messages. 6 | * If the log information is an instance of Error, it assigns the error stack to the message property. 7 | * This helps in providing detailed error information in the logs. 8 | * 9 | * @param {Object} info - The log information object. 10 | * @returns {Object} The modified log information object with error stack as message if it is an Error. 11 | */ 12 | const enumerateErrorFormat = winston.format((info) => { 13 | if (info instanceof Error) { 14 | Object.assign(info, { message: info.stack }); 15 | } 16 | return info; 17 | }); 18 | 19 | /** 20 | * Create a Winston logger instance with customized settings. 21 | * The logger configuration changes based on the environment (development or production). 22 | * In development, the logs are colored for better readability. 23 | * In production, the logs are uncolored for simplicity. 24 | * 25 | * The logger captures messages at 'debug' level in development and 'info' level in production. 26 | * 27 | * @returns {Object} Winston logger instance. 28 | */ 29 | const logger = winston.createLogger({ 30 | // Set the logging level based on the environment. 31 | level: config.env === 'development' ? 'debug' : 'info', 32 | format: winston.format.combine( 33 | // Apply the custom error format. 34 | enumerateErrorFormat(), 35 | // Colorize logs in development for better readability. 36 | config.env === 'development' ? winston.format.colorize() : winston.format.uncolorize(), 37 | // Format log messages with string interpolation. 38 | winston.format.splat(), 39 | // Customize the final log message format. 40 | winston.format.printf(({ level, message }) => `${level}: ${message}`) 41 | ), 42 | transports: [ 43 | // Log to the console. Errors are logged to stderr. 44 | new winston.transports.Console({ 45 | stderrLevels: ['error'], 46 | }), 47 | ], 48 | }); 49 | 50 | module.exports = logger; 51 | -------------------------------------------------------------------------------- /src/config/mailtype.js: -------------------------------------------------------------------------------- 1 | const mailTypes = { 2 | ACCOUNT_VERIFY: 'account_verify', 3 | FORGOT_PASSWORD: 'forgot_password', 4 | }; 5 | 6 | module.exports = { 7 | mailTypes, 8 | }; 9 | -------------------------------------------------------------------------------- /src/config/morgan.js: -------------------------------------------------------------------------------- 1 | const morgan = require('morgan'); 2 | const config = require('./config'); 3 | const logger = require('./logger'); 4 | 5 | // Define a custom token to include the error message in the log if available 6 | morgan.token('message', (req, res) => res.locals.errorMessage || ''); 7 | 8 | /** 9 | * Helper function to get the IP format based on the environment. 10 | * In production, include the remote address in the logs. 11 | * In other environments, exclude the remote address for simplicity. 12 | * 13 | * @returns {string} The format string for the IP address. 14 | */ 15 | const getIpFormat = () => (config.env === 'production' ? ':remote-addr - ' : ''); 16 | 17 | // Define the format for successful responses 18 | const successResponseFormat = `${getIpFormat()}:method :url :status - :response-time ms`; 19 | 20 | // Define the format for error responses, including the error message if available 21 | const errorResponseFormat = `${getIpFormat()}:method :url :status - :response-time ms - message: :message`; 22 | 23 | /** 24 | * Morgan middleware to log successful HTTP requests. 25 | * 26 | * @param {Object} options - The options for morgan middleware. 27 | * @returns {Function} The morgan middleware function for logging successful requests. 28 | */ 29 | const successHandler = morgan(successResponseFormat, { 30 | // Skip logging for error responses (status codes >= 400) 31 | skip: (req, res) => res.statusCode >= 400, 32 | // Stream the log messages to the custom logger at the 'info' level 33 | stream: { write: (message) => logger.info(message.trim()) }, 34 | }); 35 | 36 | /** 37 | * Morgan middleware to log error HTTP requests. 38 | * 39 | * @param {Object} options - The options for morgan middleware. 40 | * @returns {Function} The morgan middleware function for logging error requests. 41 | */ 42 | const errorHandler = morgan(errorResponseFormat, { 43 | // Skip logging for successful responses (status codes < 400) 44 | skip: (req, res) => res.statusCode < 400, 45 | // Stream the log messages to the custom logger at the 'error' level 46 | stream: { write: (message) => logger.error(message.trim()) }, 47 | }); 48 | 49 | module.exports = { 50 | successHandler, 51 | errorHandler, 52 | }; 53 | -------------------------------------------------------------------------------- /src/config/passport.js: -------------------------------------------------------------------------------- 1 | const { Strategy: JwtStrategy, ExtractJwt } = require('passport-jwt'); 2 | const { Strategy: GoogleStrategy } = require('passport-google-oauth20'); 3 | const { Strategy: FacebookStrategy } = require('passport-facebook'); 4 | const { jwt, googleOauth2, facebookOauth } = require('./config'); 5 | const { strategyVerify } = require('../services/user.service'); 6 | const { authTypes } = require('./auths'); 7 | const { tokenTypes } = require('./tokens'); 8 | const { User } = require('../models'); 9 | 10 | // Check if the environment is development 11 | const isDevelopment = process.env.NODE_ENV === 'development'; 12 | 13 | // Define the Google OAuth callback URL based on the environment 14 | const googleCallbackURL = isDevelopment 15 | ? 'http://localhost:8080/v1/auth/google/callback' 16 | : 'https://api.copywriterpro.ai/v1/auth/google/callback'; 17 | 18 | // Options for JWT strategy 19 | const jwtOptions = { 20 | secretOrKey: jwt.secret, 21 | jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), 22 | }; 23 | 24 | // Options for Google OAuth strategy 25 | const googleOptions = { 26 | clientID: googleOauth2.clientId, 27 | clientSecret: googleOauth2.secretId, 28 | callbackURL: googleCallbackURL, 29 | userProfileURL: 'https://www.googleapis.com/oauth2/v3/userinfo', 30 | }; 31 | 32 | // Options for Facebook OAuth strategy 33 | const facebookOptions = { 34 | clientID: facebookOauth.appId, 35 | clientSecret: facebookOauth.appSecret, 36 | callbackURL: 'https://api.copywriterpro.ai/v1/auth/facebook/callback', 37 | profileFields: ['email', 'displayName', 'photos', 'first_name', 'last_name'], 38 | }; 39 | 40 | // Function to verify JWT tokens 41 | const jwtVerify = async (payload, done) => { 42 | try { 43 | // Check if the token type is ACCESS 44 | if (payload.type !== tokenTypes.ACCESS) { 45 | throw new Error('Invalid token type'); 46 | } 47 | // Find the user by the ID in the payload 48 | const user = await User.findById(payload.sub); 49 | if (!user) { 50 | return done(null, false); 51 | } 52 | // If user is found, pass the user object to the next middleware 53 | done(null, user); 54 | } catch (error) { 55 | // If an error occurs, pass the error to the next middleware 56 | done(error, false); 57 | } 58 | }; 59 | 60 | // Create instances of each strategy with the options and verification functions 61 | const jwtStrategy = new JwtStrategy(jwtOptions, jwtVerify); 62 | const googleStrategy = new GoogleStrategy(googleOptions, strategyVerify(authTypes.GOOGLE)); 63 | const facebookStrategy = new FacebookStrategy(facebookOptions, strategyVerify(authTypes.FACEBOOK)); 64 | 65 | module.exports = { 66 | jwtStrategy, 67 | googleStrategy, 68 | facebookStrategy, 69 | }; 70 | -------------------------------------------------------------------------------- /src/config/plan.js: -------------------------------------------------------------------------------- 1 | const { trial } = require('./config'); 2 | 3 | const trialInfo = { 4 | ...trial, 5 | // dailyLimit: Math.floor(trial.words / trial.days), 6 | dailyLimit: trial.words, 7 | }; 8 | 9 | const subscription = { 10 | FREEMIUM: 'FREEMIUM', 11 | BASIC_1MONTH: 'BASIC_1MONTH', 12 | BASIC_6MONTH: 'BASIC_6MONTH', 13 | STANDARD_1MONTH: 'STANDARD_1MONTH', 14 | STANDARD_6MONTH: 'STANDARD_6MONTH', 15 | PROFESSIONAL_1MONTH: 'PROFESSIONAL_1MONTH', 16 | PROFESSIONAL_6MONTH: 'PROFESSIONAL_6MONTH', 17 | }; 18 | 19 | const subscriptionPlan = { 20 | [subscription.BASIC_1MONTH]: { 21 | words: 7000, 22 | plagiarismCheckerWords: 0, 23 | package: subscription.BASIC_1MONTH, 24 | month: 1, 25 | price: { bdt: '399' }, 26 | }, 27 | [subscription.BASIC_6MONTH]: { 28 | words: 7000 * 6, 29 | plagiarismCheckerWords: 0, 30 | package: subscription.BASIC_6MONTH, 31 | month: 6, 32 | price: { bdt: '1999' }, 33 | }, 34 | [subscription.STANDARD_1MONTH]: { 35 | words: 50000, 36 | plagiarismCheckerWords: 10000, 37 | package: subscription.STANDARD_1MONTH, 38 | month: 1, 39 | price: { bdt: '1999' }, 40 | }, 41 | [subscription.STANDARD_6MONTH]: { 42 | words: 50000 * 6, 43 | plagiarismCheckerWords: 10000 * 6, 44 | package: subscription.STANDARD_6MONTH, 45 | month: 6, 46 | price: { bdt: '9999' }, 47 | }, 48 | [subscription.PROFESSIONAL_1MONTH]: { 49 | words: 200000, 50 | plagiarismCheckerWords: 50000, 51 | package: subscription.PROFESSIONAL_1MONTH, 52 | month: 1, 53 | price: { bdt: '4999' }, 54 | }, 55 | [subscription.PROFESSIONAL_6MONTH]: { 56 | words: 200000 * 6, 57 | plagiarismCheckerWords: 50000 * 6, 58 | package: subscription.PROFESSIONAL_6MONTH, 59 | month: 6, 60 | price: { bdt: '24999' }, 61 | }, 62 | }; 63 | 64 | module.exports = { subscription, trial: trialInfo, subscriptionPlan }; 65 | -------------------------------------------------------------------------------- /src/config/roles.js: -------------------------------------------------------------------------------- 1 | const roles = ['user', 'admin']; 2 | 3 | const roleRights = new Map(); 4 | roleRights.set(roles[0], [ 5 | 'getUserInfo', 6 | 'updateUserInfo', 7 | 'generateContent', 8 | 'checkPlagiarism', 9 | 'generateContentExtension', 10 | 'manageContent', 11 | 'getBlog', 12 | 'manageBlog', 13 | ]); 14 | roleRights.set(roles[1], [ 15 | 'getUserInfo', 16 | 'updateUserInfo', 17 | 'getUsers', 18 | 'manageUsers', 19 | 'generateContent', 20 | 'checkPlagiarism', 21 | 'generateContentExtension', 22 | 'manageTools', 23 | 'manageContent', 24 | 'getBlog', 25 | 'manageBlog', 26 | 'updateNotice', 27 | ]); 28 | 29 | module.exports = { 30 | roles, 31 | roleRights, 32 | }; 33 | -------------------------------------------------------------------------------- /src/config/tokens.js: -------------------------------------------------------------------------------- 1 | const tokenTypes = { 2 | ACCESS: 'access', 3 | REFRESH: 'refresh', 4 | RESET_PASSWORD: 'resetPassword', 5 | }; 6 | 7 | module.exports = { 8 | tokenTypes, 9 | }; 10 | -------------------------------------------------------------------------------- /src/controllers/blog.controller.js: -------------------------------------------------------------------------------- 1 | const httpStatus = require('http-status'); 2 | const pick = require('../utils/pick'); 3 | const catchAsync = require('../utils/catchAsync'); 4 | const { blogService } = require('../services'); 5 | 6 | /** 7 | * Get all blogs for the authenticated user. 8 | * This function retrieves blogs based on the user's email and query options such as sorting, pagination, and limit. 9 | * 10 | * @param {Object} req - The request object containing user information and query parameters. 11 | * @param {Object} res - The response object. 12 | */ 13 | const getBlogs = catchAsync(async (req, res) => { 14 | const filter = { userEmail: req.user.email }; 15 | const options = pick(req.query, ['sortBy', 'limit', 'page']); 16 | const result = await blogService.queryBlogs(filter, options); 17 | res.send(result); 18 | }); 19 | 20 | /** 21 | * Get a single blog by ID for the authenticated user. 22 | * This function retrieves a blog based on the blog ID and user's email. 23 | * 24 | * @param {Object} req - The request object containing the blog ID and user information. 25 | * @param {Object} res - The response object. 26 | */ 27 | const getBlog = catchAsync(async (req, res) => { 28 | const blog = await blogService.checkBlogExistsOrNot(req.params.blogId, req.user.email); 29 | res.send(blog); 30 | }); 31 | 32 | /** 33 | * Create a new blog for the authenticated user. 34 | * This function creates a new blog entry with the provided user ID, email, and blog data. 35 | * 36 | * @param {Object} req - The request object containing the user information and blog data. 37 | * @param {Object} res - The response object. 38 | */ 39 | const createBlog = catchAsync(async (req, res) => { 40 | const user = await blogService.createBlog(req.user._id, req.user.email, req.body); 41 | res.status(httpStatus.CREATED).send(user); 42 | }); 43 | 44 | /** 45 | * Update an existing blog for the authenticated user. 46 | * This function updates a blog entry based on the blog ID, user's email, and the new blog data. 47 | * 48 | * @param {Object} req - The request object containing the blog ID, user information, and new blog data. 49 | * @param {Object} res - The response object. 50 | */ 51 | const updateBlog = catchAsync(async (req, res) => { 52 | const blog = await blogService.checkBlogExistsOrNot(req.params.blogId, req.user.email); 53 | const updatedBlog = await blogService.updateBlog(blog, req.body); 54 | res.status(httpStatus.OK).send(updatedBlog); 55 | }); 56 | 57 | /** 58 | * Delete a blog by ID for the authenticated user. 59 | * This function deletes a blog entry based on the blog ID and user's email. 60 | * 61 | * @param {Object} req - The request object containing the blog ID and user information. 62 | * @param {Object} res - The response object. 63 | */ 64 | const deleteBlog = catchAsync(async (req, res) => { 65 | await blogService.deleteBlogById(req.params.blogId, req.user.email); 66 | res.status(httpStatus.NO_CONTENT).send(); 67 | }); 68 | 69 | module.exports = { 70 | getBlogs, 71 | createBlog, 72 | getBlog, 73 | updateBlog, 74 | deleteBlog, 75 | }; 76 | -------------------------------------------------------------------------------- /src/controllers/demo.controller.js: -------------------------------------------------------------------------------- 1 | const httpStatus = require('http-status'); 2 | const catchAsync = require('../utils/catchAsync'); 3 | const generator = require('../services/contents'); 4 | 5 | const generate = catchAsync(async (req, res) => { 6 | const { task } = req.body; 7 | 8 | let generatedContent = {}; 9 | 10 | if (task === 'paraphrasing') { 11 | generatedContent = await generator.demo.paraphrase(req.body); 12 | } else if (task === 'blog-headline') { 13 | generatedContent = await generator.demo.blogHeadline(req.body); 14 | } 15 | 16 | res.status(httpStatus.OK).send(generatedContent); 17 | }); 18 | 19 | module.exports = { 20 | generate, 21 | }; 22 | -------------------------------------------------------------------------------- /src/controllers/extension.controller.js: -------------------------------------------------------------------------------- 1 | const httpStatus = require('http-status'); 2 | const catchAsync = require('../utils/catchAsync'); 3 | const generator = require('../services/contents'); 4 | const { subscriberService } = require('../services'); 5 | 6 | const generate = catchAsync(async (req, res) => { 7 | const { _id, email, role } = req.user; 8 | const { 9 | words, 10 | subscriberInfo: { isPaidSubscribers }, 11 | } = await subscriberService.getOwnSubscribe(email); 12 | 13 | const isAdmin = role === 'admin'; 14 | 15 | const { task } = req.body; 16 | let generatedContent = {}; 17 | 18 | if ((words === 0 || isPaidSubscribers === false) && task !== 'paraphrasing' && task !== 'grammar-fixer' && !isAdmin) { 19 | res.status(httpStatus.PAYMENT_REQUIRED).send({ message: 'Upgrade our friendship today!' }); 20 | } 21 | 22 | if (task === 'paraphrasing') { 23 | generatedContent = await generator.extension.paraphrase(_id, email, req.body); 24 | } else if (task === 'grammar-fixer') { 25 | generatedContent = await generator.extension.grammarFixer(_id, email, req.body); 26 | } else if (task === 'simplifier') { 27 | generatedContent = await generator.extension.simplifier(_id, email, req.body); 28 | } else if (task === 'summarizer') { 29 | generatedContent = await generator.extension.summarizer(_id, email, req.body); 30 | } else if (task === 'change-tone') { 31 | generatedContent = await generator.extension.changeTone(_id, email, req.body); 32 | } 33 | 34 | res.status(httpStatus.OK).send(generatedContent); 35 | }); 36 | 37 | module.exports = { 38 | generate, 39 | }; 40 | -------------------------------------------------------------------------------- /src/controllers/index.js: -------------------------------------------------------------------------------- 1 | module.exports.authController = require('./auth.controller'); 2 | module.exports.userController = require('./user.controller'); 3 | module.exports.interestController = require('./interest.controller'); 4 | module.exports.contentController = require('./content.controller'); 5 | module.exports.paymentController = require('./payment.controller'); 6 | module.exports.supportController = require('./support.controller'); 7 | module.exports.toolController = require('./tool.controller'); 8 | module.exports.blogController = require('./blog.controller'); 9 | module.exports.subscriberController = require('./subscriber.controller'); 10 | module.exports.demoController = require('./demo.controller'); 11 | module.exports.extensionController = require('./extension.controller'); 12 | module.exports.plagiarismCheckerController = require('./plagiarismChecker.controller'); 13 | -------------------------------------------------------------------------------- /src/controllers/interest.controller.js: -------------------------------------------------------------------------------- 1 | const httpStatus = require('http-status'); 2 | const catchAsync = require('../utils/catchAsync'); 3 | const { interestService, contentService } = require('../services'); 4 | 5 | const updateInterests = catchAsync(async (req, res) => { 6 | const userInterest = await interestService.checkDocumentExistsOrNot(req.user.email); 7 | await contentService.checkContentExistsOrNot(req.user.email, { _id: req.body.contentId, index: req.body.index }); 8 | await interestService.updateInterest(userInterest, req.query.action, req.body); 9 | res.status(httpStatus.OK).send({ message: `${req.query.action} added!` }); 10 | }); 11 | 12 | module.exports = { 13 | updateInterests, 14 | }; 15 | -------------------------------------------------------------------------------- /src/controllers/notice.controller.js: -------------------------------------------------------------------------------- 1 | const { join } = require('path'); 2 | const fsPromises = require('fs/promises'); 3 | const httpStatus = require('http-status'); 4 | const catchAsync = require('../utils/catchAsync'); 5 | 6 | const noticeUrl = join(__dirname, '../data/notice.json'); 7 | 8 | const getNotice = catchAsync(async (req, res) => { 9 | const notice = await fsPromises.readFile(noticeUrl, 'utf-8'); 10 | res.status(httpStatus.OK).send({ status: httpStatus.OK, notice: JSON.parse(notice) }); 11 | }); 12 | 13 | const updateNotice = catchAsync(async (req, res) => { 14 | await fsPromises.writeFile(noticeUrl, JSON.stringify(req.body), 'utf-8'); 15 | res.status(httpStatus.OK).send({ status: httpStatus.OK, notice: req.body }); 16 | }); 17 | 18 | module.exports = { 19 | getNotice, 20 | updateNotice, 21 | }; 22 | -------------------------------------------------------------------------------- /src/controllers/plagiarismChecker.controller.js: -------------------------------------------------------------------------------- 1 | const httpStatus = require('http-status'); 2 | const catchAsync = require('../utils/catchAsync'); 3 | const { plagiarismChecker } = require('../config/config'); 4 | const { 5 | subscriberService, 6 | plagiarismCheckerService, 7 | utilsService: { numberOfWords }, 8 | } = require('../services'); 9 | 10 | const searchContent = catchAsync(async (req, res) => { 11 | const { userId } = req.user; 12 | const { 13 | activeSubscription, 14 | subscriberInfo: { isPaidSubscribers }, 15 | } = await subscriberService.getOwnSubscribe(userId); 16 | 17 | const { plagiarismCheckerWords, subscription: currentPackage } = activeSubscription; 18 | 19 | const { text } = req.body; 20 | const numberOfWordsInText = numberOfWords(text); 21 | 22 | const isUserPackageAllowed = plagiarismChecker.allowedPackages.includes(currentPackage); 23 | const isWordsAvailable = plagiarismCheckerWords - numberOfWordsInText >= 0; 24 | 25 | if (isPaidSubscribers && isUserPackageAllowed && isWordsAvailable) { 26 | const results = await plagiarismCheckerService.searchContent(text); 27 | res.status(httpStatus.OK).send({ status: httpStatus.OK, data: results }); 28 | 29 | await subscriberService.updateOwnSubscribe(userId, { 30 | activeSubscription: { 31 | ...activeSubscription, 32 | plagiarismCheckerWords: plagiarismCheckerWords - numberOfWordsInText, 33 | }, 34 | }); 35 | } else if (isPaidSubscribers && !isUserPackageAllowed) { 36 | res.status(httpStatus.PAYMENT_REQUIRED).send({ message: 'Please upgrade to Premium package!' }); 37 | } else if (isPaidSubscribers && isUserPackageAllowed && !isWordsAvailable) { 38 | res.status(httpStatus.PAYMENT_REQUIRED).send({ message: "You don't have enough words to check plagiarism!" }); 39 | } else { 40 | res.status(httpStatus.FORBIDDEN).send({ message: "You don't have access to this feature!" }); 41 | } 42 | }); 43 | 44 | module.exports = { 45 | searchContent, 46 | }; 47 | -------------------------------------------------------------------------------- /src/controllers/subscriber.controller.js: -------------------------------------------------------------------------------- 1 | const httpStatus = require('http-status'); 2 | const moment = require('moment-timezone'); 3 | const catchAsync = require('../utils/catchAsync'); 4 | const { subscriberService } = require('../services'); 5 | 6 | const getOwnSubscribe = catchAsync(async (req, res) => { 7 | const subscriber = await subscriberService.getOwnSubscribe(req.user.userId); 8 | res.status(httpStatus.OK).send({ status: httpStatus.OK, subscriber }); 9 | }); 10 | 11 | const updateOwnSubscribe = catchAsync(async (req, res) => { 12 | const subscriber = await subscriberService.updateOwnSubscribe(req.user.userId, req.body); 13 | res.status(httpStatus.OK).send({ status: httpStatus.OK, subscriber }); 14 | }); 15 | 16 | const updateOwnCopyCounter = catchAsync(async (req, res) => { 17 | await subscriberService.updateOwnCopyCounter(req.user.userId); 18 | res.status(httpStatus.OK).send({ status: httpStatus.OK, message: 'success' }); 19 | }); 20 | 21 | const generateUpdate = catchAsync(async (req, res) => { 22 | let calDailyCreaditUsage; 23 | const { role, userId, UserOwnOpenAIApiKey } = req.user; 24 | const { useWords = 0 } = req.body; 25 | const { activeSubscription, dailyCreaditUsage } = await subscriberService.getOwnSubscribe(userId); 26 | 27 | const todayDate = moment().startOf('day').format(); 28 | const { date, usage } = dailyCreaditUsage; 29 | const isSameDay = moment(date).isSame(todayDate); 30 | const isAdmin = role === 'admin'; 31 | 32 | if (isSameDay) { 33 | calDailyCreaditUsage = { date, usage: usage + useWords }; 34 | } else { 35 | calDailyCreaditUsage = { date: todayDate, usage: useWords }; 36 | } 37 | 38 | let remainingWords = activeSubscription.words; 39 | if (!UserOwnOpenAIApiKey) { 40 | const subtractionWords = activeSubscription.words - useWords; 41 | remainingWords = subtractionWords < 0 ? 0 : subtractionWords; 42 | } 43 | 44 | const subscriber = await subscriberService.updateOwnSubscribe(userId, { 45 | activeSubscription: { 46 | ...activeSubscription, 47 | words: isAdmin ? activeSubscription.words : remainingWords, 48 | }, 49 | dailyCreaditUsage: calDailyCreaditUsage, 50 | }); 51 | res.status(httpStatus.OK).send({ status: httpStatus.OK, subscriber }); 52 | }); 53 | 54 | const subscriberSwitcher = catchAsync(async (req, res) => { 55 | const subscriber = await subscriberService.subscriberSwitcher(req); 56 | res.status(httpStatus.OK).send({ status: httpStatus.OK, subscriber }); 57 | }); 58 | 59 | // Add new endpoints to manage trial and enforce subscription 60 | const manageTrial = catchAsync(async (req, res) => { 61 | const subscriber = await subscriberService.manageTrial(req.user.userId); 62 | res.status(httpStatus.OK).send({ status: httpStatus.OK, subscriber }); 63 | }); 64 | 65 | const enforceSubscription = catchAsync(async (req, res) => { 66 | const subscriber = await subscriberService.enforceSubscription(req.user.userId); 67 | res.status(httpStatus.OK).send({ status: httpStatus.OK, subscriber }); 68 | }); 69 | 70 | module.exports = { 71 | getOwnSubscribe, 72 | updateOwnSubscribe, 73 | updateOwnCopyCounter, 74 | generateUpdate, 75 | subscriberSwitcher, 76 | manageTrial, 77 | enforceSubscription, 78 | }; 79 | -------------------------------------------------------------------------------- /src/controllers/support.controller.js: -------------------------------------------------------------------------------- 1 | const httpStatus = require('http-status'); 2 | const catchAsync = require('../utils/catchAsync'); 3 | const { emailService } = require('../services'); 4 | 5 | const featureRequest = catchAsync(async (req, res) => { 6 | const subject = 'Feature Request!'; 7 | await emailService.userMessage(subject, req.body); 8 | res.status(httpStatus.CREATED).send({ 9 | status: httpStatus.CREATED, 10 | message: 'Thank you for your suggestion!', 11 | }); 12 | }); 13 | 14 | const bugReport = catchAsync(async (req, res) => { 15 | const subject = 'Bug Report!'; 16 | await emailService.userMessage(subject, req.body); 17 | res.status(httpStatus.CREATED).send({ 18 | status: httpStatus.CREATED, 19 | message: 'Thank you for reporting! We are looking into it.', 20 | }); 21 | }); 22 | 23 | const userMessage = catchAsync(async (req, res) => { 24 | const subject = 'User Message!'; 25 | await emailService.userMessage(subject, req.body); 26 | res.status(httpStatus.CREATED).send({ 27 | status: httpStatus.CREATED, 28 | message: 'Thank you for reaching out. We will get back to you soon.', 29 | }); 30 | }); 31 | 32 | module.exports = { 33 | featureRequest, 34 | bugReport, 35 | userMessage, 36 | }; 37 | -------------------------------------------------------------------------------- /src/controllers/tool.controller.js: -------------------------------------------------------------------------------- 1 | const httpStatus = require('http-status'); 2 | const catchAsync = require('../utils/catchAsync'); 3 | const { toolService } = require('../services'); 4 | 5 | const postToolCategory = catchAsync(async (req, res) => { 6 | const category = await toolService.postToolCategory(req.body); 7 | res.status(httpStatus.CREATED).send({ status: httpStatus.CREATED, category }); 8 | }); 9 | 10 | const getToolCategories = catchAsync(async (req, res) => { 11 | const categories = await toolService.getToolCategories(); 12 | res.status(httpStatus.OK).send({ status: httpStatus.OK, categories }); 13 | }); 14 | 15 | const getToolCategory = catchAsync(async (req, res) => { 16 | const category = await toolService.getToolCategory(req.params.categoryId); 17 | res.status(httpStatus.OK).send({ status: httpStatus.OK, category }); 18 | }); 19 | 20 | const deleteToolCategory = catchAsync(async (req, res) => { 21 | await toolService.deleteToolCategory(req.params.categoryId); 22 | res.status(httpStatus.NO_CONTENT).send({ status: httpStatus.NO_CONTENT }); 23 | }); 24 | 25 | const patchToolCategory = catchAsync(async (req, res) => { 26 | const category = await toolService.patchToolCategory(req.params.categoryId, req.body); 27 | res.status(httpStatus.OK).send({ status: httpStatus.OK, category }); 28 | }); 29 | 30 | const postTool = catchAsync(async (req, res) => { 31 | const tool = await toolService.postTool(req.body); 32 | res.status(httpStatus.CREATED).send({ status: httpStatus.CREATED, tool }); 33 | }); 34 | 35 | const getTools = catchAsync(async (req, res) => { 36 | const tools = await toolService.getTools(); 37 | res.status(httpStatus.OK).send({ status: httpStatus.OK, tools }); 38 | }); 39 | 40 | const getTool = catchAsync(async (req, res) => { 41 | const tool = await toolService.getTool(req.params.toolId); 42 | res.status(httpStatus.OK).send({ status: httpStatus.OK, tool }); 43 | }); 44 | 45 | const deleteTool = catchAsync(async (req, res) => { 46 | await toolService.deleteTool(req.params.toolId); 47 | res.status(httpStatus.NO_CONTENT).send({ status: httpStatus.NO_CONTENT }); 48 | }); 49 | 50 | const patchTool = catchAsync(async (req, res) => { 51 | const tool = await toolService.patchTool(req.params.toolId, req.body); 52 | res.status(httpStatus.OK).send({ status: httpStatus.OK, tool }); 53 | }); 54 | 55 | module.exports = { 56 | postToolCategory, 57 | getToolCategory, 58 | getToolCategories, 59 | deleteToolCategory, 60 | patchToolCategory, 61 | postTool, 62 | getTools, 63 | getTool, 64 | patchTool, 65 | deleteTool, 66 | }; 67 | -------------------------------------------------------------------------------- /src/controllers/user.controller.js: -------------------------------------------------------------------------------- 1 | const httpStatus = require('http-status'); 2 | const pick = require('../utils/pick'); 3 | const catchAsync = require('../utils/catchAsync'); 4 | const { userService, contentService, tokenService } = require('../services'); 5 | 6 | const createUser = catchAsync(async (req, res) => { 7 | const user = await userService.createUser(req.body); 8 | res.status(httpStatus.CREATED).send(user); 9 | }); 10 | 11 | const getMe = catchAsync(async (req, res) => { 12 | res.status(httpStatus.OK).send({ status: httpStatus.OK, profile: req.user }); 13 | }); 14 | 15 | const getUsers = catchAsync(async (req, res) => { 16 | const filter = pick(req.query, ['name', 'role']); 17 | const options = pick(req.query, ['sortBy', 'limit', 'page']); 18 | const result = await userService.queryUsers(filter, options); 19 | res.send(result); 20 | }); 21 | 22 | const getUser = catchAsync(async (req, res) => { 23 | const user = await userService.checkUserExistsOrNot(req.params.userId); 24 | res.send(user); 25 | }); 26 | 27 | const getUserBookmarks = catchAsync(async (req, res) => { 28 | const bookmarks = await userService.getBookmarks(req.user.email, req.query); 29 | res.send(bookmarks); 30 | }); 31 | 32 | const deleteUser = catchAsync(async (req, res) => { 33 | await userService.deleteUserById(req.params.userId); 34 | res.status(httpStatus.NO_CONTENT).send(); 35 | }); 36 | 37 | const updateUser = catchAsync(async (req, res) => { 38 | const user = await userService.checkUserExistsOrNot(req.params.userId); 39 | const updatedUserInfo = await userService.updateUserById(user, req.params.userId, req.body); 40 | res.status(httpStatus.OK).send(updatedUserInfo); 41 | }); 42 | 43 | const updateUserBookmarks = catchAsync(async (req, res) => { 44 | await userService.updateBookmarks(req.user.email, req.body); 45 | res.status(httpStatus.OK).send({ message: 'Bookmarks Updated!' }); 46 | }); 47 | 48 | const updateUserContent = catchAsync(async (req, res) => { 49 | await contentService.updateBookmarkedText(req.user.email, req.body); 50 | res.status(httpStatus.OK).send({ message: 'Content Updated!' }); 51 | }); 52 | 53 | const getUserFavouriteTools = catchAsync(async (req, res) => { 54 | const user = await userService.checkUserExistsOrNot(req.params.userId); 55 | res.send(user.favouriteTools); 56 | }); 57 | 58 | const updateUserFavouriteTools = catchAsync(async (req, res) => { 59 | const user = await userService.checkUserExistsOrNot(req.params.userId); 60 | const updatedUserInfo = await userService.updateFavouriteTools(user, req.body.tool); 61 | res.status(httpStatus.OK).send(updatedUserInfo); 62 | }); 63 | 64 | const updateUserCopyCounter = catchAsync(async (req, res) => { 65 | const user = await userService.checkUserExistsOrNot(req.params.userId); 66 | const copyCounter = await userService.updateUserCopyCounter(user); 67 | res.status(httpStatus.OK).send(copyCounter); 68 | }); 69 | 70 | const extensionAccessToken = catchAsync(async (req, res) => { 71 | const accessToken = await tokenService.generateExtensionAccessToken(req.user); 72 | res.status(httpStatus.OK).send(accessToken); 73 | }); 74 | 75 | module.exports = { 76 | createUser, 77 | getMe, 78 | getUsers, 79 | getUser, 80 | getUserBookmarks, 81 | deleteUser, 82 | updateUser, 83 | updateUserBookmarks, 84 | getUserFavouriteTools, 85 | updateUserContent, 86 | updateUserFavouriteTools, 87 | updateUserCopyCounter, 88 | extensionAccessToken, 89 | }; 90 | -------------------------------------------------------------------------------- /src/data/notice.json: -------------------------------------------------------------------------------- 1 | {"active":false,"expiryTime":"2022-01-27T18:00:00.000Z","description":"Apply MYSTERYDEAL and Get a Flat 60% OFF!","title":"New Year’s Sale!"} -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | const app = require('./app'); 3 | const config = require('./config/config'); 4 | const logger = require('./config/logger'); 5 | 6 | let server; 7 | mongoose.connect(config.mongoose.url, config.mongoose.options).then(() => { 8 | logger.info('Connected to MongoDB'); 9 | server = app.listen(config.port, () => { 10 | logger.info(`Listening to port ${config.port}`); 11 | }); 12 | }); 13 | 14 | const exitHandler = () => { 15 | if (server) { 16 | server.close(() => { 17 | logger.info('Server closed'); 18 | process.exit(1); 19 | }); 20 | } else { 21 | process.exit(1); 22 | } 23 | }; 24 | 25 | const unexpectedErrorHandler = (error) => { 26 | logger.error(error); 27 | exitHandler(); 28 | }; 29 | 30 | process.on('uncaughtException', unexpectedErrorHandler); 31 | process.on('unhandledRejection', unexpectedErrorHandler); 32 | 33 | process.on('SIGTERM', () => { 34 | logger.info('SIGTERM received'); 35 | if (server) { 36 | server.close(); 37 | } 38 | }); 39 | -------------------------------------------------------------------------------- /src/middlewares/auth.js: -------------------------------------------------------------------------------- 1 | const passport = require('passport'); 2 | const httpStatus = require('http-status'); 3 | const ApiError = require('../utils/ApiError'); 4 | const { roleRights } = require('../config/roles'); 5 | 6 | const verifyCallback = (req, resolve, reject, requiredRights) => async (err, user, info) => { 7 | if (err || info || !user) { 8 | return reject(new ApiError(httpStatus.UNAUTHORIZED, 'Please authenticate')); 9 | } 10 | req.user = user; 11 | 12 | if (requiredRights.length) { 13 | const userRights = roleRights.get(user.role); 14 | const hasRequiredRights = requiredRights.every((requiredRight) => userRights.includes(requiredRight)); 15 | if (!user.isVerified) { 16 | return reject(new ApiError(httpStatus.FORBIDDEN, 'Account has not been verified yet!')); 17 | } 18 | if (user.role === 'admin' && !hasRequiredRights) { 19 | return reject(new ApiError(httpStatus.FORBIDDEN, 'Forbidden!')); 20 | } 21 | if (user.role !== 'admin' && (!hasRequiredRights || (req.params.userId && req.params.userId !== user.id))) { 22 | return reject(new ApiError(httpStatus.FORBIDDEN, 'Forbidden!')); 23 | } 24 | } 25 | resolve(); 26 | }; 27 | 28 | const auth = 29 | (...requiredRights) => 30 | async (req, res, next) => { 31 | return new Promise((resolve, reject) => { 32 | passport.authenticate('jwt', { session: false }, verifyCallback(req, resolve, reject, requiredRights))(req, res, next); 33 | }) 34 | .then(() => next()) 35 | .catch((err) => next(err)); 36 | }; 37 | 38 | module.exports = auth; 39 | -------------------------------------------------------------------------------- /src/middlewares/content.validate.js: -------------------------------------------------------------------------------- 1 | const Joi = require('joi'); 2 | const httpStatus = require('http-status'); 3 | const pick = require('../utils/pick'); 4 | const ApiError = require('../utils/ApiError'); 5 | const { Subscriber } = require('../models'); 6 | 7 | const getSubscriberInfo = async ({ userId }, next) => { 8 | const subscriber = await Subscriber.findOne({ userId }); 9 | if (!subscriber) { 10 | return next(new ApiError(httpStatus.NOT_FOUND, 'Subscriber not found')); 11 | } 12 | const { subscription } = subscriber.activeSubscription; 13 | return subscription; 14 | }; 15 | 16 | const validate = (generateSchema) => async (req, res, next) => { 17 | const subscription = req.user ? await getSubscriberInfo(req.user, next) : undefined; 18 | const schema = generateSchema(subscription); 19 | 20 | const validSchema = pick(schema, ['params', 'query', 'body']); 21 | const object = pick(req, Object.keys(validSchema)); 22 | const { value, error } = Joi.compile(validSchema) 23 | .prefs({ errors: { label: 'key' } }) 24 | .validate(object); 25 | 26 | if (error) { 27 | const errorMessage = error.details.map((details) => details.message).join(', '); 28 | return next(new ApiError(httpStatus.BAD_REQUEST, errorMessage)); 29 | } 30 | Object.assign(req, value); 31 | return next(); 32 | }; 33 | 34 | module.exports = validate; 35 | -------------------------------------------------------------------------------- /src/middlewares/error.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | const httpStatus = require('http-status'); 3 | const config = require('../config/config'); 4 | const logger = require('../config/logger'); 5 | const ApiError = require('../utils/ApiError'); 6 | 7 | const handleCastError = () => new ApiError(httpStatus.NOT_FOUND, 'Not found'); 8 | const handleOpenApiError = () => new ApiError(httpStatus.INTERNAL_SERVER_ERROR, 'Something went wrong'); 9 | 10 | const errorConverter = (err, req, res, next) => { 11 | let error = err; 12 | 13 | if (error.name === 'CastError') error = handleCastError(); 14 | if (error.hostname === 'api.openai.com') error = handleOpenApiError(); 15 | if (!(error instanceof ApiError)) { 16 | const statusCode = 17 | error.statusCode || error instanceof mongoose.Error ? httpStatus.BAD_REQUEST : httpStatus.INTERNAL_SERVER_ERROR; 18 | const message = error.message || httpStatus[statusCode]; 19 | error = new ApiError(statusCode, message, false, err.stack); 20 | } 21 | next(error); 22 | }; 23 | 24 | // eslint-disable-next-line no-unused-vars 25 | const errorHandler = (err, req, res, next) => { 26 | let { statusCode, message } = err; 27 | if (config.env === 'production' && !err.isOperational) { 28 | statusCode = httpStatus.INTERNAL_SERVER_ERROR; 29 | message = httpStatus[httpStatus.INTERNAL_SERVER_ERROR]; 30 | } 31 | 32 | res.locals.errorMessage = err.message; 33 | 34 | const response = { 35 | status: statusCode, 36 | message, 37 | ...(config.env === 'development' && { stack: err.stack }), 38 | }; 39 | 40 | if (config.env === 'development') { 41 | logger.error(err); 42 | } 43 | 44 | res.status(statusCode).send(response); 45 | }; 46 | 47 | module.exports = { 48 | errorConverter, 49 | errorHandler, 50 | }; 51 | -------------------------------------------------------------------------------- /src/middlewares/passportAuth.js: -------------------------------------------------------------------------------- 1 | const httpStatus = require('http-status'); 2 | const jwt = require('jsonwebtoken'); 3 | const ApiError = require('../utils/ApiError'); 4 | const { passportConfig } = require('../config/config'); 5 | 6 | const passportAuth = () => (req, res, next) => { 7 | const { token } = req.query; 8 | jwt.verify(token, passportConfig.authSecretKey, (err, decoded) => { 9 | if (err) { 10 | throw new ApiError(httpStatus.BAD_REQUEST, 'verifytoken is invalid or expired'); 11 | } else { 12 | req.user = decoded; 13 | next(); 14 | } 15 | }); 16 | }; 17 | 18 | module.exports = passportAuth; 19 | -------------------------------------------------------------------------------- /src/middlewares/rateLimiter.js: -------------------------------------------------------------------------------- 1 | const rateLimit = require('express-rate-limit'); 2 | 3 | const authLimiter = rateLimit({ 4 | windowMs: 15 * 60 * 1000, 5 | max: 20, 6 | skipSuccessfulRequests: true, 7 | }); 8 | 9 | module.exports = { 10 | authLimiter, 11 | }; 12 | -------------------------------------------------------------------------------- /src/middlewares/validate.js: -------------------------------------------------------------------------------- 1 | const Joi = require('joi'); 2 | const httpStatus = require('http-status'); 3 | const pick = require('../utils/pick'); 4 | const ApiError = require('../utils/ApiError'); 5 | 6 | const validate = (schema) => (req, res, next) => { 7 | const validSchema = pick(schema, ['params', 'query', 'body']); 8 | const object = pick(req, Object.keys(validSchema)); 9 | const { value, error } = Joi.compile(validSchema) 10 | .prefs({ errors: { label: 'key' } }) 11 | .validate(object); 12 | 13 | if (error) { 14 | const errorMessage = error.details.map((details) => details.message).join(', '); 15 | return next(new ApiError(httpStatus.BAD_REQUEST, errorMessage)); 16 | } 17 | Object.assign(req, value); 18 | return next(); 19 | }; 20 | 21 | module.exports = validate; 22 | -------------------------------------------------------------------------------- /src/middlewares/verifyEmail.js: -------------------------------------------------------------------------------- 1 | const httpStatus = require('http-status'); 2 | const jwt = require('jsonwebtoken'); 3 | const ApiError = require('../utils/ApiError'); 4 | const { verifyMail } = require('../config/config'); 5 | 6 | const verifyEmail = () => (req, res, next) => { 7 | const { token } = req.query; 8 | jwt.verify(token, verifyMail.jwtSecret, (err, decoded) => { 9 | if (err) { 10 | throw new ApiError(httpStatus.BAD_REQUEST, 'verify token is invalid or expired'); 11 | } else { 12 | req.token = decoded; 13 | next(); 14 | } 15 | }); 16 | }; 17 | 18 | module.exports = verifyEmail; 19 | -------------------------------------------------------------------------------- /src/models/blog.model.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | const { toJSON, paginate } = require('./plugins'); 3 | 4 | const BLOG_TYPE = ['WRITE_ALONG', 'GHOSTWRITER']; 5 | 6 | const blogSchema = mongoose.Schema( 7 | { 8 | userId: { 9 | type: mongoose.Schema.ObjectId, 10 | required: true, 11 | trim: true, 12 | }, 13 | userEmail: { 14 | type: String, 15 | required: true, 16 | trim: true, 17 | lowercase: true, 18 | }, 19 | blogAbout: { 20 | type: String, 21 | required: true, 22 | trim: true, 23 | }, 24 | headline: { 25 | type: String, 26 | required: true, 27 | trim: true, 28 | }, 29 | blogPost: { 30 | type: String, 31 | required: true, 32 | }, 33 | blogType: { 34 | type: String, 35 | enum: BLOG_TYPE, 36 | default: BLOG_TYPE[0], 37 | }, 38 | }, 39 | { 40 | timestamps: true, 41 | } 42 | ); 43 | 44 | // add plugin that converts mongoose to json 45 | blogSchema.plugin(toJSON); 46 | blogSchema.plugin(paginate); 47 | 48 | /** 49 | * @typedef Blog 50 | */ 51 | const Blog = mongoose.model('Blog', blogSchema); 52 | 53 | module.exports = Blog; 54 | -------------------------------------------------------------------------------- /src/models/content.model.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | const { toJSON, paginate } = require('./plugins'); 3 | 4 | const contentSchema = mongoose.Schema( 5 | { 6 | userId: { 7 | type: mongoose.Schema.ObjectId, 8 | required: true, 9 | trim: true, 10 | }, 11 | userEmail: { 12 | type: String, 13 | required: true, 14 | trim: true, 15 | lowercase: true, 16 | }, 17 | prompt: { 18 | type: String, 19 | required: true, 20 | trim: true, 21 | }, 22 | documentType: { 23 | type: String, 24 | required: true, 25 | trim: true, 26 | }, 27 | tone: { 28 | type: String, 29 | trim: true, 30 | default: 'neutral', 31 | }, 32 | openAPIInformation: { 33 | type: mongoose.Schema.Types.Mixed, 34 | }, 35 | generatedContents: { 36 | type: Array, 37 | required: true, 38 | default: [], 39 | }, 40 | bookmarks: { 41 | type: Array, 42 | required: true, 43 | default: [], 44 | }, 45 | }, 46 | { 47 | timestamps: true, 48 | } 49 | ); 50 | 51 | // add plugin that converts mongoose to json 52 | contentSchema.plugin(toJSON); 53 | contentSchema.plugin(paginate); 54 | 55 | /** 56 | * @typedef Content 57 | */ 58 | const Content = mongoose.model('Content', contentSchema); 59 | 60 | module.exports = Content; 61 | -------------------------------------------------------------------------------- /src/models/demo.model.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | const { toJSON } = require('./plugins'); 3 | 4 | const demoSchema = mongoose.Schema( 5 | { 6 | prompt: { 7 | type: String, 8 | required: true, 9 | trim: true, 10 | }, 11 | documentType: { 12 | type: String, 13 | required: true, 14 | trim: true, 15 | }, 16 | openAPIInformation: { 17 | type: mongoose.Schema.Types.Mixed, 18 | }, 19 | generatedContents: { 20 | type: Array, 21 | required: true, 22 | default: [], 23 | }, 24 | }, 25 | { 26 | timestamps: true, 27 | } 28 | ); 29 | 30 | // add plugin that converts mongoose to json 31 | demoSchema.plugin(toJSON); 32 | 33 | /** 34 | * @typedef Content 35 | */ 36 | const Demo = mongoose.model('Demo', demoSchema); 37 | 38 | module.exports = Demo; 39 | -------------------------------------------------------------------------------- /src/models/extension.model.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | const { toJSON, paginate } = require('./plugins'); 3 | 4 | const extensionSchema = mongoose.Schema( 5 | { 6 | userId: { 7 | type: mongoose.Schema.ObjectId, 8 | required: true, 9 | trim: true, 10 | }, 11 | userEmail: { 12 | type: String, 13 | required: true, 14 | trim: true, 15 | lowercase: true, 16 | }, 17 | prompt: { 18 | type: String, 19 | required: true, 20 | trim: true, 21 | }, 22 | documentType: { 23 | type: String, 24 | required: true, 25 | trim: true, 26 | }, 27 | tone: { 28 | type: String, 29 | trim: true, 30 | default: 'neutral', 31 | }, 32 | openAPIInformation: { 33 | type: mongoose.Schema.Types.Mixed, 34 | }, 35 | generatedContents: { 36 | type: Array, 37 | required: true, 38 | default: [], 39 | }, 40 | bookmarks: { 41 | type: Array, 42 | required: true, 43 | default: [], 44 | }, 45 | }, 46 | { 47 | timestamps: true, 48 | } 49 | ); 50 | 51 | // add plugin that converts mongoose to json 52 | extensionSchema.plugin(toJSON); 53 | extensionSchema.plugin(paginate); 54 | 55 | const Extension = mongoose.model('Extension', extensionSchema); 56 | 57 | module.exports = Extension; 58 | -------------------------------------------------------------------------------- /src/models/index.js: -------------------------------------------------------------------------------- 1 | module.exports.Token = require('./token.model'); 2 | module.exports.User = require('./user.model'); 3 | module.exports.Content = require('./content.model'); 4 | module.exports.Interest = require('./interest.model'); 5 | // module.exports.Payment = require('./payment.model'); 6 | module.exports.Tool = require('./tool.model'); 7 | module.exports.Blog = require('./blog.model'); 8 | module.exports.Subscriber = require('./subscriber.model'); 9 | module.exports.Demo = require('./demo.model'); 10 | module.exports.Extension = require('./extension.model'); 11 | -------------------------------------------------------------------------------- /src/models/interest.model.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | const { toJSON, paginate } = require('./plugins'); 3 | 4 | const interestSchema = mongoose.Schema( 5 | { 6 | // _id: { 7 | // type: mongoose.Schema.Types.ObjectId, 8 | // trim: true, 9 | // ref: 'User', 10 | // }, 11 | userEmail: { 12 | type: String, 13 | required: true, 14 | trim: true, 15 | lowercase: true, 16 | }, 17 | interests: { 18 | type: mongoose.Schema.Types.Mixed, 19 | }, 20 | }, 21 | { 22 | timestamps: true, 23 | minimize: false, 24 | } 25 | ); 26 | 27 | // add plugin that converts mongoose to json 28 | interestSchema.plugin(toJSON); 29 | interestSchema.plugin(paginate); 30 | 31 | /** 32 | * @typedef Interest 33 | */ 34 | const Interest = mongoose.model('Interest', interestSchema); 35 | 36 | module.exports = Interest; 37 | -------------------------------------------------------------------------------- /src/models/payment.model.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | const { toJSON, paginate } = require('./plugins'); 3 | 4 | const paymentSchema = mongoose.Schema( 5 | { 6 | userId: { 7 | type: String, 8 | required: true, 9 | }, 10 | customerStripeId: { 11 | type: String, 12 | }, 13 | customerSubscription: [ 14 | { 15 | Subscription: { 16 | type: String, 17 | }, 18 | subscriptionId: { 19 | type: String, 20 | }, 21 | subscriptionExpire: { 22 | type: Date, 23 | }, 24 | words: { 25 | type: Number, 26 | }, 27 | }, 28 | ], 29 | }, 30 | { 31 | timestamps: true, 32 | } 33 | ); 34 | 35 | // add plugin that converts mongoose to json 36 | paymentSchema.plugin(toJSON); 37 | paymentSchema.plugin(paginate); 38 | 39 | // Indexing 40 | paymentSchema.index({ userId: 1 }); 41 | 42 | /** 43 | * @typedef Payment 44 | */ 45 | const Payment = mongoose.model('Payment', paymentSchema); 46 | 47 | module.exports = Payment; 48 | -------------------------------------------------------------------------------- /src/models/plugins/index.js: -------------------------------------------------------------------------------- 1 | module.exports.toJSON = require('./toJSON.plugin'); 2 | module.exports.paginate = require('./paginate.plugin'); 3 | -------------------------------------------------------------------------------- /src/models/plugins/paginate.plugin.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-param-reassign */ 2 | 3 | const paginate = (schema) => { 4 | /** 5 | * @typedef {Object} QueryResult 6 | * @property {Document[]} results - Results found 7 | * @property {number} page - Current page 8 | * @property {number} limit - Maximum number of results per page 9 | * @property {number} totalPages - Total number of pages 10 | * @property {number} totalResults - Total number of documents 11 | */ 12 | /** 13 | * Query for documents with pagination 14 | * @param {Object} [filter] - Mongo filter 15 | * @param {Object} [options] - Query options 16 | * @param {string} [options.sortBy] - Sorting criteria using the format: sortField:(desc|asc). Multiple sorting criteria should be separated by commas (,) 17 | * @param {string} [options.populate] - Populate data fields. Hierarchy of fields should be separated by (.). Multiple populating criteria should be separated by commas (,) 18 | * @param {number} [options.limit] - Maximum number of results per page (default = 10) 19 | * @param {number} [options.page] - Current page (default = 1) 20 | * @returns {Promise} 21 | */ 22 | schema.statics.paginate = async function (filter, options) { 23 | let sort = ''; 24 | if (options.sortBy) { 25 | const sortingCriteria = []; 26 | options.sortBy.split(',').forEach((sortOption) => { 27 | const [key, order] = sortOption.split(':'); 28 | sortingCriteria.push((order === 'desc' ? '-' : '') + key); 29 | }); 30 | sort = sortingCriteria.join(' '); 31 | } else { 32 | sort = 'createdAt'; 33 | } 34 | 35 | const limit = options.limit && parseInt(options.limit, 10) > 0 ? parseInt(options.limit, 10) : 10; 36 | const page = options.page && parseInt(options.page, 10) > 0 ? parseInt(options.page, 10) : 1; 37 | const skip = (page - 1) * limit; 38 | 39 | const countPromise = this.countDocuments(filter).exec(); 40 | let docsPromise = this.find(filter).sort(sort).skip(skip).limit(limit); 41 | 42 | if (options.populate) { 43 | options.populate.split(',').forEach((populateOption) => { 44 | docsPromise = docsPromise.populate( 45 | populateOption 46 | .split('.') 47 | .reverse() 48 | .reduce((a, b) => ({ path: b, populate: a })) 49 | ); 50 | }); 51 | } 52 | 53 | docsPromise = docsPromise.exec(); 54 | 55 | return Promise.all([countPromise, docsPromise]).then((values) => { 56 | const [totalResults, results] = values; 57 | const totalPages = Math.ceil(totalResults / limit); 58 | const result = { 59 | results, 60 | page, 61 | limit, 62 | totalPages, 63 | totalResults, 64 | }; 65 | return Promise.resolve(result); 66 | }); 67 | }; 68 | }; 69 | 70 | module.exports = paginate; 71 | -------------------------------------------------------------------------------- /src/models/plugins/toJSON.plugin.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-param-reassign */ 2 | 3 | /** 4 | * A mongoose schema plugin which applies the following in the toJSON transform call: 5 | * - removes __v, createdAt, updatedAt, and any path that has private: true 6 | * - replaces _id with id 7 | */ 8 | 9 | const deleteAtPath = (obj, path, index) => { 10 | if (index === path.length - 1) { 11 | delete obj[path[index]]; 12 | return; 13 | } 14 | deleteAtPath(obj[path[index]], path, index + 1); 15 | }; 16 | 17 | const toJSON = (schema) => { 18 | let transform; 19 | if (schema.options.toJSON && schema.options.toJSON.transform) { 20 | transform = schema.options.toJSON.transform; 21 | } 22 | 23 | schema.options.toJSON = Object.assign(schema.options.toJSON || {}, { 24 | transform(doc, ret, options) { 25 | Object.keys(schema.paths).forEach((path) => { 26 | if (schema.paths[path].options && schema.paths[path].options.private) { 27 | deleteAtPath(ret, path.split('.'), 0); 28 | } 29 | }); 30 | 31 | ret.id = ret._id.toString(); 32 | delete ret._id; 33 | delete ret.__v; 34 | delete ret.createdAt; 35 | // delete ret.updatedAt; 36 | if (transform) { 37 | return transform(doc, ret, options); 38 | } 39 | }, 40 | }); 41 | }; 42 | 43 | module.exports = toJSON; 44 | -------------------------------------------------------------------------------- /src/models/token.model.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | const { toJSON } = require('./plugins'); 3 | const { tokenTypes } = require('../config/tokens'); 4 | 5 | const tokenSchema = mongoose.Schema( 6 | { 7 | token: { 8 | type: String, 9 | required: true, 10 | index: true, 11 | }, 12 | user: { 13 | type: mongoose.SchemaTypes.ObjectId, 14 | ref: 'User', 15 | required: true, 16 | }, 17 | type: { 18 | type: String, 19 | enum: [tokenTypes.REFRESH, tokenTypes.RESET_PASSWORD], 20 | required: true, 21 | }, 22 | expires: { 23 | type: Date, 24 | required: true, 25 | }, 26 | blacklisted: { 27 | type: Boolean, 28 | default: false, 29 | }, 30 | }, 31 | { 32 | timestamps: true, 33 | } 34 | ); 35 | 36 | // add plugin that converts mongoose to json 37 | tokenSchema.plugin(toJSON); 38 | 39 | /** 40 | * @typedef Token 41 | */ 42 | const Token = mongoose.model('Token', tokenSchema); 43 | 44 | module.exports = Token; 45 | -------------------------------------------------------------------------------- /src/models/tool.model.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | const { toJSON } = require('./plugins'); 3 | 4 | const toolCategorySchema = mongoose.Schema({ 5 | name: { 6 | type: String, 7 | required: true, 8 | }, 9 | key: { 10 | type: String, 11 | required: true, 12 | }, 13 | description: { 14 | type: String, 15 | }, 16 | icon: { 17 | src: String, 18 | }, 19 | }); 20 | 21 | const toolFieldSchema = mongoose.Schema({ 22 | name: { 23 | type: String, 24 | required: true, 25 | }, 26 | key: { 27 | type: String, 28 | required: true, 29 | }, 30 | tips: { 31 | text: String, 32 | }, 33 | type: { 34 | type: String, 35 | required: true, 36 | }, 37 | placeholder: { 38 | type: String, 39 | }, 40 | validation: { 41 | required: { type: Boolean, default: true }, 42 | max: { type: Number }, 43 | }, 44 | }); 45 | 46 | const toolSchema = mongoose.Schema({ 47 | name: { 48 | type: String, 49 | required: true, 50 | }, 51 | key: { 52 | type: String, 53 | required: true, 54 | }, 55 | videoId: { 56 | type: String, 57 | }, 58 | fields: [toolFieldSchema], 59 | category: { 60 | type: mongoose.Schema.Types.ObjectId, 61 | ref: 'ToolCategory', 62 | required: true, 63 | }, 64 | }); 65 | 66 | // add plugin that converts mongoose to json 67 | toolSchema.plugin(toJSON); 68 | toolCategorySchema.plugin(toJSON); 69 | 70 | const Tool = mongoose.model('Tool', toolSchema); 71 | 72 | const ToolCategory = mongoose.model('ToolCategory', toolCategorySchema); 73 | 74 | module.exports = { ToolCategory, Tool }; 75 | -------------------------------------------------------------------------------- /src/models/user.model.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | const bcrypt = require('bcryptjs'); 3 | const { toJSON, paginate } = require('./plugins'); 4 | const { roles } = require('../config/roles'); 5 | const { authTypes } = require('../config/auths'); 6 | 7 | const authTypeEnum = Object.values(authTypes); 8 | 9 | const userSchema = mongoose.Schema( 10 | { 11 | userId: { 12 | type: String, 13 | unique: true, 14 | }, 15 | firstName: { 16 | type: String, 17 | }, 18 | lastName: { 19 | type: String, 20 | }, 21 | authType: { 22 | type: String, 23 | enum: authTypeEnum, 24 | private: true, 25 | }, 26 | strategyId: { 27 | type: String, 28 | private: true, 29 | }, 30 | profileAvatar: { 31 | type: String, 32 | }, 33 | password: { 34 | type: String, 35 | required: true, 36 | trim: true, 37 | private: true, // used by the toJSON plugin 38 | }, 39 | email: { 40 | type: String, 41 | required: true, 42 | trim: true, 43 | lowercase: true, 44 | }, 45 | role: { 46 | type: String, 47 | enum: roles, 48 | default: 'user', 49 | }, 50 | isVerified: { 51 | type: Boolean, 52 | default: false, 53 | }, 54 | accountStatus: { 55 | type: String, 56 | enum: ['active', 'blocked'], 57 | default: 'active', 58 | }, 59 | favouriteTools: { 60 | type: Array, 61 | required: true, 62 | default: [], 63 | }, 64 | UserOwnOpenAIApiKey: { 65 | type: String, 66 | required: false, 67 | }, 68 | hasCompletedOnboarding: { 69 | type: Boolean, 70 | default: true, 71 | }, 72 | }, 73 | { 74 | timestamps: true, 75 | minimize: false, 76 | } 77 | ); 78 | 79 | // Check if email is taken 80 | userSchema.statics.isEmailTaken = async function (email, excludeUserId) { 81 | const user = await this.findOne({ email, authType: 'email', _id: { $ne: excludeUserId } }); 82 | return !!user; 83 | }; 84 | 85 | userSchema.statics.isVerifiedEmailTaken = async function (email) { 86 | const user = await this.findOne({ email, isVerified: true, authType: 'email' }); 87 | return !!user; 88 | }; 89 | 90 | // Check if password matches the user's password 91 | userSchema.methods.isPasswordMatch = async function (password) { 92 | const user = this; 93 | return bcrypt.compare(password, user.password); 94 | }; 95 | 96 | userSchema.pre('save', async function (next) { 97 | const user = this; 98 | if (user.isModified('password')) { 99 | user.password = await bcrypt.hash(user.password, 8); 100 | } 101 | next(); 102 | }); 103 | 104 | // add plugin that converts mongoose to json 105 | userSchema.plugin(toJSON); 106 | userSchema.plugin(paginate); 107 | 108 | // Indexing 109 | userSchema.index({ userId: 1 }); 110 | 111 | const User = mongoose.model('User', userSchema); 112 | 113 | module.exports = User; 114 | -------------------------------------------------------------------------------- /src/routes/v1/auth.route.js: -------------------------------------------------------------------------------- 1 | const passport = require('passport'); 2 | const express = require('express'); 3 | const validate = require('../../middlewares/validate'); 4 | const passportAuth = require('../../middlewares/passportAuth'); 5 | const verifyEmail = require('../../middlewares/verifyEmail'); 6 | const authValidation = require('../../validations/auth.validation'); 7 | const authController = require('../../controllers/auth.controller'); 8 | const auth = require('../../middlewares/auth'); 9 | 10 | const router = express.Router(); 11 | 12 | router.post('/register', validate(authValidation.register), authController.register); 13 | router.post('/verify-account', validate(authValidation.verifyAccount), verifyEmail(), authController.verifyAccount); 14 | router.post('/login', validate(authValidation.login), authController.login); 15 | router.post('/logout', validate(authValidation.logout), authController.logout); 16 | router.post('/refresh-tokens', validate(authValidation.refreshTokens), authController.refreshTokens); 17 | router.post('/forgot-password', validate(authValidation.forgotPassword), authController.forgotPassword); 18 | router.post('/reset-password', validate(authValidation.resetPassword), verifyEmail(), authController.resetPassword); 19 | router.post('/strategy-login', validate(authValidation.strategyLogin), passportAuth(), authController.strategyLogin); 20 | router.get('/google', passport.authenticate('google', { scope: ['profile', 'email'] })); 21 | router.get('/facebook', passport.authenticate('facebook', { scope: ['public_profile', 'email'] })); 22 | router.get( 23 | '/google/callback', 24 | passport.authenticate('google', { 25 | failureRedirect: '/login', 26 | session: false, 27 | }), 28 | authController.strategyCallback 29 | ); 30 | router.get( 31 | '/facebook/callback', 32 | passport.authenticate('facebook', { failureRedirect: '/login', session: false }), 33 | authController.strategyCallback 34 | ); 35 | 36 | // Route to handle the submission of user's own OpenAI API key during onboarding 37 | router.post('/submit-own-openai-api-key', auth(), authController.submitOwnOpenAIApiKey); 38 | 39 | // Route to handle completing onboarding 40 | router.post('/complete-onboarding', auth(), authController.completeOnboarding); 41 | 42 | module.exports = router; 43 | -------------------------------------------------------------------------------- /src/routes/v1/blog.route.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const auth = require('../../middlewares/auth'); 3 | const validate = require('../../middlewares/validate'); 4 | const { blogValidation } = require('../../validations'); 5 | const { blogController } = require('../../controllers'); 6 | 7 | const router = express.Router(); 8 | 9 | router.route('/').get(auth('getBlog'), validate(blogValidation.getBlogs), blogController.getBlogs); 10 | 11 | router.route('/create-new').post(auth('manageBlog'), validate(blogValidation.createBlog), blogController.createBlog); 12 | 13 | router 14 | .route('/:blogId') 15 | .get(auth('getBlog'), validate(blogValidation.getBlog), blogController.getBlog) 16 | .patch(auth('manageBlog'), validate(blogValidation.updateBlog), blogController.updateBlog) 17 | .delete(auth('manageBlog'), validate(blogValidation.deleteBlog), blogController.deleteBlog); 18 | 19 | module.exports = router; 20 | -------------------------------------------------------------------------------- /src/routes/v1/demo.route.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const validate = require('../../middlewares/content.validate'); 3 | const { demo } = require('../../validations/contents'); 4 | const { demoController } = require('../../controllers'); 5 | 6 | const router = express.Router(); 7 | 8 | router.post('/paraphrasing', validate(demo.paraphrase), demoController.generate); 9 | 10 | router.post('/blog-headline', validate(demo.blogHeadline), demoController.generate); 11 | 12 | module.exports = router; 13 | -------------------------------------------------------------------------------- /src/routes/v1/extension.route.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const auth = require('../../middlewares/auth'); 3 | const validate = require('../../middlewares/content.validate'); 4 | const { extension } = require('../../validations/contents'); 5 | const { extensionController } = require('../../controllers'); 6 | 7 | const router = express.Router(); 8 | 9 | router 10 | .route('/generate/paraphrasing') 11 | .post(auth('generateContentExtension'), validate(extension.paraphrase), extensionController.generate); 12 | 13 | router 14 | .route('/generate/grammar-fixer') 15 | .post(auth('generateContent'), validate(extension.grammarFixer), extensionController.generate); 16 | 17 | router 18 | .route('/generate/simplifier') 19 | .post(auth('generateContentExtension'), validate(extension.simplifier), extensionController.generate); 20 | 21 | router 22 | .route('/generate/summarizer') 23 | .post(auth('generateContentExtension'), validate(extension.summarizer), extensionController.generate); 24 | 25 | router 26 | .route('/generate/change-tone') 27 | .post(auth('generateContentExtension'), validate(extension.changeTone), extensionController.generate); 28 | 29 | module.exports = router; 30 | -------------------------------------------------------------------------------- /src/routes/v1/index.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | 3 | const authRoute = require('./auth.route'); 4 | const userRoute = require('./user.route'); 5 | const contentRoute = require('./content.route'); 6 | const plagiarismCheckerRoute = require('./plagiarismChecker.routes'); 7 | const paymentRoute = require('./payment.route'); 8 | const interestRoute = require('./interest.route'); 9 | const supportRoute = require('./support.route'); 10 | const toolRoute = require('./tool.route'); 11 | const blogRoute = require('./blog.route'); 12 | const subscriberRoute = require('./subscriber.route'); 13 | const demoRoute = require('./demo.route'); 14 | const extensionRoute = require('./extension.route'); 15 | const noticeRoute = require('./notice.route'); 16 | 17 | const router = express.Router(); 18 | 19 | const defaultRoutes = [ 20 | { 21 | path: '/auth', 22 | route: authRoute, 23 | }, 24 | { 25 | path: '/users', 26 | route: userRoute, 27 | }, 28 | { 29 | path: '/contents', 30 | route: contentRoute, 31 | }, 32 | { 33 | path: '/plagiarism-checker', 34 | route: plagiarismCheckerRoute, 35 | }, 36 | { 37 | path: '/payments', 38 | route: paymentRoute, 39 | }, 40 | { 41 | path: '/interests', 42 | route: interestRoute, 43 | }, 44 | { 45 | path: '/support', 46 | route: supportRoute, 47 | }, 48 | { 49 | path: '/tools', 50 | route: toolRoute, 51 | }, 52 | { 53 | path: '/blogs', 54 | route: blogRoute, 55 | }, 56 | { 57 | path: '/subscriber', 58 | route: subscriberRoute, 59 | }, 60 | { 61 | path: '/demo', 62 | route: demoRoute, 63 | }, 64 | { 65 | path: '/extension', 66 | route: extensionRoute, 67 | }, 68 | { 69 | path: '/notice', 70 | route: noticeRoute, 71 | }, 72 | ]; 73 | 74 | defaultRoutes.forEach((route) => { 75 | router.use(route.path, route.route); 76 | }); 77 | 78 | module.exports = router; 79 | -------------------------------------------------------------------------------- /src/routes/v1/interest.route.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const auth = require('../../middlewares/auth'); 3 | const validate = require('../../middlewares/validate'); 4 | const { interestValidation } = require('../../validations'); 5 | const { interestController } = require('../../controllers'); 6 | 7 | const router = express.Router(); 8 | 9 | router 10 | .route('/:userId') 11 | .patch(auth('updateUserInfo'), validate(interestValidation.updateInterests), interestController.updateInterests); 12 | 13 | module.exports = router; 14 | -------------------------------------------------------------------------------- /src/routes/v1/notice.route.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const auth = require('../../middlewares/auth'); 3 | const validate = require('../../middlewares/validate'); 4 | const { noticeValidation } = require('../../validations'); 5 | const noticeController = require('../../controllers/notice.controller'); 6 | 7 | const router = express.Router(); 8 | 9 | router.get('/get-notice', noticeController.getNotice); 10 | router.patch('/update-notice', auth('updateNotice'), validate(noticeValidation.updateNotice), noticeController.updateNotice); 11 | 12 | module.exports = router; 13 | -------------------------------------------------------------------------------- /src/routes/v1/onboarding.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CopywriterPro-ai/copywriterproai-backend/048cc8359a98f9dd575182b8c47b80f271846a98/src/routes/v1/onboarding.js -------------------------------------------------------------------------------- /src/routes/v1/payment.route.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const auth = require('../../middlewares/auth'); 3 | const validate = require('../../middlewares/validate'); 4 | const paymentValidation = require('../../validations/payment.validation'); 5 | const paymentController = require('../../controllers/payment.controller'); 6 | 7 | const router = express.Router(); 8 | 9 | router.get('/product-prices', paymentController.priceList); 10 | router.get('/subscriptions', auth(), validate(paymentValidation.getSubscriptions), paymentController.getSubscriptions); 11 | router.get('/subscriptions/me', auth(), paymentController.getSubscriptionMe); 12 | router.post('/create-customer', auth(), paymentController.createCustomer); 13 | router.post( 14 | '/create-checkout-session', 15 | auth(), 16 | validate(paymentValidation.createCheckoutSession), 17 | paymentController.createCheckoutSessions 18 | ); 19 | router.get('/checkout-session', auth(), validate(paymentValidation.checkoutSession), paymentController.checkoutSessions); 20 | // router.get('/subscription-invoice', auth(), validate(paymentValidation.invoicePreview), paymentController.invoicePreview); 21 | router.post( 22 | '/update-subscription-plan', 23 | auth(), 24 | validate(paymentValidation.updateSubscriptionPlan), 25 | paymentController.updateSubscriptionPlan 26 | ); 27 | router.post('/trial-end', auth(), paymentController.handleTrialEnd); 28 | // router.post( 29 | // '/update-subscription', 30 | // auth(), 31 | // validate(paymentValidation.updateSubscription), 32 | // paymentController.updateSubscription 33 | // ); 34 | router.post('/create-customer-portal-session', auth(), paymentController.customerPortal); 35 | router.post('/payment-webhook', express.raw({ type: 'application/json' }), paymentController.paymentWebhook); 36 | 37 | module.exports = router; 38 | -------------------------------------------------------------------------------- /src/routes/v1/plagiarismChecker.routes.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const auth = require('../../middlewares/auth'); 3 | const validate = require('../../middlewares/validate'); 4 | const { plagiarismCheckerValidation } = require('../../validations'); 5 | const { plagiarismCheckerController } = require('../../controllers'); 6 | 7 | const router = express.Router(); 8 | 9 | router 10 | .route('/check-plagiarism') 11 | .post( 12 | auth('checkPlagiarism'), 13 | validate(plagiarismCheckerValidation.searchContent), 14 | plagiarismCheckerController.searchContent 15 | ); 16 | 17 | module.exports = router; 18 | -------------------------------------------------------------------------------- /src/routes/v1/subscriber.route.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const auth = require('../../middlewares/auth'); 3 | const { subscriberController } = require('../../controllers'); 4 | const validate = require('../../middlewares/validate'); 5 | const { subscriberValidation } = require('../../validations'); 6 | 7 | const router = express.Router(); 8 | 9 | router.route('/me').get(auth(), subscriberController.getOwnSubscribe); 10 | 11 | // router.route('/update').patch(auth(), subscriberController.updateOwnSubscribe); 12 | 13 | router.route('/copycounter').patch(auth(), subscriberController.updateOwnCopyCounter); 14 | 15 | router.route('/generate-update').patch(auth(), subscriberController.generateUpdate); 16 | 17 | router 18 | .route('/sub-switcher') 19 | .post(auth(), validate(subscriberValidation.subscriberSwitcher), subscriberController.subscriberSwitcher); 20 | 21 | // New routes for managing trial and enforcing subscription 22 | router.get('/manage-trial', auth(), subscriberController.manageTrial); 23 | router.get('/enforce-subscription', auth(), subscriberController.enforceSubscription); 24 | 25 | module.exports = router; 26 | -------------------------------------------------------------------------------- /src/routes/v1/support.route.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const multer = require('multer'); 3 | const validate = require('../../middlewares/validate'); 4 | const { supportValidation } = require('../../validations'); 5 | const { supportController } = require('../../controllers'); 6 | 7 | const storage = multer.memoryStorage(); 8 | const upload = multer({ storage }).single('image'); 9 | 10 | const router = express.Router(); 11 | 12 | router.post('/feature-request', validate(supportValidation.userMessage), supportController.featureRequest); 13 | router.post('/bug-report', [upload, validate(supportValidation.userMessage)], supportController.bugReport); 14 | router.post('/contact', validate(supportValidation.userMessage), supportController.userMessage); 15 | 16 | module.exports = router; 17 | -------------------------------------------------------------------------------- /src/routes/v1/tool.route.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const validate = require('../../middlewares/validate'); 3 | const auth = require('../../middlewares/auth'); 4 | const { toolValidation } = require('../../validations'); 5 | const { toolController } = require('../../controllers'); 6 | 7 | const router = express.Router(); 8 | 9 | router 10 | .route('/') 11 | .post(auth('manageTools'), validate(toolValidation.tool), toolController.postTool) 12 | .get(toolController.getTools); 13 | 14 | router 15 | .route('/categories') 16 | .post(auth('manageTools'), validate(toolValidation.toolCategory), toolController.postToolCategory) 17 | .get(toolController.getToolCategories); 18 | 19 | router 20 | .route('/:toolId') 21 | .get(toolController.getTool) 22 | .patch(auth('manageTools'), toolController.patchTool) 23 | .delete(auth('manageTools'), toolController.deleteTool); 24 | 25 | router 26 | .route('/categories/:categoryId') 27 | .get(toolController.getToolCategory) 28 | .patch(auth('manageTools'), toolController.patchToolCategory) 29 | .delete(auth('manageTools'), toolController.deleteToolCategory); 30 | 31 | module.exports = router; 32 | -------------------------------------------------------------------------------- /src/routes/v1/user.route.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const auth = require('../../middlewares/auth'); 3 | const validate = require('../../middlewares/validate'); 4 | const userValidation = require('../../validations/user.validation'); 5 | const { userController } = require('../../controllers'); 6 | 7 | const router = express.Router(); 8 | 9 | router.route('/me').get(auth(), userController.getMe); 10 | 11 | router 12 | .route('/') 13 | // .post(auth('manageUsers'), validate(userValidation.createUser), userController.createUser) 14 | .get(auth('getUsers'), validate(userValidation.getUsers), userController.getUsers); 15 | 16 | router 17 | .route('/:userId') 18 | .get(auth('getUserInfo'), validate(userValidation.getUser), userController.getUser) 19 | .patch(auth('manageUsers'), validate(userValidation.updateUser), userController.updateUser); 20 | // .delete(auth('manageUsers'), validate(userValidation.deleteUser), userController.deleteUser); 21 | 22 | router 23 | .route('/:userId/update/info') 24 | .patch(auth('updateUserInfo'), validate(userValidation.updateUserInfo), userController.updateUser); 25 | 26 | router 27 | .route('/:userId/bookmarks') 28 | .get(auth('getUserInfo'), validate(userValidation.getUserBookmarks), userController.getUserBookmarks) 29 | .patch(auth('updateUserInfo'), validate(userValidation.updateUserBookmarks), userController.updateUserBookmarks); 30 | 31 | router 32 | .route('/:userId/contents') 33 | .patch(auth('manageContent'), validate(userValidation.updateUserContent), userController.updateUserContent); 34 | 35 | router 36 | .route('/:userId/favourite-tools') 37 | .get(auth('getUserInfo'), validate(userValidation.getUserFavouriteTools), userController.getUserFavouriteTools) 38 | .patch(auth('updateUserInfo'), validate(userValidation.updateUserFavouriteTools), userController.updateUserFavouriteTools); 39 | 40 | router.route('/:userId/copycounter').patch(auth('updateUserInfo'), userController.updateUserCopyCounter); 41 | 42 | router.route('/extension/access').post(auth(), userController.extensionAccessToken); 43 | 44 | module.exports = router; 45 | -------------------------------------------------------------------------------- /src/services/auth.service.js: -------------------------------------------------------------------------------- 1 | const httpStatus = require('http-status'); 2 | const tokenService = require('./token.service'); 3 | const userService = require('./user.service'); 4 | const Token = require('../models/token.model'); 5 | const ApiError = require('../utils/ApiError'); 6 | const { tokenTypes } = require('../config/tokens'); 7 | 8 | /** 9 | * Login with username and password 10 | * @param {string} email 11 | * @param {string} password 12 | * @returns {Promise} 13 | */ 14 | const loginUser = async (identity, password) => { 15 | const user = await userService.getUser(identity); 16 | if (!user || !(await user.isPasswordMatch(password))) { 17 | throw new ApiError(httpStatus.UNAUTHORIZED, 'Incorrect email or password'); 18 | } else if (!user.isVerified) { 19 | throw new ApiError(httpStatus.FORBIDDEN, 'Account not verified!'); 20 | } 21 | return user; 22 | }; 23 | 24 | /** 25 | * Logout 26 | * @param {string} refreshToken 27 | * @returns {Promise} 28 | */ 29 | const logout = async (refreshToken) => { 30 | const refreshTokenDoc = await Token.findOne({ token: refreshToken, type: tokenTypes.REFRESH, blacklisted: false }); 31 | if (!refreshTokenDoc) { 32 | throw new ApiError(httpStatus.NOT_FOUND, 'Not found'); 33 | } 34 | await refreshTokenDoc.remove(); 35 | }; 36 | 37 | /** 38 | * Refresh auth tokens 39 | * @param {string} refreshToken 40 | * @returns {Promise} 41 | */ 42 | const refreshAuth = async (refreshToken) => { 43 | try { 44 | const refreshTokenDoc = await tokenService.verifyToken(refreshToken, tokenTypes.REFRESH); 45 | const user = await userService.getUserById(refreshTokenDoc.user); 46 | if (!user) { 47 | throw new Error(); 48 | } 49 | await refreshTokenDoc.remove(); 50 | return tokenService.generateAuthTokens(user); 51 | } catch (error) { 52 | throw new ApiError(httpStatus.UNAUTHORIZED, 'Please authenticate'); 53 | } 54 | }; 55 | 56 | // /** 57 | // * Reset password 58 | // * @param {string} resetPasswordToken 59 | // * @param {string} newPassword 60 | // * @returns {Promise} 61 | // */ 62 | // const resetPassword = async (resetPasswordToken, newPassword) => { 63 | // try { 64 | // const resetPasswordTokenDoc = await tokenService.verifyToken(resetPasswordToken, tokenTypes.RESET_PASSWORD); 65 | // const user = await userService.getUserById(resetPasswordTokenDoc.user); 66 | // if (!user) { 67 | // throw new Error(); 68 | // } 69 | // await userService.updateUserById(user.id, { password: newPassword }); 70 | // await Token.deleteMany({ user: user.id, type: tokenTypes.RESET_PASSWORD }); 71 | // } catch (error) { 72 | // throw new ApiError(httpStatus.UNAUTHORIZED, 'Password reset failed'); 73 | // } 74 | // }; 75 | 76 | const resetPassword = async ({ email, password }) => { 77 | try { 78 | const user = await userService.getUser({ email }); 79 | if (!user) { 80 | throw new Error(); 81 | } 82 | await userService.updateUserById(user, user.id, { password }); 83 | } catch (error) { 84 | throw new ApiError(httpStatus.UNAUTHORIZED, 'Password reset failed'); 85 | } 86 | }; 87 | 88 | const strategyUser = async ({ sub, authType, userId }) => { 89 | const user = await userService.getUser({ _id: sub, authType, userId }); 90 | if (!user) { 91 | throw new ApiError(httpStatus.NOT_FOUND, 'User not found'); 92 | } 93 | return user; 94 | }; 95 | 96 | module.exports = { 97 | loginUser, 98 | logout, 99 | refreshAuth, 100 | resetPassword, 101 | strategyUser, 102 | }; 103 | -------------------------------------------------------------------------------- /src/services/blog.service.js: -------------------------------------------------------------------------------- 1 | const httpStatus = require('http-status'); 2 | const { Blog } = require('../models'); 3 | const ApiError = require('../utils/ApiError'); 4 | 5 | const getBlog = async (_id, userEmail) => { 6 | return Blog.findOne({ _id, userEmail }); 7 | }; 8 | 9 | const checkBlogExistsOrNot = async (id, userEmail) => { 10 | const blog = await getBlog(id, userEmail); 11 | if (!blog) { 12 | throw new ApiError(httpStatus.NOT_FOUND, 'Blog not found'); 13 | } 14 | return blog; 15 | }; 16 | 17 | const queryBlogs = async (filter, options) => { 18 | const blogs = await Blog.paginate(filter, options); 19 | return blogs; 20 | }; 21 | 22 | const createBlog = async (userId, userEmail, body) => { 23 | const blog = await Blog.create({ userId, userEmail, ...body }); 24 | return blog; 25 | }; 26 | 27 | const updateBlog = async (blog, updatedBlog) => { 28 | Object.assign(blog, updatedBlog); 29 | await blog.save(); 30 | return blog; 31 | }; 32 | 33 | const deleteBlogById = async (blogId, userEmail) => { 34 | const blog = await getBlog(blogId, userEmail); 35 | if (!blog) { 36 | throw new ApiError(httpStatus.NOT_FOUND, 'Blog not found'); 37 | } 38 | await blog.remove(); 39 | return blog; 40 | }; 41 | 42 | module.exports = { 43 | checkBlogExistsOrNot, 44 | queryBlogs, 45 | createBlog, 46 | updateBlog, 47 | deleteBlogById, 48 | }; 49 | -------------------------------------------------------------------------------- /src/services/contents/amazon.contents.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-await-in-loop */ 2 | const { generateContentWithModel, removeSpaces, storeData, formatResponse } = require('../content.service'); 3 | 4 | const generateAmazonProductListings = async ( 5 | userId, 6 | userEmail, 7 | { productName, productCategories, productFeatures, numberOfSuggestions }, 8 | apiKey 9 | ) => { 10 | const userPrompt = `Product Name: ${removeSpaces(productName)} 11 | Product Categories: ${removeSpaces(productCategories)} 12 | Product Features: ${removeSpaces(productFeatures)}`; 13 | 14 | const prompt = `You are an expert in creating Amazon product descriptions. Your task is to write a compelling, SEO-friendly product description for the given product. Please follow the guidelines below to ensure the highest quality. 15 | 16 | Guidelines: 17 | 1. **Product Name:** Include the product name at the beginning. 18 | 2. **Product Categories:** List the product categories. 19 | 3. **Product Features:** Highlight key features, focusing on benefits and unique selling points. 20 | 4. **Description:** 21 | - Start with a strong opening statement that grabs attention. 22 | - Provide detailed descriptions of the product features. 23 | - Use bullet points for clarity and readability. 24 | - Include any specifications, dimensions, and other relevant details. 25 | - Ensure the content is engaging and persuasive, encouraging potential customers to purchase. 26 | - Maintain a professional and informative tone. 27 | - Optimize the description with relevant keywords for SEO. 28 | 29 | Example: 30 | Product Name: ZINUS Shalini Upholstered Platform Bed 31 | Product Categories: Beds 32 | Product Features: Twin size supports a maximum weight capacity of 350 pounds, slats are spaced 2.7 inches apart, 2-person assembly in under an hour, Wooden slats 33 | Description: 34 | Woven Fabric Upholstery With Steel Framework 35 | GOOD LOOKS, CONFIDENT STYLE - With its easy-as-pie assembly, sturdy construction and elegant diamond pattern stitching, the Shalini stands the test of time and makes a stunningly chic addition to your bedroom 36 | DURABLY DESIGNED - Interior steel framework and dense foam padding add comfort and longevity; Twin size supports a maximum weight capacity of 350 pounds, while all other sizes can support up to 700 pounds 37 | NO BOX SPRING NEEDED - Durable wood slats support and extend the life of your latex, memory foam or spring mattress without the need for a box spring; For the twin and full sizes, slats are spaced 2.7 inches apart, and for other sizes, slats are spaced 3.2 inches apart 38 | EASY ASSEMBLY – Everything you need is efficiently packed into one box and shipped straight to your door; all parts, tools and instructions are conveniently located in the zippered compartment of the headboard for easy, 2-person assembly in under an hour 39 | Worry-free 5 year limited warranty included; Mattress sold separately 40 | Furniture Finish: Wooden slats 41 | 42 | ${userPrompt} 43 | Description:`; 44 | 45 | const openAPIInformationsList = []; 46 | const amazonProductListingsList = []; 47 | 48 | for (let i = 0; i < numberOfSuggestions; i++) { 49 | const amazonProductListings = await generateContentWithModel('gpt-4', 500, prompt, 0.9, 0, 0, ['\n\n'], apiKey); 50 | const { id, object, created, model, choices } = amazonProductListings; 51 | 52 | // Log choices[0] and choices[0].message.content for debugging 53 | console.log('amazonProductListings.choices[0]:', choices[0]); 54 | console.log('amazonProductListings.choices[0].message.content:', choices[0].message.content); 55 | 56 | openAPIInformationsList.push({ id, object, created, model }); 57 | amazonProductListingsList.push(choices[0].message.content.trim()); 58 | } 59 | 60 | const { _id, generatedContents } = await storeData( 61 | userId, 62 | userEmail, 63 | 'amazon-product-listings', 64 | userPrompt, 65 | openAPIInformationsList, 66 | amazonProductListingsList 67 | ); 68 | 69 | const userResponse = formatResponse(_id, 'amazon-product-listings', generatedContents); 70 | 71 | return userResponse; 72 | }; 73 | 74 | module.exports = { 75 | generateAmazonProductListings, 76 | }; 77 | -------------------------------------------------------------------------------- /src/services/contents/business.contents.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-plusplus */ 2 | /* eslint-disable no-await-in-loop */ 3 | const { generateContentWithModel, removeSpaces, storeData, formatResponse } = require('../content.service'); 4 | 5 | const generateCatchyBusinessTaglines = async (userId, userEmail, { companyName, businessType, numberOfSuggestions }, apiKey) => { 6 | const userPrompt = `Company Name: ${removeSpaces(companyName)} 7 | Business Type: ${removeSpaces(businessType)}`; 8 | 9 | const prompt = `You are a creative copywriter with expertise in crafting memorable and impactful business taglines. Your task is to generate catchy business taglines for the given company. Use your expertise to create engaging, unique, and memorable taglines that effectively communicate the brand's message and resonate with the target audience. 10 | 11 | Guidelines: 12 | 1. **Relevance:** Ensure each tagline is directly related to the company and its business type. 13 | 2. **Memorability:** Create taglines that are easy to remember and repeat. 14 | 3. **Brand Message:** Clearly convey the brand’s message and values. 15 | 4. **Brevity:** Keep taglines short and to the point. 16 | 5. **Uniqueness:** Ensure each tagline stands out from the competition. 17 | 6. **Emotional Appeal:** Use words that evoke emotions and connect with the audience. 18 | 19 | Examples: 20 | Company Name: Mastercard 21 | Business Type: Financial services 22 | Taglines: There are some things money can't buy. For everything else, there's MasterCard. 23 | 24 | Company Name: M&M 25 | Business Type: Chocolates, Food 26 | Taglines: Melts in Your Mouth, Not in Your Hands. 27 | 28 | Company Name: Verizon 29 | Business Type: Wireless network operator 30 | Taglines: Can You Hear Me Now? Good. 31 | 32 | Company Name: Nike 33 | Business Type: Footwear, apparel, equipment, accessories 34 | Taglines: Just Do It. 35 | 36 | Company Name: Apple 37 | Business Type: Designs, develops, and sells consumer electronics, computer software, and online services 38 | Taglines: Think Different. 39 | 40 | Company Name: BMW 41 | Business Type: Produces luxury vehicles and motorcycles 42 | Taglines: Designed for Driving Pleasure. 43 | 44 | Company Name: The New York Times 45 | Business Type: Daily newspaper, worldwide news 46 | Taglines: All the News That's Fit to Print. 47 | 48 | ${userPrompt} 49 | Taglines:`; 50 | 51 | const openAPIInformationsList = []; 52 | const catchyBusinessTaglinesList = []; 53 | 54 | for (let i = 0; i < numberOfSuggestions; i++) { 55 | const catchyBusinessTaglines = await generateContentWithModel('gpt-4o', 20, prompt, 0.9, 0, 0, [ 56 | '\n', 57 | 'Taglines:', 58 | ], apiKey); 59 | const { id, object, created, model, choices } = catchyBusinessTaglines; 60 | 61 | // Log choices[0] and choices[0].message.content for debugging 62 | console.log('catchyBusinessTaglines.choices[0]:', choices[0]); 63 | console.log('catchyBusinessTaglines.choices[0].message.content:', choices[0].message.content); 64 | 65 | openAPIInformationsList.push({ id, object, created, model }); 66 | catchyBusinessTaglinesList.push(choices[0].message.content.trim()); 67 | } 68 | 69 | const { _id, generatedContents } = await storeData( 70 | userId, 71 | userEmail, 72 | 'catchy-business-taglines', 73 | userPrompt, 74 | openAPIInformationsList, 75 | catchyBusinessTaglinesList 76 | ); 77 | 78 | const userResponse = formatResponse(_id, 'catchy-business-taglines', generatedContents); 79 | 80 | return userResponse; 81 | }; 82 | 83 | module.exports = { 84 | generateCatchyBusinessTaglines, 85 | }; 86 | -------------------------------------------------------------------------------- /src/services/contents/common.contents.js: -------------------------------------------------------------------------------- 1 | const { generateContentWithModel, processListContents, removeSpaces } = require('../content.service'); 2 | 3 | const imageIdeasFromAdText = async (userId, userEmail, { product, adText, numberOfSuggestions }, apiKey) => { 4 | const userPrompt = `Product: ${removeSpaces(product)} 5 | Ad: ${removeSpaces(adText)}`; 6 | 7 | const prompt = `You are a creative director with expertise in generating compelling visual content ideas. Your task is to generate image content ideas for the following ad of a product. Use your expertise to create engaging, innovative, and visually appealing content that effectively communicates the ad's message and captures the audience's attention. 8 | 9 | Guidelines: 10 | 1. **Relevance:** Ensure each image idea is directly related to the product and the ad text. 11 | 2. **Visual Appeal:** Create ideas that are visually striking and likely to catch the viewer’s eye. 12 | 3. **Message Clarity:** Ensure the image ideas clearly convey the ad's message. 13 | 4. **Innovation:** Propose unique and creative visual concepts. 14 | 5. **Engagement:** Focus on ideas that will engage the audience and encourage them to learn more about the product. 15 | 6. **Versatility:** Consider ideas that can be used across various platforms and formats (e.g., social media, print, online ads). 16 | 17 | Examples: 18 | Product: Eco-Friendly Water Bottle 19 | Ad: "Stay hydrated and save the planet with our eco-friendly water bottle. Perfect for on-the-go lifestyles." 20 | Image Content Ideas: 21 | - A close-up shot of the water bottle with a backdrop of a lush, green forest. 22 | - A lifestyle shot of a person using the water bottle while hiking, with a clear focus on the product. 23 | - An image of the water bottle surrounded by recyclable materials, highlighting its eco-friendly aspect. 24 | 25 | Product: Noise-Canceling Headphones 26 | Ad: "Experience the sound of silence with our top-rated noise-canceling headphones." 27 | Image Content Ideas: 28 | - A serene image of someone wearing the headphones in a bustling city, with the city noise visibly muted. 29 | - A close-up shot of the headphones with a sleek, modern background. 30 | - An image of the headphones placed on a desk next to a 'Do Not Disturb' sign, emphasizing the peace and quiet they provide. 31 | 32 | Product: Organic Skincare Cream 33 | Ad: "Nourish your skin with our 100% organic skincare cream. Gentle on your skin, tough on imperfections." 34 | Image Content Ideas: 35 | - A close-up shot of the skincare cream jar surrounded by fresh, organic ingredients. 36 | - A person applying the cream, with a focus on the smooth, glowing skin. 37 | - An image of the skincare cream placed in a serene, spa-like setting, emphasizing relaxation and self-care. 38 | 39 | ${userPrompt} 40 | List of ${numberOfSuggestions} Image Content Ideas: 41 | -`; 42 | 43 | const imageIdeas = await generateContentWithModel('gpt-4o', 50, prompt, 0.8, 0.1, 0.2, ['\n\n'], apiKey); 44 | 45 | // Log choices[0] and choices[0].message.content for debugging 46 | console.log('imageIdeas.choices[0]:', imageIdeas.choices[0]); 47 | console.log('imageIdeas.choices[0].message.content:', imageIdeas.choices[0].message.content); 48 | 49 | return processListContents(userId, userEmail, 'image-idea-from-ad-text', userPrompt, imageIdeas); 50 | }; 51 | 52 | module.exports = { 53 | imageIdeasFromAdText, 54 | }; 55 | -------------------------------------------------------------------------------- /src/services/contents/cooking.contents.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-await-in-loop */ 2 | const { generateContentWithModel, storeData, formatResponse } = require('../content.service'); 3 | 4 | const generateRecipe = async (userId, userEmail, { recipeName, ingredients, numberOfSuggestions }) => { 5 | const userPrompt = ` 6 | Recipe Name: ${recipeName} 7 | Ingredients: ${ingredients}`; 8 | 9 | const prompt = `You are a professional chef and recipe creator. Your task is to write a detailed and delicious recipe based on the given ingredients and instructions. Use your expertise to create engaging, easy-to-follow, and mouth-watering recipes that inspire home cooks. 10 | 11 | Guidelines: 12 | 1. **Clarity:** Ensure the directions are clear and easy to follow. 13 | 2. **Detail:** Provide detailed steps, including preparation and cooking times. 14 | 3. **Flavor:** Emphasize the flavors and how they complement each other. 15 | 4. **Serving Suggestions:** Include suggestions for serving or pairing with other dishes. 16 | 5. **Variations:** Mention any variations or substitutions that can be made. 17 | 6. **Engagement:** Write in an engaging and enthusiastic tone to inspire the reader to try the recipe. 18 | 19 | Example: 20 | Recipe Name: Frito Pie 21 | Ingredients: Fritos, Chili, Shredded cheddar cheese, Sweet white or red onions (diced small), Sour cream 22 | Directions: Preheat oven to 350 degrees. Spread fritos on an oven-safe dish. Top with chili and cover with cheese. Bake for 10 minutes. Garnish with onion and sour cream. 23 | 24 | ${userPrompt} 25 | Directions:`; 26 | 27 | const openAPIInformationsList = []; 28 | const generateRecipeList = []; 29 | 30 | for (let i = 0; i < numberOfSuggestions; i++) { 31 | const recipe = await generateContentWithModel('gpt-4o', 50, prompt, 0, 0, 0, ['\n', 'Directions:']); 32 | const { id, object, created, model, choices } = recipe; 33 | 34 | // Log choices[0] and choices[0].message.content for debugging 35 | console.log('recipe.choices[0]:', choices[0]); 36 | console.log('recipe.choices[0].message.content:', choices[0].message.content); 37 | 38 | openAPIInformationsList.push({ id, object, created, model }); 39 | generateRecipeList.push(choices[0].message.content.trim()); 40 | } 41 | const { _id, generatedContents } = await storeData( 42 | userId, 43 | userEmail, 44 | 'generate-recipe', 45 | userPrompt, 46 | openAPIInformationsList, 47 | generateRecipeList 48 | ); 49 | 50 | const userResponse = formatResponse(_id, 'generate-recipe', generatedContents); 51 | 52 | return userResponse; 53 | }; 54 | 55 | module.exports = { 56 | generateRecipe, 57 | }; 58 | -------------------------------------------------------------------------------- /src/services/contents/cv.contents.js: -------------------------------------------------------------------------------- 1 | const { generateContentWithModel, storeData, formatResponse, removeSpaces } = require('../content.service'); 2 | 3 | const generateCVSummary = async (userId, userEmail, { yourJobTitle, yearsOfExperience, keyAchievements, numberOfSuggestions }) => { 4 | const userPrompt = `Job Title: ${removeSpaces(yourJobTitle)} 5 | Years Of Experience: ${yearsOfExperience} 6 | Key Achievements: ${removeSpaces(keyAchievements)}`; 7 | 8 | const prompt = `You are a professional CV writer with expertise in crafting compelling and concise resume summaries. Your task is to write a resume summary based on the given job title, years of experience, and key achievements. Use your expertise to create an engaging, professional, and impactful summary that highlights the individual's strengths and achievements. 9 | 10 | Guidelines: 11 | 1. **Relevance:** Ensure the summary is directly related to the job title and highlights relevant experience. 12 | 2. **Conciseness:** Keep the summary brief and to the point, ideally within 3-4 sentences. 13 | 3. **Achievements:** Clearly mention key achievements and their impact. 14 | 4. **Clarity:** Ensure the summary is easy to read and understand. 15 | 5. **Engagement:** Write in a way that captures the attention of hiring managers and recruiters. 16 | 6. **Professional Tone:** Maintain a professional tone throughout the summary. 17 | 18 | Example: 19 | Job Title: Data Analyst 20 | Years Of Experience: 6 21 | Key Achievements: reduced operating costs by over 20%, saved upwards of USD 500,000 a year 22 | Summary: Disciplined and insightful Data Analyst with 6 years of experience analyzing business processes. Eager to leverage big data interpreting and visualizing skills to drive growth and boost sales results. In current role, identified a major bottleneck, reduced operating costs by over 20%, and saved upwards of USD 500,000 a year. 23 | 24 | Job Title: Software Developer 25 | Years Of Experience: 8 26 | Key Achievements: led development of a new e-commerce platform that increased sales by 30%, implemented CI/CD pipelines reducing deployment time by 50% 27 | Summary: Highly skilled Software Developer with 8 years of experience in full-stack development. Proven track record in leading development projects from concept to completion. Successfully led the development of a new e-commerce platform that increased sales by 30%. Implemented CI/CD pipelines reducing deployment time by 50%, enhancing operational efficiency. 28 | 29 | ${userPrompt} 30 | Summary:`; 31 | 32 | const openAPIInformationsList = []; 33 | const generateCVSummaryList = []; 34 | 35 | for (let i = 0; i < numberOfSuggestions; i++) { 36 | const cvSummary = await generateContentWithModel('gpt-4o', 200, prompt, 0.7, 0, 0, ['\n', 'Summary:']); 37 | const { id, object, created, model, choices } = cvSummary; 38 | 39 | // Log choices[0] and choices[0].message.content for debugging 40 | console.log('cvSummary.choices[0]:', choices[0]); 41 | console.log('cvSummary.choices[0].message.content:', choices[0].message.content); 42 | 43 | openAPIInformationsList.push({ id, object, created, model }); 44 | generateCVSummaryList.push(choices[0].message.content.trim()); 45 | } 46 | 47 | const { _id, generatedContents } = await storeData( 48 | userId, 49 | userEmail, 50 | 'cv-summary', 51 | userPrompt, 52 | openAPIInformationsList, 53 | generateCVSummaryList 54 | ); 55 | 56 | const userResponse = formatResponse(_id, 'cv-summary', generatedContents); 57 | 58 | return userResponse; 59 | }; 60 | 61 | module.exports = { 62 | generateCVSummary, 63 | }; 64 | -------------------------------------------------------------------------------- /src/services/contents/index.js: -------------------------------------------------------------------------------- 1 | module.exports.product = require('./product.contents'); 2 | module.exports.writing = require('./writing.contents'); 3 | module.exports.headline = require('./headline.contents'); 4 | module.exports.google = require('./google.contents'); 5 | module.exports.facebook = require('./facebook.contents'); 6 | module.exports.linkedIn = require('./linkedIn.contents'); 7 | module.exports.instagram = require('./instagram.contents'); 8 | module.exports.youtube = require('./youtube.contents'); 9 | module.exports.commonTask = require('./common.contents'); 10 | module.exports.email = require('./email.contents'); 11 | module.exports.website = require('./website.contents'); 12 | module.exports.business = require('./business.contents'); 13 | module.exports.fiverr = require('./fiverr.contents'); 14 | module.exports.cv = require('./cv.contents'); 15 | module.exports.amazon = require('./amazon.contents'); 16 | module.exports.sales = require('./sales.contents'); 17 | module.exports.recipe = require('./cooking.contents'); 18 | module.exports.blog = require('./blog.contents'); 19 | module.exports.features = require('./features.contents'); 20 | module.exports.demo = require('./demo.contents'); 21 | module.exports.extension = require('./extension.contents'); 22 | -------------------------------------------------------------------------------- /src/services/contents/instagram.contents.js: -------------------------------------------------------------------------------- 1 | const { generateContentWithModel, removeSpaces, processListContents } = require('../content.service'); 2 | 3 | const instagramAdTexts = async (userId, userEmail, { platformType, context, numberOfSuggestions }) => { 4 | const userPrompt = `Platform: ${removeSpaces(platformType)} 5 | Context: ${removeSpaces(context)}`; 6 | 7 | const prompt = `You are an expert in creating compelling Instagram ad texts. Your task is to generate engaging and persuasive ad texts for the given platform type and context, similar to the provided samples. 8 | 9 | Guidelines: 10 | 1. **Relevance:** Ensure the ad texts are relevant to the platform type and context. 11 | 2. **Engagement:** Create ad texts that capture the audience’s interest. 12 | 3. **Clarity:** Ensure the ad texts are clear and concise. 13 | 4. **Call to Action:** Include a strong call to action to encourage user engagement. 14 | 5. **Creativity:** Use creative language to make the ad texts stand out. 15 | 16 | Examples: 17 | Platform: Fitness App 18 | Context: New Year Resolution 19 | Instagram Ad Text: 20 | - "Kickstart your New Year with our Fitness App! Get personalized workout plans, track your progress, and achieve your fitness goals. Download now and start your journey to a healthier you!" 21 | 22 | Platform: Online Learning Platform 23 | Context: Back to School 24 | Instagram Ad Text: 25 | - "Ready to ace your classes this semester? Join our Online Learning Platform and access thousands of courses from top universities. Learn at your own pace and get ahead. Sign up today!" 26 | 27 | Platform: Eco-Friendly Products 28 | Context: Earth Day 29 | Instagram Ad Text: 30 | - "Celebrate Earth Day with our range of eco-friendly products! From reusable bags to biodegradable straws, we've got everything you need to make a positive impact. Shop now and save the planet!" 31 | 32 | ${userPrompt} 33 | List of ${numberOfSuggestions} Instagram Ad Texts: 34 | -`; 35 | 36 | const adTexts = await generateContentWithModel('gpt-4o', 100, prompt, 0.8, 0.2, 0.1, ['\n\n']); 37 | 38 | // Log choices[0] and choices[0].message.content for debugging 39 | console.log('choices[0]:', adTexts.choices[0]); 40 | console.log('choices[0].message.content:', adTexts.choices[0].message.content); 41 | 42 | return processListContents(userId, userEmail, 'instagram-ad-texts', userPrompt, adTexts); 43 | }; 44 | 45 | module.exports = { 46 | instagramAdTexts, 47 | }; 48 | -------------------------------------------------------------------------------- /src/services/index.js: -------------------------------------------------------------------------------- 1 | module.exports.authService = require('./auth.service'); 2 | module.exports.emailService = require('./email.service'); 3 | module.exports.tokenService = require('./token.service'); 4 | module.exports.userService = require('./user.service'); 5 | module.exports.contentService = require('./content.service'); 6 | module.exports.interestService = require('./interest.service'); 7 | module.exports.toolService = require('./tool.service'); 8 | module.exports.blogService = require('./blog.service'); 9 | module.exports.subscriberService = require('./subscriber.service'); 10 | module.exports.plagiarismCheckerService = require('./plagiarismChecker.services'); 11 | module.exports.utilsService = require('./utils.service'); -------------------------------------------------------------------------------- /src/services/interest.service.js: -------------------------------------------------------------------------------- 1 | const httpStatus = require('http-status'); 2 | const { Interest } = require('../models'); 3 | const ApiError = require('../utils/ApiError'); 4 | 5 | const createUserInterest = async (userEmail) => { 6 | const userInterest = { 7 | interests: {}, 8 | userEmail, 9 | }; 10 | await Interest.create(userInterest); 11 | }; 12 | 13 | const getUserInterestsById = async (id) => { 14 | return Interest.findById(id); 15 | }; 16 | 17 | const getUserInterestsByEmail = async (userEmail) => { 18 | return Interest.findOne({ userEmail }); 19 | }; 20 | 21 | const checkDocumentExistsOrNot = async (userEmail) => { 22 | const userInterest = await getUserInterestsByEmail(userEmail); 23 | if (!userInterest) { 24 | throw new ApiError(httpStatus.NOT_FOUND, 'Document not found'); 25 | } 26 | return userInterest; 27 | }; 28 | 29 | const updateInterest = async (userInterest, interestType, { contentId, index }) => { 30 | const { interests } = userInterest; 31 | if ([contentId] in interests) { 32 | if (interests[contentId][`${interestType}s`].includes(index)) { 33 | throw new ApiError(httpStatus.CONFLICT, `Already ${interestType}d!`); 34 | } else { 35 | if (interestType === 'dislike' && interests[contentId]['likes'].includes(index)) { 36 | interests[contentId]['likes'] = interests[contentId]['likes'].filter((ind) => ind !== index); 37 | } else if (interestType === 'like' && interests[contentId]['dislikes'].includes(index)) { 38 | interests[contentId]['dislikes'] = interests[contentId]['dislikes'].filter((ind) => ind !== index); 39 | } 40 | interests[contentId][`${interestType}s`].push(index); 41 | } 42 | } else { 43 | interests[contentId] = { likes: [], dislikes: [] }; 44 | interests[contentId][`${interestType}s`] = [index]; 45 | } 46 | await userInterest.markModified('interests'); 47 | await userInterest.save(); 48 | return userInterest; 49 | }; 50 | 51 | module.exports = { 52 | createUserInterest, 53 | getUserInterestsById, 54 | checkDocumentExistsOrNot, 55 | updateInterest, 56 | }; 57 | -------------------------------------------------------------------------------- /src/services/tool.service.js: -------------------------------------------------------------------------------- 1 | const httpStatus = require('http-status'); 2 | const ApiError = require('../utils/ApiError'); 3 | const { Tool } = require('../models'); 4 | 5 | const notFound = async (cond) => { 6 | if (cond) { 7 | throw new ApiError(httpStatus.NOT_FOUND, 'Not found'); 8 | } 9 | }; 10 | 11 | const alreadyExist = async (cond) => { 12 | if (cond) { 13 | throw new ApiError(httpStatus.CONFLICT, 'Already Exist'); 14 | } 15 | }; 16 | 17 | const checkModelNotFound = async (Model, id) => { 18 | const tool = await Model.findById(id); 19 | await notFound(!tool); 20 | }; 21 | 22 | const checkModelExist = async (Model, key) => { 23 | const existTool = await Model.findOne({ key }); 24 | await alreadyExist(existTool); 25 | }; 26 | 27 | const postToolCategory = async (data) => { 28 | await checkModelExist(Tool.ToolCategory, data.key); 29 | const toolCategory = await Tool.ToolCategory.create(data); 30 | return toolCategory; 31 | }; 32 | 33 | const getToolCategories = async () => { 34 | const toolCategory = await Tool.ToolCategory.find({}); 35 | return toolCategory; 36 | }; 37 | 38 | const getToolCategory = async (id) => { 39 | const toolCategory = await Tool.ToolCategory.findById(id); 40 | await notFound(!toolCategory); 41 | return toolCategory; 42 | }; 43 | 44 | const deleteToolCategory = async (id) => { 45 | await checkModelNotFound(Tool.ToolCategory, id); 46 | const toolCategory = await Tool.ToolCategory.findByIdAndDelete(id); 47 | await Tool.Tool.deleteMany({ category: id }); 48 | return toolCategory; 49 | }; 50 | 51 | const patchToolCategory = async (id, data) => { 52 | await checkModelNotFound(Tool.ToolCategory, id); 53 | const toolCategory = await Tool.ToolCategory.findByIdAndUpdate(id, data, { new: true }); 54 | return toolCategory; 55 | }; 56 | 57 | const postTool = async (data) => { 58 | const category = await Tool.ToolCategory.findById(data.category); 59 | if (!category) { 60 | throw new ApiError(httpStatus.BAD_REQUEST, 'Please provide valid category id'); 61 | } 62 | await checkModelExist(Tool.Tool, data.key); 63 | const createdTool = await Tool.Tool.create(data); 64 | const tool = await Tool.Tool.findById(createdTool.id).populate('category', 'name key'); 65 | return tool; 66 | }; 67 | 68 | const getTools = async () => { 69 | const tool = await Tool.Tool.find({}).populate('category', 'name key'); 70 | return tool; 71 | }; 72 | 73 | const getTool = async (id) => { 74 | const tool = await Tool.Tool.findById(id).populate('category', 'name key'); 75 | await notFound(!tool); 76 | return tool; 77 | }; 78 | 79 | const patchTool = async (id, data) => { 80 | await checkModelNotFound(Tool.Tool, id); 81 | const tool = await Tool.Tool.findByIdAndUpdate(id, data, { new: true }).populate('category', 'name key'); 82 | return tool; 83 | }; 84 | 85 | const deleteTool = async (id) => { 86 | await checkModelNotFound(Tool.Tool, id); 87 | const tool = await Tool.Tool.findByIdAndDelete(id); 88 | return tool; 89 | }; 90 | 91 | module.exports = { 92 | postToolCategory, 93 | getToolCategories, 94 | getToolCategory, 95 | deleteToolCategory, 96 | patchToolCategory, 97 | postTool, 98 | getTools, 99 | getTool, 100 | patchTool, 101 | deleteTool, 102 | }; 103 | -------------------------------------------------------------------------------- /src/services/utils.service.js: -------------------------------------------------------------------------------- 1 | // const countWords = (text, flag) { 2 | // var $this = this; 3 | // var words = this.wordsOfText(text); 4 | // var words_count; 5 | // this.uniqueWords = []; 6 | // if (this.optionsObject.details['unique_word_count']) { 7 | // if (Array.isArray(words)) { 8 | // $this.uniqueWords = $this.uniqueArray(words); 9 | // } 10 | // } 11 | // words_count = words !== null ? words.length : 0; 12 | // if (flag) { 13 | // this.count_for_selection['word_count'] = words_count; 14 | // } else { 15 | // this.count['word_count'] = words_count; 16 | // } 17 | // } 18 | 19 | const wordsOfText = (text) => { 20 | let words; 21 | const textWithoutSpace = text.replace(/\s+/g, ''); 22 | const matches = textWithoutSpace.match(/([^\x00-\x7F\u2013\u2014])+/gi); 23 | const latinOnly = matches === null; 24 | if (!latinOnly) { 25 | words = text.match(/\S+/g); 26 | } else { 27 | words = text 28 | .replace(/[;!:\-—\/]/g, ' ') 29 | .replace(/\.\s+/g, ' ') 30 | .replace(/[^a-zA-Z\d\s&:,]/g, '') 31 | .replace(/,([^0-9])/g, ' $1') 32 | .match(/\S+/g); 33 | } 34 | return words; 35 | }; 36 | 37 | const numberOfWords = (text) => { 38 | return wordsOfText(text).length; 39 | } 40 | 41 | module.exports = { 42 | numberOfWords, 43 | }; -------------------------------------------------------------------------------- /src/utils/ApiError.js: -------------------------------------------------------------------------------- 1 | class ApiError extends Error { 2 | constructor(statusCode, message, isOperational = true, stack = '') { 3 | super(message); 4 | this.statusCode = statusCode; 5 | this.isOperational = isOperational; 6 | if (stack) { 7 | this.stack = stack; 8 | } else { 9 | Error.captureStackTrace(this, this.constructor); 10 | } 11 | } 12 | } 13 | 14 | module.exports = ApiError; 15 | -------------------------------------------------------------------------------- /src/utils/catchAsync.js: -------------------------------------------------------------------------------- 1 | const catchAsync = (fn) => (req, res, next) => { 2 | Promise.resolve(fn(req, res, next)).catch((err) => next(err)); 3 | }; 4 | 5 | module.exports = catchAsync; 6 | -------------------------------------------------------------------------------- /src/utils/pick.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Create an object composed of the picked object properties 3 | * @param {Object} object 4 | * @param {string[]} keys 5 | * @returns {Object} 6 | */ 7 | const pick = (object, keys) => { 8 | return keys.reduce((obj, key) => { 9 | if (object && Object.prototype.hasOwnProperty.call(object, key)) { 10 | // eslint-disable-next-line no-param-reassign 11 | obj[key] = object[key]; 12 | } 13 | return obj; 14 | }, {}); 15 | }; 16 | 17 | module.exports = pick; 18 | -------------------------------------------------------------------------------- /src/validations/auth.validation.js: -------------------------------------------------------------------------------- 1 | const Joi = require('joi'); 2 | const { password } = require('./custom.validation'); 3 | const { authTypes } = require('../config/auths'); 4 | 5 | const register = { 6 | body: Joi.object().keys({ 7 | firstName: Joi.string().max(20).required(), 8 | lastName: Joi.string().max(20).required(), 9 | password: Joi.string().required().custom(password), 10 | email: Joi.string().email().required(), 11 | authType: Joi.string().default(authTypes.EMAIL).valid(authTypes.EMAIL), 12 | }), 13 | }; 14 | 15 | const verifyAccount = { 16 | query: Joi.object().keys({ 17 | token: Joi.string().required(), 18 | }), 19 | }; 20 | 21 | const login = { 22 | body: Joi.object().keys({ 23 | email: Joi.string().email().required(), 24 | password: Joi.string().required(), 25 | }), 26 | }; 27 | 28 | const logout = { 29 | body: Joi.object().keys({ 30 | refreshToken: Joi.string().required(), 31 | }), 32 | }; 33 | 34 | const refreshTokens = { 35 | body: Joi.object().keys({ 36 | refreshToken: Joi.string().required(), 37 | }), 38 | }; 39 | 40 | const forgotPassword = { 41 | body: Joi.object().keys({ 42 | email: Joi.string().email().required(), 43 | }), 44 | }; 45 | 46 | const resetPassword = { 47 | query: Joi.object().keys({ 48 | token: Joi.string().required(), 49 | }), 50 | body: Joi.object().keys({ 51 | password: Joi.string().required().custom(password), 52 | }), 53 | }; 54 | 55 | const strategyLogin = { 56 | query: Joi.object().keys({ 57 | token: Joi.string().required(), 58 | }), 59 | }; 60 | 61 | module.exports = { 62 | register, 63 | verifyAccount, 64 | login, 65 | logout, 66 | refreshTokens, 67 | forgotPassword, 68 | resetPassword, 69 | strategyLogin, 70 | }; 71 | -------------------------------------------------------------------------------- /src/validations/blog.validation.js: -------------------------------------------------------------------------------- 1 | const Joi = require('joi'); 2 | const { objectId } = require('./custom.validation'); 3 | 4 | const createBlog = { 5 | body: Joi.object().keys({ 6 | blogAbout: Joi.string(), 7 | headline: Joi.string(), 8 | blogPost: Joi.string(), 9 | blogType: Joi.string().valid('WRITE_ALONG', 'GHOSTWRITER').default('WRITE_ALONG'), 10 | }), 11 | }; 12 | 13 | const getBlogs = { 14 | query: Joi.object().keys({ 15 | sortBy: Joi.string(), 16 | limit: Joi.number().integer(), 17 | page: Joi.number().integer(), 18 | }), 19 | }; 20 | 21 | const getBlog = { 22 | params: Joi.object().keys({ 23 | blogId: Joi.required().custom(objectId), 24 | }), 25 | }; 26 | 27 | const updateBlog = { 28 | params: Joi.object().keys({ 29 | blogId: Joi.required().custom(objectId), 30 | }), 31 | body: Joi.object().keys({ 32 | blogAbout: Joi.string(), 33 | headline: Joi.string(), 34 | blogPost: Joi.string(), 35 | blogType: Joi.string().valid('WRITE_ALONG', 'GHOSTWRITER'), 36 | }), 37 | }; 38 | 39 | const deleteBlog = { 40 | params: Joi.object().keys({ 41 | blogId: Joi.required().custom(objectId), 42 | }), 43 | }; 44 | 45 | module.exports = { 46 | createBlog, 47 | getBlogs, 48 | getBlog, 49 | updateBlog, 50 | deleteBlog, 51 | }; 52 | -------------------------------------------------------------------------------- /src/validations/contents/amazon.validation.js: -------------------------------------------------------------------------------- 1 | const Joi = require('joi'); 2 | const inputLimit = require('../../config/inputLimit'); 3 | const { amazonValidation } = require('./validationData'); 4 | const getLimits = require('./helper.validation'); 5 | 6 | const amazonProductListings = (subscription) => { 7 | const { task, productName, productCategories, productFeatures, numberOfSuggestions } = getLimits( 8 | amazonValidation.amazonProductListings, 9 | inputLimit[subscription] 10 | ); 11 | return { 12 | body: Joi.object().keys({ 13 | task: Joi.valid(task).required(), 14 | // productName: Joi.string().min(productName.min).max(productName.max).required(), 15 | // productCategories: Joi.string().min(productCategories.min).max(productCategories.max).required(), 16 | // productFeatures: Joi.string().min(productFeatures.min).max(productFeatures.max).required(), 17 | productName: Joi.string().max(productName.max).required(), 18 | productCategories: Joi.string().max(productCategories.max).required(), 19 | productFeatures: Joi.string().max(productFeatures.max).required(), 20 | numberOfSuggestions: Joi.number().min(numberOfSuggestions.min).max(numberOfSuggestions.max).required(), 21 | }), 22 | }; 23 | }; 24 | 25 | module.exports = { 26 | amazonProductListings, 27 | }; 28 | -------------------------------------------------------------------------------- /src/validations/contents/business.validation.js: -------------------------------------------------------------------------------- 1 | const Joi = require('joi'); 2 | const inputLimit = require('../../config/inputLimit'); 3 | const { businessValidation } = require('./validationData'); 4 | const getLimits = require('./helper.validation'); 5 | 6 | const catchyBusinessTaglines = (subscription) => { 7 | const { task, companyName, businessType, numberOfSuggestions } = getLimits( 8 | businessValidation.catchyBusinessTaglines, 9 | inputLimit[subscription] 10 | ); 11 | return { 12 | body: Joi.object().keys({ 13 | task: Joi.valid(task).required(), 14 | // companyName: Joi.string().min(companyName.min).max(companyName.max).required(), 15 | // businessType: Joi.string().min(businessType.min).max(businessType.max).required(), 16 | companyName: Joi.string().max(companyName.max).required(), 17 | businessType: Joi.string().max(businessType.max).required(), 18 | numberOfSuggestions: Joi.number().min(numberOfSuggestions.min).max(numberOfSuggestions.max).required(), 19 | }), 20 | } 21 | }; 22 | 23 | module.exports = { 24 | catchyBusinessTaglines, 25 | }; 26 | -------------------------------------------------------------------------------- /src/validations/contents/common.validation.js: -------------------------------------------------------------------------------- 1 | const Joi = require('joi'); 2 | const inputLimit = require('../../config/inputLimit'); 3 | const { commonValidation } = require('./validationData'); 4 | const getLimits = require('./helper.validation'); 5 | 6 | const imageIdeasFromAdText = (subscription) => { 7 | const { task, product, adText, numberOfSuggestions } = getLimits( 8 | commonValidation.imageIdeasFromAdText, 9 | inputLimit[subscription] 10 | ); 11 | return { 12 | body: Joi.object().keys({ 13 | task: Joi.valid(task).required(), 14 | // product: Joi.string().min(product.min).max(product.max).required(), 15 | // adText: Joi.string().min(adText.min).max(adText.max).required(), 16 | product: Joi.string().max(product.max).required(), 17 | adText: Joi.string().max(adText.max).required(), 18 | numberOfSuggestions: Joi.number().min(numberOfSuggestions.min).max(numberOfSuggestions.max).required(), 19 | }), 20 | }; 21 | }; 22 | 23 | module.exports = { 24 | imageIdeasFromAdText, 25 | }; 26 | -------------------------------------------------------------------------------- /src/validations/contents/cooking.validation.js: -------------------------------------------------------------------------------- 1 | const Joi = require('joi'); 2 | const inputLimit = require('../../config/inputLimit'); 3 | const { cookingValidation } = require('./validationData'); 4 | const getLimits = require('./helper.validation'); 5 | 6 | const recipe = (subscription) => { 7 | const { task, recipeName, ingredients } = getLimits(cookingValidation.recipe, inputLimit[subscription]); 8 | return { 9 | body: Joi.object().keys({ 10 | task: Joi.valid(task).required(), 11 | // recipeName: Joi.string().min(recipeName.min).max(recipeName.max).required(), 12 | // ingredients: Joi.string().min(ingredients.min).max(ingredients.max).required(), 13 | recipeName: Joi.string().max(recipeName.max).required(), 14 | ingredients: Joi.string().max(ingredients.max).required(), 15 | }), 16 | }; 17 | }; 18 | 19 | module.exports = { 20 | recipe, 21 | }; 22 | -------------------------------------------------------------------------------- /src/validations/contents/cv.validation.js: -------------------------------------------------------------------------------- 1 | const Joi = require('joi'); 2 | const inputLimit = require('../../config/inputLimit'); 3 | const { cvValidation } = require('./validationData'); 4 | const getLimits = require('./helper.validation'); 5 | 6 | const CVSummary = (subscription) => { 7 | const { task, yourJobTitle, keyAchievements, yearsOfExperience, numberOfSuggestions } = getLimits( 8 | cvValidation.CVSummary, 9 | inputLimit[subscription] 10 | ); 11 | return { 12 | body: Joi.object().keys({ 13 | task: Joi.valid(task).required(), 14 | // yourJobTitle: Joi.string().min(yourJobTitle.min).max(yourJobTitle.max).required(), 15 | // keyAchievements: Joi.string().min(keyAchievements.min).max(keyAchievements.max).required(), 16 | yourJobTitle: Joi.string().max(yourJobTitle.max).required(), 17 | keyAchievements: Joi.string().max(keyAchievements.max).required(), 18 | yearsOfExperience: Joi.number().min(yearsOfExperience.min).max(yearsOfExperience.max).required(), 19 | numberOfSuggestions: Joi.number().min(numberOfSuggestions.min).max(numberOfSuggestions.max).required(), 20 | }), 21 | }; 22 | }; 23 | 24 | module.exports = { 25 | CVSummary, 26 | }; 27 | -------------------------------------------------------------------------------- /src/validations/contents/demo.validation.js: -------------------------------------------------------------------------------- 1 | const Joi = require('joi'); 2 | const { demoValidation } = require('./validationData'); 3 | 4 | const paraphrase = () => { 5 | const { task, userText } = demoValidation.paraphrase; 6 | return { 7 | body: Joi.object().keys({ 8 | task: Joi.valid(task).required(), 9 | userText: Joi.string().min(userText.min).max(userText.max).required(), 10 | }), 11 | }; 12 | }; 13 | 14 | const blogHeadline = () => { 15 | const { task, blogAbout } = demoValidation.blogHeadline; 16 | return { 17 | body: Joi.object().keys({ 18 | task: Joi.valid(task).required(), 19 | blogAbout: Joi.string().min(blogAbout.min).max(blogAbout.max).required(), 20 | }), 21 | } 22 | }; 23 | 24 | module.exports = { 25 | paraphrase, 26 | blogHeadline, 27 | }; 28 | -------------------------------------------------------------------------------- /src/validations/contents/email.validation.js: -------------------------------------------------------------------------------- 1 | const Joi = require('joi'); 2 | const inputLimit = require('../../config/inputLimit'); 3 | const { emailValidation } = require('./validationData'); 4 | const getLimits = require('./helper.validation'); 5 | 6 | const emailMarketingCampaignSubject = (subscription) => { 7 | const { task, productDescription, numberOfSuggestions } = getLimits( 8 | emailValidation.emailMarketingCampaignSubject, 9 | inputLimit[subscription] 10 | ); 11 | return { 12 | body: Joi.object().keys({ 13 | task: Joi.valid(task).required(), 14 | // productDescription: Joi.string().min(productDescription.min).max(productDescription.max).required(), 15 | productDescription: Joi.string().max(productDescription.max).required(), 16 | numberOfSuggestions: Joi.number().min(numberOfSuggestions.min).max(numberOfSuggestions.max).required(), 17 | }), 18 | }; 19 | }; 20 | 21 | const emailMarketingCampaignBody = (subscription) => { 22 | const { task, productDescription, about, subjectLine, numberOfSuggestions } = getLimits( 23 | emailValidation.emailMarketingCampaignBody, 24 | inputLimit[subscription] 25 | ); 26 | return { 27 | body: Joi.object().keys({ 28 | task: Joi.valid(task).required(), 29 | // productDescription: Joi.string().min(productDescription.min).max(productDescription.max).required(), 30 | // about: Joi.string().min(about.min).max(about.max).required(), 31 | // subjectLine: Joi.string().min(subjectLine.min).max(subjectLine.max).required(), 32 | productDescription: Joi.string().max(productDescription.max).required(), 33 | about: Joi.string().max(about.max).required(), 34 | subjectLine: Joi.string().max(subjectLine.max).required(), 35 | numberOfSuggestions: Joi.number().min(numberOfSuggestions.min).max(numberOfSuggestions.max).required(), 36 | }), 37 | }; 38 | }; 39 | 40 | const generateEmailBody = (subscription) => { 41 | const { task, about, to, tone, numberOfSuggestions } = getLimits(emailValidation.emailBody, inputLimit[subscription]); 42 | return { 43 | body: Joi.object().keys({ 44 | task: Joi.valid(task).required(), 45 | // about: Joi.string().min(about.min).max(about.max).required(), 46 | // to: Joi.string().min(to.min).max(to.max).required(), 47 | about: Joi.string().max(about.max).required(), 48 | to: Joi.string().max(to.max).required(), 49 | tone: Joi.string() 50 | .required() 51 | .valid(...tone), 52 | numberOfSuggestions: Joi.number().min(numberOfSuggestions.min).max(numberOfSuggestions.max).required(), 53 | }), 54 | }; 55 | }; 56 | 57 | const generatedSubjectFromBody = (subscription) => { 58 | const { task, emailBody, numberOfSuggestions } = getLimits( 59 | emailValidation.generatedSubjectFromBody, 60 | inputLimit[subscription] 61 | ); 62 | return { 63 | body: Joi.object().keys({ 64 | task: Joi.valid(task).required(), 65 | // emailBody: Joi.string().min(emailBody.min).max(emailBody.max).required(), 66 | emailBody: Joi.string().max(emailBody.max).required(), 67 | numberOfSuggestions: Joi.number().min(numberOfSuggestions.min).max(numberOfSuggestions.max).required(), 68 | }), 69 | }; 70 | }; 71 | 72 | module.exports = { 73 | emailMarketingCampaignSubject, 74 | emailMarketingCampaignBody, 75 | generateEmailBody, 76 | generatedSubjectFromBody, 77 | }; 78 | -------------------------------------------------------------------------------- /src/validations/contents/extension.validation.js: -------------------------------------------------------------------------------- 1 | const Joi = require('joi'); 2 | const inputLimit = require('../../config/inputLimit'); 3 | const { extensionValidation } = require('./validationData'); 4 | const getLimits = require('./helper.validation'); 5 | 6 | const paraphrase = (subscription) => { 7 | const { task, userText } = getLimits(extensionValidation.paraphrase, inputLimit[subscription]); 8 | return { 9 | body: Joi.object().keys({ 10 | task: Joi.valid(task).required(), 11 | // userText: Joi.string().min(userText.min).max(userText.max).required(), 12 | userText: Joi.string().max(userText.max).required(), 13 | }), 14 | }; 15 | }; 16 | 17 | const grammarFixer = (subscription) => { 18 | const { task, userText } = getLimits(extensionValidation.grammarFixer, inputLimit[subscription]); 19 | return { 20 | body: Joi.object().keys({ 21 | task: Joi.valid(task).required(), 22 | // userText: Joi.string().min(userText.min).max(userText.max).required(), 23 | userText: Joi.string().max(userText.max).required(), 24 | }), 25 | }; 26 | }; 27 | 28 | const simplifier = (subscription) => { 29 | const { task, userText } = getLimits(extensionValidation.simplifier, inputLimit[subscription]); 30 | return { 31 | body: Joi.object().keys({ 32 | task: Joi.valid(task).required(), 33 | // userText: Joi.string().min(userText.min).max(userText.max).required(), 34 | userText: Joi.string().max(userText.max).required(), 35 | }), 36 | }; 37 | }; 38 | 39 | const summarizer = (subscription) => { 40 | const { task, userText } = getLimits(extensionValidation.summarizer, inputLimit[subscription]); 41 | return { 42 | body: Joi.object().keys({ 43 | task: Joi.valid(task).required(), 44 | // userText: Joi.string().min(userText.min).max(userText.max).required(), 45 | userText: Joi.string().max(userText.max).required(), 46 | }), 47 | }; 48 | }; 49 | 50 | const changeTone = (subscription) => { 51 | const { task, userText, tone } = getLimits(extensionValidation.changeTone, inputLimit[subscription]); 52 | return { 53 | body: Joi.object().keys({ 54 | task: Joi.valid(task).required(), 55 | // userText: Joi.string().min(userText.min).max(userText.max).required(), 56 | userText: Joi.string().max(userText.max).required(), 57 | tone: Joi.string() 58 | .required() 59 | .valid(...tone), 60 | }), 61 | }; 62 | }; 63 | 64 | module.exports = { 65 | paraphrase, 66 | grammarFixer, 67 | simplifier, 68 | summarizer, 69 | changeTone, 70 | }; 71 | -------------------------------------------------------------------------------- /src/validations/contents/features.validation.js: -------------------------------------------------------------------------------- 1 | const Joi = require('joi'); 2 | const inputLimit = require('../../config/inputLimit'); 3 | const { featuresValidation } = require('./validationData'); 4 | const getLimits = require('./helper.validation'); 5 | 6 | const proofread = (subscription) => { 7 | const { task, userText } = getLimits(featuresValidation.proofread, inputLimit[subscription]); 8 | return { 9 | body: Joi.object().keys({ 10 | task: Joi.valid(task).required(), 11 | // userText: Joi.string().min(userText.min).max(userText.max).required(), 12 | userText: Joi.string().max(userText.max).required(), 13 | }), 14 | }; 15 | }; 16 | 17 | module.exports = { 18 | proofread, 19 | }; 20 | -------------------------------------------------------------------------------- /src/validations/contents/fiverr.validation.js: -------------------------------------------------------------------------------- 1 | const Joi = require('joi'); 2 | const inputLimit = require('../../config/inputLimit'); 3 | const { fiverrValidation } = require('./validationData'); 4 | const getLimits = require('./helper.validation'); 5 | 6 | const fiverrProfileDescription = (subscription) => { 7 | const { task, profession, experience, numberOfSuggestions } = getLimits( 8 | fiverrValidation.fiverrProfileDescription, 9 | inputLimit[subscription] 10 | ); 11 | return { 12 | body: Joi.object().keys({ 13 | task: Joi.valid(task).required(), 14 | // profession: Joi.string().min(profession.min).max(profession.max).required(), 15 | // experience: Joi.string().min(experience.min).max(experience.max).required(), 16 | profession: Joi.string().max(profession.max).required(), 17 | experience: Joi.string().max(experience.max).required(), 18 | numberOfSuggestions: Joi.number().min(numberOfSuggestions.min).max(numberOfSuggestions.max).required(), 19 | }), 20 | }; 21 | }; 22 | 23 | const fiverrCategoriesHeadline = (subscription) => { 24 | const { task, categoriesName } = getLimits(fiverrValidation.fiverrCategoriesHeadline, inputLimit[subscription]); 25 | return { 26 | body: Joi.object().keys({ 27 | task: Joi.valid(task).required(), 28 | // categoriesName: Joi.string().min(categoriesName.min).max(categoriesName.max).required(), 29 | categoriesName: Joi.string().max(categoriesName.max).required(), 30 | }), 31 | }; 32 | }; 33 | 34 | module.exports = { 35 | fiverrProfileDescription, 36 | fiverrCategoriesHeadline, 37 | }; 38 | -------------------------------------------------------------------------------- /src/validations/contents/google.validation.js: -------------------------------------------------------------------------------- 1 | const Joi = require('joi'); 2 | const inputLimit = require('../../config/inputLimit'); 3 | const { googleValidation } = require('./validationData'); 4 | const getLimits = require('./helper.validation'); 5 | 6 | const googleAdHeadlines = (subscription) => { 7 | const { task, name, businessType, numberOfSuggestions } = getLimits( 8 | googleValidation.googleAdHeadlines, 9 | inputLimit[subscription] 10 | ); 11 | return { 12 | body: Joi.object().keys({ 13 | task: Joi.valid(task).required(), 14 | // name: Joi.string().min(name.min).max(name.max).required(), 15 | // businessType: Joi.string().min(businessType.min).max(businessType.max).required(), 16 | name: Joi.string().max(name.max).required(), 17 | businessType: Joi.string().max(businessType.max).required(), 18 | numberOfSuggestions: Joi.number().min(numberOfSuggestions.min).max(numberOfSuggestions.max).required(), 19 | }), 20 | }; 21 | }; 22 | 23 | const googleAdDescriptions = (subscription) => { 24 | const { task, businessName, productCategories, uniqueness, promotions, keywords, numberOfSuggestions } = getLimits( 25 | googleValidation.googleAdDescriptions, 26 | inputLimit[subscription] 27 | ); 28 | return { 29 | body: Joi.object().keys({ 30 | task: Joi.valid(task).required(), 31 | // businessName: Joi.string().min(businessName.min).max(businessName.max).required(), 32 | // productCategories: Joi.string().min(productCategories.min).max(productCategories.max).required(), 33 | // uniqueness: Joi.string().min(uniqueness.min).max(uniqueness.max).required(), 34 | // promotions: Joi.string().min(promotions.min).max(promotions.max).required(), 35 | // keywords: Joi.string().min(keywords.min).max(keywords.max).required(), 36 | businessName: Joi.string().max(businessName.max).required(), 37 | productCategories: Joi.string().max(productCategories.max).required(), 38 | uniqueness: Joi.string().max(uniqueness.max).required(), 39 | promotions: Joi.string().max(promotions.max).required(), 40 | keywords: Joi.string().max(keywords.max).required(), 41 | numberOfSuggestions: Joi.number().min(numberOfSuggestions.min).max(numberOfSuggestions.max).required(), 42 | }), 43 | }; 44 | }; 45 | 46 | module.exports = { 47 | googleAdHeadlines, 48 | googleAdDescriptions, 49 | }; 50 | -------------------------------------------------------------------------------- /src/validations/contents/headline.validation.js: -------------------------------------------------------------------------------- 1 | const Joi = require('joi'); 2 | const inputLimit = require('../../config/inputLimit'); 3 | const { headlineValidation } = require('./validationData'); 4 | const getLimits = require('./helper.validation'); 5 | 6 | const catchyHeadline = (subscription) => { 7 | const { task, content, numberOfSuggestions } = getLimits(headlineValidation.catchyHeadline, inputLimit[subscription]); 8 | return { 9 | body: Joi.object().keys({ 10 | task: Joi.valid(task).required(), 11 | // content: Joi.string().min(content.min).max(content.max).required(), 12 | content: Joi.string().max(content.max).required(), 13 | numberOfSuggestions: Joi.number().min(numberOfSuggestions.min).max(numberOfSuggestions.max).required(), 14 | }), 15 | }; 16 | }; 17 | 18 | const attentionGrabbingHeadline = (subscription) => { 19 | const { task, content, numberOfSuggestions } = getLimits( 20 | headlineValidation.attentionGrabbingHeadline, 21 | inputLimit[subscription] 22 | ); 23 | return { 24 | body: Joi.object().keys({ 25 | task: Joi.valid(task).required(), 26 | // content: Joi.string().min(content.min).max(content.max).required(), 27 | content: Joi.string().max(content.max).required(), 28 | numberOfSuggestions: Joi.number().min(numberOfSuggestions.min).max(numberOfSuggestions.max).required(), 29 | }), 30 | }; 31 | }; 32 | 33 | const newspaperHeadline = (subscription) => { 34 | const { task, content, numberOfSuggestions } = getLimits(headlineValidation.newspaperHeadline, inputLimit[subscription]); 35 | return { 36 | body: Joi.object().keys({ 37 | task: Joi.valid(task).required(), 38 | // content: Joi.string().min(content.min).max(content.max).required(), 39 | content: Joi.string().max(content.max).required(), 40 | numberOfSuggestions: Joi.number().min(numberOfSuggestions.min).max(numberOfSuggestions.max).required(), 41 | }), 42 | }; 43 | }; 44 | 45 | const resumeHeadline = (subscription) => { 46 | const { task, profession, numberOfSuggestions } = getLimits(headlineValidation.resumeHeadline, inputLimit[subscription]); 47 | return { 48 | body: Joi.object().keys({ 49 | task: Joi.valid(task).required(), 50 | // profession: Joi.string().min(profession.min).max(profession.max).required(), 51 | profession: Joi.string().max(profession.max).required(), 52 | numberOfSuggestions: Joi.number().min(numberOfSuggestions.min).max(numberOfSuggestions.max).required(), 53 | }), 54 | }; 55 | }; 56 | 57 | module.exports = { 58 | catchyHeadline, 59 | attentionGrabbingHeadline, 60 | newspaperHeadline, 61 | resumeHeadline, 62 | }; 63 | -------------------------------------------------------------------------------- /src/validations/contents/helper.validation.js: -------------------------------------------------------------------------------- 1 | const getLimits = (validationData, inputLimit) => { 2 | console.log('Original validation data:', validationData); 3 | console.log('Input limit:', inputLimit); 4 | 5 | const data = JSON.parse(JSON.stringify(validationData)); // deep copy 6 | Object.keys(data).forEach((key) => { 7 | if (data[key].variable && data[key].max) { 8 | data[key].max *= inputLimit; // Ensure correct multiplication 9 | } 10 | }); 11 | 12 | console.log('Processed data:', data); // Log the processed data 13 | 14 | return data; 15 | }; 16 | 17 | module.exports = getLimits; 18 | -------------------------------------------------------------------------------- /src/validations/contents/index.js: -------------------------------------------------------------------------------- 1 | module.exports.product = require('./product.validation'); 2 | module.exports.writing = require('./writing.validation'); 3 | module.exports.headline = require('./headline.validation'); 4 | module.exports.google = require('./google.validation'); 5 | module.exports.facebook = require('./facebook.validation'); 6 | module.exports.linkedIn = require('./linkedIn.validation'); 7 | module.exports.instagram = require('./instagram.validation'); 8 | module.exports.youtube = require('./youtube.validation'); 9 | module.exports.common = require('./common.validation'); 10 | module.exports.email = require('./email.validation'); 11 | module.exports.website = require('./website.validation'); 12 | module.exports.business = require('./business.validation'); 13 | module.exports.fiverr = require('./fiverr.validation'); 14 | module.exports.cv = require('./cv.validation'); 15 | module.exports.amazon = require('./amazon.validation'); 16 | module.exports.sales = require('./sales.validation'); 17 | module.exports.recipe = require('./cooking.validation'); 18 | module.exports.blog = require('./blog.validation'); 19 | module.exports.features = require('./features.validation'); 20 | module.exports.demo = require('./demo.validation'); 21 | module.exports.extension = require('./extension.validation'); 22 | -------------------------------------------------------------------------------- /src/validations/contents/instagram.validation.js: -------------------------------------------------------------------------------- 1 | const Joi = require('joi'); 2 | const inputLimit = require('../../config/inputLimit'); 3 | const { instagramValidation } = require('./validationData'); 4 | const getLimits = require('./helper.validation'); 5 | 6 | const instagramAdTexts = (subscription) => { 7 | const { task, platformType, context, numberOfSuggestions } = getLimits( 8 | instagramValidation.instagramAdTexts, 9 | inputLimit[subscription] 10 | ); 11 | return { 12 | body: Joi.object().keys({ 13 | task: Joi.valid(task).required(), 14 | // platformType: Joi.string().min(platformType.min).max(platformType.max).required(), 15 | // context: Joi.string().min(context.min).max(context.max).required(), 16 | platformType: Joi.string().max(platformType.max).required(), 17 | context: Joi.string().max(context.max).required(), 18 | numberOfSuggestions: Joi.number().min(numberOfSuggestions.min).max(numberOfSuggestions.max).required(), 19 | }), 20 | }; 21 | }; 22 | 23 | module.exports = { 24 | instagramAdTexts, 25 | }; 26 | -------------------------------------------------------------------------------- /src/validations/contents/linkedIn.validation.js: -------------------------------------------------------------------------------- 1 | const Joi = require('joi'); 2 | const inputLimit = require('../../config/inputLimit'); 3 | const { linkedinValidation } = require('./validationData'); 4 | const getLimits = require('./helper.validation'); 5 | 6 | const linkedinAdTexts = (subscription) => { 7 | const { task, companyName, businessType, benefits, numberOfSuggestions } = getLimits( 8 | linkedinValidation.linkedinAdTexts, 9 | inputLimit[subscription] 10 | ); 11 | return { 12 | body: Joi.object().keys({ 13 | task: Joi.valid(task).required(), 14 | // companyName: Joi.string().min(companyName.min).max(companyName.max).required(), 15 | // businessType: Joi.string().min(businessType.min).max(businessType.max).required(), 16 | // benefits: Joi.string().min(benefits.min).max(benefits.max).required(), 17 | companyName: Joi.string().max(companyName.max).required(), 18 | businessType: Joi.string().max(businessType.max).required(), 19 | benefits: Joi.string().max(benefits.max).required(), 20 | numberOfSuggestions: Joi.number().min(numberOfSuggestions.min).max(numberOfSuggestions.max).required(), 21 | }), 22 | } 23 | }; 24 | 25 | const linkedInSummary = (subscription) => { 26 | const { task, profession, skills, numberOfSuggestions } = getLimits( 27 | linkedinValidation.linkedInSummary, 28 | inputLimit[subscription] 29 | ); 30 | return { 31 | body: Joi.object().keys({ 32 | task: Joi.valid(task).required(), 33 | // profession: Joi.string().min(profession.min).max(profession.max).required(), 34 | // skills: Joi.string().min(skills.min).max(skills.max).required(), 35 | profession: Joi.string().max(profession.max).required(), 36 | skills: Joi.string().max(skills.max).required(), 37 | numberOfSuggestions: Joi.number().min(numberOfSuggestions.min).max(numberOfSuggestions.max).required(), 38 | }), 39 | } 40 | }; 41 | 42 | module.exports = { 43 | linkedinAdTexts, 44 | linkedInSummary, 45 | }; 46 | -------------------------------------------------------------------------------- /src/validations/contents/product.validation.js: -------------------------------------------------------------------------------- 1 | const Joi = require('joi'); 2 | const inputLimit = require('../../config/inputLimit'); 3 | const { productValidation } = require('./validationData'); 4 | const getLimits = require('./helper.validation'); 5 | 6 | const productDescription = (subscription) => { 7 | const { task, productName, productType, numberOfSuggestions } = getLimits( 8 | productValidation.productDescription, 9 | inputLimit[subscription] 10 | ); 11 | return { 12 | body: Joi.object().keys({ 13 | task: Joi.valid(task).required(), 14 | // productName: Joi.string().min(productName.min).max(productName.max).required(), 15 | // productType: Joi.string().min(productType.min).max(productType.max).required(), 16 | productName: Joi.string().max(productName.max).required(), 17 | productType: Joi.string().max(productType.max).required(), 18 | numberOfSuggestions: Joi.number().min(numberOfSuggestions.min).max(numberOfSuggestions.max).required(), 19 | }), 20 | }; 21 | }; 22 | 23 | const makeProductDescriptionSEOFriendly = (subscription) => { 24 | const { task, productName, productType, productFeatures, productBenefits, targetAudience, numberOfSuggestions } = 25 | getLimits(productValidation.makeProductDescriptionSEOFriendly, inputLimit[subscription]); 26 | return { 27 | body: Joi.object().keys({ 28 | task: Joi.valid(task).required(), 29 | // productName: Joi.string().min(productName.min).max(productName.max).required(), 30 | // productType: Joi.string().min(productType.min).max(productType.max).required(), 31 | // productFeatures: Joi.string().min(productFeatures.min).max(productFeatures.max).required(), 32 | // productBenefits: Joi.string().min(productBenefits.min).max(productBenefits.max).required(), 33 | // targetAudience: Joi.string().min(targetAudience.min).max(targetAudience.max).required(), 34 | productName: Joi.string().max(productName.max).required(), 35 | productType: Joi.string().max(productType.max).required(), 36 | productFeatures: Joi.string().max(productFeatures.max).required(), 37 | productBenefits: Joi.string().max(productBenefits.max).required(), 38 | targetAudience: Joi.string().max(targetAudience.max).required(), 39 | numberOfSuggestions: Joi.number().min(numberOfSuggestions.min).max(numberOfSuggestions.max).required(), 40 | }), 41 | }; 42 | }; 43 | 44 | const productReview = (subscription) => { 45 | const { task, product, rating, comment, numberOfSuggestions } = getLimits( 46 | productValidation.productReview, 47 | inputLimit[subscription] 48 | ); 49 | return { 50 | body: Joi.object().keys({ 51 | task: Joi.valid(task).required(), 52 | // product: Joi.string().min(product.min).max(product.max).required(), 53 | product: Joi.string().max(product.max).required(), 54 | rating: Joi.string() 55 | .required() 56 | .valid(...rating), 57 | // comment: Joi.string().min(comment.min).max(comment.max).required(), 58 | comment: Joi.string().max(comment.max).required(), 59 | numberOfSuggestions: Joi.number().min(numberOfSuggestions.min).max(numberOfSuggestions.max).required(), 60 | }), 61 | }; 62 | }; 63 | 64 | const productName = (subscription) => { 65 | const { task, productDescription, keywords, numberOfSuggestions } = getLimits( 66 | productValidation.productName, 67 | inputLimit[subscription] 68 | ); 69 | return { 70 | body: Joi.object().keys({ 71 | task: Joi.valid(task).required(), 72 | // productDescription: Joi.string().min(productDescription.min).max(productDescription.max).required(), 73 | // keywords: Joi.string().min(keywords.min).max(keywords.max).required(), 74 | productDescription: Joi.string().max(productDescription.max).required(), 75 | keywords: Joi.string().max(keywords.max).required(), 76 | numberOfSuggestions: Joi.number().min(numberOfSuggestions.min).max(numberOfSuggestions.max).required(), 77 | }), 78 | }; 79 | }; 80 | 81 | module.exports = { 82 | productDescription, 83 | makeProductDescriptionSEOFriendly, 84 | productReview, 85 | productName, 86 | }; 87 | -------------------------------------------------------------------------------- /src/validations/contents/sales.validation.js: -------------------------------------------------------------------------------- 1 | const Joi = require('joi'); 2 | const inputLimit = require('../../config/inputLimit'); 3 | const { salesValidation } = require('./validationData'); 4 | const getLimits = require('./helper.validation'); 5 | 6 | const problemAgitateSolution = (subscription) => { 7 | const { task, productName, productDescription, numberOfSuggestions } = getLimits( 8 | salesValidation.problemAgitateSolution, 9 | inputLimit[subscription] 10 | ); 11 | return { 12 | body: Joi.object().keys({ 13 | task: Joi.valid(task).required(), 14 | // productName: Joi.string().min(productName.min).max(productName.max).required(), 15 | // productDescription: Joi.string().min(productDescription.min).max(productDescription.max).required(), 16 | productName: Joi.string().max(productName.max).required(), 17 | productDescription: Joi.string().max(productDescription.max).required(), 18 | numberOfSuggestions: Joi.number().min(numberOfSuggestions.min).max(numberOfSuggestions.max).required(), 19 | }), 20 | }; 21 | }; 22 | 23 | const problemAgitateSolutionOutcome = (subscription) => { 24 | const { task, productName, productDescription, numberOfSuggestions } = getLimits( 25 | salesValidation.problemAgitateSolutionOutcome, 26 | inputLimit[subscription] 27 | ); 28 | return { 29 | body: Joi.object().keys({ 30 | task: Joi.valid(task).required(), 31 | // productName: Joi.string().min(productName.min).max(productName.max).required(), 32 | // productDescription: Joi.string().min(productDescription.min).max(productDescription.max).required(), 33 | productName: Joi.string().max(productName.max).required(), 34 | productDescription: Joi.string().max(productDescription.max).required(), 35 | numberOfSuggestions: Joi.number().min(numberOfSuggestions.min).max(numberOfSuggestions.max).required(), 36 | }), 37 | }; 38 | }; 39 | 40 | const attentionInterestDesireAction = (subscription) => { 41 | const { task, productName, productDescription, numberOfSuggestions } = getLimits( 42 | salesValidation.attentionInterestDesireAction, 43 | inputLimit[subscription] 44 | ); 45 | return { 46 | body: Joi.object().keys({ 47 | task: Joi.valid(task).required(), 48 | // productName: Joi.string().min(productName.min).max(productName.max).required(), 49 | // productDescription: Joi.string().min(productDescription.min).max(productDescription.max).required(), 50 | productName: Joi.string().max(productName.max).required(), 51 | productDescription: Joi.string().max(productDescription.max).required(), 52 | numberOfSuggestions: Joi.number().min(numberOfSuggestions.min).max(numberOfSuggestions.max).required(), 53 | }), 54 | }; 55 | }; 56 | 57 | module.exports = { 58 | problemAgitateSolution, 59 | problemAgitateSolutionOutcome, 60 | attentionInterestDesireAction, 61 | }; 62 | -------------------------------------------------------------------------------- /src/validations/contents/validationData/amazon.data.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | amazonProductListings: { 3 | task: 'amazon-product-listings', 4 | productName: { 5 | min: 3, 6 | max: 50, 7 | }, 8 | productCategories: { 9 | min: 5, 10 | max: 50, 11 | }, 12 | productFeatures: { 13 | min: 10, 14 | max: 250, 15 | }, 16 | numberOfSuggestions: { 17 | min: 1, 18 | max: 10, 19 | }, 20 | }, 21 | }; 22 | -------------------------------------------------------------------------------- /src/validations/contents/validationData/blog.data.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | blogIdea: { 3 | task: 'blog-idea', 4 | productName: { 5 | min: 3, 6 | max: 50, 7 | }, 8 | productDescription: { 9 | min: 10, 10 | max: 300, 11 | }, 12 | numberOfSuggestions: { 13 | min: 1, 14 | max: 10, 15 | }, 16 | }, 17 | 18 | blogHeadline: { 19 | task: 'blog-headline', 20 | about: { 21 | min: 10, 22 | max: 200, 23 | }, 24 | numberOfSuggestions: { 25 | min: 1, 26 | max: 10, 27 | }, 28 | }, 29 | 30 | blogIntro: { 31 | task: 'blog-intro', 32 | about: { 33 | min: 10, 34 | max: 200, 35 | }, 36 | headline: { 37 | min: 10, 38 | max: 150, 39 | }, 40 | numberOfSuggestions: { 41 | min: 1, 42 | max: 10, 43 | }, 44 | }, 45 | 46 | blogOutline: { 47 | task: 'blog-outline', 48 | about: { 49 | min: 10, 50 | max: 200, 51 | }, 52 | headline: { 53 | min: 10, 54 | max: 150, 55 | }, 56 | numberOfPoints: { 57 | min: 3, 58 | max: 10, 59 | }, 60 | numberOfSuggestions: { 61 | min: 1, 62 | max: 10, 63 | }, 64 | }, 65 | 66 | blogTopic: { 67 | task: 'blog-topic', 68 | about: { 69 | min: 10, 70 | max: 200, 71 | }, 72 | headline: { 73 | min: 10, 74 | max: 150, 75 | }, 76 | userText: { 77 | min: 10, 78 | max: 500, 79 | }, 80 | numberOfSuggestions: { 81 | min: 1, 82 | max: 10, 83 | }, 84 | }, 85 | 86 | blogOutro: { 87 | task: 'blog-outro', 88 | about: { 89 | min: 10, 90 | max: 200, 91 | }, 92 | headline: { 93 | min: 10, 94 | max: 150, 95 | }, 96 | numberOfSuggestions: { 97 | min: 1, 98 | max: 10, 99 | }, 100 | }, 101 | 102 | shortBlog: { 103 | task: 'short-blog', 104 | about: { 105 | min: 10, 106 | max: 400, 107 | variable: true, 108 | }, 109 | headline: { 110 | min: 10, 111 | max: 150, 112 | }, 113 | keywords: { 114 | min: 0, 115 | max: 2, 116 | }, 117 | }, 118 | 119 | longBlog: { 120 | task: 'long-blog', 121 | about: { 122 | min: 10, 123 | max: 400, 124 | variable: true, 125 | }, 126 | headline: { 127 | min: 10, 128 | max: 150, 129 | }, 130 | keywords: { 131 | min: 0, 132 | max: 4, 133 | }, 134 | contents: { 135 | min: 10, 136 | max: 12000, 137 | }, 138 | }, 139 | 140 | blogFromOutline: { 141 | task: 'blog-from-outline', 142 | headline: { 143 | min: 10, 144 | max: 150, 145 | }, 146 | intro: { 147 | min: 10, 148 | max: 1000, 149 | }, 150 | outline: { 151 | min: 3, 152 | max: 10, 153 | }, 154 | }, 155 | 156 | blogRewriter: { 157 | task: 'blog-rewriter', 158 | blog: { 159 | min: 100, 160 | max: 3000, 161 | }, 162 | }, 163 | }; 164 | -------------------------------------------------------------------------------- /src/validations/contents/validationData/business.data.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | catchyBusinessTaglines: { 3 | task: 'catchy-business-taglines', 4 | companyName: { 5 | min: 3, 6 | max: 50, 7 | }, 8 | businessType: { 9 | min: 5, 10 | max: 100, 11 | }, 12 | numberOfSuggestions: { 13 | min: 1, 14 | max: 10, 15 | }, 16 | }, 17 | }; 18 | -------------------------------------------------------------------------------- /src/validations/contents/validationData/common.data.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | imageIdeasFromAdText: { 3 | task: 'image-idea-from-ad-text', 4 | product: { 5 | min: 5, 6 | max: 100, 7 | }, 8 | adText: { 9 | min: 10, 10 | max: 200, 11 | }, 12 | numberOfSuggestions: { 13 | min: 1, 14 | max: 10, 15 | }, 16 | }, 17 | }; 18 | -------------------------------------------------------------------------------- /src/validations/contents/validationData/cooking.data.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | recipe: { 3 | task: 'generate-recipe', 4 | recipeName: { 5 | min: 3, 6 | max: 50, 7 | }, 8 | ingredients: { 9 | min: 5, 10 | max: 200, 11 | }, 12 | }, 13 | }; 14 | -------------------------------------------------------------------------------- /src/validations/contents/validationData/cv.data.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | CVSummary: { 3 | task: 'cv-summary', 4 | yourJobTitle: { 5 | min: 3, 6 | max: 50, 7 | }, 8 | keyAchievements: { 9 | min: 10, 10 | max: 150, 11 | }, 12 | yearsOfExperience: { 13 | min: 0, 14 | max: 100, 15 | }, 16 | numberOfSuggestions: { 17 | min: 1, 18 | max: 10, 19 | }, 20 | }, 21 | }; 22 | -------------------------------------------------------------------------------- /src/validations/contents/validationData/demo.data.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | paraphrase: { 3 | task: 'paraphrasing', 4 | userText: { 5 | min: 10, 6 | max: 100, 7 | }, 8 | }, 9 | blogHeadline: { 10 | task: 'blog-headline', 11 | blogAbout: { 12 | min: 10, 13 | max: 100, 14 | }, 15 | }, 16 | }; 17 | -------------------------------------------------------------------------------- /src/validations/contents/validationData/email.data.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | emailMarketingCampaignSubject: { 3 | task: 'email-marketing-campaign-subject', 4 | productDescription: { 5 | min: 10, 6 | max: 300, 7 | }, 8 | numberOfSuggestions: { 9 | min: 1, 10 | max: 10, 11 | }, 12 | }, 13 | 14 | emailMarketingCampaignBody: { 15 | task: 'email-marketing-campaign-body', 16 | productDescription: { 17 | min: 10, 18 | max: 200, 19 | }, 20 | about: { 21 | min: 10, 22 | max: 150, 23 | }, 24 | subjectLine: { 25 | min: 5, 26 | max: 60, 27 | }, 28 | numberOfSuggestions: { 29 | min: 1, 30 | max: 10, 31 | }, 32 | }, 33 | 34 | emailBody: { 35 | task: 'email-body', 36 | about: { 37 | min: 10, 38 | max: 150, 39 | }, 40 | to: { 41 | min: 5, 42 | max: 30, 43 | }, 44 | tone: [ 45 | 'Formal', 46 | 'Friendly', 47 | 'Neutral', 48 | 'Confident', 49 | 'Curious', 50 | 'Surprised', 51 | 'Happy', 52 | 'Angry', 53 | 'Sad', 54 | 'Concerned', 55 | 'Encouraging', 56 | 'Regretful', 57 | 'Optimistic', 58 | 'Excited', 59 | 'Witty', 60 | 'Persuasive', 61 | 'Empathetic' 62 | ], 63 | numberOfSuggestions: { 64 | min: 1, 65 | max: 10, 66 | }, 67 | }, 68 | 69 | generatedSubjectFromBody: { 70 | task: 'email-subject-from-body', 71 | emailBody: { 72 | min: 10, 73 | max: 400, 74 | variable: true, 75 | }, 76 | numberOfSuggestions: { 77 | min: 1, 78 | max: 10, 79 | }, 80 | }, 81 | }; 82 | -------------------------------------------------------------------------------- /src/validations/contents/validationData/extension.data.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | paraphrase: { 3 | task: 'paraphrasing', 4 | userText: { 5 | min: 5, 6 | max: 400, 7 | variable: true, 8 | }, 9 | }, 10 | 11 | simplifier: { 12 | task: 'simplifier', 13 | userText: { 14 | min: 5, 15 | max: 400, 16 | variable: true, 17 | }, 18 | }, 19 | 20 | summarizer: { 21 | task: 'summarizer', 22 | userText: { 23 | min: 5, 24 | max: 400, 25 | variable: true, 26 | }, 27 | }, 28 | 29 | grammarFixer: { 30 | task: 'grammar-fixer', 31 | userText: { 32 | min: 5, 33 | max: 400, 34 | variable: true, 35 | }, 36 | }, 37 | 38 | changeTone: { 39 | task: 'change-tone', 40 | userText: { 41 | min: 5, 42 | max: 400, 43 | variable: true, 44 | }, 45 | tone: [ 46 | 'Formal', 47 | 'Friendly', 48 | 'Neutral', 49 | 'Confident', 50 | 'Curious', 51 | 'Surprised', 52 | 'Happy', 53 | 'Angry', 54 | 'Sad', 55 | 'Concerned', 56 | 'Encouraging', 57 | 'Regretful', 58 | 'Optimistic', 59 | 'Excited', 60 | 'Witty', 61 | 'Persuasive', 62 | 'Empathetic', 63 | ], 64 | }, 65 | }; 66 | -------------------------------------------------------------------------------- /src/validations/contents/validationData/facebook.data.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | campaignPostIdeaFromBusinessType: { 3 | task: 'campaign-facebook-post', 4 | platformType: { 5 | min: 5, 6 | max: 100, 7 | }, 8 | numberOfSuggestions: { 9 | min: 1, 10 | max: 10, 11 | }, 12 | }, 13 | 14 | facebookAdPrimaryTexts: { 15 | task: 'ads-facebook-primary-texts', 16 | companyName: { 17 | min: 3, 18 | max: 50, 19 | }, 20 | businessType: { 21 | min: 5, 22 | max: 100, 23 | }, 24 | benefits: { 25 | min: 10, 26 | max: 200, 27 | }, 28 | numberOfSuggestions: { 29 | min: 1, 30 | max: 10, 31 | }, 32 | }, 33 | 34 | facebookAdHeadlines: { 35 | task: 'ads-facebook-headlines', 36 | productName: { 37 | min: 3, 38 | max: 50, 39 | }, 40 | businessType: { 41 | min: 5, 42 | max: 100, 43 | }, 44 | customerBenefit: { 45 | min: 10, 46 | max: 200, 47 | }, 48 | numberOfSuggestions: { 49 | min: 1, 50 | max: 10, 51 | }, 52 | }, 53 | 54 | facebookAdLinkDescription: { 55 | task: 'ads-facebook-link-descriptions', 56 | companyName: { 57 | min: 3, 58 | max: 50, 59 | }, 60 | platformType: { 61 | min: 5, 62 | max: 100, 63 | }, 64 | numberOfSuggestions: { 65 | min: 1, 66 | max: 10, 67 | }, 68 | }, 69 | 70 | facebookAdsFromProductDescription: { 71 | task: 'facebook-ads-from-product-description', 72 | product: { 73 | min: 10, 74 | max: 200, 75 | }, 76 | numberOfSuggestions: { 77 | min: 1, 78 | max: 10, 79 | }, 80 | }, 81 | }; 82 | -------------------------------------------------------------------------------- /src/validations/contents/validationData/features.data.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | proofread: { 3 | task: 'proofread', 4 | userText: { 5 | min: 5, 6 | max: 1000, 7 | variable: true, 8 | }, 9 | }, 10 | }; 11 | -------------------------------------------------------------------------------- /src/validations/contents/validationData/fiverr.data.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | fiverrProfileDescription: { 3 | task: 'fiverr-profile-description', 4 | profession: { 5 | min: 5, 6 | max: 100, 7 | }, 8 | experience: { 9 | min: 5, 10 | max: 30, 11 | }, 12 | numberOfSuggestions: { 13 | min: 1, 14 | max: 10, 15 | }, 16 | }, 17 | 18 | fiverrCategoriesHeadline: { 19 | task: 'fiverr-categories-headline', 20 | categoriesName: { 21 | min: 5, 22 | max: 200, 23 | }, 24 | }, 25 | }; 26 | -------------------------------------------------------------------------------- /src/validations/contents/validationData/google.data.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | googleAdHeadlines: { 3 | task: 'ads-google-headlines', 4 | name: { 5 | min: 3, 6 | max: 50, 7 | }, 8 | businessType: { 9 | min: 5, 10 | max: 100, 11 | }, 12 | numberOfSuggestions: { 13 | min: 1, 14 | max: 10, 15 | }, 16 | }, 17 | 18 | googleAdDescriptions: { 19 | task: 'ads-google-descriptions', 20 | businessName: { 21 | min: 3, 22 | max: 50, 23 | }, 24 | productCategories: { 25 | min: 5, 26 | max: 100, 27 | variable: true, 28 | }, 29 | uniqueness: { 30 | min: 10, 31 | max: 100, 32 | variable: true, 33 | }, 34 | promotions: { 35 | min: 5, 36 | max: 50, 37 | }, 38 | keywords: { 39 | min: 3, 40 | max: 100, 41 | variable: true, 42 | }, 43 | numberOfSuggestions: { 44 | min: 1, 45 | max: 10, 46 | }, 47 | }, 48 | }; 49 | -------------------------------------------------------------------------------- /src/validations/contents/validationData/headline.data.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | catchyHeadline: { 3 | task: 'catchy-headline', 4 | content: { 5 | min: 5, 6 | max: 200, 7 | }, 8 | numberOfSuggestions: { 9 | min: 1, 10 | max: 10, 11 | }, 12 | }, 13 | 14 | attentionGrabbingHeadline: { 15 | task: 'attention-grabbing-headline', 16 | content: { 17 | min: 5, 18 | max: 200, 19 | }, 20 | numberOfSuggestions: { 21 | min: 1, 22 | max: 10, 23 | }, 24 | }, 25 | 26 | newspaperHeadline: { 27 | task: 'newspaper-headline', 28 | content: { 29 | min: 5, 30 | max: 200, 31 | }, 32 | numberOfSuggestions: { 33 | min: 1, 34 | max: 10, 35 | }, 36 | }, 37 | 38 | resumeHeadline: { 39 | task: 'resume-headline', 40 | profession: { 41 | min: 5, 42 | max: 50, 43 | }, 44 | numberOfSuggestions: { 45 | min: 1, 46 | max: 10, 47 | }, 48 | }, 49 | }; 50 | -------------------------------------------------------------------------------- /src/validations/contents/validationData/index.js: -------------------------------------------------------------------------------- 1 | module.exports.amazonValidation = require('./amazon.data'); 2 | module.exports.blogValidation = require('./blog.data'); 3 | module.exports.businessValidation = require('./business.data'); 4 | module.exports.commonValidation = require('./common.data'); 5 | module.exports.cookingValidation = require('./cooking.data'); 6 | module.exports.cvValidation = require('./cv.data'); 7 | module.exports.demoValidation = require('./demo.data'); 8 | module.exports.emailValidation = require('./email.data'); 9 | module.exports.extensionValidation = require('./extension.data'); 10 | module.exports.facebookValidation = require('./facebook.data'); 11 | module.exports.featuresValidation = require('./features.data'); 12 | module.exports.fiverrValidation = require('./fiverr.data'); 13 | module.exports.googleValidation = require('./google.data'); 14 | module.exports.headlineValidation = require('./headline.data'); 15 | module.exports.instagramValidation = require('./instagram.data'); 16 | module.exports.linkedinValidation = require('./linkedIn.data'); 17 | module.exports.productValidation = require('./product.data'); 18 | module.exports.salesValidation = require('./sales.data'); 19 | module.exports.websiteValidation = require('./website.data'); 20 | module.exports.writingValidation = require('./writing.data'); 21 | module.exports.youtubeValidation = require('./youtube.data'); 22 | -------------------------------------------------------------------------------- /src/validations/contents/validationData/instagram.data.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | instagramAdTexts: { 3 | task: 'instagram-ad-texts', 4 | platformType: { 5 | min: 5, 6 | max: 100, 7 | }, 8 | context: { 9 | min: 10, 10 | max: 200, 11 | }, 12 | numberOfSuggestions: { 13 | min: 1, 14 | max: 10, 15 | }, 16 | }, 17 | }; 18 | -------------------------------------------------------------------------------- /src/validations/contents/validationData/linkedIn.data.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | linkedinAdTexts: { 3 | task: 'linkedin-ad-texts', 4 | companyName: { 5 | min: 3, 6 | max: 50, 7 | }, 8 | businessType: { 9 | min: 5, 10 | max: 100, 11 | }, 12 | benefits: { 13 | min: 10, 14 | max: 200, 15 | }, 16 | numberOfSuggestions: { 17 | min: 1, 18 | max: 10, 19 | }, 20 | }, 21 | 22 | linkedInSummary: { 23 | task: 'linkedin-summary', 24 | profession: { 25 | min: 5, 26 | max: 50, 27 | }, 28 | skills: { 29 | min: 5, 30 | max: 100, 31 | }, 32 | numberOfSuggestions: { 33 | min: 1, 34 | max: 10, 35 | }, 36 | }, 37 | }; 38 | -------------------------------------------------------------------------------- /src/validations/contents/validationData/product.data.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | productDescription: { 3 | task: 'product-description', 4 | productName: { 5 | min: 3, 6 | max: 50, 7 | }, 8 | productType: { 9 | min: 5, 10 | max: 100, 11 | }, 12 | numberOfSuggestions: { 13 | min: 1, 14 | max: 10, 15 | }, 16 | }, 17 | 18 | makeProductDescriptionSEOFriendly: { 19 | task: 'seo-friendly-product-description', 20 | productName: { 21 | min: 3, 22 | max: 50, 23 | }, 24 | productType: { 25 | min: 5, 26 | max: 100, 27 | }, 28 | productFeatures: { 29 | min: 5, 30 | max: 150, 31 | variable: true, 32 | }, 33 | productBenefits: { 34 | min: 5, 35 | max: 150, 36 | variable: true, 37 | }, 38 | targetAudience: { 39 | min: 3, 40 | max: 50, 41 | }, 42 | numberOfSuggestions: { 43 | min: 1, 44 | max: 10, 45 | }, 46 | }, 47 | 48 | productReview: { 49 | task: 'product-review', 50 | product: { 51 | min: 3, 52 | max: 50, 53 | }, 54 | rating: ['Worst', 'Bad', 'Average', 'Good', 'Best'], 55 | comment: { 56 | min: 10, 57 | max: 200, 58 | }, 59 | numberOfSuggestions: { 60 | min: 1, 61 | max: 10, 62 | }, 63 | }, 64 | 65 | productName: { 66 | task: 'product-name', 67 | productDescription: { 68 | min: 10, 69 | max: 200, 70 | }, 71 | keywords: { 72 | min: 3, 73 | max: 100, 74 | }, 75 | numberOfSuggestions: { 76 | min: 1, 77 | max: 10, 78 | }, 79 | }, 80 | }; 81 | -------------------------------------------------------------------------------- /src/validations/contents/validationData/sales.data.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | problemAgitateSolution: { 3 | task: 'problem-agitate-solution', 4 | productName: { 5 | min: 3, 6 | max: 50, 7 | }, 8 | productDescription: { 9 | min: 10, 10 | max: 300, 11 | }, 12 | numberOfSuggestions: { 13 | min: 1, 14 | max: 10, 15 | }, 16 | }, 17 | 18 | problemAgitateSolutionOutcome: { 19 | task: 'problem-agitate-solution-outcome', 20 | productName: { 21 | min: 3, 22 | max: 50, 23 | }, 24 | productDescription: { 25 | min: 10, 26 | max: 300, 27 | }, 28 | numberOfSuggestions: { 29 | min: 1, 30 | max: 10, 31 | }, 32 | }, 33 | 34 | attentionInterestDesireAction: { 35 | task: 'attention-interest-desire-action', 36 | productName: { 37 | min: 3, 38 | max: 50, 39 | }, 40 | productDescription: { 41 | min: 10, 42 | max: 300, 43 | }, 44 | numberOfSuggestions: { 45 | min: 1, 46 | max: 10, 47 | }, 48 | }, 49 | }; 50 | -------------------------------------------------------------------------------- /src/validations/contents/validationData/website.data.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | websiteShortDescription: { 3 | task: 'website-short-description', 4 | businessName: { 5 | min: 3, 6 | max: 50, 7 | }, 8 | industryType: { 9 | min: 5, 10 | max: 100, 11 | }, 12 | numberOfSuggestions: { 13 | min: 1, 14 | max: 10, 15 | }, 16 | }, 17 | 18 | keywordsFromText: { 19 | task: 'website-keywords-from-text', 20 | primaryText: { 21 | min: 10, 22 | max: 400, 23 | variable: true, 24 | }, 25 | numberOfSuggestions: { 26 | min: 1, 27 | max: 10, 28 | }, 29 | }, 30 | 31 | seoFriendlyBlogIdeas: { 32 | task: 'website-seo-friendly-blog-ideas', 33 | content: { 34 | min: 5, 35 | max: 100, 36 | }, 37 | desiredOutcome: { 38 | min: 5, 39 | max: 100, 40 | }, 41 | industry: { 42 | min: 5, 43 | max: 50, 44 | }, 45 | targetAudience: { 46 | min: 3, 47 | max: 50, 48 | }, 49 | numberOfSuggestions: { 50 | min: 1, 51 | max: 10, 52 | }, 53 | }, 54 | 55 | landingPageHeadline: { 56 | task: 'website-landing-page-headline', 57 | businessType: { 58 | min: 5, 59 | max: 100, 60 | }, 61 | numberOfSuggestions: { 62 | min: 1, 63 | max: 10, 64 | }, 65 | }, 66 | }; 67 | -------------------------------------------------------------------------------- /src/validations/contents/validationData/writing.data.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | paraphrase: { 3 | task: 'paraphrasing', 4 | userText: { 5 | min: 5, 6 | max: 400, 7 | variable: true, 8 | }, 9 | numberOfSuggestions: { 10 | min: 1, 11 | max: 10, 12 | }, 13 | }, 14 | 15 | expander: { 16 | task: 'expander', 17 | userText: { 18 | min: 5, 19 | max: 400, 20 | variable: true, 21 | }, 22 | numberOfSuggestions: { 23 | min: 1, 24 | max: 10, 25 | }, 26 | }, 27 | 28 | simplifier: { 29 | task: 'simplifier', 30 | userText: { 31 | min: 5, 32 | max: 400, 33 | variable: true, 34 | }, 35 | numberOfSuggestions: { 36 | min: 1, 37 | max: 10, 38 | }, 39 | }, 40 | 41 | summarizer: { 42 | task: 'summarizer', 43 | userText: { 44 | min: 5, 45 | max: 400, 46 | variable: true, 47 | }, 48 | numberOfSuggestions: { 49 | min: 1, 50 | max: 10, 51 | }, 52 | }, 53 | 54 | abstractGenerator: { 55 | task: 'abstract', 56 | userText: { 57 | min: 5, 58 | max: 400, 59 | variable: true, 60 | }, 61 | numberOfSuggestions: { 62 | min: 1, 63 | max: 10, 64 | }, 65 | }, 66 | 67 | notesFromPassage: { 68 | task: 'notes-from-passage', 69 | userText: { 70 | min: 5, 71 | max: 400, 72 | variable: true, 73 | }, 74 | numberOfPoints: { 75 | min: 2, 76 | max: 10, 77 | }, 78 | }, 79 | 80 | grammarFixer: { 81 | task: 'grammar-fixer', 82 | userText: { 83 | min: 5, 84 | max: 400, 85 | variable: true, 86 | }, 87 | }, 88 | 89 | changeTone: { 90 | task: 'change-tone', 91 | userText: { 92 | min: 5, 93 | max: 400, 94 | variable: true, 95 | }, 96 | tone: [ 97 | 'Formal', 98 | 'Friendly', 99 | 'Neutral', 100 | 'Confident', 101 | 'Curious', 102 | 'Surprised', 103 | 'Happy', 104 | 'Angry', 105 | 'Sad', 106 | 'Concerned', 107 | 'Encouraging', 108 | 'Regretful', 109 | 'Optimistic', 110 | 'Excited', 111 | 'Witty', 112 | 'Persuasive', 113 | 'Empathetic', 114 | ], 115 | numberOfSuggestions: { 116 | min: 1, 117 | max: 10, 118 | }, 119 | }, 120 | 121 | activePassive: { 122 | task: 'active-passive', 123 | userText: { 124 | min: 5, 125 | max: 200, 126 | variable: true, 127 | }, 128 | voice: ['Active', 'Passive'], 129 | }, 130 | 131 | pointOfView: { 132 | task: 'point-of-view', 133 | userText: { 134 | min: 5, 135 | max: 200, 136 | variable: true, 137 | }, 138 | person: ['first-person', 'second-person', 'third-person'], 139 | gender: ['male', 'female'], 140 | }, 141 | }; 142 | -------------------------------------------------------------------------------- /src/validations/contents/validationData/youtube.data.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | youtubeVideoTitleFromDescription: { 3 | task: 'youtube-video-titles-from-description', 4 | description: { 5 | min: 10, 6 | max: 300, 7 | }, 8 | numberOfSuggestions: { 9 | min: 1, 10 | max: 10, 11 | }, 12 | }, 13 | 14 | youtubeVideoIdeas: { 15 | task: 'youtube-video-ideas', 16 | topic: { 17 | min: 5, 18 | max: 200, 19 | }, 20 | numberOfSuggestions: { 21 | min: 1, 22 | max: 10, 23 | }, 24 | }, 25 | 26 | youtubeVideoScript: { 27 | task: 'youtube-video-script', 28 | title: { 29 | min: 5, 30 | max: 400, 31 | }, 32 | numberOfSuggestions: { 33 | min: 1, 34 | max: 10, 35 | }, 36 | }, 37 | 38 | videoTagsFromDescription: { 39 | task: 'youtube-video-tags-from-description', 40 | primaryText: { 41 | min: 10, 42 | max: 400, 43 | variable: true, 44 | }, 45 | numberOfSuggestions: { 46 | min: 1, 47 | max: 10, 48 | }, 49 | }, 50 | 51 | channelTagsFromDescription: { 52 | task: 'youtube-channel-tags-from-description', 53 | primaryText: { 54 | min: 10, 55 | max: 400, 56 | variable: true, 57 | }, 58 | numberOfSuggestions: { 59 | min: 1, 60 | max: 10, 61 | }, 62 | }, 63 | }; 64 | -------------------------------------------------------------------------------- /src/validations/contents/website.validation.js: -------------------------------------------------------------------------------- 1 | const Joi = require('joi'); 2 | const inputLimit = require('../../config/inputLimit'); 3 | const { websiteValidation } = require('./validationData'); 4 | const getLimits = require('./helper.validation'); 5 | 6 | const websiteShortDescription = (subscription) => { 7 | const { task, businessName, industryType, numberOfSuggestions } = getLimits( 8 | websiteValidation.websiteShortDescription, 9 | inputLimit[subscription] 10 | ); 11 | return { 12 | body: Joi.object().keys({ 13 | task: Joi.valid(task).required(), 14 | // businessName: Joi.string().min(businessName.min).max(businessName.max).required(), 15 | // industryType: Joi.string().min(industryType.min).max(industryType.max).required(), 16 | businessName: Joi.string().max(businessName.max).required(), 17 | industryType: Joi.string().max(industryType.max).required(), 18 | numberOfSuggestions: Joi.number().min(numberOfSuggestions.min).max(numberOfSuggestions.max).required(), 19 | }), 20 | }; 21 | }; 22 | 23 | const keywordsFromText = (subscription) => { 24 | const { task, primaryText, numberOfSuggestions } = getLimits(websiteValidation.keywordsFromText, inputLimit[subscription]); 25 | return { 26 | body: Joi.object().keys({ 27 | task: Joi.valid(task).required(), 28 | // primaryText: Joi.string().min(primaryText.min).max(primaryText.max).required(), 29 | primaryText: Joi.string().max(primaryText.max).required(), 30 | numberOfSuggestions: Joi.number().min(numberOfSuggestions.min).max(numberOfSuggestions.max).required(), 31 | }), 32 | }; 33 | }; 34 | 35 | const seoFriendlyBlogIdeas = (subscription) => { 36 | const { task, content, desiredOutcome, industry, targetAudience, numberOfSuggestions } = getLimits( 37 | websiteValidation.seoFriendlyBlogIdeas, 38 | inputLimit[subscription] 39 | ); 40 | return { 41 | body: Joi.object().keys({ 42 | task: Joi.valid(task).required(), 43 | // content: Joi.string().min(content.min).max(content.max).required(), 44 | // desiredOutcome: Joi.string().min(desiredOutcome.min).max(desiredOutcome.max).required(), 45 | // industry: Joi.string().min(industry.min).max(industry.max).required(), 46 | // targetAudience: Joi.string().min(targetAudience.min).max(targetAudience.max).required(), 47 | content: Joi.string().max(content.max).required(), 48 | desiredOutcome: Joi.string().max(desiredOutcome.max).required(), 49 | industry: Joi.string().max(industry.max).required(), 50 | targetAudience: Joi.string().max(targetAudience.max).required(), 51 | numberOfSuggestions: Joi.number().min(numberOfSuggestions.min).max(numberOfSuggestions.max).required(), 52 | }), 53 | }; 54 | }; 55 | 56 | const landingPageHeadline = (subscription) => { 57 | const { task, businessType, numberOfSuggestions } = getLimits( 58 | websiteValidation.landingPageHeadline, 59 | inputLimit[subscription] 60 | ); 61 | return { 62 | body: Joi.object().keys({ 63 | task: Joi.valid(task).required(), 64 | // businessType: Joi.string().min(businessType.min).max(businessType.max).required(), 65 | businessType: Joi.string().max(businessType.max).required(), 66 | numberOfSuggestions: Joi.number().min(numberOfSuggestions.min).max(numberOfSuggestions.max).required(), 67 | }), 68 | } 69 | }; 70 | 71 | module.exports = { 72 | websiteShortDescription, 73 | keywordsFromText, 74 | seoFriendlyBlogIdeas, 75 | landingPageHeadline, 76 | }; 77 | -------------------------------------------------------------------------------- /src/validations/contents/youtube.validation.js: -------------------------------------------------------------------------------- 1 | const Joi = require('joi'); 2 | const inputLimit = require('../../config/inputLimit'); 3 | const { youtubeValidation } = require('./validationData'); 4 | const getLimits = require('./helper.validation'); 5 | 6 | const youtubeVideoTitleFromDescription = (subscription) => { 7 | const { task, description, numberOfSuggestions } = getLimits( 8 | youtubeValidation.youtubeVideoTitleFromDescription, 9 | inputLimit[subscription] 10 | ); 11 | return { 12 | body: Joi.object().keys({ 13 | task: Joi.valid(task).required(), 14 | // description: Joi.string().min(description.min).max(description.nax).required(), 15 | description: Joi.string().max(description.nax).required(), 16 | numberOfSuggestions: Joi.number().min(numberOfSuggestions.min).max(numberOfSuggestions.max).required(), 17 | }), 18 | }; 19 | }; 20 | 21 | const youtubeVideoIdeas = (subscription) => { 22 | const { task, topic, numberOfSuggestions } = getLimits(youtubeValidation.youtubeVideoIdeas, inputLimit[subscription]); 23 | return { 24 | body: Joi.object().keys({ 25 | task: Joi.valid(task).required(), 26 | // topic: Joi.string().min(topic.min).max(topic.max).required(), 27 | topic: Joi.string().max(topic.max).required(), 28 | numberOfSuggestions: Joi.number().min(numberOfSuggestions.min).max(numberOfSuggestions.max).required(), 29 | }), 30 | }; 31 | }; 32 | 33 | const youtubeVideoScript = (subscription) => { 34 | const { task, title, numberOfSuggestions } = getLimits(youtubeValidation.youtubeVideoScript, inputLimit[subscription]); 35 | return { 36 | body: Joi.object().keys({ 37 | task: Joi.valid(task).required(), 38 | // title: Joi.string().min(title.min).max(title.max).required(), 39 | title: Joi.string().max(title.max).required(), 40 | numberOfSuggestions: Joi.number().min(numberOfSuggestions.min).max(numberOfSuggestions.max).required(), 41 | }), 42 | }; 43 | }; 44 | 45 | const videoTagsFromDescription = (subscription) => { 46 | const { task, primaryText, numberOfSuggestions } = getLimits( 47 | youtubeValidation.videoTagsFromDescription, 48 | inputLimit[subscription] 49 | ); 50 | return { 51 | body: Joi.object().keys({ 52 | task: Joi.valid(task).required(), 53 | // primaryText: Joi.string().min(primaryText.min).max(primaryText.max).required(), 54 | primaryText: Joi.string().max(primaryText.max).required(), 55 | numberOfSuggestions: Joi.number().min(numberOfSuggestions.min).max(numberOfSuggestions.max).required(), 56 | }), 57 | }; 58 | }; 59 | 60 | const channelTagsFromDescription = (subscription) => { 61 | const { task, primaryText, numberOfSuggestions } = getLimits( 62 | youtubeValidation.channelTagsFromDescription, 63 | inputLimit[subscription] 64 | ); 65 | return { 66 | body: Joi.object().keys({ 67 | task: Joi.valid(task).required(), 68 | // primaryText: Joi.string().min(primaryText.min).max(primaryText.max).required(), 69 | primaryText: Joi.string().max(primaryText.max).required(), 70 | numberOfSuggestions: Joi.number().min(numberOfSuggestions.min).max(numberOfSuggestions.max).required(), 71 | }), 72 | }; 73 | }; 74 | 75 | module.exports = { 76 | youtubeVideoTitleFromDescription, 77 | youtubeVideoIdeas, 78 | youtubeVideoScript, 79 | videoTagsFromDescription, 80 | channelTagsFromDescription, 81 | }; 82 | -------------------------------------------------------------------------------- /src/validations/custom.validation.js: -------------------------------------------------------------------------------- 1 | const objectId = (value, helpers) => { 2 | if (!value.match(/^[0-9a-fA-F]{24}$/)) { 3 | return helpers.message('"{{#label}}" must be a valid mongo id'); 4 | } 5 | return value; 6 | }; 7 | 8 | const password = (value, helpers) => { 9 | if (value.length < 6) { 10 | return helpers.message('password must be at least 6 characters'); 11 | } 12 | // if (!value.match(/\d/) || !value.match(/[a-zA-Z]/)) { 13 | // return helpers.message('password must contain at least 1 letter and 1 number'); 14 | // } 15 | return value; 16 | }; 17 | 18 | module.exports = { 19 | objectId, 20 | password, 21 | }; 22 | -------------------------------------------------------------------------------- /src/validations/index.js: -------------------------------------------------------------------------------- 1 | module.exports.authValidation = require('./auth.validation'); 2 | module.exports.userValidation = require('./user.validation'); 3 | module.exports.paymentValidation = require('./payment.validation'); 4 | module.exports.interestValidation = require('./interest.validation'); 5 | module.exports.supportValidation = require('./support.validation'); 6 | module.exports.toolValidation = require('./tool.validation'); 7 | module.exports.blogValidation = require('./blog.validation'); 8 | module.exports.noticeValidation = require('./notice.validation'); 9 | module.exports.subscriberValidation = require('./subscriber.validation'); 10 | module.exports.plagiarismCheckerValidation = require('./plagiarismChecker.validation'); 11 | -------------------------------------------------------------------------------- /src/validations/interest.validation.js: -------------------------------------------------------------------------------- 1 | const Joi = require('joi'); 2 | const { objectId } = require('./custom.validation'); 3 | 4 | const updateInterests = { 5 | query: Joi.object().keys({ 6 | action: Joi.string().required().valid('like', 'dislike'), 7 | }), 8 | params: Joi.object().keys({ 9 | userId: Joi.required().custom(objectId), 10 | }), 11 | body: Joi.object().keys({ 12 | contentId: Joi.string().custom(objectId).required(), 13 | index: Joi.number().required(), 14 | }), 15 | }; 16 | 17 | module.exports = { 18 | updateInterests, 19 | }; 20 | -------------------------------------------------------------------------------- /src/validations/notice.validation.js: -------------------------------------------------------------------------------- 1 | const Joi = require('joi'); 2 | 3 | const updateNotice = { 4 | body: Joi.object().keys({ 5 | active: Joi.boolean().required(), 6 | title: Joi.string().required(), 7 | description: Joi.string().required(), 8 | expiryTime: Joi.date().required(), 9 | }), 10 | }; 11 | 12 | module.exports = { updateNotice }; 13 | -------------------------------------------------------------------------------- /src/validations/payment.validation.js: -------------------------------------------------------------------------------- 1 | const Joi = require('joi'); 2 | 3 | const createCheckoutSession = { 4 | body: Joi.object().keys({ 5 | // customerId: Joi.string().required(), 6 | priceId: Joi.string().required(), 7 | referenceId: Joi.string(), 8 | }), 9 | }; 10 | 11 | const checkoutSession = { 12 | query: Joi.object().keys({ 13 | sessionId: Joi.string().required(), 14 | }), 15 | }; 16 | 17 | const getSubscriptions = { 18 | query: Joi.object().keys({ 19 | status: Joi.string() 20 | .valid('active', 'past_due', 'unpaid', 'canceled', 'incomplete', 'incomplete_expired', 'trialing', 'all', 'ended') 21 | .required(), 22 | }), 23 | }; 24 | 25 | const updateSubscriptionPlan = { 26 | body: Joi.object().keys({ 27 | subscriptionId: Joi.string().required(), 28 | bool: Joi.boolean().required(), 29 | }), 30 | }; 31 | 32 | // const updateSubscription = { 33 | // body: Joi.object().keys({ 34 | // subscriptionId: Joi.string().required(), 35 | // newPriceId: Joi.string().required(), 36 | // }), 37 | // }; 38 | 39 | // const invoicePreview = { 40 | // body: Joi.object().keys({ 41 | // subscriptionId: Joi.string().required(), 42 | // priceId: Joi.string().required(), 43 | // customerId: Joi.string().required(), 44 | // }), 45 | // }; 46 | 47 | module.exports = { 48 | createCheckoutSession, 49 | checkoutSession, 50 | updateSubscriptionPlan, 51 | // updateSubscription, 52 | // invoicePreview, 53 | getSubscriptions, 54 | }; 55 | -------------------------------------------------------------------------------- /src/validations/plagiarismChecker.validation.js: -------------------------------------------------------------------------------- 1 | const Joi = require('joi'); 2 | 3 | const searchContent = { 4 | body: Joi.object().keys({ 5 | text: Joi.string().required(), 6 | }), 7 | }; 8 | 9 | module.exports = { 10 | searchContent, 11 | }; -------------------------------------------------------------------------------- /src/validations/services.txt: -------------------------------------------------------------------------------- 1 | paraphrasing 2 | simplifier 3 | summarizer 4 | expander 5 | blog-idea 6 | blog-headline 7 | blog-outline 8 | blog-intro 9 | blog-topic 10 | product-description 11 | seo-friendly-product-description 12 | product-review 13 | catchy-headline 14 | attention-grabbing-headline 15 | newspaper-headline 16 | resume-headline 17 | campaign-facebook-post 18 | ads-facebook-primary-texts 19 | ads-facebook-headlines 20 | ads-facebook-link-descriptions 21 | facebook-ads-from-product-description 22 | instagram-ad-texts 23 | linkedin-ad-texts 24 | ads-google-headlines 25 | ads-google-descriptions 26 | youtube-video-titles-from-description 27 | youtube-video-ideas 28 | image-idea-from-ad-text 29 | email-marketing-campaign-subject 30 | email-marketing-campaign-body 31 | email-body 32 | email-subject-from-body 33 | website-short-description 34 | website-keywords-from-text 35 | youtube-video-tags-from-description 36 | youtube-channel-tags-from-description 37 | website-seo-friendly-blog-ideas 38 | website-landing-page-headline 39 | product-name 40 | linkedin-summary 41 | catchy-business-taglines 42 | fiverr-categories-headline 43 | cv-summary 44 | amazon-product-listings 45 | problem-agitate-solution 46 | problem-agitate-solution-outcome 47 | attention-interest-desire-action 48 | generate-recipe 49 | notes-from-passage 50 | grammar-fixer 51 | change-tone -------------------------------------------------------------------------------- /src/validations/subscriber.validation.js: -------------------------------------------------------------------------------- 1 | const Joi = require('joi'); 2 | 3 | const subscriberSwitcher = { 4 | body: Joi.object().keys({ 5 | subscriptionId: Joi.string().required(), 6 | }), 7 | }; 8 | 9 | module.exports = { subscriberSwitcher }; 10 | -------------------------------------------------------------------------------- /src/validations/support.validation.js: -------------------------------------------------------------------------------- 1 | const Joi = require('joi'); 2 | 3 | // const featureRequest = { 4 | // body: Joi.object().keys({ 5 | // name: Joi.string().required(), 6 | // email: Joi.string().email().required(), 7 | // feature: Joi.string().max(1000).required(), 8 | // image: Joi.any(), 9 | // }), 10 | // }; 11 | 12 | // const bugReport = { 13 | // body: Joi.object().keys({ 14 | // name: Joi.string().required(), 15 | // email: Joi.string().email().required(), 16 | // report: Joi.string().max(1000).required(), 17 | // image: Joi.any(), 18 | // }), 19 | // }; 20 | 21 | const userMessage = { 22 | body: Joi.object().keys({ 23 | name: Joi.string().required(), 24 | email: Joi.string().email().required(), 25 | message: Joi.string().max(1000).required(), 26 | // image: Joi.any(), 27 | }), 28 | }; 29 | 30 | module.exports = { 31 | // featureRequest, 32 | // bugReport, 33 | userMessage, 34 | }; 35 | -------------------------------------------------------------------------------- /src/validations/tool.validation.js: -------------------------------------------------------------------------------- 1 | const Joi = require('joi'); 2 | const { objectId } = require('./custom.validation'); 3 | 4 | const toolfields = Joi.object({ 5 | name: Joi.string().required(), 6 | key: Joi.string().required(), 7 | tips: Joi.object() 8 | .keys({ 9 | text: Joi.string().required(), 10 | }) 11 | .optional(), 12 | type: Joi.string().required(), 13 | placeholder: Joi.string().optional(), 14 | validation: Joi.object().keys({ 15 | required: Joi.boolean().default(true), 16 | max: Joi.number().optional(), 17 | }), 18 | }); 19 | 20 | const toolCategory = { 21 | body: Joi.object().keys({ 22 | name: Joi.string().required(), 23 | key: Joi.string().required(), 24 | description: Joi.string().optional(), 25 | icon: Joi.object().keys({ 26 | src: Joi.string().required(), 27 | }), 28 | }), 29 | }; 30 | 31 | const tool = { 32 | body: Joi.object().keys({ 33 | name: Joi.string().required(), 34 | key: Joi.string().required(), 35 | videoId: Joi.string().optional(), 36 | fields: Joi.array().items(toolfields).min(1), 37 | category: Joi.string().custom(objectId), 38 | }), 39 | }; 40 | 41 | module.exports = { 42 | toolCategory, 43 | tool, 44 | }; 45 | -------------------------------------------------------------------------------- /src/validations/user.validation.js: -------------------------------------------------------------------------------- 1 | const Joi = require('joi'); 2 | const { password, objectId } = require('./custom.validation'); 3 | 4 | const createUser = { 5 | body: Joi.object().keys({ 6 | email: Joi.string().email().required(), 7 | name: Joi.string().required(), 8 | password: Joi.string().required().custom(password), 9 | otp: Joi.string().length(6).required(), 10 | role: Joi.string().required().valid('user', 'admin'), 11 | }), 12 | }; 13 | 14 | const getUsers = { 15 | query: Joi.object().keys({ 16 | sortBy: Joi.string(), 17 | limit: Joi.number().integer(), 18 | page: Joi.number().integer(), 19 | }), 20 | }; 21 | 22 | const getUser = { 23 | params: Joi.object().keys({ 24 | userId: Joi.string().custom(objectId), 25 | }), 26 | }; 27 | 28 | const getUserBookmarks = { 29 | params: Joi.object().keys({ 30 | userId: Joi.required().custom(objectId), 31 | }), 32 | query: Joi.object().keys({ 33 | sortBy: Joi.string(), 34 | page: Joi.number().integer(), 35 | limit: Joi.number().integer(), 36 | }), 37 | }; 38 | 39 | const getUserFavouriteTools = { 40 | params: Joi.object().keys({ 41 | userId: Joi.required().custom(objectId), 42 | }), 43 | }; 44 | 45 | const updateUser = { 46 | params: Joi.object().keys({ 47 | userId: Joi.required().custom(objectId), 48 | }), 49 | body: Joi.object() 50 | .keys({ 51 | email: Joi.string().email(), 52 | role: Joi.string(), 53 | isVerified: Joi.boolean(), 54 | accountStatus: Joi.string(), 55 | wordsLeft: Joi.number(), 56 | }) 57 | .min(1), 58 | }; 59 | 60 | const updateUserInfo = { 61 | params: Joi.object().keys({ 62 | userId: Joi.required().custom(objectId), 63 | }), 64 | body: Joi.object().keys({ 65 | email: Joi.string().email(), 66 | }), 67 | }; 68 | 69 | const updateUserBookmarks = { 70 | params: Joi.object().keys({ 71 | userId: Joi.required().custom(objectId), 72 | }), 73 | body: Joi.object().keys({ 74 | contentId: Joi.string().custom(objectId).required(), 75 | index: Joi.number().required(), 76 | }), 77 | }; 78 | 79 | const updateUserContent = { 80 | params: Joi.object().keys({ 81 | userId: Joi.required().custom(objectId), 82 | }), 83 | body: Joi.object().keys({ 84 | contentId: Joi.string().custom(objectId).required(), 85 | index: Joi.number().required(), 86 | bookmarkedText: Joi.string().required(), 87 | }), 88 | }; 89 | 90 | const updateUserFavouriteTools = { 91 | params: Joi.object().keys({ 92 | userId: Joi.required().custom(objectId), 93 | }), 94 | body: Joi.object().keys({ 95 | tool: Joi.string().required(), 96 | }), 97 | }; 98 | 99 | module.exports = { 100 | createUser, 101 | getUsers, 102 | getUser, 103 | getUserBookmarks, 104 | getUserFavouriteTools, 105 | updateUser, 106 | updateUserInfo, 107 | updateUserBookmarks, 108 | updateUserContent, 109 | updateUserFavouriteTools, 110 | }; 111 | -------------------------------------------------------------------------------- /stripe.json: -------------------------------------------------------------------------------- 1 | {"id":"pi_3JSghCLf5DeFjOGM2IorT2KW","object":"payment_intent","amount":2000,"amount_capturable":0,"amount_received":2000,"application":null,"application_fee_amount":null,"canceled_at":null,"cancellation_reason":null,"capture_method":"automatic","charges":{"object":"list","data":[{"id":"ch_3JSghCLf5DeFjOGM2t9Yv2z0","object":"charge","amount":2000,"amount_captured":2000,"amount_refunded":0,"application":null,"application_fee":null,"application_fee_amount":null,"balance_transaction":"txn_3JSghCLf5DeFjOGM2pIGKkrC","billing_details":{"address":{"city":null,"country":"BD","line1":null,"line2":null,"postal_code":"44444","state":null},"email":"prosabujali@gmail.com","name":"Sabuj Ali","phone":null},"calculated_statement_descriptor":"Stripe","captured":true,"created":1629977075,"currency":"usd","customer":"cus_JcDp4DEQQZSbuy","description":"Subscription creation","destination":null,"dispute":null,"disputed":false,"failure_code":null,"failure_message":null,"fraud_details":{},"invoice":"in_1JSghCLf5DeFjOGM3IbE8TJ6","livemode":false,"metadata":{},"on_behalf_of":null,"order":null,"outcome":{"network_status":"approved_by_network","reason":null,"risk_level":"normal","risk_score":51,"seller_message":"Payment complete.","type":"authorized"},"paid":true,"payment_intent":"pi_3JSghCLf5DeFjOGM2IorT2KW","payment_method":"pm_1JSNCnLf5DeFjOGMVIS2Jmzv","payment_method_details":{"card":{"brand":"visa","checks":{"address_line1_check":null,"address_postal_code_check":"pass","cvc_check":null},"country":"US","exp_month":2,"exp_year":2025,"fingerprint":"KSDK4UncjJ91lUmW","funding":"credit","installments":null,"last4":"4242","network":"visa","three_d_secure":null,"wallet":null},"type":"card"},"receipt_email":null,"receipt_number":null,"receipt_url":"https://pay.stripe.com/receipts/acct_1IbtY8Lf5DeFjOGM/ch_3JSghCLf5DeFjOGM2t9Yv2z0/rcpt_K6uWu3bweIiKj6AQJvHyXFclWuroIpu","refunded":false,"refunds":{"object":"list","data":[],"has_more":false,"total_count":0,"url":"/v1/charges/ch_3JSghCLf5DeFjOGM2t9Yv2z0/refunds"},"review":null,"shipping":null,"source":null,"source_transfer":null,"statement_descriptor":null,"statement_descriptor_suffix":null,"status":"succeeded","transfer_data":null,"transfer_group":null}],"has_more":false,"total_count":1,"url":"/v1/charges?payment_intent=pi_3JSghCLf5DeFjOGM2IorT2KW"},"client_secret":"pi_3JSghCLf5DeFjOGM2IorT2KW_secret_PzvidKhLQbpp1zCyeg4Z7P5Y6","confirmation_method":"automatic","created":1629977074,"currency":"usd","customer":"cus_JcDp4DEQQZSbuy","description":"Subscription creation","invoice":"in_1JSghCLf5DeFjOGM3IbE8TJ6","last_payment_error":null,"livemode":false,"metadata":{},"next_action":null,"on_behalf_of":null,"payment_method":"pm_1JSNCnLf5DeFjOGMVIS2Jmzv","payment_method_options":{"card":{"installments":null,"network":null,"request_three_d_secure":"automatic"}},"payment_method_types":["card"],"receipt_email":null,"review":null,"setup_future_usage":"off_session","shipping":null,"source":null,"statement_descriptor":null,"statement_descriptor_suffix":null,"status":"succeeded","transfer_data":null,"transfer_group":null} --------------------------------------------------------------------------------