├── .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 | NFTHost Logo 4 | 5 |

6 | 7 |

NFT Host - Backend

8 | 9 |

10 | Docker Deployment 11 | 12 | NFTHost Discord 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 | ![Technologies](https://skillicons.dev/icons?i=nodejs,express,nextjs,vercel,mongodb,docker,sass,git&theme=light) 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 | --------------------------------------------------------------------------------