home and uplaod view with pay management/wip admin token

master
Chakib Benziane 5 years ago
parent 6b74f86164
commit a2ae0ce517

@ -66,14 +66,14 @@ func PollPaidInvoice(invoiceId string, invoicePaidChan chan<- *Invoice, errorCha
for {
msg := <-watchPaidChannel
//log.Printf("received message on %s", msg.Channel)
log.Printf("received message on %s", msg.Channel)
split := strings.Split(msg.Channel, ":")
if len(split) != 2 {
errorChan <- fmt.Errorf("error in pubsub channel parsing %s", msg.Channel)
}
targetInvoiceId := strings.Split(msg.Channel, ":")[1]
//log.Printf("target invoice is %s", targetInvoiceId)
log.Printf("target invoice is %s", targetInvoiceId)
if targetInvoiceId == invoiceId {
invoice := Invoice{}

@ -3,9 +3,9 @@ package ln
import (
"encoding/hex"
"encoding/json"
"fmt"
"log"
"strconv"
"strings"
"time"
"github.com/lightningnetwork/lnd/lnrpc"
@ -65,14 +65,31 @@ func (t *timestamp) UnmarshalJSON(in []byte) error {
return nil
}
val, err := strconv.Atoi(string(in))
// Try to first parse in RFC3339
var val time.Time
var err error
// put the input in string mode
strIn := string(in)
// clean
strIn = strings.Replace(strIn, "\"", "", -1)
if val, err = time.Parse(time.RFC3339, strIn); err != nil {
val, err := strconv.Atoi(strIn)
if err != nil {
log.Printf("error unmarshal time %s", in)
return err
}
parsedTime := time.Unix(int64(val), 0)
*t = timestamp(parsedTime)
return nil
if err != nil {
return fmt.Errorf("cannot unmarshal timestamp int")
}
parsedTime := time.Unix(int64(val), 0)
*t = timestamp(parsedTime)
*t = timestamp(val)
return nil
}

@ -170,12 +170,18 @@ func (ctrl UploadCtrl) CheckStatus(c *gin.Context) {
}
// Get invoice id for upload
invoiceId, err := GetUploadIdInvoice(uploadId)
invoiceId, err := GetUploadIdInvoiceId(uploadId)
if err != nil {
utils.JSONErrPriv(c, http.StatusInternalServerError, err)
return
}
// No invoice found
if invoiceId == "" {
utils.JSONErr(c, http.StatusNotFound, "no invoice found")
return
}
// Get invoice
invoice, err := ln.CheckInvoice(invoiceId)
if err != nil {
@ -225,6 +231,11 @@ func (ctrl UploadCtrl) PollStatus(c *gin.Context) {
return
}
if invoice == nil {
utils.JSONErr(c, http.StatusInternalServerError, "no invoice found")
return
}
if uploadStatus.WaitPay() {
invoiceChan := make(chan *ln.Invoice)
@ -238,6 +249,7 @@ func (ctrl UploadCtrl) PollStatus(c *gin.Context) {
log.Println("blocking")
select {
case invoice := <-invoiceChan:
log.Printf("poll: received invoice notif %s", invoice.RHash)
// if expired return with expired response
if invoice.Expired {
@ -257,6 +269,8 @@ func (ctrl UploadCtrl) PollStatus(c *gin.Context) {
////////////////
///// Invoice was paid
////////////////
//
log.Println("POLL: invoice was paid")
uploadStatus |= UpPaid
err := SetUploadStatus(uploadId, uploadStatus)

@ -207,7 +207,7 @@ type Upload struct {
//}
func GetUploadInvoice(uploadId string) (*ln.Invoice, error) {
invoice := ln.Invoice{}
var invoice ln.Invoice
uploadInvoiceKey := fmt.Sprintf("upload_%s_invoice", uploadId)
@ -219,7 +219,8 @@ func GetUploadInvoice(uploadId string) (*ln.Invoice, error) {
return &invoice, nil
}
func GetUploadIdInvoice(uploadId string) (string, error) {
func GetUploadIdInvoiceId(uploadId string) (string, error) {
invoice, err := GetUploadInvoice(uploadId)
return invoice.RHash, err

@ -28,7 +28,7 @@ func getLastSettledInvoiceIndex() uint64 {
err := db.DB.Redis.Do(radix.FlatCmd(&cursor, "GET", LastSettledInvoiceIndexKey))
if err != nil {
log.Fatal(err)
return uint64(0)
}
return cursor
@ -37,7 +37,7 @@ func getLastSettledInvoiceIndex() uint64 {
func setLastSettledInvoiceIndex(index uint64) {
err := db.DB.Redis.Do(radix.FlatCmd(nil, "SET", LastSettledInvoiceIndexKey, index))
if err != nil {
log.Fatal(err)
panic(err)
}
}
@ -162,4 +162,8 @@ func handleSettledInvoice(invoice *lnrpc.Invoice) {
err = db.DB.Redis.Do(radix.FlatCmd(nil, "PUBLISH",
key, newInvoiceJson))
if err != nil {
panic(err)
}
}

@ -5,7 +5,7 @@
<meta name="viewport" content="width=device-width">
<title></title>
</head>
<body class="avenir bg-white">
<body class="sans-serif bg-white">
<div id="app">
</div>

@ -22,7 +22,8 @@
"tachyons": "4.10.0",
"vue": "^2.6.8",
"vue-hot-reload-api": "^2.3.3",
"vue-router": "^3.0.2"
"vue-router": "^3.0.2",
"vuex": "^3.1.0"
},
"alias": {
"vue": "./node_modules/vue/dist/vue.common.js"

@ -1,15 +1,41 @@
<template>
<div id="app" class="flex flex-column items-center">
<div id="logo" class="w-100 flex justify-center items-end">
<span class="logo-titla f1 lh-title tracked">BIT4SAT</span>
<a href="/" class="logo-titla f1 lh-title tracked">BIT4SAT</a>
</div>
<div class="main h-100 w-100 flex mt5 items-start justify-center">
<div class="main h-100 w-100 flex mt4 items-start justify-center">
<router-view />
</div>
</div>
</template>
<script charset="utf-8">
import GetWorker from './workerInterface.js';
const Worker = GetWorker('main');
export default {
mounted(){
this.worker = Worker
// listen and route
this.worker.listenTo('upload-invoice', (e) => {
console.log("received invoice ", e.data)
this.$store.commit('setInvoice', e.data.invoice)
this.$store.commit('setStatus', e.data.status)
// if we are not on upload route go there
if ( this.$router.currentRoute.name !== 'upload' ) {
this.$router.push({name: 'upload', params: { uploadId: e.data.upload_id }})
}
// self.invoice = e.data.invoice;
// self.uploadId = e.data.upload_id;
// self.status = e.data.status;
})
}
}
</script>
<style>
@ -18,31 +44,15 @@
}
#logo {
height: 35%;
height: 25%;
}
#logo a {
color: #FF725C;
font-style: italic;
font-weight: bolder;
text-shadow: 2px 3px #444, -2px -2px #444;
text-decoration: none;
}
.unpaid {
background-color: pink;
}
.expired {
background-color: yellow;
}
.paid {
background-color: lightgreen;
}
.last-upload {
background: cyan;
}
.dl-link{
color: #d33b00
}
</style>

@ -1,6 +1,16 @@
<template>
<div id="home">
<div id="home" class="flex flex-column justify-center items-center">
<upload></upload>
<div class="desc f6 pt2 b lh-copy measure-narrow gray tc">
<p>
If choose to setup a fee for your link, every download will
require a payment.
</p>
<p>You can redeem your payments at any time.</p>
</div>
<!--<pay :uploadId="uploadId" :status="status" :invoice="invoice"></pay>-->
<!--<download-link v-if="uploadId && paid" :id="uploadId"></download-link>-->
@ -9,9 +19,6 @@
<script charset="utf-8">
import Upload from './Upload.vue';
import Pay from './Pay.vue';
import DownloadLink from './DownloadLink.vue';
import GetWorker from './workerInterface.js';
import { lastSession } from './api.js';
@ -20,85 +27,48 @@ const Worker = GetWorker('main');
export default {
name: 'app',
data() {
return {
uploadId: 0,
invoice: {},
status: {},
}
},
props: ['uploadId', 'invoice', 'status'],
mounted (){
let self = this;
this.worker = Worker;
// listen to worker events
this.worker.listenTo('upload-invoice', (e) => {
console.log("received invoice ", e.data)
self.invoice = e.data.invoice;
self.uploadId = e.data.upload_id;
self.status = e.data.status;
})
this.worker.listenTo('payment-received', (e)=>{
console.log('pay received', e.data)
this.invoice = e.data.invoice;
this.status = e.data.status;
})
// this.worker.listenTo('upload-invoice', (e) => {
// console.log("received invoice ", e.data)
// self.invoice = e.data.invoice;
// self.uploadId = e.data.upload_id;
// self.status = e.data.status;
//
// })
//
// this.worker.listenTo('payment-received', (e)=>{
// console.log('pay received', e.data)
// this.invoice = e.data.invoice;
// this.status = e.data.status;
// })
// Get last session uploadId if it exists
lastSession()
.then((data) => {
// If upload id is unpaid
if (data.uploadId !== 0){
this.invoice = data.invoice;
this.uploadId = data.uploadId;
this.status = data.status;
if (this.invoice.status === 'unpaid') {
console.log("last session unpaid")
// start waiting for the payment status
this.worker.post({
msg: 'watch-payment',
uploadId: this.uploadId
})
}
// Send to upload view
this.$store.commit('setInvoice', data.invoice)
this.$store.commit('setStatus', data.status)
console.log("push to upload view ", data.uploadId)
this.$router.push({name: 'upload',
params: { uploadId: data.upload_id }
})
}
})
},
computed: {
paid() {
return this.invoice.status == 'paid';
},
},
components: {
Upload,
Pay,
DownloadLink,
}
}
</script>
<style>
.unpaid {
background-color: pink;
}
.expired {
background-color: yellow;
}
.paid {
background-color: lightgreen;
}
.last-upload {
background: cyan;
}
.dl-link{
color: #d33b00
}
</style>

@ -1,17 +1,43 @@
<template>
<div id="payment">
<img v-if="unpaid" :src="payreqURI" />
<timer v-if="showTimer" :expires="expires" :expired="expired"></timer>
<p class="paid" v-if="paid">Paid on {{paidAt}}</p>
<p class="unpaid" v-if="unpaid">UNPAID</p>
<p class="expired" v-if="expired">EXPIRED</p>
<div id="payment" class="flex flex-column">
<div class="header flex flex-column justify-between items-start mb1">
<div class="uid flex justify-start items-center w-100 f5">upload #<span class="b f4 dark-gray">{{uploadId}}</span></div>
</div>
<img class="ba bw3 light-gray" v-if="unpaid" :src="payreqURI" />
<div v-if="showTimer" class="payreq-wrapper bg-washed-yellow flex justify-center">
<div class="payreq b f6 tc courier gray">
<p>
{{invoice.payreq}}
</p>
</div>
</div>
<div class="status flex flex-row justify-between items-center">
<div class="paid">
<p class="light-red f5 mv2" v-if="unpaid">UNPAID</p>
<p class="yellow b f5 mv2" v-if="expired">EXPIRED</p>
<p class="green b f5 mv2" v-if="paid">PAID</p>
</div>
<timer v-if="showTimer" :expires="expires" :expired="expired"></timer>
<div class="paid-date f6 mid-gray" v-if="paid">on &nbsp;{{paidAt}}</div>
</div>
<canvas id="canvas" hidden>
</div>
</template>
<script charset="utf-8">
import { mapState, mapGetters } from 'vuex'
import QRCode from 'qrcode';
import Timer from './Timer.vue';
@ -35,6 +61,9 @@ export default {
width: 340,
errorCorrectionLevel: 'H',
type: 'png',
color: {
dark: '#ff725cff',
},
renderOpts:{
quality: 1,
}
@ -49,23 +78,29 @@ export default {
},
computed: {
...mapGetters([
'paid',
'unpaid'
]),
paidAt: function(){
return new Date(this.invoice.paid_at).toGMTString();
},
paid: function() {
return this.invoice.status == 'paid';
},
//paid: function() {
// return this.invoice.status == 'paid';
//},
unpaid: function(){
return this.invoice.status == 'unpaid';
},
//unpaid: function(){
// return this.invoice.status == 'unpaid';
//},
showTimer: function(){
if (this.invoice.status !== undefined) {
return this.invoice.status != 'paid';
}
return !this.paid
//if (this.invoice.status !== undefined) {
// return this.invoice.status != 'paid';
//}
return false;
//return false;
}
},
watch: {
@ -95,8 +130,17 @@ export default {
</script>
<style>
img{
width: 340px;
height: 340px;
img {
width: var(--qrcode-width);
height: var(--qrcode-width);
}
.status {
min-width: var(--qrcode-width);
}
.payreq {
word-wrap: break-word;
max-width: var(--qrcode-width);
}
</style>

@ -1,17 +1,16 @@
<template>
<div @dragenter.stop.prevent @dragover.stop.prevent @drop.stop.prevent="drop" id="upload" class="flex flex-column ph4 pv4 ba b--dashed bw2 b--light-red br4 bg-near-white mid-gray">
<div class="title f5 b lh-title ttu tc">drop files here to start</div>
<div class="title f6 b lh-title ttu tc">drop files here</div>
<div class="or f6 mv2 tc">OR</div>
<div class="or f6 mv2 tc">or</div>
<form class="input-reset flex flex-column justify-center">
<!--hidden file input-->
<input id="fileInput" class="dn" v-on:change="fileChange($event)" type="file" multiple />
<button class="mb3" @click="chooseFiles" id="fileSelect">choose files</button>
<button class="mb3" @click.stop.prevent="chooseFiles" id="fileSelect">pick files</button>
<div class="options w-100 flex flex-row justify-between">
<input v-model.trim.number="amount" type="number">
@ -36,26 +35,14 @@
</form>
<div class="desc f6 pt2 b lh-copy measure-narrow gray tc">
<p>
If choose to setup a fee for your link, every download will
require a payment.
</p>
<p>You can redeem your payments at any time.</p>
</div>
</div>
</template>
<script charset="utf-8">
import GetWorker from './workerInterface.js';
const w = GetWorker('main');
const Worker = GetWorker('main');
//w.listenTo('upload-id', (e) => {
// console.log('vue received upload id ', e.data.id)
// console.log('vue received upload id ', e.data.invoice)
//})
export default {
@ -70,6 +57,9 @@ export default {
},
mounted(){
this.fileInput = this.$el.querySelector('#fileInput')
this.worker = Worker
},
methods: {
drop(event){
@ -77,13 +67,16 @@ export default {
const dt = event.dataTransfer;
this.fileCount = dt.files.length;
this.files = dt.files;
w.post({
this.worker.post({
msg: 'new-upload',
payload: [...this.files],
})
},
chooseFiles(event){
console.log("calling choose files")
this.fileInput.click();
},
fileChange(event) {
@ -94,7 +87,8 @@ export default {
this.fileCount = event.target.files.length
this.files = event.target.files
w.post({
console.log('filechange notifying new-upload')
this.worker.post({
msg: 'new-upload',
payload: [...this.files],
})

@ -0,0 +1,83 @@
<template>
<div class="flex flex-column items-center justify-center" id="upload-view">
<pay :uploadId="uploadId" :status="status" :invoice="invoice"></pay>
<div class="hr"></div>
<!--<upload></upload>-->
<!--<download-link v-if="uploadId && paid" :id="uploadId"></download-link>-->
</div>
</template>
<script charset="utf-8">
import { mapState, mapGetters } from 'vuex'
import Upload from './Upload.vue';
import Pay from './Pay.vue';
import DownloadLink from './DownloadLink.vue';
import GetWorker from './workerInterface.js';
import Api from './api.js';
const Worker = GetWorker('main');
export default {
name: 'UploadView',
data() {
return {
}
},
props: ['uploadId'],
mounted (){
let self = this;
this.worker = Worker;
this.worker.listenTo('payment-received', (e)=>{
console.log('pay received', e.data)
this.$store.commit('setInvoice', e.data.invoice)
this.$store.commit('setStatus', e.data.status)
})
// First check the status to get the invoice
Api.checkUploadStatus(this.uploadId)
.then((data)=>{
// Set upload metadata
this.$store.commit('setInvoice', data.invoice)
this.$store.commit('setStatus', data.status)
// if paayment required
if (data.status.pay_status === 'unpaid') {
Api.pollUploadStatus(this.uploadId)
.then((data)=>{
console.log(data)
this.$store.commit('setInvoice', data.invoice)
this.$store.commit('setStatus', data.status)
})
}
})
.catch((err)=>{
console.error(err)
})
},
computed: {
...mapState({
status: state => state.upload.status,
invoice: state => state.upload.invoice,
}),
},
components: {
Upload,
Pay,
DownloadLink,
}
}
</script>
<style>
</style>

@ -7,7 +7,8 @@ const endPoints = {
upload: '/api/upload',
session: '/api/session',
checkstatus: '/api/upload/poll'
pollstatus: '/api/upload/poll',
checkstatus: '/api/upload/check'
}
@ -30,11 +31,30 @@ export async function lastSession(){
return res.json()
}
export async function checkUploadStatus(uploadId){
let req = new Request(endPoints.checkstatus + '/' + uploadId,{
method: 'GET',
credentials: 'same-origin,'
})
let res = await fetch(req)
if (!res.ok && res.status != 402){
let json = await res.json()
.catch((e)=>{
console.error(res.text())
})
console.error(`${res.status}: ` + json.error)
}
return res.json().catch((e)=>{console.error(res.text())})
}
export async function pollUploadStatus(uploadId){
console.log('checking upload status')
//console.log('polling upload status')
let req = new Request(endPoints.checkstatus + '/' + uploadId,{
let req = new Request(endPoints.pollstatus + '/' + uploadId,{
method: 'GET',
credentials: 'same-origin,'
})
@ -48,7 +68,7 @@ export async function pollUploadStatus(uploadId){
.catch((e)=>{
console.error(res.text())
})
throw(`${res.status}: ` + json.error)
console.error(`${res.status}: ` + json.error)
}
return res.json().catch((e)=>{console.error(res.text())})
@ -128,4 +148,5 @@ export default {
endPoints: endPoints,
Upload: Upload,
pollUploadStatus: pollUploadStatus,
checkUploadStatus: checkUploadStatus
}

@ -1,12 +1,18 @@
import 'tachyons';
import './styles/index.css';
import 'tachyons'
import './styles/index.css'
import Vue from 'vue';
import Home from './Home.vue';
import App from './App.vue';
import GetWorker from './workerInterface.js';
import Router from 'vue-router';
import Vue from 'vue'
import store from './store.js'
import Home from './Home.vue'
import App from './App.vue'
import UploadView from './UploadView.vue'
import GetWorker from './workerInterface.js'
import Router from 'vue-router'
import Api from './api.js'
//window.api = Api
Vue.use(Router)
@ -20,7 +26,14 @@ const router = new Router({
{
path:'/u/:uploadId',
name: 'upload',
//component:
component: UploadView,
props: true,
},
{
path:'/a/:adminToken',
name: 'admin',
//component: AdminView,
props: true,
}
]
@ -34,9 +47,10 @@ const router = new Router({
const app = new Vue({
template: '<App/>',
components: { App },
router: router
}).$mount('#app');
router: router,
store
}).$mount('#app')
//window.worker = GetWorker('main');
//window.worker = GetWorker('main')

@ -0,0 +1,49 @@
import Vue from 'vue'
import Vuex from 'vuex'
import { mapState } from 'vuex'
Vue.use(Vuex)
const upload ={
state: {
invoice: {},
status: {},
},
getters: {
paid(state) {
return state.status.pay_status == 'paid'
},
expired (state){
return state.status.pay_status == 'expired'
},
unpaid (state){
return state.status.pay_status == 'waiting'
}
},
mutations:{
setInvoice (state, invoice) {
state.invoice = invoice
},
setStatus (state, status) {
state.status = status
}
}
}
const base = {
state: {
uploadId: 0
},
mutations: {
setUploadId (state, id) {
state.uploadId = id
}
}
}
export default new Vuex.Store({
modules: {
upload,
base
}
})

@ -1,10 +1,6 @@
@import './variables.css';
body {
font-family:
}
body {
/*font-size: var(--test);*/
overflow: hidden;
}

@ -1,3 +1,4 @@
:root{
--test: 16px;
--qrcode-width: 350px;
}

@ -6382,6 +6382,11 @@ vue@^2.6.8:
resolved "https://registry.yarnpkg.com/vue/-/vue-2.6.9.tgz#415c1cc1a5ed00c8f0acdd0a948139d12b7ea6b3"
integrity sha512-t1+tvH8hybPM86oNne3ZozCD02zj/VoZIiojOBPJLjwBn7hxYU5e1gBObFpq8ts1NEn1VhPf/hVXBDAJ3X5ljg==
vuex@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/vuex/-/vuex-3.1.0.tgz#634b81515cf0cfe976bd1ffe9601755e51f843b9"
integrity sha512-mdHeHT/7u4BncpUZMlxNaIdcN/HIt1GsGG5LKByArvYG/v6DvHcOxvDCts+7SRdCoIRGllK8IMZvQtQXLppDYg==
w3c-hr-time@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/w3c-hr-time/-/w3c-hr-time-1.0.1.tgz#82ac2bff63d950ea9e3189a58a65625fedf19045"

Loading…
Cancel
Save