You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
bit4sat/api/upload_ctrl.go

523 lines
11 KiB
Go

package api
import (
"crypto/sha256"
"encoding/hex"
"fmt"
"io"
"log"
"net/http"
"os"
"git.sp4ke.com/sp4ke/bit4sat/db"
"git.sp4ke.com/sp4ke/bit4sat/ln"
"git.sp4ke.com/sp4ke/bit4sat/storage"
"git.sp4ke.com/sp4ke/bit4sat/utils"
"github.com/gin-gonic/gin"
)
type UploadCtrl struct{}
//TODO: tell client to avoid sending duplicate if we already have hash
func (ctrl UploadCtrl) New(c *gin.Context) {
uploadForm := UploadForm{}
if err := c.ShouldBindJSON(&uploadForm); err != nil {
log.Printf("error parsing form: %s", err)
utils.JSONErr(c, http.StatusNotAcceptable,
"invalid form")
return
}
// Create unique id
uploadId, err := storage.GetShortId()
if err != nil {
utils.JSONErrPriv(c, http.StatusInternalServerError, err)
return
}
tx, err := db.DB.Sql.Beginx()
if err != nil {
utils.JSONErrPriv(c, http.StatusInternalServerError, err)
return
}
if uploadForm.RequestPayment {
if _, ok := ln.CurrencyID[uploadForm.PaymentCurrency]; !ok {
utils.JSONErr(c, http.StatusInternalServerError,
"currency not handled or recognized")
return
}
}
// Create new upload
up := &storage.Upload{}
up.UploadId = uploadId
up.AskFee = uploadForm.RequestPayment
up.AskCurrency = uploadForm.PaymentCurrency
up.AskAmount = uploadForm.RequestPaymentAmount
// Generate unique download id
//
dlId, err := storage.GetShortId()
if err != nil {
utils.JSONErrPriv(c, http.StatusInternalServerError, err)
return
}
up.DownloadId = dlId
err = up.TxWrite(tx)
if err != nil {
utils.JSONErrPriv(c, http.StatusInternalServerError, err)
db.RollbackTx(tx, nil)
return
}
for _, file := range uploadForm.Files {
fup := &storage.FileUpload{}
fup.UploadId = uploadId
fup.FileName, fup.FileExt = utils.CleanFileName(file.Name)
fup.FileSize = file.Size
fup.FileType = file.Type
fup.SHA256 = file.SHA256
err := fup.TxWrite(tx)
if err != nil {
utils.JSONErrPriv(c, http.StatusInternalServerError, err)
db.RollbackTx(tx, nil)
return
}
}
err = tx.Commit()
if err != nil {
utils.JSONErrPriv(c, http.StatusInternalServerError, err)
return
}
// Set upload status to new
err = storage.SetUploadStatus(uploadId, storage.UpNew)
if err != nil {
utils.JSONErrPriv(c, http.StatusInternalServerError, err)
return
}
// Upload seems valid, handle new upload procedure
//
// Get the upload session
upSession, err := SessionStore.Get(c.Request, UpSessionKey)
if err != nil {
utils.JSONErrPriv(c, http.StatusInternalServerError, err)
return
}
// Set the uploadId as last upload for session
upSession.AddFlash(uploadId)
err = upSession.Save(c.Request, c.Writer)
if err != nil {
utils.JSONErrPriv(c, http.StatusInternalServerError, err)
return
}
// If not free upload generate a an invoice
// TODO: check if upload fee is free
if uploadForm.RequestPayment {
invoiceOpts := ln.InvoiceOpts{
Amount: uploadForm.RequestPaymentAmount,
Curr: ln.CurrencyID[uploadForm.PaymentCurrency],
Memo: fmt.Sprintf("bit4sat upload: %s", uploadId),
}
invoice, err := ln.NewInvoice(invoiceOpts)
if err != nil {
utils.JSONErrPriv(c, http.StatusInternalServerError, err)
return
}
// Update Upload Invoice
err = storage.SetUploadInvoice(uploadId, invoice)
if err != nil {
utils.JSONErrPriv(c, http.StatusInternalServerError, err)
return
}
// Start watching upload payment status for this client
//err = WatchUploadPayment(invoice)
//if err != nil {
//log.Println("error watching payment ", err)
//utils.JSONErrPriv(c, http.StatusInternalServerError, err)
//return
//}
c.JSON(http.StatusOK, gin.H{
"status": storage.UpNew,
"invoice": invoice,
"upload_id": uploadId,
})
return
// Handle free uploads
} else {
log.Println("new upload created")
c.JSON(http.StatusOK, gin.H{
"status": "ready for upload",
"result": gin.H{
"id": uploadId,
},
})
}
}
func (ctrl UploadCtrl) CheckStatus(c *gin.Context) {
uploadId := c.Param("id")
exists, err := storage.IdExists(uploadId)
if err != nil {
utils.JSONErrPriv(c, http.StatusInternalServerError, err)
return
}
if !exists {
utils.JSONErr(c, http.StatusNotFound, "upload id not found")
return
}
// Get upload status
uploadStatus, err := storage.GetUploadStatus(uploadId)
if err != nil {
utils.JSONErrPriv(c, http.StatusInternalServerError, err)
return
}
if uploadStatus.IsNew() {
c.JSON(http.StatusOK, gin.H{
"status": uploadStatus,
})
return
}
// Get invoice id for upload
invoiceId, err := storage.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 {
utils.JSONErrPriv(c, http.StatusInternalServerError, err)
return
}
c.JSON(ln.InvoiceHttpCode(invoice), gin.H{
"upload_id": uploadId,
"invoice": invoice,
"status": uploadStatus,
})
}
func (ctrl UploadCtrl) PollStatus(c *gin.Context) {
uploadId := c.Param("id")
exists, err := storage.IdExists(uploadId)
if err != nil {
utils.JSONErrPriv(c, http.StatusInternalServerError, err)
return
}
if !exists {
utils.JSONErr(c, http.StatusNotFound, "id not found")
return
}
uploadStatus, err := storage.GetUploadStatus(uploadId)
if err != nil {
utils.JSONErrPriv(c, http.StatusInternalServerError, err)
return
}
if uploadStatus&storage.UpNew != 0 {
c.JSON(http.StatusCreated, gin.H{
"status": uploadStatus,
})
return
}
// we are not in UpNew state so we should have an invoice
invoice, err := storage.GetUploadInvoice(uploadId)
if err != nil {
utils.JSONErrPriv(c, http.StatusInternalServerError, err)
return
}
if invoice == nil {
utils.JSONErr(c, http.StatusInternalServerError, "no invoice found")
return
}
if uploadStatus.WaitPay() {
invoiceChan := make(chan *ln.Invoice)
errorChan := make(chan error)
log.Printf("starting invoice poll for %s", invoice.RHash)
// If waiting payment, wait until invoice is paid
go ln.PollPaidInvoice(invoice.RHash, invoiceChan, errorChan)
// Block until payment done or error
log.Println("blocking")
select {
case invoice := <-invoiceChan:
log.Printf("poll: received invoice notif %s", invoice.RHash)
////////////////
///// Invoice was paid
////////////////
//
log.Println("POLL: invoice was paid")
uploadStatus |= storage.UpPaid
err := storage.SetUploadStatus(uploadId, uploadStatus)
if err != nil {
log.Printf("Redis error: %s", err)
}
// Update Upload Invoice
err = storage.SetUploadInvoice(uploadId, invoice)
if err != nil {
utils.JSONErrPriv(c, http.StatusInternalServerError, err)
return
}
// Generate admin token and save it
adminToken, err := storage.GetLongId()
if err != nil {
utils.JSONErrPriv(c, http.StatusInternalServerError, err)
return
}
err = storage.SetUploadAdminToken(uploadId, adminToken)
if err != nil {
utils.JSONErrPriv(c, http.StatusInternalServerError, err)
return
}
// Get the download id
dlId, err := storage.GetDownloadId(uploadId)
if err != nil {
utils.JSONErrPriv(c, http.StatusInternalServerError, err)
return
}
// Clear the session
upSession, err := SessionStore.Get(c.Request, UpSessionKey)
if err != nil {
utils.JSONErrPriv(c, http.StatusInternalServerError, err)
return
}
upSession.Flashes()
err = upSession.Save(c.Request, c.Writer)
if err != nil {
utils.JSONErrPriv(c, http.StatusInternalServerError, err)
return
}
// invoice was paid
c.JSON(http.StatusAccepted, gin.H{
"status": uploadStatus,
"invoice": invoice,
"admin_token": adminToken,
"download_id": dlId,
})
return
case err := <-errorChan:
utils.JSONErrPriv(c, http.StatusInternalServerError, err)
return
}
// We stopped waiting for the invoice, it must have expired
err := storage.SetUploadStatus(uploadId, storage.UpPayExpired)
if err != nil {
log.Printf("Redis error: %s", err)
}
c.JSON(http.StatusGone, gin.H{
"status": uploadStatus,
"invoice": invoice,
})
return
} else {
c.JSON(http.StatusOK, gin.H{
"status": uploadStatus,
"invoice": invoice,
})
}
return
}
func (ctrl UploadCtrl) Upload(c *gin.Context) {
// Check that upload ID exists
uploadId := c.Param("id")
exists, err := storage.IdExists(uploadId)
if err != nil {
utils.JSONErrPriv(c, http.StatusInternalServerError, err)
return
}
if !exists {
utils.JSONErr(c, http.StatusNotFound, "id not found")
return
}
// Check if payment done
// NOTE: we pre upload and remove the files if the payment was expired
//uploadStatus, err := GetUploadStatus(uploadId)
//if err != nil {
//utils.JSONErrPriv(c, http.StatusInternalServerError, err)
//return
//}
//if uploadStatus < UpWaitingStorage {
//utils.JSONErr(c, http.StatusPaymentRequired,
//fmt.Sprintf("invoice not paid"))
//return
//}
form, err := c.MultipartForm()
if err != nil {
utils.JSONErr(c, http.StatusNotAcceptable,
fmt.Sprintf("Could not parse form: %s", err.Error()))
return
}
files := form.File["upload[]"]
tx, err := db.DB.Sql.Beginx()
if err != nil {
utils.JSONErrPriv(c, http.StatusInternalServerError, err)
return
}
for _, file := range files {
log.Printf("Handling %s", file.Filename)
// Get the file's sha256
hasher := sha256.New()
formFd, err := file.Open()
defer formFd.Close()
if err != nil {
utils.JSONErr(
c, http.StatusInternalServerError,
fmt.Sprintf("Could not read file %s", file.Filename),
)
db.RollbackTx(tx, nil)
return
}
_, err = io.Copy(hasher, formFd)
if err != nil {
utils.JSONErr(c, http.StatusInternalServerError,
"")
db.RollbackTx(tx, nil)
return
}
sum256 := hex.EncodeToString(hasher.Sum(nil))
// Get the file's metadata from upload table
up, err := storage.GetByHashID(sum256, uploadId)
if err != nil {
utils.JSONErrPriv(c, http.StatusNotFound, err)
db.RollbackTx(tx, nil)
return
}
log.Printf("Found received file's %s metadata in local database\n", up.FileName)
// If file already stored for this upload report conflict
if up.Stored {
utils.JSONErr(c, http.StatusConflict,
fmt.Sprintf("%s already uploaded", up.FileName))
return
}
//
//
log.Println("Storing file")
err = storage.StoreFormFile(formFd, up.SHA256+up.FileExt)
if err != nil {
utils.JSONErrPriv(c, http.StatusInternalServerError, err)
db.RollbackTx(tx, nil)
return
}
log.Printf("%s stored at %s", up.FileName,
storage.GetStoreDestination(up.SHA256+up.FileExt))
// Setting status to stored
log.Println("Updating file upload stored status")
err = up.TxSetFileStored(tx)
if err != nil {
utils.JSONErrPriv(c, http.StatusInternalServerError, err)
// Rollback and remove the file
db.RollbackTx(tx, func() {
err := os.Remove(storage.GetStoreDestination(up.SHA256 + up.FileExt))
if err != nil {
log.Println(err)
}
})
return
}
}
err = tx.Commit()
if err != nil {
utils.JSONErrPriv(c, http.StatusInternalServerError, err)
return
}
// Set the whole transaction as stored
err = storage.SetUploadStatus(uploadId, storage.UpStored)
if err != nil {
utils.JSONErrPriv(c, http.StatusInternalServerError, err)
return
}
c.JSON(http.StatusOK, gin.H{
"status": http.StatusOK,
"store_status": storage.UpStored,
"upload_id": uploadId,
})
}