├── .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 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
--------------------------------------------------------------------------------
/.idea/codeStyles/codeStyleConfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
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 |
4 |
5 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/profiles_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
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