DEV Community

spiritupbro
spiritupbro

Posted on • Edited on

Create An Open Source Private Marketplace With IBM Cloud

Create An Open Source Private Marketplace With IBM Cloud

Introduction

Today im gonna show you how to make an ecommerce website using IBM cloud. I wish after this tutorial you can make the same or much more beautiful ecommerce website using IBM Cloud only, because IBM cloud has so much feature that you need to run a beautiful ecommerce website. The ecommerce i want to create here is a marketplace of private repo of github. Why i want to create a marketplace for it? while github is an open source community. The reason of this is, because i really love open source and if you use github you already know all of this open source maintainer is really struggling with their life some of them is a really great developer and make a great open source contribution but rarely getting sponsored, by creating this ecommerce of collection of private repo i hope they have some kind of way to monetize their open source especially the open source that can't be open to the public because for example an exclusivity of it like in public repo it doesn't have face recognition feature but in private repo it has face recognition feature. So i hope you enjoy this tutorial let's get started.

Prerequisites

To complete this tutorial you will need :

  • A local development environment for Node.js. Follow How to Install Node.js and Create a Local Development Environment.
  • A text editor like Visual Studio Code or Atom.
  • A web browser like Firefox or Chrome.
  • Familiarity with JavaScript. You can look at the How To Code in JavaScript series to learn more.
  • Familiarity with Nuxtjs. You can take a look at Nuxtjs official documentation here.
  • Familiarity with Vuejs. You can take a look at Vuejs official documentation here.
  • Familiarity with Typescript. You can take a look at Typescript official documentation here.
  • Familiarity with Nuxtjs Typescript. You can take a look at Nuxtjs Typescript official documentation here.
  • Sign up for a free IBM Cloud account here
  • Cloud Object Storage (for hosting static web sites, storing data, backups) here
  • Functions (for serverless code) here
  • Cloudant (NoSQL DB) here
  • Github Account here
  • Paypal Developer Account here
  • Clone this code from github to make you much more understand about some of the code i create here.

Step 1 - Create A Bucket In IBM COS

Create a bucket using setting like this so that we can serve static website from our storage :

bucket name

Name your bucket, pick Cross Region and ap-geo as location and use smart tier as storage class now we want also to use this as a static website so add rule on the static website rule like this :

static webhost

Save it and create bucket now we need to create service credential to upload much more easily using rclone if you haven't download rclone use this tutorial to get started, now after you create your bucket click on service credentials menu on the left hand side of your screen then create service credential like this :

service credential

Then you will get access key id and secret like this :

access key

copy paste that for the rclone config so that we can use rclone config to easily upload our frontend file later use this config:

type = s3
provider = IBMCOS
env_auth = false
access_key_id = xxxxxx
secret_access_key = xxxxxxxxx
endpoint = s3.ap.cloud-object-storage.appdomain.cloud
location_constraint = ap-standard
acl = private
Enter fullscreen mode Exit fullscreen mode

Step 2 - Create A Frontend And Deploy It To IBM Cloud Object Storage

First of all let's create a simple frontend by using nuxt-typescript run this code in your terminal :

yarn create nuxt-app frontend
Enter fullscreen mode Exit fullscreen mode

Create your nuxt app using this setting :

? Project name: frontend
? Programming language: TypeScript
? Package manager: Yarn
? UI framework: Vuetify.js
? Nuxt.js modules: Axios
? Linting tools: ESLint, Prettier
? Testing framework: Jest
? Rendering mode: Universal (SSR / SSG)
? Deployment target: Static (Static/JAMStack hosting)
? Development tools: (Press <space> to select, <a> to toggle all, <i> to invert selection)
? Continuous integration: None
? Version control system: None
Enter fullscreen mode Exit fullscreen mode

Run the app using yarn dev if you see this screen, then you're successfully create the nuxt application :

homepage

Now we need to create our frontend to suit our needs first let's remove all of the html in pages/index.vue so it will look like this in pages folder :

delete indexvue

Also remove all the component in components folder, and create Home.vue for our homepage like this :

homepage

Now we need to install nuxt-property-decorator, to make our component much more tidier install the package using this command :

yarn add nuxt-property-decorator
Enter fullscreen mode Exit fullscreen mode

Now go to components/Home.vue and let's create our homepage, we are using vuetify here so we gonna use vuetify component as well to create our homepage so lets create our homepage like this :

<template>
  <div :class="['mx-5', 'my-5']">
    <v-row justify="center">
      <div
        :class="[`text-h1`, 'text-center']"
        v-text="'Open Source Private Marketplace'"
      ></div>
    </v-row>
    <v-row justify="center">
      <div
        :class="[`text-h3`, 'px-5', 'text-center', 'my-5']"
        v-text="'Here you can buy or sell open source private repo'"
      ></div>
    </v-row>
    <v-row justify="center" :class="['my-2']">
      <v-btn :class="['my-2']" color="info" elevation="2">LOGIN</v-btn>
      <v-btn :class="['my-2', 'mx-2']" color="error" elevation="2"
        >LOGOUT</v-btn
      >
    </v-row>
    <v-row>
      <v-card class="mx-auto my-12" max-width="374" :key="i" v-for="i in 6">
        <v-img
          height="250"
          src="https://cdn.vuetifyjs.com/images/cards/cooking.png"
        ></v-img>

        <v-card-title>Fast Bitcoin Miner</v-card-title>

        <v-card-text>
          <v-row align="center" class="mx-0">
            <v-rating
              :value="4.5"
              color="amber"
              dense
              half-increments
              readonly
              size="14"
            ></v-rating>

            <div class="grey--text ml-4">4.5 (413)</div>
          </v-row>

          <div>This is a private repo that can mine bitcoin super fast</div>
        </v-card-text>

        <v-divider class="mx-4"></v-divider>

        <v-card-actions>
          <v-btn color="deep-purple lighten-2" text> Detail </v-btn>
          <v-btn color="deep-purple lighten-2" text> Buy Now </v-btn>
        </v-card-actions>
      </v-card>
    </v-row>
  </div>
</template>
<script lang="ts">
import { Component, Vue } from 'nuxt-property-decorator'
@Component
export default class MyStore extends Vue {}
</script>
Enter fullscreen mode Exit fullscreen mode

Now go to your pages/index.vue and import this

<template>
  <Home />
</template>
<script lang="ts">
import Home from '@/components/Home.vue'
import { Component, Vue } from 'nuxt-property-decorator'
@Component({
  components: {
    Home,
  },
})
export default class MyStore extends Vue {}
</script>
Enter fullscreen mode Exit fullscreen mode

After that go to layouts/default.vue and change the whole content like this :

<template>
  <v-app >
<nuxt/>
  </v-app>
</template>
Enter fullscreen mode Exit fullscreen mode

Go to the browser and go to http://localhost:3000 and if its successful you will see something like this :

homepage

Now let's upload it to IBM cloud storage using rclone :

yarn generate
cd dist
rclone copy ./ <remote name>:<bucket name>
Enter fullscreen mode Exit fullscreen mode

Find your static website url under bucket configuration like this :

configuration menu

static website url

Create public access for your static website like this on access policies menu :

access policy

Then open it up in browser like this :

homepage preview

Step 3 - Create Login Page To Authorize User Github Account

After you create homepage let's create login page this login page is used for login to github and get the private repo create pages/login.vue and add this code :

<template>
  <Login />
</template>
<script lang="ts">
import Login from '@/components/Login.vue'
import { Component, Vue } from 'nuxt-property-decorator'
@Component({
  components: {
    Login,
  },
})
export default class MyStore extends Vue {}
</script>
Enter fullscreen mode Exit fullscreen mode

We need to use fontawesome icon to make our login page more beautiful so download this using this command :

