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 :
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 :
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 :
Then you will get access key id and secret like this :
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
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
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
Run the app using yarn dev
if you see this screen, then you're successfully create the nuxt application :
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 :
Also remove all the component in components folder, and create Home.vue for our homepage like this :
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
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>
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>
After that go to layouts/default.vue
and change the whole content like this :
<template>
<v-app >
<nuxt/>
</v-app>
</template>
Go to the browser and go to http://localhost:3000
and if its successful you will see something like this :
Now let's upload it to IBM cloud storage using rclone :
yarn generate
cd dist
rclone copy ./ <remote name>:<bucket name>
Find your static website url under bucket configuration like this :
Create public access for your static website like this on access policies menu :
Then open it up in browser like this :
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>
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
Modify nuxt.config.js adding to the vuetify section.
vuetify: {
defaultAssets: {icons: 'fa'},
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>
Go to http://localhost:3000/login
and you will see your login page look something like this :
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 :
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 :
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 :
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
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
Now go to the root of your folder and type this command :
serverless create --template openwhisk-nodejs --path functions
cd functions
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 :
After that run this command to select group :
# you can find group name by running command `ibmcloud resource groups`
ibmcloud target -g <group name>
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
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>
Let's download the necessary dependencies first :
yarn add axios
yarn add -D serverless-dotenv-plugin serverless-openwhisk
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};
}
}
After you done setting up IBM cloud functions run this command to deploy it :
serverless deploy
Copy the URL you got from serverless deploy
for github authorization callback url :
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
Then in nuxt.config.js
add this line :
buildModules: [
...
'@nuxtjs/dotenv'
],
Add .env
file in the frontend
folder and add this line :
github_client_id=<your github client id>
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>
...
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
}
}
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>
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>
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>
And now generate the frontend just like before and rclone it :
yarn generate
cd dist
rclone copy ./ <remote name>:<bucket name>
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 :
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>
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>
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>
This is how our dashboard will look like for now :
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>
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 []
}
}
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
Now let's deploy it using serverless
command line like this :
serverless deploy
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>
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>
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>
Go to http://localhost:3000/dashboard
and you will see something like this if you're successfully shown your private repo :
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;
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>
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>
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>
Now you can sell your repo when you put the amount and clicking sell button like this :
Then when you go to homepage it also shown the price and buy button :
Step 6 - Integrate Paypal To Accept Payment
First create sandbox paypal account here, after that create a demo account on developer 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>
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>
it will now look like this :
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>
Now whenever the paypal button not connected the sell button is 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;
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)