├── .gitignore
├── .husky
└── pre-push
├── .prettierignore
├── vercel.json
├── .prettierrc
├── src
├── middlewares
│ ├── errorHandler.js
│ ├── corsHandler.js
│ ├── tools.js
│ └── jwt.js
├── database
│ ├── index.js
│ └── models
│ │ ├── Core.js
│ │ ├── Tokens.js
│ │ ├── Payments.js
│ │ ├── Members.js
│ │ └── Websites.js
├── routes
│ ├── core
│ │ ├── validators.js
│ │ ├── index.js
│ │ └── controller.js
│ ├── index.js
│ ├── member
│ │ ├── validators.js
│ │ ├── index.js
│ │ └── controller.js
│ ├── payment
│ │ ├── index.js
│ │ ├── validators.js
│ │ └── controller.js
│ └── website
│ │ ├── validators.js
│ │ ├── index.js
│ │ └── controller.js
└── config
│ ├── index.js
│ ├── test.js
│ ├── dev.js
│ └── prod.js
├── .vscode
└── settings.json
├── Dockerfile
├── .env.example
├── LICENSE
├── index.js
├── package.json
├── .github
└── workflows
│ └── docker-deployment.yml
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules
2 |
3 | .env
--------------------------------------------------------------------------------
/.husky/pre-push:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 | . "$(dirname -- "$0")/_/husky.sh"
3 |
4 | npm run format
5 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | build/
3 | coverage/
4 | cache/
5 | .next/
6 |
7 | next-env.d.ts
8 | *.yml
9 | *.md
--------------------------------------------------------------------------------
/vercel.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": 2,
3 | "builds": [{ "src": "index.js", "use": "@vercel/node" }],
4 | "routes": [{ "src": "(.*)", "dest": "index.js" }]
5 | }
6 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "singleQuote": false,
3 | "tabWidth": 2,
4 | "printWidth": 80,
5 | "semi": true,
6 | "trailingComma": "all",
7 | "arrowParens": "always"
8 | }
9 |
--------------------------------------------------------------------------------
/src/middlewares/errorHandler.js:
--------------------------------------------------------------------------------
1 | const errorHandler = (err, req, res, next) => {
2 | const { status } = err;
3 | res.status(status | 500).json({ status: err.status, message: err.message });
4 | };
5 |
6 | module.exports = errorHandler;
7 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "editor.formatOnPaste": false,
3 | "editor.formatOnSave": true,
4 | "editor.defaultFormatter": "esbenp.prettier-vscode",
5 | "editor.codeActionsOnSave": {
6 | "source.fixAll.eslint": true,
7 | "source.fixAll.format": true
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:16.13.1
2 |
3 | # Create app directory
4 | RUN mkdir -p /usr/src/app
5 | WORKDIR /usr/src/app
6 |
7 | # Install app dpendencies
8 | COPY package.json /usr/src/app/
9 | RUN npm install
10 |
11 | # Bundle app source
12 | COPY . /usr/src/app
13 | EXPOSE 8080
14 | CMD ["npm", "run", "dev"]
--------------------------------------------------------------------------------
/src/database/index.js:
--------------------------------------------------------------------------------
1 | const mongoose = require("mongoose");
2 |
3 | mongoose.connect(process.env.MONGODB_URI);
4 |
5 | const connection = mongoose.connection;
6 |
7 | connection.on(
8 | "error",
9 | console.error.bind(console, "[nfthost] mongoDB connection error:"),
10 | );
11 |
12 | module.exports = connection;
13 |
--------------------------------------------------------------------------------
/src/routes/core/validators.js:
--------------------------------------------------------------------------------
1 | const { check } = require("express-validator");
2 |
3 | exports.AddReferralValidator = [
4 | check("name", "name is empty").notEmpty(),
5 |
6 | check("service", "service is empty").notEmpty(),
7 | ];
8 |
9 | exports.GetReferralValidator = [
10 | check("name", "name is empty").notEmpty(),
11 |
12 | check("service", "service is empty").notEmpty(),
13 | ];
14 |
--------------------------------------------------------------------------------
/src/config/index.js:
--------------------------------------------------------------------------------
1 | const development = require("./dev");
2 | const production = require("./prod");
3 | const test = require("./test");
4 |
5 | const configs = {
6 | development,
7 | production,
8 | test,
9 | };
10 |
11 | const exportedConfig = configs[process.env.NODE_ENV];
12 | console.log("[nfthost] running", process.env.NODE_ENV, "configuration");
13 |
14 | module.exports = exportedConfig;
15 |
--------------------------------------------------------------------------------
/src/routes/index.js:
--------------------------------------------------------------------------------
1 | const router = require("express").Router();
2 | const core = require("./core");
3 | const payment = require("./payment");
4 | const member = require("./member");
5 | const website = require("./website");
6 |
7 | router.use("/core", core);
8 | router.use("/payment", payment);
9 | router.use("/member", member);
10 | router.use("/website", website);
11 |
12 | module.exports = router;
13 |
--------------------------------------------------------------------------------
/.env.example:
--------------------------------------------------------------------------------
1 | MONGODB_URI=
2 | ACCESS_TOKEN_SECRET=
3 | REFRESH_TOKEN_SECRET=
4 | THIRDPARTY_TOKEN_SECRET= (generated access token, see bottom of index.js)
5 | STRIPE_SECRET=
6 | DOCKER_HUB_ACCESS_TOKEN= (put on github secrets)
7 | DOCKER_HUB_USERNAME= (put on github secrets)
8 | RECAPTCHA_KEY= (secret key of recaptcha v2)
9 | VERCEL_PROJECT_ID= (for custom domain)
10 | VERCEL_AUTH_TOKEN= (for custom domain)
--------------------------------------------------------------------------------
/src/database/models/Core.js:
--------------------------------------------------------------------------------
1 | const mongoose = require("mongoose");
2 |
3 | const Schema = mongoose.Schema;
4 |
5 | const CoreSchema = new Schema(
6 | {
7 | featuredWebsites: {
8 | type: [String],
9 | default: Array(5),
10 | },
11 | referrals: {
12 | type: Array,
13 | default: [],
14 | },
15 | },
16 | { timestamps: true },
17 | );
18 |
19 | exports.Core = mongoose.model("Core", CoreSchema);
20 |
--------------------------------------------------------------------------------
/src/database/models/Tokens.js:
--------------------------------------------------------------------------------
1 | const mongoose = require("mongoose");
2 |
3 | const Schema = mongoose.Schema;
4 |
5 | const TokensSchema = new Schema(
6 | {
7 | address: {
8 | type: String,
9 | required: true,
10 | },
11 | refreshToken: {
12 | type: String,
13 | required: true,
14 | },
15 | },
16 | { timestamps: true },
17 | );
18 |
19 | exports.Token = mongoose.model("token", TokensSchema);
20 |
--------------------------------------------------------------------------------
/src/config/test.js:
--------------------------------------------------------------------------------
1 | const test = {
2 | clientUrl: "http://localhost:3000",
3 | serverUrl: "http://localhost:8080",
4 | stripe: {
5 | products: {
6 | generator: {
7 | productId: "prod_MUjIgHsydbMGLu",
8 | priceId: "price_1LljpQHjrZpuqKHtmIxLJf2C",
9 | },
10 | website: {
11 | productId: "prod_MUjJIzvXHI56gw",
12 | priceId: "price_1LljrGHjrZpuqKHtqH9yBZCP",
13 | },
14 | utils: {
15 | productId: "prod_MUjKZNifbdGKgI",
16 | priceId: "price_1LljrcHjrZpuqKHt1x9LRWiT",
17 | },
18 | },
19 | },
20 | };
21 |
22 | module.exports = test;
23 |
--------------------------------------------------------------------------------
/src/config/dev.js:
--------------------------------------------------------------------------------
1 | const development = {
2 | clientUrl: "http://localhost:3000",
3 | serverUrl: "http://localhost:8080",
4 | stripe: {
5 | products: {
6 | generator: {
7 | productId: "prod_MUjIgHsydbMGLu",
8 | priceId: "price_1LljpQHjrZpuqKHtmIxLJf2C",
9 | },
10 | website: {
11 | productId: "prod_MUjJIzvXHI56gw",
12 | priceId: "price_1LljrGHjrZpuqKHtqH9yBZCP",
13 | },
14 | utils: {
15 | productId: "prod_MUjKZNifbdGKgI",
16 | priceId: "price_1LljrcHjrZpuqKHt1x9LRWiT",
17 | },
18 | },
19 | },
20 | };
21 |
22 | module.exports = development;
23 |
--------------------------------------------------------------------------------
/src/config/prod.js:
--------------------------------------------------------------------------------
1 | const production = {
2 | clientUrl: "https://www.nfthost.app",
3 | serverUrl: "http://localhost:8080",
4 | stripe: {
5 | products: {
6 | generator: {
7 | productId: "prod_MUTRTKzbIDNx90",
8 | priceId: "price_1LlUUMHjrZpuqKHtI8T2eCPj",
9 | },
10 | website: {
11 | productId: "prod_MUTS2m2Jw4BUJe",
12 | priceId: "price_1LlUVaHjrZpuqKHtyWSqAG9Z",
13 | },
14 | utils: {
15 | productId: "prod_MUTSj2PA5iVWHP",
16 | priceId: "price_1LlUW1HjrZpuqKHtbRc8RIyE",
17 | },
18 | },
19 | },
20 | };
21 |
22 | module.exports = production;
23 |
--------------------------------------------------------------------------------
/src/routes/core/index.js:
--------------------------------------------------------------------------------
1 | const router = require("express").Router();
2 | const {
3 | getFeaturedWebsites,
4 | addReferral,
5 | getReferral,
6 | } = require("./controller");
7 | const { AddReferralValidator, GetReferralValidator } = require("./validators");
8 | const { authenticateToken } = require("#middlewares/jwt.js");
9 |
10 | router.get("/getFeaturedWebsites", authenticateToken, getFeaturedWebsites);
11 | router.post(
12 | "/addReferral",
13 | authenticateToken,
14 | AddReferralValidator,
15 | addReferral,
16 | );
17 | router.get(
18 | "/getReferral",
19 | authenticateToken,
20 | GetReferralValidator,
21 | getReferral,
22 | );
23 |
24 | module.exports = router;
25 |
--------------------------------------------------------------------------------
/src/database/models/Payments.js:
--------------------------------------------------------------------------------
1 | const mongoose = require("mongoose");
2 |
3 | const Schema = mongoose.Schema;
4 |
5 | const PaymentsSchema = new Schema(
6 | {
7 | memberId: {
8 | type: Schema.ObjectId,
9 | required: true,
10 | },
11 | hash: {
12 | type: String,
13 | default: "",
14 | required: true,
15 | },
16 | service: {
17 | type: String,
18 | default: "",
19 | required: true,
20 | },
21 | price: {
22 | type: Number,
23 | default: 25,
24 | required: true,
25 | },
26 | isCanceled: {
27 | type: Boolean,
28 | default: false,
29 | },
30 | },
31 | { timestamps: true },
32 | );
33 |
34 | exports.Payment = mongoose.model("payment", PaymentsSchema);
35 |
--------------------------------------------------------------------------------
/src/middlewares/corsHandler.js:
--------------------------------------------------------------------------------
1 | const contructCors = (corsOption) => {
2 | const corsHandler = (req, res, next) => {
3 | const origin = req.headers.origin;
4 | const optOrigin = corsOption.origin;
5 |
6 | if (
7 | req.headers.host === "localhost:8080" ||
8 | req.headers.host === "nfthost-backend.vercel.app" ||
9 | optOrigin.includes(origin) ||
10 | origin.includes(".nfthost.app")
11 | ) {
12 | res.header("Access-Control-Allow-Origin", origin);
13 | }
14 |
15 | res.header("Access-Control-Allow-Credentials", "true");
16 | res.header(
17 | "Access-Control-Allow-Methods",
18 | "GET,OPTIONS,PATCH,DELETE,POST,PUT",
19 | );
20 | res.header(
21 | "Access-Control-Allow-Headers",
22 | "Origin, X-Requested-With, Content-Type, Accept, Authorization",
23 | );
24 |
25 | next();
26 | };
27 |
28 | return corsHandler;
29 | };
30 |
31 | module.exports = contructCors;
32 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 Typedef
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/src/routes/member/validators.js:
--------------------------------------------------------------------------------
1 | const { check } = require("express-validator");
2 |
3 | exports.WalletLoginValidator = [
4 | check("address", "address is empty").notEmpty(),
5 |
6 | check("wallet", "wallet is empty").notEmpty(),
7 | ];
8 |
9 | exports.GetMemberByAddressValidator = [
10 | check("address", "address is empty").notEmpty(),
11 | ];
12 |
13 | exports.AddUnitValidator = [
14 | check("address", "address is empty").notEmpty(),
15 |
16 | check("service", "service is empty").notEmpty(),
17 | ];
18 |
19 | exports.DeductUnitValidator = [
20 | check("address", "address is empty").notEmpty(),
21 |
22 | check("service", "service is empty").notEmpty(),
23 | ];
24 |
25 | exports.UpdateEmailValidator = [
26 | check("memberId", "memberId is empty").notEmpty(),
27 |
28 | check("email", "email is empty").notEmpty(),
29 |
30 | check("email", "email is invalid").isEmail(),
31 | ];
32 |
33 | exports.LogoutValidator = [
34 | check("refreshToken", "refreshToken is empty").notEmpty(),
35 | ];
36 |
37 | exports.RenewTokenValidator = [
38 | check("refreshToken", "refreshToken is empty").notEmpty(),
39 | ];
40 |
41 | exports.DeleteValidator = [check("memberId", "memberId is empty").notEmpty()];
42 |
--------------------------------------------------------------------------------
/src/middlewares/tools.js:
--------------------------------------------------------------------------------
1 | const lz = require("lzutf8");
2 | const dns = require("dns");
3 |
4 | module.exports.EncodeWebsiteData = (dataObj) => {
5 | return lz.encodeBase64(lz.compress(JSON.stringify(dataObj)));
6 | };
7 |
8 | module.exports.ParseWebsiteData = (data) => {
9 | return JSON.parse(lz.decompress(lz.decodeBase64(data)));
10 | };
11 |
12 | module.exports.VerifyDns = (domain) => {
13 | return new Promise((resolve) => {
14 | try {
15 | dns.resolveNs(domain, (err, addresses) => {
16 | if (err || !addresses) {
17 | return resolve({
18 | message:
19 | "Please make sure you are using the right nameservers. Err: 0x1",
20 | status: false,
21 | });
22 | }
23 |
24 | if (!addresses[0].indexOf("vercel-dns.com") === -1)
25 | return resolve({
26 | message:
27 | "Please make sure you are using the right nameservers. Err: 0x2",
28 | status: false,
29 | });
30 |
31 | return resolve({ message: "Successfuly verified.", status: true });
32 | });
33 | } catch (err) {
34 | return resolve({ message: err.message, status: false });
35 | }
36 | });
37 | };
38 |
--------------------------------------------------------------------------------
/src/database/models/Members.js:
--------------------------------------------------------------------------------
1 | const mongoose = require("mongoose");
2 |
3 | const Schema = mongoose.Schema;
4 |
5 | const MembersSchema = new Schema(
6 | {
7 | address: {
8 | type: String,
9 | required: true,
10 | unique: true,
11 | },
12 | wallet: {
13 | type: String,
14 | required: true,
15 | },
16 | email: {
17 | type: String,
18 | default: "",
19 | },
20 | picture: {
21 | type: String,
22 | default: "https://www.nfthost.app/assets/logo.png",
23 | },
24 | isBanned: {
25 | type: Boolean,
26 | default: false,
27 | },
28 | customerId: {
29 | type: String,
30 | default: "none",
31 | },
32 | services: {
33 | generator: {
34 | units: {
35 | type: Number,
36 | default: 0,
37 | },
38 | },
39 | website: {
40 | units: {
41 | type: Number,
42 | default: 0,
43 | },
44 | },
45 | utils: {
46 | units: {
47 | type: Number,
48 | default: 0,
49 | },
50 | },
51 | },
52 | },
53 | { timestamps: true },
54 | );
55 |
56 | exports.Member = mongoose.model("member", MembersSchema);
57 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | // Dependencies
2 | const path = require("path");
3 | require("dotenv").config({ path: path.join(__dirname, ".env") });
4 | const express = require("express");
5 | const cors = require("cors");
6 | const errorHandler = require("#middlewares/errorHandler.js");
7 | const contructCors = require("#middlewares/corsHandler.js");
8 | const connection = require("#database/index.js");
9 | const router = require("#routes/index.js");
10 |
11 | const app = express();
12 |
13 | const corsOption = {
14 | origin: ["http://localhost:3000", "https://www.nfthost.app"],
15 | optionsSuccessStatus: 200,
16 | };
17 |
18 | const corsHandler = contructCors(corsOption);
19 |
20 | app.options(cors(corsOption));
21 | app.use(corsHandler);
22 | app.use(express.urlencoded({ extended: true }));
23 | app.use(express.json());
24 | app.use("/api", router);
25 | app.use(errorHandler);
26 |
27 | connection.once("open", () => {
28 | console.log("[nfthost] connected to MongoDB");
29 | app.listen(process.env.PORT || 8080, () => {
30 | console.log(`[nfthost] listening at port ${process.env.PORT || 8080}`);
31 | });
32 | });
33 |
34 | // const { generateThirdPartyToken } = require('../middlewares/jwt');
35 | // console.log(generateThirdPartyToken({ origin: 'https://www.nfthost.app/' }))
36 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "nfthost-backend",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "jest -i",
8 | "dev": "nodemon index.js",
9 | "start": "vercel dev",
10 | "format": "prettier --write ."
11 | },
12 | "repository": {
13 | "type": "git",
14 | "url": "git+https://github.com/stephenasuncionDEV/nfthost-backend.git"
15 | },
16 | "author": "Stephen Asuncion",
17 | "license": "MIT",
18 | "bugs": {
19 | "url": "https://github.com/stephenasuncionDEV/nfthost-backend/issues"
20 | },
21 | "homepage": "https://github.com/stephenasuncionDEV/nfthost-backend#readme",
22 | "dependencies": {
23 | "cors": "^2.8.5",
24 | "express": "^4.18.2",
25 | "express-validator": "^6.14.2",
26 | "jsonwebtoken": "^9.0.0",
27 | "lzutf8": "^0.6.3",
28 | "mongoose": "^6.8.2",
29 | "stripe": "^11.5.0"
30 | },
31 | "devDependencies": {
32 | "dotenv": "^16.0.3",
33 | "husky": "^8.0.2",
34 | "nodemon": "^2.0.20",
35 | "prettier": "^2.8.1"
36 | },
37 | "imports": {
38 | "#database/*": "./src/database/*",
39 | "#middlewares/*": "./src/middlewares/*",
40 | "#models/*": "./src/database/models/*",
41 | "#routes/*": "./src/routes/*",
42 | "#config/*": "./src/config/*"
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/routes/member/index.js:
--------------------------------------------------------------------------------
1 | const router = require("express").Router();
2 | const controller = require("./controller");
3 | const {
4 | WalletLoginValidator,
5 | GetMemberByAddressValidator,
6 | AddUnitValidator,
7 | DeductUnitValidator,
8 | UpdateEmailValidator,
9 | LogoutValidator,
10 | RenewTokenValidator,
11 | DeleteValidator,
12 | } = require("./validators");
13 | const { authenticateToken } = require("#middlewares/jwt.js");
14 |
15 | router.post("/walletLogin", WalletLoginValidator, controller.walletLogin);
16 | router.get(
17 | "/getByAddress",
18 | authenticateToken,
19 | GetMemberByAddressValidator,
20 | controller.getMemberByAddress,
21 | );
22 | router.patch(
23 | "/addUnit",
24 | authenticateToken,
25 | AddUnitValidator,
26 | controller.addUnit,
27 | );
28 | router.patch(
29 | "/deductUnit",
30 | authenticateToken,
31 | DeductUnitValidator,
32 | controller.deductUnit,
33 | );
34 | router.patch(
35 | "/updateEmail",
36 | authenticateToken,
37 | UpdateEmailValidator,
38 | controller.updateEmail,
39 | );
40 | router.delete("/logout", authenticateToken, LogoutValidator, controller.logout);
41 | router.post("/renewToken", RenewTokenValidator, controller.renewToken);
42 | router.delete("/delete", authenticateToken, DeleteValidator, controller.delete);
43 |
44 | module.exports = router;
45 |
--------------------------------------------------------------------------------
/.github/workflows/docker-deployment.yml:
--------------------------------------------------------------------------------
1 | name: CI to Docker Hub
2 | on:
3 | push:
4 | branches: [ main ]
5 | jobs:
6 | build:
7 | runs-on: ubuntu-latest
8 | steps:
9 | - name: Check Out Repo
10 | uses: actions/checkout@v2
11 | - name: Docker meta
12 | id: meta
13 | uses: docker/metadata-action@v4
14 | with:
15 | images: |
16 | ${{ secrets.DOCKER_HUB_USERNAME }}/nfthost-backend
17 | tags: |
18 | type=ref,event=branch
19 | type=ref,event=pr
20 | type=semver,pattern={{version}}
21 | type=semver,pattern={{major}}.{{minor}}
22 | - name: Login to Docker Hub
23 | uses: docker/login-action@v1
24 | with:
25 | username: ${{ secrets.DOCKER_HUB_USERNAME }}
26 | password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}
27 | - name: Set up Docker Buildx
28 | id: buildx
29 | uses: docker/setup-buildx-action@v1
30 | - name: Build and push
31 | id: docker_build
32 | uses: docker/build-push-action@v2
33 | with:
34 | context: ./
35 | file: ./Dockerfile
36 | push: true
37 | tags: ${{ steps.meta.outputs.tags }}
38 | labels: ${{ steps.meta.outputs.labels }}
39 | - name: Image digest
40 | run: echo ${{ steps.docker_build.outputs.digest }}
--------------------------------------------------------------------------------
/src/routes/payment/index.js:
--------------------------------------------------------------------------------
1 | const router = require("express").Router();
2 | const controller = require("./controller");
3 | const {
4 | RequestSubscriptionValidator,
5 | RequestPaymentValidator,
6 | AddPaymentValidator,
7 | GetPaymentsValidator,
8 | CancelSubscriptionValidator,
9 | GetSubscriptionsValidator,
10 | GetSubscriptionValidator,
11 | } = require("./validators.js");
12 | const { authenticateToken } = require("#middlewares/jwt.js");
13 |
14 | router.post(
15 | "/requestSubscription",
16 | authenticateToken,
17 | RequestSubscriptionValidator,
18 | controller.requestSubscription,
19 | );
20 | router.post(
21 | "/request",
22 | authenticateToken,
23 | RequestPaymentValidator,
24 | controller.requestPayment,
25 | );
26 | router.post(
27 | "/add",
28 | authenticateToken,
29 | AddPaymentValidator,
30 | controller.addPayment,
31 | );
32 | router.get(
33 | "/get",
34 | authenticateToken,
35 | GetPaymentsValidator,
36 | controller.getPayments,
37 | );
38 | router.post(
39 | "/cancelSubscription",
40 | authenticateToken,
41 | CancelSubscriptionValidator,
42 | controller.cancelSubscription,
43 | );
44 | router.get(
45 | "/getSubscriptions",
46 | authenticateToken,
47 | GetSubscriptionsValidator,
48 | controller.getSubscriptions,
49 | );
50 | router.get(
51 | "/getSubscription",
52 | authenticateToken,
53 | GetSubscriptionValidator,
54 | controller.getSubscription,
55 | );
56 |
57 | module.exports = router;
58 |
--------------------------------------------------------------------------------
/src/routes/payment/validators.js:
--------------------------------------------------------------------------------
1 | const { check } = require("express-validator");
2 |
3 | exports.RequestSubscriptionValidator = [
4 | check("service", "service is empty").notEmpty(),
5 |
6 | check("billingDetails", "billingDetails is empty").notEmpty(),
7 |
8 | check("customerId", "customerId is empty").notEmpty(),
9 |
10 | check("metadata", "metadata is empty").notEmpty(),
11 | ];
12 |
13 | exports.RequestPaymentValidator = [
14 | check("billingDetails", "billingDetails is empty").notEmpty(),
15 |
16 | check("amount", "amount is empty").notEmpty(),
17 |
18 | check("customerId", "customerId is empty").notEmpty(),
19 |
20 | check("metadata", "metadata is empty").notEmpty(),
21 | ];
22 |
23 | exports.AddPaymentValidator = [
24 | check("memberId", "memberId is empty").notEmpty(),
25 |
26 | check("hash", "hash is empty").notEmpty(),
27 |
28 | check("service", "service is empty").notEmpty(),
29 |
30 | check("price", "price is empty").notEmpty(),
31 | ];
32 |
33 | exports.GetPaymentsValidator = [
34 | check("memberId", "memberId is empty").notEmpty(),
35 |
36 | check("pageNumber", "pageNumber is empty").notEmpty(),
37 | ];
38 |
39 | exports.CancelSubscriptionValidator = [
40 | check("subscriptionId", "subscriptionId is empty").notEmpty(),
41 | ];
42 |
43 | exports.GetSubscriptionsValidator = [
44 | check("customerId", "customerId is empty").notEmpty(),
45 | ];
46 |
47 | exports.GetSubscriptionValidator = [
48 | check("subscriptionId", "subscriptionId is empty").notEmpty(),
49 | ];
50 |
--------------------------------------------------------------------------------
/src/middlewares/jwt.js:
--------------------------------------------------------------------------------
1 | const jwt = require("jsonwebtoken");
2 |
3 | module.exports.generateAccessToken = (data) => {
4 | return jwt.sign(data, process.env.ACCESS_TOKEN_SECRET, { expiresIn: "7d" });
5 | };
6 |
7 | module.exports.generateThirdPartyToken = (data) => {
8 | return jwt.sign(data, process.env.THIRDPARTY_TOKEN_SECRET);
9 | };
10 |
11 | module.exports.generateRefreshToken = (data) => {
12 | return jwt.sign(data, process.env.REFRESH_TOKEN_SECRET);
13 | };
14 |
15 | module.exports.authenticateToken = (req, res, next) => {
16 | const authHeader = req.headers["authorization"];
17 | const token = authHeader && authHeader.split(" ")[1];
18 | if (token == null)
19 | return res.status(401).json({ message: "Invalid access token" });
20 | jwt.verify(token, process.env.ACCESS_TOKEN_SECRET, (err, data) => {
21 | if (err)
22 | return res.status(403).json({
23 | message: "Your access token has expired please re-log in",
24 | isExpired: true,
25 | });
26 | next();
27 | });
28 | };
29 |
30 | module.exports.authenticateThirdPartyToken = (req, res, next) => {
31 | const authHeader = req.headers["authorization"];
32 | const token = authHeader && authHeader.split(" ")[1];
33 | if (token == null)
34 | return res.status(401).json({ message: "Invalid third-party token" });
35 | jwt.verify(token, process.env.THIRDPARTY_TOKEN_SECRET, (err, data) => {
36 | if (err)
37 | return res.status(403).json({ message: err.message, isExpired: true });
38 | next();
39 | });
40 | };
41 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | NFT Host - Backend
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | Backend Server for NFTHost.app
18 |
19 |
20 | ## Services
21 |
22 |
26 |
27 | ## Setup
28 |
29 | ```
30 | npm i
31 | npm run dev
32 | ```
33 | or
34 |
35 | ```
36 | https://hub.docker.com/repository/docker/stephenasuncion/nfthost-backend
37 | ```
38 |
39 | ## Technologies
40 |
41 | 
42 |
43 | Other: [Chakra UI](https://chakra-ui.com/), [Stripe](https://stripe.com/en-ca), [PostHog](https://posthog.com/), [GrapesJS](https://grapesjs.com/), [Web3](https://web3js.readthedocs.io/en/v1.7.5/), [Chart.js](https://www.chartjs.org/), [Ethers](https://docs.ethers.io/v5/), [JSZip](https://stuk.github.io/jszip/)
44 |
45 | ## Support
46 |
47 | If you need help with anything please contact us on [Discord](https://discord.gg/BMZZXZMnmv)
48 |
49 | Want to donate? [https://www.buymeacoffee.com/stephenasuncion](https://www.buymeacoffee.com/stephenasuncion)
50 |
51 | ## License
52 |
53 | [MIT License](https://github.com/stephenasuncionDEV/nfthost-backend/blob/main/LICENSE)
54 |
--------------------------------------------------------------------------------
/src/routes/core/controller.js:
--------------------------------------------------------------------------------
1 | const { validationResult } = require("express-validator");
2 | const { Website } = require("#models/Websites.js");
3 | const { Core } = require("#models/Core.js");
4 |
5 | const ObjectId = require("mongoose").Types.ObjectId;
6 |
7 | exports.getFeaturedWebsites = async (req, res, next) => {
8 | try {
9 | const coreRes = await Core.findOne({ key: "core" });
10 |
11 | const websiteIdArray = coreRes.featuredWebsites.map((websiteId) => {
12 | return ObjectId(websiteId);
13 | });
14 |
15 | const websiteRes = await Website.find({
16 | _id: {
17 | $in: websiteIdArray,
18 | },
19 | });
20 |
21 | res.status(200).json(websiteRes);
22 | } catch (err) {
23 | next(err);
24 | }
25 | };
26 |
27 | exports.addReferral = async (req, res, next) => {
28 | try {
29 | const errors = validationResult(req).errors;
30 | if (errors.length > 0) throw new Error(errors[0].msg);
31 |
32 | const { name, service } = req.body;
33 |
34 | const nameTemp = name.trim().toLowerCase();
35 |
36 | const core = await Core.findOne({ key: "core" });
37 |
38 | let newUser = core.referrals.find((user) => user.name === nameTemp);
39 | if (!newUser) {
40 | newUser = {
41 | name: nameTemp,
42 | value: 0,
43 | service,
44 | };
45 | }
46 |
47 | newUser.value += 1;
48 |
49 | let coreRes = await Core.findOneAndUpdate(
50 | {
51 | key: "core",
52 | "referrals.name": newUser.name,
53 | "referrals.service": newUser.service,
54 | },
55 | {
56 | $set: {
57 | "referrals.$.value": newUser.value,
58 | },
59 | },
60 | );
61 |
62 | if (!coreRes) {
63 | coreRes = await Core.findOneAndUpdate(
64 | {
65 | key: "core",
66 | },
67 | {
68 | $push: {
69 | referrals: newUser,
70 | },
71 | },
72 | );
73 | }
74 |
75 | res.status(200).json({ message: "Successfully added referral" });
76 | } catch (err) {
77 | next(err);
78 | }
79 | };
80 |
81 | exports.getReferral = async (req, res, next) => {
82 | try {
83 | const errors = validationResult(req).errors;
84 | if (errors.length > 0) throw new Error(errors[0].msg);
85 |
86 | const { name, service } = req.query;
87 |
88 | const result = await Core.findOne({ key: "core" });
89 | const referral = result.referrals.find(
90 | (referral) => referral.service === service && referral.name === name,
91 | );
92 |
93 | res.status(200).json(referral);
94 | } catch (err) {
95 | next(err);
96 | }
97 | };
98 |
--------------------------------------------------------------------------------
/src/database/models/Websites.js:
--------------------------------------------------------------------------------
1 | const mongoose = require("mongoose");
2 |
3 | const Schema = mongoose.Schema;
4 |
5 | const WebsitesSchema = new Schema(
6 | {
7 | memberId: {
8 | type: Schema.ObjectId,
9 | default: null,
10 | },
11 | isPremium: {
12 | type: Boolean,
13 | default: false,
14 | },
15 | isExpired: {
16 | type: Boolean,
17 | default: false,
18 | },
19 | isPublished: {
20 | type: Boolean,
21 | default: false,
22 | },
23 | premiumStartDate: {
24 | type: Date,
25 | default: null,
26 | },
27 | premiumEndDate: {
28 | type: Date,
29 | default: null,
30 | },
31 | revealDate: {
32 | type: Date,
33 | default: null,
34 | },
35 | route: {
36 | type: String,
37 | default: "",
38 | required: true,
39 | },
40 | subscriptionId: {
41 | type: String,
42 | default: "",
43 | },
44 | components: {
45 | title: {
46 | type: String,
47 | default: "",
48 | required: true,
49 | },
50 | unrevealedImage: {
51 | type: String,
52 | default: "https://www.nfthost.app/assets/logo.png",
53 | required: true,
54 | },
55 | description: {
56 | type: String,
57 | default: "",
58 | required: true,
59 | },
60 | embed: {
61 | type: String,
62 | default: "",
63 | required: true,
64 | },
65 | script: {
66 | type: String,
67 | default: "",
68 | },
69 | addons: {
70 | type: [String],
71 | default: [],
72 | },
73 | template: {
74 | type: String,
75 | default: "Template1",
76 | },
77 | },
78 | meta: {
79 | robot: {
80 | type: String,
81 | default: "if",
82 | },
83 | favicon: {
84 | type: String,
85 | default: "https://www.nfthost.app/favicon.ico",
86 | },
87 | language: {
88 | type: String,
89 | default: "EN",
90 | },
91 | },
92 | externalLinks: {
93 | twitter: {
94 | type: String,
95 | default: "",
96 | },
97 | instagram: {
98 | type: String,
99 | default: "",
100 | },
101 | youtube: {
102 | type: String,
103 | default: "",
104 | },
105 | tiktok: {
106 | type: String,
107 | default: "",
108 | },
109 | discord: {
110 | type: String,
111 | default: "",
112 | },
113 | reddit: {
114 | type: String,
115 | default: "",
116 | },
117 | facebook: {
118 | type: String,
119 | default: "",
120 | },
121 | opensea: {
122 | type: String,
123 | default: "",
124 | },
125 | },
126 | custom: {
127 | domain: {
128 | type: String,
129 | default: "",
130 | },
131 | },
132 | },
133 | { timestamps: true },
134 | );
135 |
136 | exports.Website = mongoose.model("website", WebsitesSchema);
137 |
--------------------------------------------------------------------------------
/src/routes/website/validators.js:
--------------------------------------------------------------------------------
1 | const { check } = require("express-validator");
2 |
3 | exports.CreateWebsiteValidator = [
4 | check("route", "route is empty").notEmpty(),
5 |
6 | check("components.title", "components.title is empty").notEmpty(),
7 |
8 | check(
9 | "components.unrevealedImage",
10 | "components.unrevealedImage is empty",
11 | ).notEmpty(),
12 |
13 | check("components.description", "components.description is empty").notEmpty(),
14 |
15 | check("components.embed", "components.embed is empty").notEmpty(),
16 | ];
17 |
18 | exports.DeleteWebsiteValidator = [
19 | check("websiteId", "websiteId is empty").notEmpty(),
20 | ];
21 |
22 | exports.GetWebsiteByRouteValidator = [
23 | check("route", "route is empty").notEmpty(),
24 | ];
25 |
26 | exports.GetWebsiteByDomainValidator = [
27 | check("domain", "domain is empty").notEmpty(),
28 | ];
29 |
30 | exports.GetWebsitesValidator = [
31 | check("memberId", "memberId is empty").notEmpty(),
32 | ];
33 |
34 | exports.UpdateDataValidator = [
35 | check("websiteId", "websiteId is empty").notEmpty(),
36 |
37 | check("data", "data is empty").notEmpty(),
38 | ];
39 |
40 | exports.UpdateIsPremiumValidator = [
41 | check("websiteId", "websiteId is empty").notEmpty(),
42 |
43 | check("isPremium", "isPremium is empty").notEmpty(),
44 | ];
45 |
46 | exports.UpdateIsExpiredValidator = [
47 | check("websiteId", "websiteId is empty").notEmpty(),
48 |
49 | check("isExpired", "isExpired is empty").notEmpty(),
50 | ];
51 |
52 | exports.UpdateIsPublishedValidator = [
53 | check("websiteId", "websiteId is empty").notEmpty(),
54 |
55 | check("isPublished", "isPublished is empty").notEmpty(),
56 | ];
57 |
58 | exports.UpdatePremiumStartDateValidator = [
59 | check("websiteId", "websiteId is empty").notEmpty(),
60 |
61 | check("premiumStartDate", "premiumStartDate is empty").notEmpty(),
62 | ];
63 |
64 | exports.UpdateRevealDateValidator = [
65 | check("websiteId", "websiteId is empty").notEmpty(),
66 |
67 | check("revealDate", "revealDate is empty").notEmpty(),
68 | ];
69 |
70 | exports.UpdateRouteValidator = [
71 | check("websiteId", "websiteId is empty").notEmpty(),
72 |
73 | check("route", "route is empty").notEmpty(),
74 | ];
75 |
76 | exports.UpdateTitleValidator = [
77 | check("websiteId", "websiteId is empty").notEmpty(),
78 |
79 | check("title", "title is empty").notEmpty(),
80 | ];
81 |
82 | exports.UpdateDescriptionValidator = [
83 | check("websiteId", "websiteId is empty").notEmpty(),
84 |
85 | check("description", "description is empty").notEmpty(),
86 | ];
87 |
88 | exports.UpdateLogoValidator = [
89 | check("websiteId", "websiteId is empty").notEmpty(),
90 |
91 | check("logo", "logo is empty").notEmpty(),
92 | ];
93 |
94 | exports.UpdateScriptValidator = [
95 | check("websiteId", "websiteId is empty").notEmpty(),
96 | ];
97 |
98 | exports.UpdateEmbedValidator = [
99 | check("websiteId", "websiteId is empty").notEmpty(),
100 |
101 | check("embed", "embed is empty").notEmpty(),
102 | ];
103 |
104 | exports.AddAddonValidator = [
105 | check("websiteId", "websiteId is empty").notEmpty(),
106 |
107 | check("addon", "addon is empty").notEmpty(),
108 | ];
109 |
110 | exports.DeleteAddonValidator = [
111 | check("websiteId", "websiteId is empty").notEmpty(),
112 |
113 | check("addon", "addon is empty").notEmpty(),
114 | ];
115 |
116 | exports.UpdateTemplateValidator = [
117 | check("websiteId", "websiteId is empty").notEmpty(),
118 |
119 | check("template", "template is empty").notEmpty(),
120 | ];
121 |
122 | exports.DeleteTemplateValidator = [
123 | check("websiteId", "websiteId is empty").notEmpty(),
124 |
125 | check("template", "template is empty").notEmpty(),
126 | ];
127 |
128 | exports.UpdateRobotValidator = [
129 | check("websiteId", "websiteId is empty").notEmpty(),
130 |
131 | check("robot", "robot is empty").notEmpty(),
132 | ];
133 |
134 | exports.UpdateFaviconValidator = [
135 | check("websiteId", "websiteId is empty").notEmpty(),
136 |
137 | check("favicon", "favicon is empty").notEmpty(),
138 | ];
139 |
140 | exports.UpdateLanguageValidator = [
141 | check("websiteId", "websiteId is empty").notEmpty(),
142 |
143 | check("language", "language is empty").notEmpty(),
144 | ];
145 |
146 | exports.UpdateExternalLinkValidator = [
147 | check("websiteId", "websiteId is empty").notEmpty(),
148 |
149 | check("social", "social is empty").notEmpty(),
150 |
151 | // check('link', 'link is empty')
152 | // .notEmpty()
153 | ];
154 |
155 | exports.UpdateDomainValidator = [
156 | check("websiteId", "websiteId is empty").notEmpty(),
157 |
158 | check("domain", "domain is empty").notEmpty(),
159 | ];
160 |
161 | exports.VerifyDomainValidator = [check("domain", "domain is empty").notEmpty()];
162 |
163 | exports.UpdateSubscriptionValidator = [
164 | check("memberId", "memberId is empty").notEmpty(),
165 |
166 | check("subscriptionId", "subscriptionId is empty").notEmpty(),
167 |
168 | check("isPremium", "isPremium is empty").notEmpty(),
169 |
170 | check("isExpired", "isExpired is empty").notEmpty(),
171 |
172 | check("isPublished", "isPublished is empty").notEmpty(),
173 | ];
174 |
--------------------------------------------------------------------------------
/src/routes/website/index.js:
--------------------------------------------------------------------------------
1 | const router = require("express").Router();
2 | const controller = require("./controller");
3 | const {
4 | CreateWebsiteValidator,
5 | DeleteWebsiteValidator,
6 | GetWebsiteByRouteValidator,
7 | GetWebsiteByDomainValidator,
8 | GetWebsitesValidator,
9 | UpdateDataValidator,
10 | UpdateIsPremiumValidator,
11 | UpdateIsExpiredValidator,
12 | UpdateIsPublishedValidator,
13 | UpdatePremiumStartDateValidator,
14 | UpdateRevealDateValidator,
15 | UpdateRouteValidator,
16 | UpdateTitleValidator,
17 | UpdateDescriptionValidator,
18 | UpdateLogoValidator,
19 | UpdateScriptValidator,
20 | UpdateEmbedValidator,
21 | AddAddonValidator,
22 | DeleteAddonValidator,
23 | UpdateTemplateValidator,
24 | UpdateRobotValidator,
25 | UpdateFaviconValidator,
26 | UpdateLanguageValidator,
27 | UpdateExternalLinkValidator,
28 | UpdateDomainValidator,
29 | VerifyDomainValidator,
30 | UpdateSubscriptionValidator,
31 | } = require("./validators");
32 | const {
33 | authenticateToken,
34 | authenticateThirdPartyToken,
35 | } = require("#middlewares/jwt.js");
36 |
37 | router.post(
38 | "/create",
39 | authenticateThirdPartyToken,
40 | CreateWebsiteValidator,
41 | controller.createWebsite,
42 | );
43 | router.delete(
44 | "/delete",
45 | authenticateToken,
46 | DeleteWebsiteValidator,
47 | controller.deleteWebsite,
48 | );
49 | router.get(
50 | "/getWebsiteByRoute",
51 | authenticateThirdPartyToken,
52 | GetWebsiteByRouteValidator,
53 | controller.getWebsiteByRoute,
54 | );
55 | router.get(
56 | "/getWebsiteByDomain",
57 | authenticateThirdPartyToken,
58 | GetWebsiteByDomainValidator,
59 | controller.getWebsiteByDomain,
60 | );
61 | router.get(
62 | "/getWebsites",
63 | authenticateToken,
64 | GetWebsitesValidator,
65 | controller.getWebsites,
66 | );
67 | router.get(
68 | "/getMappedSubdomains",
69 | authenticateThirdPartyToken,
70 | controller.getMappedSubdomains,
71 | );
72 | router.patch(
73 | "/updateData",
74 | authenticateToken,
75 | UpdateDataValidator,
76 | controller.updateData,
77 | );
78 | router.patch(
79 | "/updateIsPremium",
80 | authenticateToken,
81 | UpdateIsPremiumValidator,
82 | controller.updateIsPremium,
83 | );
84 | router.patch(
85 | "/updateIsExpired",
86 | authenticateToken,
87 | UpdateIsExpiredValidator,
88 | controller.updateIsExpired,
89 | );
90 | router.patch(
91 | "/updateIsPublished",
92 | authenticateToken,
93 | UpdateIsPublishedValidator,
94 | controller.updateIsPublished,
95 | );
96 | router.patch(
97 | "/updatePremiumStartDate",
98 | authenticateToken,
99 | UpdatePremiumStartDateValidator,
100 | controller.updatePremiumStartDate,
101 | );
102 | router.patch(
103 | "/updateRevealDate",
104 | authenticateToken,
105 | UpdateRevealDateValidator,
106 | controller.updateRevealDate,
107 | );
108 | router.patch(
109 | "/updateRoute",
110 | authenticateToken,
111 | UpdateRouteValidator,
112 | controller.updateRoute,
113 | );
114 | router.patch(
115 | "/updateTitle",
116 | authenticateToken,
117 | UpdateTitleValidator,
118 | controller.updateTitle,
119 | );
120 | router.patch(
121 | "/updateDescription",
122 | authenticateToken,
123 | UpdateDescriptionValidator,
124 | controller.updateDescription,
125 | );
126 | router.patch(
127 | "/updateLogo",
128 | authenticateToken,
129 | UpdateLogoValidator,
130 | controller.updateLogo,
131 | );
132 | router.patch(
133 | "/updateScript",
134 | authenticateToken,
135 | UpdateScriptValidator,
136 | controller.updateScript,
137 | );
138 | router.patch(
139 | "/updateEmbed",
140 | authenticateToken,
141 | UpdateEmbedValidator,
142 | controller.updateEmbed,
143 | );
144 | router.patch(
145 | "/addAddon",
146 | authenticateToken,
147 | AddAddonValidator,
148 | controller.addAddon,
149 | );
150 | router.patch(
151 | "/deleteAddon",
152 | authenticateToken,
153 | DeleteAddonValidator,
154 | controller.deleteAddon,
155 | );
156 | router.patch(
157 | "/updateTemplate",
158 | authenticateToken,
159 | UpdateTemplateValidator,
160 | controller.updateTemplate,
161 | );
162 | router.patch(
163 | "/updateRobot",
164 | authenticateToken,
165 | UpdateRobotValidator,
166 | controller.updateRobot,
167 | );
168 | router.patch(
169 | "/updateFavicon",
170 | authenticateToken,
171 | UpdateFaviconValidator,
172 | controller.updateFavicon,
173 | );
174 | router.patch(
175 | "/updateLanguage",
176 | authenticateToken,
177 | UpdateLanguageValidator,
178 | controller.updateLanguage,
179 | );
180 | router.patch(
181 | "/updateExternalLink",
182 | authenticateToken,
183 | UpdateExternalLinkValidator,
184 | controller.updateExternalLink,
185 | );
186 | router.patch(
187 | "/updateDomain",
188 | authenticateToken,
189 | UpdateDomainValidator,
190 | controller.updateDomain,
191 | );
192 | router.patch(
193 | "/verifyDomain",
194 | authenticateToken,
195 | VerifyDomainValidator,
196 | controller.verifyDomain,
197 | );
198 | router.patch(
199 | "/updateSubscription",
200 | authenticateToken,
201 | UpdateSubscriptionValidator,
202 | controller.updateSubscription,
203 | );
204 |
205 | module.exports = router;
206 |
--------------------------------------------------------------------------------
/src/routes/member/controller.js:
--------------------------------------------------------------------------------
1 | const {
2 | generateAccessToken,
3 | generateRefreshToken,
4 | } = require("#middlewares/jwt.js");
5 | const { validationResult } = require("express-validator");
6 | const { Member } = require("#models/Members.js");
7 | const { Payment } = require("#models/Payments.js");
8 | const { Website } = require("#models/Websites.js");
9 | const { Token } = require("#models/Tokens.js");
10 | const jwt = require("jsonwebtoken");
11 |
12 | exports.walletLogin = async (req, res, next) => {
13 | try {
14 | const errors = validationResult(req).array();
15 | if (errors.length > 0)
16 | throw new Error(errors.map((err) => err.msg).join(", "));
17 |
18 | const { address, wallet } = req.body;
19 |
20 | const userCount = await Member.count({ address });
21 |
22 | let newMember = {
23 | address,
24 | wallet,
25 | };
26 |
27 | if (!userCount) {
28 | const member = new Member(newMember);
29 | await member.save();
30 | }
31 |
32 | const memberData = { address, wallet };
33 | const accessToken = generateAccessToken(memberData);
34 | const refreshToken = generateRefreshToken(memberData);
35 |
36 | const tokenCount = await Token.findOne({ address });
37 |
38 | const newToken = new Token({
39 | address,
40 | refreshToken,
41 | });
42 |
43 | if (!tokenCount) await newToken.save();
44 | else
45 | await Token.updateOne(
46 | { address },
47 | {
48 | $set: {
49 | refreshToken,
50 | },
51 | },
52 | );
53 |
54 | res.status(200).json({ accessToken, refreshToken });
55 | } catch (err) {
56 | next(err);
57 | }
58 | };
59 |
60 | exports.getMemberByAddress = async (req, res, next) => {
61 | try {
62 | const errors = validationResult(req).array();
63 | if (errors.length > 0)
64 | throw new Error(errors.map((err) => err.msg).join(", "));
65 |
66 | const { address } = req.query;
67 |
68 | const member = await Member.findOne({ address });
69 |
70 | res.status(200).json(member);
71 | } catch (err) {
72 | next(err);
73 | }
74 | };
75 |
76 | exports.addUnit = async (req, res, next) => {
77 | try {
78 | const errors = validationResult(req).array();
79 | if (errors.length > 0)
80 | throw new Error(errors.map((err) => err.msg).join(", "));
81 |
82 | const { address, service } = req.body;
83 |
84 | const result = await Member.findOneAndUpdate(
85 | { address },
86 | {
87 | $inc: {
88 | [`services.${service}.units`]: 1,
89 | },
90 | },
91 | {
92 | new: true,
93 | },
94 | );
95 |
96 | res.status(200).json(result);
97 | } catch (err) {
98 | next(err);
99 | }
100 | };
101 |
102 | exports.deductUnit = async (req, res, next) => {
103 | try {
104 | const errors = validationResult(req).array();
105 | if (errors.length > 0)
106 | throw new Error(errors.map((err) => err.msg).join(", "));
107 |
108 | const { address, service } = req.body;
109 |
110 | const result = await Member.findOneAndUpdate(
111 | { address },
112 | {
113 | $inc: {
114 | [`services.${service}.units`]: -1,
115 | },
116 | },
117 | {
118 | new: true,
119 | },
120 | );
121 |
122 | res.status(200).json(result);
123 | } catch (err) {
124 | next(err);
125 | }
126 | };
127 |
128 | exports.updateEmail = async (req, res, next) => {
129 | try {
130 | const errors = validationResult(req).array();
131 | if (errors.length > 0)
132 | throw new Error(errors.map((err) => err.msg).join(", "));
133 |
134 | const { memberId, email } = req.body;
135 |
136 | const result = await Member.findOneAndUpdate(
137 | { _id: memberId },
138 | {
139 | $set: {
140 | email,
141 | },
142 | },
143 | {
144 | new: true,
145 | },
146 | );
147 |
148 | res.status(200).json(result);
149 | } catch (err) {
150 | next(err);
151 | }
152 | };
153 |
154 | exports.logout = async (req, res, next) => {
155 | try {
156 | const errors = validationResult(req).array();
157 | if (errors.length > 0)
158 | throw new Error(errors.map((err) => err.msg).join(", "));
159 |
160 | const { refreshToken } = req.body;
161 |
162 | const result = await Token.deleteOne({ refreshToken });
163 |
164 | res.status(204).json(result);
165 | } catch (err) {
166 | next(err);
167 | }
168 | };
169 |
170 | exports.renewToken = async (req, res, next) => {
171 | try {
172 | const errors = validationResult(req).array();
173 | if (errors.length > 0)
174 | throw new Error(errors.map((err) => err.msg).join(", "));
175 |
176 | const { refreshToken } = req.body;
177 |
178 | const tokenCount = await Token.count({ refreshToken });
179 |
180 | if (!tokenCount)
181 | return res.status(403).json({ message: "Cannot fetch refresh token" });
182 |
183 | jwt.verify(refreshToken, process.env.REFRESH_TOKEN_SECRET, (err, data) => {
184 | if (err)
185 | return res.status(403).json({ message: "Invalid refresh token" });
186 |
187 | const accessToken = generateAccessToken({
188 | address: data.address,
189 | wallet: data.wallet,
190 | });
191 |
192 | res.json({ accessToken: accessToken });
193 | });
194 | } catch (err) {
195 | next(err);
196 | }
197 | };
198 |
199 | exports.delete = async (req, res, next) => {
200 | try {
201 | const errors = validationResult(req).array();
202 | if (errors.length > 0)
203 | throw new Error(errors.map((err) => err.msg).join(", "));
204 |
205 | const { memberId } = req.body;
206 |
207 | await Payment.deleteMany({ memberId });
208 | await Website.deleteMany({ memberId });
209 | const result = await Member.deleteOne({ _id: memberId });
210 |
211 | if (!result) throw new Error("Cannot delete user at the moment");
212 |
213 | res.status(204).json({ message: "Successfully deleted user" });
214 | } catch (err) {
215 | next(err);
216 | }
217 | };
218 |
--------------------------------------------------------------------------------
/src/routes/payment/controller.js:
--------------------------------------------------------------------------------
1 | const { validationResult } = require("express-validator");
2 | const { Payment } = require("#models/Payments.js");
3 | const { Member } = require("#models/Members.js");
4 | const config = require("#config/index.js");
5 | const Stripe = require("stripe");
6 |
7 | const stripe = new Stripe(process.env.STRIPE_SECRET);
8 |
9 | exports.requestSubscription = async (req, res, next) => {
10 | try {
11 | const errors = validationResult(req).array();
12 | if (errors.length > 0)
13 | throw new Error(errors.map((err) => err.msg).join(", "));
14 |
15 | const { service, billingDetails, customerId, metadata } = req.body;
16 |
17 | let customer;
18 | if (customerId !== "none") {
19 | customer = await stripe.customers.retrieve(customerId);
20 | } else if (customerId === "none") {
21 | customer = await stripe.customers.create({
22 | name: billingDetails.name,
23 | email: billingDetails.email,
24 | address: billingDetails.address,
25 | metadata,
26 | });
27 |
28 | await Member.findOneAndUpdate(
29 | { address: metadata.walletAddress },
30 | {
31 | $set: {
32 | customerId: customer.id,
33 | },
34 | },
35 | );
36 | }
37 |
38 | const subscription = await stripe.subscriptions.create({
39 | customer: customer.id,
40 | items: [
41 | {
42 | price: config.stripe.products[service].priceId,
43 | },
44 | ],
45 | payment_behavior: "default_incomplete",
46 | payment_settings: {
47 | save_default_payment_method: "on_subscription",
48 | },
49 | expand: ["latest_invoice.payment_intent"],
50 | });
51 |
52 | res.status(200).json({
53 | subscriptionId: subscription.id,
54 | clientSecret: subscription.latest_invoice.payment_intent.client_secret,
55 | });
56 | } catch (err) {
57 | next(err);
58 | }
59 | };
60 |
61 | exports.requestPayment = async (req, res, next) => {
62 | try {
63 | const errors = validationResult(req).array();
64 | if (errors.length > 0)
65 | throw new Error(errors.map((err) => err.msg).join(", "));
66 |
67 | const { billingDetails, amount, customerId, metadata } = req.body;
68 |
69 | let customer;
70 | if (customerId !== "none") {
71 | customer = await stripe.customers.retrieve(customerId);
72 | } else if (customerId === "none") {
73 | customer = await stripe.customers.create({
74 | name: billingDetails.name,
75 | email: billingDetails.email,
76 | address: billingDetails.address,
77 | metadata,
78 | });
79 |
80 | await Member.findOneAndUpdate(
81 | { address: metadata.walletAddress },
82 | {
83 | $set: {
84 | customerId: customer.id,
85 | },
86 | },
87 | );
88 | }
89 |
90 | const paymentIntent = await stripe.paymentIntents.create({
91 | amount: amount * 100,
92 | currency: "usd",
93 | receipt_email: billingDetails.email,
94 | customer: customer.id,
95 | });
96 |
97 | res.status(200).json({
98 | clientSecret: paymentIntent.client_secret,
99 | });
100 | } catch (err) {
101 | next(err);
102 | }
103 | };
104 |
105 | exports.addPayment = async (req, res, next) => {
106 | try {
107 | const errors = validationResult(req).array();
108 | if (errors.length > 0)
109 | throw new Error(errors.map((err) => err.msg).join(", "));
110 |
111 | const { memberId, hash, service, price } = req.body;
112 |
113 | const newPayment = {
114 | memberId,
115 | hash,
116 | service,
117 | price,
118 | };
119 |
120 | const payment = new Payment(newPayment);
121 |
122 | const result = await payment.save({ ordered: false });
123 |
124 | res.status(200).json(result);
125 | } catch (err) {
126 | next(err);
127 | }
128 | };
129 |
130 | exports.getPayments = async (req, res, next) => {
131 | try {
132 | const errors = validationResult(req).array();
133 | if (errors.length > 0)
134 | throw new Error(errors.map((err) => err.msg).join(", "));
135 |
136 | const { memberId, pageNumber } = req.query;
137 |
138 | const page = parseInt(pageNumber);
139 |
140 | const resultCount = await Payment.count({ memberId });
141 | const result = await Payment.find({ memberId })
142 | .sort({ createdAt: "desc" })
143 | .skip(page * 5)
144 | .limit(5);
145 |
146 | const pageCountPartial = parseFloat(resultCount / 5);
147 | const isWholeNumber = pageCountPartial % 1 === 0.5;
148 | const finalPageCount = Math.trunc(pageCountPartial) + 1;
149 |
150 | const data = {
151 | payments: result,
152 | totalItems: resultCount,
153 | totalPages: isWholeNumber ? pageCountPartial : finalPageCount,
154 | currentPage: page,
155 | };
156 |
157 | res.status(200).json(data);
158 | } catch (err) {
159 | next(err);
160 | }
161 | };
162 |
163 | exports.getSubscriptions = async (req, res, next) => {
164 | try {
165 | const errors = validationResult(req).array();
166 | if (errors.length > 0)
167 | throw new Error(errors.map((err) => err.msg).join(", "));
168 |
169 | const { customerId } = req.query;
170 |
171 | const customer = await stripe.customers.retrieve(customerId, {
172 | expand: ["subscriptions"],
173 | });
174 |
175 | const subscriptions = customer.subscriptions.data;
176 |
177 | res
178 | .status(200)
179 | .json(subscriptions.filter((sub) => sub.cancel_at_period_end === false));
180 | } catch (err) {
181 | next(err);
182 | }
183 | };
184 |
185 | exports.cancelSubscription = async (req, res, next) => {
186 | try {
187 | const errors = validationResult(req).array();
188 | if (errors.length > 0)
189 | throw new Error(errors.map((err) => err.msg).join(", "));
190 |
191 | const { subscriptionId } = req.body;
192 |
193 | const result = await stripe.subscriptions.update(subscriptionId, {
194 | cancel_at_period_end: true,
195 | });
196 |
197 | res.status(200).json(result);
198 | } catch (err) {
199 | next(err);
200 | }
201 | };
202 |
203 | exports.getSubscription = async (req, res, next) => {
204 | try {
205 | const errors = validationResult(req).array();
206 | if (errors.length > 0)
207 | throw new Error(errors.map((err) => err.msg).join(", "));
208 |
209 | const { subscriptionId } = req.query;
210 |
211 | const result = await stripe.subscriptions.retrieve(subscriptionId);
212 |
213 | res.status(200).json(result);
214 | } catch (err) {
215 | next(err);
216 | }
217 | };
218 |
--------------------------------------------------------------------------------
/src/routes/website/controller.js:
--------------------------------------------------------------------------------
1 | const { validationResult } = require("express-validator");
2 | const { Website } = require("#models/Websites.js");
3 | const { Member } = require("#models/Members.js");
4 | const { VerifyDns } = require("#middlewares/tools.js");
5 |
6 | exports.createWebsite = async (req, res, next) => {
7 | try {
8 | const errors = validationResult(req).array();
9 | if (errors.length > 0)
10 | throw new Error(errors.map((err) => err.msg).join(", "));
11 |
12 | const { memberId, components, meta, route } = req.body;
13 |
14 | const count = await Website.count({ route });
15 |
16 | if (count > 0) throw new Error("Subdomain already exists");
17 |
18 | let newWebsite = {
19 | route,
20 | components,
21 | meta,
22 | };
23 |
24 | if (memberId) {
25 | newWebsite.memberId = memberId;
26 | const user = await Member.findOne({ _id: memberId });
27 | if (user.services.website.units === 1) {
28 | const webArr = await Website.find({ memberId });
29 | if (webArr.length > 0) {
30 | newWebsite.isPremium = true;
31 | newWebsite.subscriptionId = webArr[0].subscriptionId;
32 | newWebsite.premiumStartDate = webArr[0].premiumStartDate;
33 | } else {
34 | await Member.findOneAndUpdate(
35 | { _id: memberId },
36 | {
37 | $set: {
38 | "services.website.units": 0,
39 | },
40 | },
41 | );
42 | }
43 | }
44 | }
45 |
46 | const website = new Website(newWebsite);
47 | const result = await website.save();
48 |
49 | res.status(200).json(result);
50 | } catch (err) {
51 | next(err);
52 | }
53 | };
54 |
55 | exports.deleteWebsite = async (req, res, next) => {
56 | try {
57 | const errors = validationResult(req).array();
58 | if (errors.length > 0)
59 | throw new Error(errors.map((err) => err.msg).join(", "));
60 |
61 | const { websiteId } = req.body;
62 |
63 | const result = await Website.deleteOne({ _id: websiteId });
64 |
65 | res.status(200).json(result);
66 | } catch (err) {
67 | next(err);
68 | }
69 | };
70 |
71 | exports.getWebsiteByRoute = async (req, res, next) => {
72 | try {
73 | const errors = validationResult(req).array();
74 | if (errors.length > 0)
75 | throw new Error(errors.map((err) => err.msg).join(", "));
76 |
77 | const { route } = req.query;
78 |
79 | const result = await Website.findOne({ route });
80 |
81 | res.status(200).json(result);
82 | } catch (err) {
83 | next(err);
84 | }
85 | };
86 |
87 | exports.getWebsiteByDomain = async (req, res, next) => {
88 | try {
89 | const errors = validationResult(req).array();
90 | if (errors.length > 0)
91 | throw new Error(errors.map((err) => err.msg).join(", "));
92 |
93 | const { domain } = req.query;
94 |
95 | const result = await Website.findOne({ "custom.domain": domain });
96 |
97 | res.status(200).json(result);
98 | } catch (err) {
99 | next(err);
100 | }
101 | };
102 |
103 | exports.getWebsites = async (req, res, next) => {
104 | try {
105 | const errors = validationResult(req).array();
106 | if (errors.length > 0)
107 | throw new Error(errors.map((err) => err.msg).join(", "));
108 |
109 | const { memberId } = req.query;
110 |
111 | const result = await Website.find({ memberId });
112 |
113 | res.status(200).json(result);
114 | } catch (err) {
115 | next(err);
116 | }
117 | };
118 |
119 | exports.updateData = async (req, res, next) => {
120 | try {
121 | const errors = validationResult(req).array();
122 | if (errors.length > 0)
123 | throw new Error(errors.map((err) => err.msg).join(", "));
124 |
125 | const { websiteId, data } = req.body;
126 |
127 | const result = await Website.findOneAndUpdate(
128 | { _id: websiteId },
129 | {
130 | $set: {
131 | data,
132 | },
133 | },
134 | {
135 | new: true,
136 | },
137 | );
138 |
139 | res.status(200).json(result);
140 | } catch (err) {
141 | next(err);
142 | }
143 | };
144 |
145 | exports.updateIsPremium = async (req, res, next) => {
146 | try {
147 | const errors = validationResult(req).array();
148 | if (errors.length > 0)
149 | throw new Error(errors.map((err) => err.msg).join(", "));
150 |
151 | const { websiteId, isPremium } = req.body;
152 |
153 | const result = await Website.findOneAndUpdate(
154 | { _id: websiteId },
155 | {
156 | $set: {
157 | isPremium,
158 | },
159 | },
160 | {
161 | new: true,
162 | },
163 | );
164 |
165 | res.status(200).json(result);
166 | } catch (err) {
167 | next(err);
168 | }
169 | };
170 |
171 | exports.updateIsExpired = async (req, res, next) => {
172 | try {
173 | const errors = validationResult(req).array();
174 | if (errors.length > 0)
175 | throw new Error(errors.map((err) => err.msg).join(", "));
176 |
177 | const { websiteId, isExpired } = req.body;
178 |
179 | const result = await Website.findOneAndUpdate(
180 | { _id: websiteId },
181 | {
182 | $set: {
183 | isExpired,
184 | },
185 | },
186 | {
187 | new: true,
188 | },
189 | );
190 |
191 | res.status(200).json(result);
192 | } catch (err) {
193 | next(err);
194 | }
195 | };
196 |
197 | exports.updateIsPublished = async (req, res, next) => {
198 | try {
199 | const errors = validationResult(req).array();
200 | if (errors.length > 0)
201 | throw new Error(errors.map((err) => err.msg).join(", "));
202 |
203 | const { websiteId, isPublished } = req.body;
204 |
205 | const result = await Website.findOneAndUpdate(
206 | { _id: websiteId },
207 | {
208 | $set: {
209 | isPublished,
210 | },
211 | },
212 | {
213 | new: true,
214 | },
215 | );
216 |
217 | res.status(200).json(result);
218 | } catch (err) {
219 | next(err);
220 | }
221 | };
222 |
223 | exports.updatePremiumStartDate = async (req, res, next) => {
224 | try {
225 | const errors = validationResult(req).array();
226 | if (errors.length > 0)
227 | throw new Error(errors.map((err) => err.msg).join(", "));
228 |
229 | const { websiteId, premiumStartDate } = req.body;
230 |
231 | const result = await Website.findOneAndUpdate(
232 | { _id: websiteId },
233 | {
234 | $set: {
235 | premiumStartDate,
236 | },
237 | },
238 | {
239 | new: true,
240 | },
241 | );
242 |
243 | res.status(200).json(result);
244 | } catch (err) {
245 | next(err);
246 | }
247 | };
248 |
249 | exports.updateRevealDate = async (req, res, next) => {
250 | try {
251 | const errors = validationResult(req).array();
252 | if (errors.length > 0)
253 | throw new Error(errors.map((err) => err.msg).join(", "));
254 |
255 | const { websiteId, revealDate } = req.body;
256 |
257 | const result = await Website.findOneAndUpdate(
258 | { _id: websiteId },
259 | {
260 | $set: {
261 | revealDate,
262 | },
263 | },
264 | {
265 | new: true,
266 | },
267 | );
268 |
269 | res.status(200).json(result);
270 | } catch (err) {
271 | next(err);
272 | }
273 | };
274 |
275 | exports.updateRoute = async (req, res, next) => {
276 | try {
277 | const errors = validationResult(req).array();
278 | if (errors.length > 0)
279 | throw new Error(errors.map((err) => err.msg).join(", "));
280 |
281 | const { websiteId, route } = req.body;
282 |
283 | const count = await Website.count({ route });
284 |
285 | if (count > 0) throw new Error("Subdomain already exists");
286 |
287 | const result = await Website.findOneAndUpdate(
288 | { _id: websiteId },
289 | {
290 | $set: {
291 | route,
292 | },
293 | },
294 | {
295 | new: true,
296 | },
297 | );
298 |
299 | res.status(200).json(result);
300 | } catch (err) {
301 | next(err);
302 | }
303 | };
304 |
305 | exports.updateTitle = async (req, res, next) => {
306 | try {
307 | const errors = validationResult(req).array();
308 | if (errors.length > 0)
309 | throw new Error(errors.map((err) => err.msg).join(", "));
310 |
311 | const { websiteId, title } = req.body;
312 |
313 | const result = await Website.findOneAndUpdate(
314 | { _id: websiteId },
315 | {
316 | $set: {
317 | "components.title": title,
318 | },
319 | },
320 | {
321 | new: true,
322 | },
323 | );
324 |
325 | res.status(200).json(result);
326 | } catch (err) {
327 | next(err);
328 | }
329 | };
330 |
331 | exports.updateDescription = async (req, res, next) => {
332 | try {
333 | const errors = validationResult(req).array();
334 | if (errors.length > 0)
335 | throw new Error(errors.map((err) => err.msg).join(", "));
336 |
337 | const { websiteId, description } = req.body;
338 |
339 | const result = await Website.findOneAndUpdate(
340 | { _id: websiteId },
341 | {
342 | $set: {
343 | "components.description": description,
344 | },
345 | },
346 | {
347 | new: true,
348 | },
349 | );
350 |
351 | res.status(200).json(result);
352 | } catch (err) {
353 | next(err);
354 | }
355 | };
356 |
357 | exports.updateLogo = async (req, res, next) => {
358 | try {
359 | const errors = validationResult(req).array();
360 | if (errors.length > 0)
361 | throw new Error(errors.map((err) => err.msg).join(", "));
362 |
363 | const { websiteId, logo } = req.body;
364 |
365 | const result = await Website.findOneAndUpdate(
366 | { _id: websiteId },
367 | {
368 | $set: {
369 | "components.unrevealedImage": logo,
370 | },
371 | },
372 | {
373 | new: true,
374 | },
375 | );
376 |
377 | res.status(200).json(result);
378 | } catch (err) {
379 | next(err);
380 | }
381 | };
382 |
383 | exports.updateScript = async (req, res, next) => {
384 | try {
385 | const errors = validationResult(req).array();
386 | if (errors.length > 0)
387 | throw new Error(errors.map((err) => err.msg).join(", "));
388 |
389 | const { websiteId, script } = req.body;
390 |
391 | const result = await Website.findOneAndUpdate(
392 | { _id: websiteId },
393 | {
394 | $set: {
395 | "components.script": script,
396 | },
397 | },
398 | {
399 | new: true,
400 | },
401 | );
402 |
403 | res.status(200).json(result);
404 | } catch (err) {
405 | next(err);
406 | }
407 | };
408 |
409 | exports.updateEmbed = async (req, res, next) => {
410 | try {
411 | const errors = validationResult(req).array();
412 | if (errors.length > 0)
413 | throw new Error(errors.map((err) => err.msg).join(", "));
414 |
415 | const { websiteId, embed } = req.body;
416 |
417 | const result = await Website.findOneAndUpdate(
418 | { _id: websiteId },
419 | {
420 | $set: {
421 | "components.embed": embed,
422 | },
423 | },
424 | {
425 | new: true,
426 | },
427 | );
428 |
429 | res.status(200).json(result);
430 | } catch (err) {
431 | next(err);
432 | }
433 | };
434 |
435 | exports.addAddon = async (req, res, next) => {
436 | try {
437 | const errors = validationResult(req).array();
438 | if (errors.length > 0)
439 | throw new Error(errors.map((err) => err.msg).join(", "));
440 |
441 | const { websiteId, addon } = req.body;
442 |
443 | const result = await Website.findOneAndUpdate(
444 | { _id: websiteId },
445 | {
446 | $push: {
447 | "components.addons": addon,
448 | },
449 | },
450 | {
451 | new: true,
452 | },
453 | );
454 |
455 | res.status(200).json(result);
456 | } catch (err) {
457 | next(err);
458 | }
459 | };
460 |
461 | exports.deleteAddon = async (req, res, next) => {
462 | try {
463 | const errors = validationResult(req).array();
464 | if (errors.length > 0)
465 | throw new Error(errors.map((err) => err.msg).join(", "));
466 |
467 | const { websiteId, addon } = req.body;
468 |
469 | const result = await Website.findOneAndUpdate(
470 | { _id: websiteId },
471 | {
472 | $pull: {
473 | "components.addons": addon,
474 | },
475 | },
476 | {
477 | new: true,
478 | },
479 | );
480 |
481 | res.status(200).json(result);
482 | } catch (err) {
483 | next(err);
484 | }
485 | };
486 |
487 | exports.updateTemplate = async (req, res, next) => {
488 | try {
489 | const errors = validationResult(req).array();
490 | if (errors.length > 0)
491 | throw new Error(errors.map((err) => err.msg).join(", "));
492 |
493 | const { websiteId, template } = req.body;
494 |
495 | const result = await Website.findOneAndUpdate(
496 | { _id: websiteId },
497 | {
498 | $set: {
499 | "components.template": template,
500 | },
501 | },
502 | {
503 | new: true,
504 | },
505 | );
506 |
507 | res.status(200).json(result);
508 | } catch (err) {
509 | next(err);
510 | }
511 | };
512 |
513 | exports.updateRobot = async (req, res, next) => {
514 | try {
515 | const errors = validationResult(req).array();
516 | if (errors.length > 0)
517 | throw new Error(errors.map((err) => err.msg).join(", "));
518 |
519 | const { websiteId, robot } = req.body;
520 |
521 | const result = await Website.findOneAndUpdate(
522 | { _id: websiteId },
523 | {
524 | $set: {
525 | "meta.robot": robot,
526 | },
527 | },
528 | {
529 | new: true,
530 | },
531 | );
532 |
533 | res.status(200).json(result);
534 | } catch (err) {
535 | next(err);
536 | }
537 | };
538 |
539 | exports.updateFavicon = async (req, res, next) => {
540 | try {
541 | const errors = validationResult(req).array();
542 | if (errors.length > 0)
543 | throw new Error(errors.map((err) => err.msg).join(", "));
544 |
545 | const { websiteId, favicon } = req.body;
546 |
547 | const result = await Website.findOneAndUpdate(
548 | { _id: websiteId },
549 | {
550 | $set: {
551 | "meta.favicon": favicon,
552 | },
553 | },
554 | {
555 | new: true,
556 | },
557 | );
558 |
559 | res.status(200).json(result);
560 | } catch (err) {
561 | next(err);
562 | }
563 | };
564 |
565 | exports.updateLanguage = async (req, res, next) => {
566 | try {
567 | const errors = validationResult(req).array();
568 | if (errors.length > 0)
569 | throw new Error(errors.map((err) => err.msg).join(", "));
570 |
571 | const { websiteId, language } = req.body;
572 |
573 | const result = await Website.findOneAndUpdate(
574 | { _id: websiteId },
575 | {
576 | $set: {
577 | "meta.language": language,
578 | },
579 | },
580 | {
581 | new: true,
582 | },
583 | );
584 |
585 | res.status(200).json(result);
586 | } catch (err) {
587 | next(err);
588 | }
589 | };
590 |
591 | exports.updateExternalLink = async (req, res, next) => {
592 | try {
593 | const errors = validationResult(req).array();
594 | if (errors.length > 0)
595 | throw new Error(errors.map((err) => err.msg).join(", "));
596 |
597 | const { websiteId, social, link } = req.body;
598 |
599 | const result = await Website.findOneAndUpdate(
600 | { _id: websiteId },
601 | {
602 | $set: {
603 | [`externalLinks.${social}`]: link,
604 | },
605 | },
606 | {
607 | new: true,
608 | },
609 | );
610 |
611 | res.status(200).json(result);
612 | } catch (err) {
613 | next(err);
614 | }
615 | };
616 |
617 | exports.updateDomain = async (req, res, next) => {
618 | try {
619 | const errors = validationResult(req).array();
620 | if (errors.length > 0)
621 | throw new Error(errors.map((err) => err.msg).join(", "));
622 |
623 | const { websiteId, domain } = req.body;
624 |
625 | const verification = await VerifyDns(domain);
626 | if (!verification.status) throw new Error(verification.message);
627 |
628 | // this only add example.com not www.example.com
629 | const domainRes = await fetch(
630 | `https://api.vercel.com/v9/projects/${process.env.VERCEL_PROJECT_ID}/domains`,
631 | {
632 | body: `{\n "name": "${domain}"\n}`,
633 | headers: {
634 | Authorization: `Bearer ${process.env.VERCEL_AUTH_TOKEN}`,
635 | "Content-Type": "application/json",
636 | },
637 | method: "POST",
638 | },
639 | );
640 |
641 | const domainData = await domainRes.json();
642 |
643 | if (domainData.error) {
644 | throw new Error(domainData.error.message);
645 | }
646 |
647 | if (!domainData.verified)
648 | throw new Error("Make sure your domain uses right nameservers.");
649 |
650 | const domainCount = await Website.count({ "custom.domain": domain });
651 | if (domainCount > 0) throw new Error("Domain already used.");
652 |
653 | const result = await Website.findOneAndUpdate(
654 | { _id: websiteId },
655 | {
656 | $set: {
657 | "custom.domain": domain,
658 | },
659 | },
660 | {
661 | new: true,
662 | },
663 | );
664 |
665 | res.status(200).json(result);
666 | } catch (err) {
667 | next(err);
668 | }
669 | };
670 |
671 | exports.verifyDomain = async (req, res, next) => {
672 | try {
673 | const errors = validationResult(req).array();
674 | if (errors.length > 0)
675 | throw new Error(errors.map((err) => err.msg).join(", "));
676 |
677 | const { domain } = req.body;
678 |
679 | const result = await VerifyDns(domain);
680 |
681 | res.status(200).json(result);
682 | } catch (err) {
683 | next(err);
684 | }
685 | };
686 |
687 | exports.updateSubscription = async (req, res, next) => {
688 | try {
689 | const errors = validationResult(req).array();
690 | if (errors.length > 0)
691 | throw new Error(errors.map((err) => err.msg).join(", "));
692 |
693 | const {
694 | memberId,
695 | subscriptionId,
696 | isPremium,
697 | isExpired,
698 | isPublished,
699 | premiumStartDate,
700 | premiumEndDate,
701 | } = req.body;
702 |
703 | let userWebsites = await Website.find({ memberId });
704 | const userWebsitesIdArr = userWebsites.map((web) => web._id);
705 |
706 | await Website.updateMany(
707 | {
708 | _id: {
709 | $in: userWebsitesIdArr,
710 | },
711 | },
712 | {
713 | $set: {
714 | subscriptionId,
715 | isPremium,
716 | isExpired,
717 | isPublished,
718 | premiumStartDate,
719 | premiumEndDate,
720 | },
721 | },
722 | );
723 |
724 | if (isExpired) {
725 | await Member.findOneAndUpdate(
726 | { _id: memberId },
727 | {
728 | $set: {
729 | "services.website.units": 0,
730 | },
731 | },
732 | );
733 | }
734 |
735 | userWebsites = await Website.find({ memberId });
736 |
737 | res.status(200).json(userWebsites);
738 | } catch (err) {
739 | next(err);
740 | }
741 | };
742 |
743 | exports.getMappedSubdomains = async (req, res, next) => {
744 | try {
745 | const errors = validationResult(req).array();
746 | if (errors.length > 0)
747 | throw new Error(errors.map((err) => err.msg).join(", "));
748 |
749 | const websites = await Website.find({
750 | route: {
751 | $exists: true,
752 | $ne: "",
753 | },
754 | });
755 |
756 | const mappedSubdomains = websites.map((web) => {
757 | return { params: { siteRoute: web.route } };
758 | });
759 |
760 | res.status(200).json(mappedSubdomains);
761 | } catch (err) {
762 | next(err);
763 | }
764 | };
765 |
--------------------------------------------------------------------------------