yarn add -D @fortawesome/fontawesome-free
Enter fullscreen mode Exit fullscreen mode

Modify nuxt.config.js adding to the vuetify section.

vuetify: {
    defaultAssets: {icons: 'fa'},
Enter fullscreen mode Exit fullscreen mode

Now create components/Login.vue and add this code :

<template>

   <v-row justify="center" align="center">
<v-card>
  <v-card-text>
    <v-row :class="['my-3','mx-3','text-h4']">LOGIN USING YOUR GITHUB ACCOUNT</v-row>
    <v-row align="center" justify="center">
      <v-btn><v-icon class="mx-2 my-2">fab fa-github</v-icon>LOGIN</v-btn>
    </v-row>
  </v-card-text>
</v-card>
   </v-row>

</template>
<script lang="ts">
import { Component, Vue } from 'nuxt-property-decorator'
@Component
export default class MyStore extends Vue {}
</script>
Enter fullscreen mode Exit fullscreen mode

Go to http://localhost:3000/login and you will see your login page look something like this :

login page

Now we need to create github oauth apps so that our application can login throught github and get personal repo of a user if you haven't create one follow the instruction here pick oauth apps because we just need the oauth to get the user private repo :

oauth apps

For the authorization url we gonna use IBM Cloud Function and IBM Cloudant NoSQL DB so that we can store the access token to be used later, so in order to do this install IBM Cli using this instruction if you have installed it login to your console and go to this link follow the instruction there and if there is an error like this :

error region

That means you haven't setup a region to determine which region you're based on go to the top of this link and you can find your region under namespace like this :

region

You can find your region name by using command ibmcloud regions then after that set your region by using this command :

ibmcloud target -r <region code>
ibmcloud target -o <org> -s <space>
ibmcloud wsk property get --auth
Enter fullscreen mode Exit fullscreen mode

Now we gonna install serverless cli to upload our function much more easier go ahead and run this command in your command line :

npm install -g serverless
Enter fullscreen mode Exit fullscreen mode

Now go to the root of your folder and type this command :

serverless create --template openwhisk-nodejs --path functions
cd functions
Enter fullscreen mode Exit fullscreen mode

Now let's rename handler.js into authorize.js so that it's easier for us to remember that this function is for authorize github account before we create our function let's create cloudant db first if you haven't create one follow this tutorial to get started don't forget to create credential for your cloudant db by clicking service credential menu on cloudant db dashboard :

cloudant service account

After that run this command to select group :

# you can find group name by running command `ibmcloud resource groups`
ibmcloud target -g <group name>
Enter fullscreen mode Exit fullscreen mode

Then now in functions folder change the code of serverless.yml file into this :

service: authorize
useDotenv: true
provider:
  name: openwhisk
  ignore_certs: true

functions:
  authorize-github:
    handler: authorize.github
    parameters:
      github_client_id: ${env:github_client_id}
      github_client_secret: ${env:github_client_secret}
    bind:
      - service:
          name: cloudantnosqldb
          instance: Cloudant-vm
    events:
      - http: GET /authorize

plugins:
  - serverless-openwhisk
  - serverless-dotenv-plugin
Enter fullscreen mode Exit fullscreen mode

Create a file called .env then add this line :

github_client_id=<github client_id>
github_client_secret=<github client_secret>
token_pass=<token password for jwt token could be random>
frontend_url=<url of your static web hosting>
Enter fullscreen mode Exit fullscreen mode

Let's download the necessary dependencies first :

yarn add axios
yarn add -D serverless-dotenv-plugin serverless-openwhisk
Enter fullscreen mode Exit fullscreen mode

Now let's create function to authorize our user open authorize.js and change the code to this one :

"use strict";
const axios = require("axios");
const jwt = require("jsonwebtoken");
const formUrlEncoded = (x) =>
  Object.keys(x).reduce((p, c) => p + `&${c}=${encodeURIComponent(x[c])}`, "");
const github = async function (params) {
  const cloudant = require("@cloudant/cloudant")({
    url: params.__bx_creds.cloudantnosqldb.url,
    plugins: [
      { iamauth: { iamApiKey: params.__bx_creds.cloudantnosqldb.apikey } },
    ],
  });
  if (params.code) {
    const { access_token } = await authenticate({ params });
    const { login,email } = await getUsername({ access_token });
    const {token} = await checkUsername({
      email,
      username: login,
      cloudant,
      access_token,
      token_pass: params.token_pass,
    });

    if(token){
      return  {
        headers: { location: `${params.frontend_url}/dashboard` },
        statusCode: 302
        };
    }else{
      return  {
        headers: { location: params.frontend_url },
        statusCode: 302
        };
    }

  } else {
    return {
      headers: { location: `${params.frontend_url}/login` },
      statusCode: 302
      };
  }
};
module.exports.github = github;
async function authenticate({ params }) {
  const { data } = await axios({
    url: "https://github.com/login/oauth/access_token",
    headers: {
      "Content-Type": "application/x-www-form-urlencoded",
      Accept: "application/json",
    },
    data: formUrlEncoded({
      client_id: params.github_client_id,
      client_secret: params.github_client_secret,
      code: params.code,
    }),
    method: "POST",
  });
  return data;
}
async function getUsername({ access_token }) {
  const { data } = await axios({
    url: "https://api.github.com/user",
    headers: {
      Authorization: `token ${access_token}`,
      Accept: "application/vnd.github.v3+json",
    },
    method: "GET",
  });
  return data;
}
async function checkUsername({ username, access_token, cloudant, token_pass,email }) {
  const token = jwt.sign({ access_token }, token_pass);
  const db = cloudant.db.use("ecommerce");
  const query = {
    selector: {
      $and: [{ username: { $eq: username } }, { collection: { $eq: "user" } }],
    },
  };

  const res = await db.find(query);
  if (res.docs.length === 1) {
    const { _id, _rev } = res.docs[0];
    const user = {
      _id,
      _rev,
      username,
      access_token,
      token,
      email,
      collection: "user",
    };

    const {ok}= await db.insert(user);
     return ok && {token};
  } else {
    const user = { username,email, access_token, token, collection: "user" };
    const {ok}=await db.insert(user);
    return ok && {token};

  }
}

Enter fullscreen mode Exit fullscreen mode

After you done setting up IBM cloud functions run this command to deploy it :

serverless deploy
Enter fullscreen mode Exit fullscreen mode

Copy the URL you got from serverless deploy for github authorization callback url :

url

authorization callback

After you done let's go back to root folder then go to frontend folder download this dependencies to get environment variable from a file :

yarn add @nuxtjs/dotenv
Enter fullscreen mode Exit fullscreen mode

Then in nuxt.config.js add this line :

 buildModules: [
...
    '@nuxtjs/dotenv'
  ],
Enter fullscreen mode Exit fullscreen mode

Add .env file in the frontend folder and add this line :

github_client_id=<your github client id>
Enter fullscreen mode Exit fullscreen mode

After that go to components/Login.vue then change the button html to this one :

...
 <v-btn @click="login_github"><v-icon class="mx-2 my-2">fab fa-github</v-icon>LOGIN</v-btn>
...
Enter fullscreen mode Exit fullscreen mode

And add script to components/Login.vue like this one :

export default class MyStore extends Vue {
  login_github(){
    window.location.href="https://github.com/login/oauth/authorize?client_id="+process.env.github_client_id
  }
}
Enter fullscreen mode Exit fullscreen mode

Now create file in pages/callback.vue to receive token and set it in cookies like this :

<template>
  <v-row justify="center" align="center" :class="['text-h1']">REDIRECTING...</v-row>
</template>
<script lang="ts">
import Cookies from 'js-cookie'
import { Component, Vue } from 'nuxt-property-decorator'
@Component
export default class MyStore extends Vue {
  mounted(){
Cookies.set('token', this.$route.query.token)
window.location.href="/"
  }
}
</script>
Enter fullscreen mode Exit fullscreen mode

And then change the script in pages/index.vue to implement if there is token in the cookies remove the login button in homepage and change it to Dashboard button and logout button

// pages/index.vue
<template>
  <Home :login="login" />
</template>
<script lang="ts">
import Cookies from 'js-cookie'
import Home from '@/components/Home.vue'
import { Component, Vue } from 'nuxt-property-decorator'
@Component({
  components: {
    Home,
  },
})
export default class MyStore extends Vue {
  public login: boolean =
    Cookies.get('token') && Cookies?.get('token')?.trim() !== ''
      ? true
      : false
}
</script>
Enter fullscreen mode Exit fullscreen mode

After that change the components/Home.vue to implement button change like this :

// components/Home.vue
<template>
...
 <v-btn :class="['my-2']" color="info" elevation="2" v-show="login"
        >DASHBOARD</v-btn
      >
      <v-btn
        :class="['my-2']"
        color="info"
        elevation="2"
        href="/login"
        v-show="!login"
        >LOGIN</v-btn
      >
      <v-btn
        :class="['my-2', 'mx-2']"
        color="error"
        elevation="2"
        v-show="login"
        >LOGOUT</v-btn
      >
...
</template>
<script lang="ts">
import { Component, Vue, Prop } from 'nuxt-property-decorator'
@Component
export default class MyStore extends Vue {
  @Prop({ required: true }) readonly login!: boolean
}
</script>
Enter fullscreen mode Exit fullscreen mode

And now generate the frontend just like before and rclone it :

yarn generate
cd dist
rclone copy ./ <remote name>:<bucket name>
Enter fullscreen mode Exit fullscreen mode

Go to your browser and open your public static web hosting url try to login using your github account and if you see something like this below then your authorization is done and you are successfully deploy serverless auth using IBM cloud function and save the token in IBM cloudant :

auth finish

Step 4 - Create Dashboard To Get Private Repo

First of all we need to change the Home.vue in frontend folder go to components/Home.vue and change the button so that it can go to dashboard and logout :

<template>
...
 <v-btn
        :class="['my-2']"
        color="info"
        elevation="2"
        v-show="login"
        href="/dashboard"
        >DASHBOARD</v-btn
      >
      ...
      <v-btn
        :class="['my-2', 'mx-2']"
        color="error"
        elevation="2"
        v-show="login"
        @click="logout"
        >LOGOUT</v-btn
      >
...
</template>
<script lang="ts">
import Cookies from 'js-cookie'
...
export default class MyStore extends Vue {
  @Prop({ required: true }) readonly login!: boolean
  logout() {
    Cookies.remove('token')
window.location.reload()
  }
}
...
</script>
Enter fullscreen mode Exit fullscreen mode

Now let's create pages/dashboard.vue to handle our Dashboard.vue component

<template>
  <Dashboard :login="login" />
</template>
<script lang="ts">
import Cookies from 'js-cookie'
import Dashboard from '@/components/Dashboard.vue'
import { Component, Vue } from 'nuxt-property-decorator'
@Component({
  components: {
    Dashboard,
  },
})
export default class MyStore extends Vue {
  public login: boolean =
    Cookies.get('token') && Cookies?.get('token')?.trim() !== ''
      ? true
      : false
}
</script>
Enter fullscreen mode Exit fullscreen mode

And if that finish now let's create components/Dashboard.vue to show balance and to show our private repo later

<template>
  <v-container>
    <v-row justify="space-between">
      <v-col cols="4" class="text-h4 font-weight-bold">Dashboard</v-col>
      <v-col cols="4" class="d-flex justify-end"
        ><v-btn @click="logout">LOGOUT</v-btn></v-col
      >
    </v-row>
    <DashboardInfo :username="username" :profilePhoto="profilePhoto" />
    <v-row justify="space-between">
      <v-col cols="4" class="text-h4 font-weight-bold">Private Repo List</v-col>
    </v-row>
    <DashboardRepo :refreshRepo="getPrivateRepo" :listRepo="privateRepo" />
    <v-row justify="space-between">
      <v-col cols="4" class="text-h4 font-weight-bold">Owned Repo</v-col>
    </v-row>
    <DashboardRepo :refreshRepo="getOwnedRepo" :listRepo="ownedRepo" />
  </v-container>
</template>

<script lang="ts">
import Cookies from 'js-cookie'
import DashboardInfo from '@/components/dashboard_components/DashboardInfo.vue'
import DashboardRepo from '@/components/dashboard_components/DashboardRepo.vue'
import { Component, Vue, Prop } from 'nuxt-property-decorator'
@Component({
  components: {
    DashboardInfo,
    DashboardRepo,
  },
})
export default class MyStore extends Vue {
  @Prop({ required: true }) readonly login!: boolean
  public username: string = ''
  public profilePhoto: string = ''
  public privateRepo: Array<object> = []
  public ownedRepo: Array<object> = []
  logout() {
    Cookies.remove('token')
    window.location.href = '/'
  }
  async created() {
    // if (!this.login) window.location.href = '/'
    const token = Cookies.get('token')
    const url = process.env.get_profile_url
    const privateRepoUrl = process.env.get_private_repo_url
    const ownedRepoUrl = process.env.get_owned_repo_url
    const {data} = await this.$axios.get(`${url}?token=${token}`)
    const privateRepo = await this.$axios.get(`${privateRepoUrl}?token=${token}`)
    const ownedRepo = await this.$axios.get(`${ownedRepoUrl}?token=${token}`)
    this.username = data.data.login
    this.profilePhoto = data.data.avatarUrl
    this.privateRepo=privateRepo.data
    this.ownedRepo=ownedRepo.data
  }
  async getPrivateRepo() {
    const token = Cookies.get('token')
    const url = process.env.get_private_repo_refresh_url
    const {data} = await this.$axios.get(`${url}?token=${token}`)
    this.privateRepo=data.data

  }

}
</script>
Enter fullscreen mode Exit fullscreen mode

This is how our dashboard will look like for now :

dashboard

After that let's change the github oauth url to enable our api token to get our private repo go to components/login.vue then change the script into this :

<script lang="ts">
import { Component, Vue } from 'nuxt-property-decorator'
@Component
export default class MyStore extends Vue {
  login_github(){
    window.location.href="https://github.com/login/oauth/authorize?scope=repo&client_id="+process.env.github_client_id
  }
}
</script>
Enter fullscreen mode Exit fullscreen mode

It will now show authorization url that will grant an access to our private and public repo, but since this is a private repo marketplace, then we gonna get private repo only, now go back to functions folder and create an IBM cloud function that can get our private repo,get profile,and get repo that we bought from other github user, create a file called private_repo.js and write this code :

"use strict";
const axios = require("axios");
const jwt = require("jsonwebtoken");
const formUrlEncoded = (x) =>
  Object.keys(x).reduce((p, c) => p + `&${c}=${encodeURIComponent(x[c])}`, "");
const privateRepoRefresh = async function (params) {
  const cloudant = require("@cloudant/cloudant")({
    url: params.__bx_creds.cloudantnosqldb.url,
    plugins: [
      { iamauth: { iamApiKey: params.__bx_creds.cloudantnosqldb.apikey } },
    ],
  });

  const data = await getPrivateRepoRefresh({
    cloudant,
    token: params.token,
    token_pass: params.token_pass,
  });

  if (data&&data.length>0) {
    return {
      data,
    };
  } else {
    return {
      data: [],
    };
  }
};
const privateRepo = async function (params) {
  const cloudant = require("@cloudant/cloudant")({
    url: params.__bx_creds.cloudantnosqldb.url,
    plugins: [
      { iamauth: { iamApiKey: params.__bx_creds.cloudantnosqldb.apikey } },
    ],
  });

  const data = await getPrivateRepo({
    cloudant,
    token: params.token,
    token_pass: params.token_pass,
  });

  if (data&&data.length>0) {
    return {
      data,
    };
  } else {
    return {
      data: [],
    };
  }
};
const ownedRepo = async function (params) {
  const cloudant = require("@cloudant/cloudant")({
    url: params.__bx_creds.cloudantnosqldb.url,
    plugins: [
      { iamauth: { iamApiKey: params.__bx_creds.cloudantnosqldb.apikey } },
    ],
  });

  const data = await getOwnedRepo({
    cloudant,
    token: params.token,
    token_pass: params.token_pass,
  });

  if (data&&data.length>0) {
    return {
      data,
    };
  } else {
    return {
      data: [],
    };
  }
};

const profile = async function (params) {
  const cloudant = require("@cloudant/cloudant")({
    url: params.__bx_creds.cloudantnosqldb.url,
    plugins: [
      { iamauth: { iamApiKey: params.__bx_creds.cloudantnosqldb.apikey } },
    ],
  });

  const data = await getProfile({
    cloudant,
    token: params.token,
    token_pass: params.token_pass,
  });

  if (data) {
    return {
      data: data.data.viewer,
    };
  } else {
    return {
      data: {},
    };
  }
};
module.exports.profile = profile;
module.exports.privateRepo = privateRepo;
module.exports.ownedRepo = ownedRepo;
module.exports.privateRepoRefresh = privateRepoRefresh;
async function getProfile({ cloudant, token_pass, token }) {
  const { access_token } = jwt.verify(token, token_pass);
  const db = cloudant.db.use("ecommerce");
  var query = {
    selector: { access_token: { $eq: access_token } },
    fields: ["access_token"],
  };

  const res = await db.find(query);
  if (res.docs.length === 1) {
    const { data } = await axios({
      url: "https://api.github.com/graphql",
      headers: {
        Authorization: `token ${res.docs[0].access_token}`,
        Accept: "application/vnd.github.v3+json",
      },
      method: "POST",
      data: { query: "query { viewer { login,avatarUrl }}" },
    });
    return data;
  } else {
    return null;
  }
}

async function getPrivateRepoRefresh({ cloudant, token_pass, token }) {
  const { access_token } = jwt.verify(token, token_pass);
  const db = cloudant.db.use("ecommerce");
  var query = {
    selector: { $and:[{access_token: { $eq: access_token } },{collection:{$eq:"user"}}]},
    fields: ["access_token", "username"],
  };

  const res = await db.find(query);
  if (res.docs.length === 1) {
    const { data } = await axios({
      url: "https://api.github.com/graphql",
      headers: {
        Authorization: `token ${res.docs[0].access_token}`,
        Accept: "application/vnd.github.v3+json",
      },
      method: "POST",
      data: {
        query: `{
        repositoryOwner(login: "${res.docs[0].username}") {
          repositories(privacy: PRIVATE,first:5,orderBy:{field:CREATED_AT,direction:DESC}) {
            nodes {
              description
              name
              openGraphImageUrl
              url
              id
            }
          }
        }
      }
      `,
      },
    });
    const nodes = data.data.repositoryOwner.repositories.nodes;
    for (let i = 0; i < nodes.length; i++) {
      const findRepo = await db.find({
        selector: {
          $and: [
            { username: { $eq: res.docs[0].username } },
            { collection: { $eq: "private-repo" } },
            { repo_id: { $eq: nodes[i].id } },
          ],
        },
      });
      if(findRepo.docs.length>0){
        const {_id,_rev,...other}=findRepo.docs[0]
        const {id,...rest}=nodes[i];
        const repoData={_id,_rev,repo_id:id,...rest,...other}
        await db.insert(repoData)
      }else{
        const {id,...rest}=nodes[i];
        const repoData={repo_id:id,...rest,username:res.docs[0].username,collection:"private-repo",sell:"UNLIST",amount:0}
        await db.insert(repoData)
      }
    }

    const final = await db.find({
      selector: {
        $and: [
          { username: { $eq: res.docs[0].username } },
          { collection: { $eq: "private-repo" } },
        ],
      },
    });
    return final.docs;
  } else {

    return [];
  }
}
async function getPrivateRepo({ cloudant, token_pass, token }) {
  const { access_token } = jwt.verify(token, token_pass);
  const db = cloudant.db.use("ecommerce");
  var query = {
    selector: { $and:[{access_token: { $eq: access_token } },{collection:{$eq:"user"}}]},
    fields: ["access_token", "username"],
  };

  const res = await db.find(query);
  if(res.docs.length===1){
    const final = await db.find({
      selector: {
        $and: [
          { username: { $eq: res.docs[0].username } },
          { collection: { $eq: "private-repo" } },
        ],
      },
    });
    return final.docs;
  }else{
    return []
  }

}
async function getOwnedRepo({ cloudant, token_pass, token }) {
  const { access_token } = jwt.verify(token, token_pass);
  const db = cloudant.db.use("ecommerce");
  var query = {
    selector: { $and:[{access_token: { $eq: access_token } },{collection:{$eq:"user"}}]},
    fields: ["access_token", "username"],
  };

  const res = await db.find(query);
  if(res.docs.length===1){
    const final = await db.find({
      selector: {
        $and: [
          { username: { $eq: res.docs[0].username } },
          { collection: { $eq: "owned-repo" } },
        ],
      },
    });
    return final.docs;
  }else{
    return []
  }

}
Enter fullscreen mode Exit fullscreen mode

Change serverless.yml to suit our needs like this :

service: authorize
useDotenv: true
provider:
  name: openwhisk
  ignore_certs: true

functions:
  authorize-github:
    handler: authorize.github
    annotations:
      web-export: true
    parameters:
      github_client_id: ${env:github_client_id}
      github_client_secret: ${env:github_client_secret}
      token_pass: ${env:token_pass}
      frontend_url: ${env:frontend_url}
    bind:
      - service:
          name: cloudantnosqldb
          instance: Cloudant-vm
  get-private-repo:
    handler: private_repo.privateRepo
    parameters:
      github_client_id: ${env:github_client_id}
      github_client_secret: ${env:github_client_secret}
      token_pass: ${env:token_pass}
      frontend_url: ${env:frontend_url}
    events: 
      - http: GET /get-private-repo
    bind:
      - service:
          name: cloudantnosqldb
          instance: Cloudant-vm
  get-private-repo-refresh:
    handler: private_repo.privateRepoRefresh
    parameters:
      github_client_id: ${env:github_client_id}
      github_client_secret: ${env:github_client_secret}
      token_pass: ${env:token_pass}
      frontend_url: ${env:frontend_url}
    events: 
      - http: GET /get-private-repo-refresh
    bind:
      - service:
          name: cloudantnosqldb
          instance: Cloudant-vm
  get-owned-repo:
    handler: private_repo.ownedRepo
    parameters:
      github_client_id: ${env:github_client_id}
      github_client_secret: ${env:github_client_secret}
      token_pass: ${env:token_pass}
      frontend_url: ${env:frontend_url}
    events: 
      - http: GET /get-owned-repo
    bind:
      - service:
          name: cloudantnosqldb
          instance: Cloudant-vm
  get-profile:
    handler: private_repo.profile
    parameters:
      github_client_id: ${env:github_client_id}
      github_client_secret: ${env:github_client_secret}
      token_pass: ${env:token_pass}
      frontend_url: ${env:frontend_url}
    events: 
      - http: GET /get-profile
    bind:
      - service:
          name: cloudantnosqldb
          instance: Cloudant-vm              

plugins:
  - serverless-openwhisk
  - serverless-dotenv-plugin
Enter fullscreen mode Exit fullscreen mode

Now let's deploy it using serverless command line like this :

serverless deploy
Enter fullscreen mode Exit fullscreen mode

Now let's change our Dashboard.vue so that it can show our private repo :

<template>
  <v-container>
    <v-row justify="space-between">
      <v-col cols="4" class="text-h4 font-weight-bold">Dashboard</v-col>
      <v-col cols="4" class="d-flex justify-end"
        ><v-btn @click="logout">LOGOUT</v-btn></v-col
      >
    </v-row>
    <DashboardInfo :username="username" :profilePhoto="profilePhoto" />
    <v-row justify="space-between">
      <v-col cols="4" class="text-h4 font-weight-bold">Private Repo List</v-col>
    </v-row>
    <DashboardRepo :refreshRepo="getPrivateRepo" :listRepo="privateRepo" />
    <v-row justify="space-between">
      <v-col cols="4" class="text-h4 font-weight-bold">Owned Repo</v-col>
    </v-row>
    <DashboardRepo  :listRepo="ownedRepo" />
  </v-container>
</template>

<script lang="ts">
import Cookies from 'js-cookie'
import DashboardInfo from '@/components/dashboard_components/DashboardInfo.vue'
import DashboardRepo from '@/components/dashboard_components/DashboardRepo.vue'
import { Component, Vue, Prop } from 'nuxt-property-decorator'
@Component({
  components: {
    DashboardInfo,
    DashboardRepo,
  },
})
export default class MyStore extends Vue {
  @Prop({ required: true }) readonly login!: boolean
  public username: string = ''
  public profilePhoto: string = ''
  public privateRepo: Array<object> = []
  public ownedRepo: Array<object> = []
  logout() {
    Cookies.remove('token')
    window.location.href = '/'
  }
  async created() {
    // if (!this.login) window.location.href = '/'
    const token = Cookies.get('token')
    const url = process.env.get_profile_url
    const privateRepoUrl = process.env.get_private_repo_url
    const ownedRepoUrl = process.env.get_owned_repo_url
    const {data} = await this.$axios.get(`${url}?token=${token}`)
    const privateRepo = await this.$axios.get(`${privateRepoUrl}?token=${token}`)
    const ownedRepo = await this.$axios.get(`${ownedRepoUrl}?token=${token}`)
    this.username = data.data.login
    this.profilePhoto = data.data.avatarUrl
    this.privateRepo=privateRepo.data.data
    this.ownedRepo=ownedRepo.data.data
  }
  async getPrivateRepo() {
    const token = Cookies.get('token')
    const url = process.env.get_private_repo_refresh_url
    const {data} = await this.$axios.get(`${url}?token=${token}`)
    this.privateRepo=data.data

  }

}
</script>
Enter fullscreen mode Exit fullscreen mode

Also create components/dashboard_components/DashboardRepo.vue component to show our private repo and owned repo :

<template>
  <div>
    <v-row class="mb-2" v-if="refreshRepo"><v-btn @click="refreshRepo">Refresh Repo List</v-btn></v-row>
    <v-row>
      <v-card max-width="400" class="mx-2 my-2" :key=index v-for="(item,index) in listRepo">
        <v-card-title>
          <v-list-item class="grow">
            <v-list-item-content>
              <v-list-item-title>{{item.name}}</v-list-item-title>
            </v-list-item-content>

            <v-row align="center" justify="end"> Private </v-row>
          </v-list-item>
        </v-card-title>

        <v-card-text class="text-md font-weight-bold">
          <v-list-item>
            <v-list-item-content
              >{{item.description}}</v-list-item-content
            >
            <v-list-item-avatar tile size="100" color="grey">
              <v-img :src="item.openGraphImageUrl" />
            </v-list-item-avatar>
          </v-list-item>
        </v-card-text>
        <v-card-action
          ><v-row class="d-flex align-content-center mx-5">
            <v-col cols="5" class="d-flex justify-start">
              <div v-if="item.sell==='SELL'">$ {{item.amount}}</div>
              <v-text-field
              v-if="item.sell==='UNLIST'"
                label="Amount"
                type="number"
                outlined
                prefix="$"
              ></v-text-field>
            </v-col>
            <v-col class="d-flex justify-end mt-2">
              <v-btn color="red" v-if="item.sell==='UNLIST'">SELL</v-btn><v-btn v-if="item.sell==='SELL'">UNLIST</v-btn>
            </v-col>
          </v-row></v-card-action
        >
      </v-card>
    </v-row>
  </div>
</template>
<script lang="ts">
import { Component, Vue, Prop } from 'nuxt-property-decorator'
@Component
export default class MyStore extends Vue {
  @Prop({ required: false }) readonly refreshRepo!: void
  @Prop({ required: true }) readonly listRepo!: Array<object>
}
</script>

Enter fullscreen mode Exit fullscreen mode

Create components/dashboard_components/DashboardInfo.vue component to show username, profile picture and balance :

<template>
  <v-row>
    <v-card max-width="344" outlined>
      <v-list-item three-line>
        <v-list-item-content>
          <v-list-item-title class="headline mb-1">
            Github Username
          </v-list-item-title>
          <v-list-item-title class="text-h5">{{username}}</v-list-item-title>
        </v-list-item-content>

        <v-list-item-avatar size="80" color="grey">
          <v-img :src="profilePhoto" />
        </v-list-item-avatar>
      </v-list-item>
    </v-card>
    <v-card class="mx-5" max-width="344" outlined>
      <v-list-item three-line>
        <v-list-item-content>
          <v-list-item-title class="headline mb-1"> Balance </v-list-item-title>
          <v-list-item-title>
            <v-row justify="space-between">
              <v-col class="font-weight-bold text-h5">$ 1000</v-col>
              <v-col><v-btn color="green">withdraw</v-btn></v-col>
            </v-row>
          </v-list-item-title>
        </v-list-item-content>
      </v-list-item>
    </v-card>
  </v-row>
</template>
<script lang="ts">
import { Component, Vue, Prop } from 'nuxt-property-decorator'
@Component
export default class MyStore extends Vue {
  @Prop({ required: true }) readonly username!: string
  @Prop({ required: true }) readonly profilePhoto!: string

}
</script>
Enter fullscreen mode Exit fullscreen mode

Go to http://localhost:3000/dashboard and you will see something like this if you're successfully shown your private repo :

dashboard

Step 5 - Create Function That Can Sell And Buy Repo

Create a file called sell_repo.js on functions folder we gonna use this file to inform the application that our repo is for sale,unlist repo for sale, and sell repo:

const axios = require("axios");
const jwt = require("jsonwebtoken");
const sell_repo = async function (params) {
    const cloudant = require("@cloudant/cloudant")({
      url: params.__bx_creds.cloudantnosqldb.url,
      plugins: [
        { iamauth: { iamApiKey: params.__bx_creds.cloudantnosqldb.apikey } },
      ],
    });

    const data = await sellRepo({
      cloudant,
      token: params.token,
      token_pass: params.token_pass,
      _id:params._id,
      amount:params.amount,

    });

    if (data && data.length>0) {
      return {
        data
      };
    } else {
      return {
        data,
      };
    }
  };
  const unlist_repo = async function (params) {
    const cloudant = require("@cloudant/cloudant")({
      url: params.__bx_creds.cloudantnosqldb.url,
      plugins: [
        { iamauth: { iamApiKey: params.__bx_creds.cloudantnosqldb.apikey } },
      ],
    });

    const data = await unlistRepo({
      cloudant,
      token: params.token,
      token_pass: params.token_pass,
      _id:params._id,

    });

    if (data && data.length>0) {
      return {
        data
      };
    } else {
      return {
        data,
      };
    }
  };
  const for_sale_repo = async function (params) {
    const cloudant = require("@cloudant/cloudant")({
      url: params.__bx_creds.cloudantnosqldb.url,
      plugins: [
        { iamauth: { iamApiKey: params.__bx_creds.cloudantnosqldb.apikey } },
      ],
    });

    const data = await forSaleRepo({
      cloudant,
      token: params.token,
      token_pass: params.token_pass,


    });

    if (data && data.length>0) {
      return {
        data
      };
    } else {
      return {
        data,
      };
    }
  };
  async function sellRepo({ cloudant, token_pass, token,_id,amount}) {
    const { access_token } = jwt.verify(token, token_pass);
    const db = cloudant.db.use("ecommerce");
    var query = {
      selector: { $and:[{access_token: { $eq: access_token } },{collection:{$eq:"user"}}]},
      fields: ["access_token", "username"],
    };

    const res = await db.find(query);
    if(res.docs.length===1){
      let repo = await db.find({
        selector: {
          _id:{$eq:_id}
        },
      });
      if(repo.docs.length===1){
        repo.docs[0].amount=amount;
        repo.docs[0].sell="SELL";
        await db.insert(repo.docs[0])
        const final=await db.find({selector:{username:res.docs[0].username,collection:"private-repo"}})
        return final.docs
      }else{
        return {status:false,data:[],message:"error selling"}
      }

    }else{
      return {status:false,data:[],message:"error token"}
    }

  }
  async function unlistRepo({ cloudant, token_pass, token,_id }) {
    const { access_token } = jwt.verify(token, token_pass);
    const db = cloudant.db.use("ecommerce");
    var query = {
      selector: { $and:[{access_token: { $eq: access_token } },{collection:{$eq:"user"}}]},
      fields: ["access_token", "username"],
    };

    const res = await db.find(query);
    if(res.docs.length===1){
      let repo = await db.find({
        selector: {
          _id:{$eq:_id}
        },
      });
      if(repo.docs.length===1){
        repo.docs[0].sell="UNLIST";
        await db.insert(repo.docs[0])
        const final=await db.find({selector:{username:res.docs[0].username,collection:"private-repo"}})
        return final.docs
      }else{
        return {status:false,data:[],message:"error unlist"}
      }

    }else{
      return {status:false,data:[],message:"error token"}
    }

  }
  async function forSaleRepo({ cloudant, token_pass, token }) {
    const { access_token } = jwt.verify(token, token_pass);
    const db = cloudant.db.use("ecommerce");
    var query = {
      selector: { $and:[{access_token: { $eq: access_token } },{collection:{$eq:"user"}}]},
      fields: ["access_token", "username"],
    };

    const res = await db.find(query);
    if(res.docs.length===1){
      let repo = await db.find({
        selector: {
          collection:{$eq:"private-repo"},
          sell:"SELL"
        },
      });
      return repo.docs

    }else{
      return {status:false,data:[],message:"error token"}
    }

  }
  module.exports.sell_repo = sell_repo;
  module.exports.unlist_repo = unlist_repo;
  module.exports.for_sale_repo = for_sale_repo;
Enter fullscreen mode Exit fullscreen mode

Now let's change our components/Dashboard.vue to implement our cloud function like this :

<template>
  <v-container>
    <v-row justify="space-between">
      <v-col cols="4" class="text-h4 font-weight-bold">Dashboard</v-col>
      <v-col cols="4" class="d-flex justify-end"
        ><v-btn @click="logout">LOGOUT</v-btn></v-col
      >
    </v-row>
    <DashboardInfo :username="username" :profilePhoto="profilePhoto" />
    <v-row justify="space-between">
      <v-col cols="4" class="text-h4 font-weight-bold">Private Repo List</v-col>
    </v-row>
    <DashboardRepo
      :refreshRepo="getPrivateRepo"
      :listRepo="privateRepo"
      @sell-repo="sellRepo($event._id, $event.amount)"
      @unlist-repo="unlistRepo($event._id)"
    />
    <v-row justify="space-between">
      <v-col cols="4" class="text-h4 font-weight-bold">Owned Repo</v-col>
    </v-row>
    <DashboardRepo :listRepo="ownedRepo" />
    <v-snackbar v-model="snackbarAmount">
      the amount shouldn't be negative or zero
    </v-snackbar>
  </v-container>
</template>

<script lang="ts">
import Cookies from 'js-cookie'
import DashboardInfo from '@/components/dashboard_components/DashboardInfo.vue'
import DashboardRepo from '@/components/dashboard_components/DashboardRepo.vue'
import { Component, Vue, Prop } from 'nuxt-property-decorator'
@Component({
  components: {
    DashboardInfo,
    DashboardRepo,
  },
})
export default class MyStore extends Vue {
  @Prop({ required: true }) readonly login!: boolean
  public username: string = ''
  public profilePhoto: string = ''
  public snackbarAmount: boolean = false
  public privateRepo: Array<object> = []
  public ownedRepo: Array<object> = []
  logout() {
    Cookies.remove('token')
    window.location.href = '/'
  }
  async created() {
    // if (!this.login) window.location.href = '/'
    const token = Cookies.get('token')
    const url = process.env.get_profile_url
    const privateRepoUrl = process.env.get_private_repo_url
    const ownedRepoUrl = process.env.get_owned_repo_url
    const { data } = await this.$axios.get(`${url}?token=${token}`)
    const privateRepo = await this.$axios.get(
      `${privateRepoUrl}?token=${token}`
    )
    const ownedRepo = await this.$axios.get(`${ownedRepoUrl}?token=${token}`)
    this.username = data.data.login
    this.profilePhoto = data.data.avatarUrl
    this.privateRepo = privateRepo.data.data
    this.ownedRepo = ownedRepo.data.data
  }
  async getPrivateRepo() {
    const token = Cookies.get('token')
    const url = process.env.get_private_repo_refresh_url
    const { data } = await this.$axios.get(`${url}?token=${token}`)
    this.privateRepo = data.data
  }
  async sellRepo(_id: string, amount: number) {
    // console.log(amount)
    if (Number(amount) === 0 || Number(amount) < 0) {
      console.log(amount)
      this.snackbarAmount = true
      return
    } else {
      const token = Cookies.get('token')
      const url = process.env.sell_repo_url

      const { data } = await this.$axios.post(`${url}?token=${token}`, {
        _id,
        amount,
      })
      this.privateRepo = data.data
    }
  }
  async unlistRepo(_id: string) {
    const token = Cookies.get('token')
    const url = process.env.unlist_repo_url
    const { data } = await this.$axios.post(`${url}?token=${token}`, { _id })
    this.privateRepo = data.data
  }
}
</script>
Enter fullscreen mode Exit fullscreen mode

Also change the components/dashboard_components/DashboardRepo.vue so that it can show the list of private repo after clicking sell button :

<template>
  <div>
    <v-row class="mb-2" v-if="refreshRepo"><v-btn @click="refreshRepo">Refresh Repo List</v-btn></v-row>
    <v-row>
      <v-card width="400" class="mx-2 my-2" :key=index v-for="(item,index) in listRepo">
        <v-card-title>
          <v-list-item class="grow">
            <v-list-item-content>
              <v-list-item-title>{{item.name}}</v-list-item-title>
            </v-list-item-content>

            <v-row align="center" justify="end"> Private </v-row>
          </v-list-item>
        </v-card-title>

        <v-card-text class="text-md font-weight-bold">
          <v-list-item>
            <v-list-item-content
              >{{item.description}}</v-list-item-content
            >
            <v-list-item-avatar tile size="100" color="grey">
              <v-img :src="item.openGraphImageUrl" />
            </v-list-item-avatar>
          </v-list-item>
        </v-card-text>
        <v-card-action
          ><v-row class="d-flex align-content-center mx-5">
            <v-col cols="5" class="d-flex justify-start align-content-center">
              <div class="d-flex align-center text-h5" :style="{height:'100%'}" v-if="item.sell==='SELL'">$ {{item.amount}}</div>
              <v-text-field
              v-if="item.sell==='UNLIST'"
                label="Amount"
                type="number"
                outlined
                prefix="$"
                v-model="amount"
              ></v-text-field>
            </v-col>
            <v-col class="d-flex mt-2 justify-end">
              <v-btn @click="sellRepo(item._id,amount)" color="red" v-if="item.sell==='UNLIST'">SELL</v-btn><v-btn v-if="item.sell==='SELL'" @click="unlistRepo(item._id)">UNLIST</v-btn>
            </v-col>
          </v-row></v-card-action
        >
      </v-card>
    </v-row>
  </div>
</template>
<script lang="ts">
import { Component, Vue, Prop,Emit } from 'nuxt-property-decorator'
@Component
export default class MyStore extends Vue {
  @Prop({ required: false }) readonly refreshRepo!: void
  @Prop({ required: true }) readonly listRepo!: Array<object>
  public amount:number
  @Emit()
sellRepo(_id: string,amount:number) {
return {_id,amount}
}
  @Emit()
unlistRepo(_id: string) {
return {_id}
}
}
</script>

Enter fullscreen mode Exit fullscreen mode

Now go to Home.vue so that we can show our for sale repo after clicking it :

<template>
  <div :class="['mx-5', 'my-5']">
    <v-row justify="center">
      <div
        :class="[`text-h1`, 'text-center']"
        v-text="'Open Source Private Marketplace'"
      ></div>
    </v-row>
    <v-row justify="center">
      <div
        :class="[`text-h3`, 'px-5', 'text-center', 'my-5']"
        v-text="'Here you can buy or sell open source private repo'"
      ></div>
    </v-row>
    <v-row justify="center" :class="['my-2']">
      <v-btn
        :class="['my-2']"
        color="info"
        elevation="2"
        v-show="login"
        href="/dashboard"
        >DASHBOARD</v-btn
      >
      <v-btn
        :class="['my-2']"
        color="info"
        elevation="2"
        href="/login"
        v-show="!login"
        >LOGIN</v-btn
      >
      <v-btn
        :class="['my-2', 'mx-2']"
        color="error"
        elevation="2"
        v-show="login"
        @click="logout"
        >LOGOUT</v-btn
      >
    </v-row>
    <v-row>
      <v-card class="mx-auto my-12" max-width="374" :key="index" v-for="(item,index) in forSale">
        <v-img
          height="250"
          :src="item.openGraphImageUrl"
        ></v-img>

        <v-card-title>{{item.name}}</v-card-title>

        <v-card-text>

          <div>{{item.description}}</div>
        </v-card-text>

        <v-divider class="mx-4"></v-divider>

        <v-card-actions>
          <v-flex>$ {{item.amount}}</v-flex>
          <v-flex ><div class="d-flex justify-end"><v-btn class="d-flex justify-end" color="deep-purple lighten-2" @click="buyNow(item._id,item.amount)" text> Buy Now </v-btn>
            </div></v-flex>

        </v-card-actions>
      </v-card>
    </v-row>
  </div>
</template>
<script lang="ts">
import Cookies from 'js-cookie'
import { Component, Vue, Prop } from 'nuxt-property-decorator'
@Component
export default class MyStore extends Vue {
  @Prop({ required: true }) readonly login!: boolean
  public forSale:Array<object>=[]
  logout() {
    Cookies.remove('token')
    window.location.reload()
  }
  async created(){
  const token = Cookies.get('token')
    const url = process.env.for_sale_repo_url
    const { data } = await this.$axios.get(`${url}?token=${token}`)
    this.forSale = data.data
  }
  async buyNow(_id,amount){

  }
}
</script>
Enter fullscreen mode Exit fullscreen mode

Now you can sell your repo when you put the amount and clicking sell button like this :

sell

Then when you go to homepage it also shown the price and buy button :

buy shop

Step 6 - Integrate Paypal To Accept Payment

First create sandbox paypal account here, after that create a demo account on developer account :

demo account

Then let's change our balance UI into a button that can accept paypal payment go to frontend folder and go to components/dashboard_components/DashboardInfo.vue and change the balance UI like this :

...
<v-row justify="space-between">
              <v-col><v-btn color="green" v-if="!paypalToken">CONNECT PAYPAL</v-btn></v-col>
              <v-col class="font-weight-bold text-h5" v-if="paypalToken">{{paypalBalance}}</v-col>
              <v-col v-if="paypalToken"><v-btn color="green">withdraw</v-btn></v-col>
            </v-row>
...
<script lang="ts">
...
    @Prop({ required: false }) readonly paypalBalance!: string
    @Prop({ required: true }) readonly paypalToken!: boolean
...
</script>
Enter fullscreen mode Exit fullscreen mode

Also in the components/Home.vue add this code :

<template>
...
  <DashboardInfo :username="username" :profilePhoto="profilePhoto" :paypalToken="paypalToken"/>
...
</template>
<script lang=ts>
...
public paypalToken: boolean = false
...
</script>
Enter fullscreen mode Exit fullscreen mode

it will now look like this :

home.vue

Now we need to add function to make that button connect to our paypal so that we can see our balance in return, also we will make a condition so that when the paypal connected, we can sell the repo, but when it doesn't connect, it can't sell the repo go ahead and change frontend/components/dashboard_components/DashboardRepo.vue to this :

<template>
  <div>
    <v-row class="mb-2" v-if="refreshRepo"
      ><v-btn @click="refreshRepo">Refresh Repo List</v-btn></v-row
    >
    <v-row>
      <v-card
        width="400"
        class="mx-2 my-2"
        :key="index"
        v-for="(item, index) in listRepo"
      >
        <v-card-title>
          <v-list-item class="grow">
            <v-list-item-content>
              <v-list-item-title>{{ item.name }}</v-list-item-title>
            </v-list-item-content>

            <v-row align="center" justify="end"> Private </v-row>
          </v-list-item>
        </v-card-title>

        <v-card-text class="text-md font-weight-bold">
          <v-list-item>
            <v-list-item-content>{{ item.description }}</v-list-item-content>
            <v-list-item-avatar tile size="100" color="grey">
              <v-img :src="item.openGraphImageUrl" />
            </v-list-item-avatar>
          </v-list-item>
        </v-card-text>
        <v-card-action
          ><v-row class="d-flex align-content-center mx-5">
            <v-col cols="5" class="d-flex justify-start align-content-center">
              <div
                class="d-flex align-center text-h5"
                :style="{ height: '100%' }"
                v-if="item.sell === 'SELL'"
              >
                $ {{ item.amount }}
              </div>
              <v-text-field
                v-if="item.sell === 'UNLIST'"
                label="Amount"
                type="number"
                outlined
                prefix="$"
                :disabled="!paypalToken"
                v-model="amount"
              ></v-text-field>
            </v-col>
            <v-col class="d-flex mt-2 justify-end">
              <v-btn
                @click="sellRepo(item._id, amount)"
                color="red"
                v-if="item.sell === 'UNLIST'"
                :disabled="!paypalToken"
                >SELL</v-btn
              ><v-btn :disabled="!paypalToken" v-if="item.sell === 'SELL'" @click="unlistRepo(item._id)"
                >UNLIST</v-btn
              >
            </v-col>
          </v-row></v-card-action
        >
      </v-card>
    </v-row>
  </div>
</template>
<script lang="ts">
import { Component, Vue, Prop, Emit } from 'nuxt-property-decorator'
@Component
export default class MyStore extends Vue {
  @Prop({ required: false }) readonly refreshRepo!: void
  @Prop({ required: false }) readonly paypalToken!: boolean
  @Prop({ required: true }) readonly listRepo!: Array<object>
  @Prop({ required: true }) readonly amountRefresh!: number
  public amount: number=this.amountRefresh
  @Emit()
  sellRepo(_id: string, amount: number) {
    return { _id, amount }
  }
  @Emit()
  unlistRepo(_id: string) {
    return { _id }
  }
}
</script>

Enter fullscreen mode Exit fullscreen mode

Now whenever the paypal button not connected the sell button is disabled

disabled

Let's now create a function that can send money through paypal when buying one of the repo,now create a read access to that repo,now go to functions folder and create manage_access.js

"use strict";
const axios = require("axios");
const jwt = require("jsonwebtoken");
function makeid(length) {
  var result = "";
  var characters =
    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
  var charactersLength = characters.length;
  for (var i = 0; i < length; i++) {
    result += characters.charAt(Math.floor(Math.random() * charactersLength));
  }
  return result;
}
const formUrlEncoded = (x) =>
  Object.keys(x).reduce((p, c) => p + `&${c}=${encodeURIComponent(x[c])}`, "");
const paypal = require("@paypal/checkout-server-sdk");
const buy_paypal = async function (params) {
  const random = makeid(5);
  const cloudant = require("@cloudant/cloudant")({
    url: params.__bx_creds.cloudantnosqldb.url,
    plugins: [
      { iamauth: { iamApiKey: params.__bx_creds.cloudantnosqldb.apikey } },
    ],
  });
  const db = cloudant.db.use("ecommerce");
  const query = {
    selector: {
      $and: [
        { _id: { $eq: params._id } },
        { collection: { $eq: "private-repo" } },
      ],
    },
  };

  const res = await db.find(query);
  const paypalQuery = {
    selector: {
      $and: [
        { github_username: { $eq: res.docs[0].username } },
        { collection: { $eq: "paypal" } },
      ],
    },
  };

  const paypalEmail = await db.find(paypalQuery);
  // if(paypalEmail.docs[0].github_username===res.docs[0].username) return {status:false,message:"you could only buy repo that is not yours"}
  let clientId = params.paypal_client_id;
  let clientSecret = params.paypal_client_secret;
  let environment = new paypal.core.SandboxEnvironment(clientId, clientSecret);
  let client = new paypal.core.PayPalHttpClient(environment);
  let request = new paypal.orders.OrdersCreateRequest();
  request.requestBody({
    intent: "CAPTURE",
    purchase_units: [
      {
        amount: {
          currency_code: "USD",
          value: res.docs[0].amount,
        },
        payee: {
          email_address: paypalEmail.docs[0].email,
        },
      },
    ],

    application_context: {
      return_url: `${params.return_url}/${params.token}/${params._id}/${random}`,
      cancel_url: params.cancel_url,
    },
  });

  let response = await client.execute(request);
  await db.insert({
    random,
    id: response.result.id,
    token: params.token,
    collection: "transaction",
  });
  return response;
};
const manage_access = async function (params) {
  const { __ow_path } = params;
  // return {headers:{"Content-Type":"application/json"},statusCode:200,body:{data:{__ow_path}}}
  const [pam, token, _id, random] = __ow_path.split("/");
  const { access_token } = jwt.verify(token, params.token_pass);
  const cloudant = require("@cloudant/cloudant")({
    url: params.__bx_creds.cloudantnosqldb.url,
    plugins: [
      { iamauth: { iamApiKey: params.__bx_creds.cloudantnosqldb.apikey } },
    ],
  });
  const db = cloudant.db.use("ecommerce");
  const query = {
    selector: {
      $and: [
        { access_token: { $eq: access_token } },
        { collection: { $eq: "user" } },
      ],
    },
  };

  const res = await db.find(query);

  if (res.docs.length === 1) {
    const transaction = await db.find({
      selector: {
        $and: [{ random }, { token }, { collection: "transaction" }],
      },
    });

    let clientId = params.paypal_client_id;
    let clientSecret = params.paypal_client_secret;
    let environment = new paypal.core.SandboxEnvironment(
      clientId,
      clientSecret
    );
    let client = new paypal.core.PayPalHttpClient(environment);
    const request = new paypal.orders.OrdersCaptureRequest(transaction.docs[0].id);
    request.requestBody({});
    let response = await client.execute(request);

    await db.destroy(transaction.docs[0]._id, transaction.docs[0]._rev);
    const queryPrivate = {
      selector: {
        $and: [{ _id: { $eq: _id } }, { collection: { $eq: "private-repo" } }],
      },
    };

    const privateRepo = await db.find(queryPrivate);

    const queryRepo = {
      selector: {
        $and: [
          { username: { $eq: privateRepo.docs[0].username } },
          { collection: { $eq: "user" } },
        ],
      },
    };

    const repoOwner = await db.find(queryRepo);
    const queryPaypal = {
      selector: {
        $and: [
          { github_username: { $eq: privateRepo.docs[0].username } },
          { collection: { $eq: "paypal" } },
        ],
      },
    };

    const paypalAcc = await db.find(queryPaypal);
    const { data, status } = await axios({
      url: `https://api.github.com/repos/${privateRepo.docs[0].username}/${privateRepo.docs[0].name}/collaborators/${res.docs[0].username}`,
      headers: {
        "Content-Type": "application/x-www-form-urlencoded",
        Authorization: `bearer ${repoOwner.docs[0].access_token}`,
      },
      data: {
        permission: "pull",
      },
      method: "PUT",
    });

    if (status === 201) {
      let paypal = paypalAcc.docs[0];
      let amount = Number(paypal.amount);
      amount += Number(privateRepo.docs[0].amount);
      paypal.amount = amount;
      await db.insert(paypal);
      const { _id:p, _rev:l, username, ...other } = privateRepo.docs[0];
      const selectOwnedRepo={selector:{
        ...other,
        owner_username: username,
        collection: "owned-repo",
        username: res.docs[0].username,
      }}
      const findOwnedRepo=await db.find(selectOwnedRepo)
const repoId=findOwnedRepo.docs.length>0?findOwnedRepo.docs[0]._id:undefined
const repoRev=findOwnedRepo.docs.length>0?findOwnedRepo.docs[0]._rev:undefined
      const ownedRepo = {
        _id:repoId,
        _rev:repoRev,
        ...other,
        owner_username: username,
        collection: "owned-repo",
        username: res.docs[0].username,
      };
      const { ok } = await db.insert(ownedRepo);
      if (ok) {
        return {
          headers: { location: `${params.frontend_url}/dashboard` },
          statusCode: 302,
        };

      } else {
        return {
          headers: { location: `${params.frontend_url}/error` },
          statusCode: 302,
        };

      }
    } else {

      return {
        headers: { location: `${params.frontend_url}/error` },
        statusCode: 302,
      };
    }
  }
};
module.exports.buy_paypal = buy_paypal;
module.exports.manage_access = manage_access;
Enter fullscreen mode Exit fullscreen mode

This manage_access.js is used to tell the backend that if someone successfully pay the private repo amount it will automatically get access to it. After that deploy the function into the IBM Cloud functions using serverless deploy and voila our application is done.

Conclusion

Finally you can create your own application now using IBM Cloud Object Storage for static web hosting, IBM Cloud Function as the backend logic, and IBM cloudant as the database, you can go forward and make your own application now. I hope you enjoy this tutorial if you have question just comment down below and i will try as much as i can to answer it

Top comments (0)