Compare commits

..

No commits in common. "b44b12f85e0104415fa04fe49bc911bb8ea105e5" and "b21a425e24f5543802e7267369f37967d4805697" have entirely different histories.

7 changed files with 206 additions and 243 deletions

View File

@ -3,7 +3,6 @@ package cid
import ( import (
"bytes" "bytes"
"encoding/binary" "encoding/binary"
"encoding/hex"
"errors" "errors"
"github.com/multiformats/go-multibase" "github.com/multiformats/go-multibase"
) )
@ -15,18 +14,7 @@ type CID struct {
Size uint64 Size uint64
} }
func (c CID) StringHash() string { func Encode(hash [32]byte, size uint64) (string, error) {
return hex.EncodeToString(c.Hash[:])
}
func Encode(hash []byte, size uint64) (string, error) {
var hashBytes [32]byte
copy(hashBytes[:], hash)
return EncodeFixed(hashBytes, size)
}
func EncodeFixed(hash [32]byte, size uint64) (string, error) {
sizeBytes := make([]byte, 8) sizeBytes := make([]byte, 8)
binary.LittleEndian.PutUint64(sizeBytes, size) binary.LittleEndian.PutUint64(sizeBytes, size)
@ -36,15 +24,6 @@ func EncodeFixed(hash [32]byte, size uint64) (string, error) {
return multibase.Encode(multibase.Base58BTC, prefixedHash) return multibase.Encode(multibase.Base58BTC, prefixedHash)
} }
func EncodeString(hash string, size uint64) (string, error) {
hashBytes, err := hex.DecodeString(hash)
if err != nil {
return "", err
}
return Encode(hashBytes, size)
}
func Valid(cid string) (bool, error) { func Valid(cid string) (bool, error) {
_, err := maybeDecode(cid) _, err := maybeDecode(cid)
if err != nil { if err != nil {

View File

@ -1,88 +0,0 @@
package controller
import (
"errors"
"git.lumeweb.com/LumeWeb/portal/cid"
"git.lumeweb.com/LumeWeb/portal/service/files"
"github.com/kataras/iris/v12"
"io"
)
type FilesController struct {
Ctx iris.Context
}
type UploadResponse struct {
Cid string `json:"cid"`
}
func (f *FilesController) PostUpload() {
ctx := f.Ctx
file, meta, err := f.Ctx.FormFile("file")
if internalErrorCustom(ctx, err, errors.New("invalid file data")) {
return
}
upload, err := files.Upload(file)
if internalError(ctx, err) {
return
}
cidString, err := cid.EncodeString(upload.Hash, uint64(meta.Size))
if internalError(ctx, err) {
return
}
_ = ctx.JSON(&UploadResponse{Cid: cidString})
}
func (f *FilesController) GetDownloadBy(cidString string) {
ctx := f.Ctx
_, err := cid.Valid(cidString)
if sendError(ctx, err, iris.StatusBadRequest) {
return
}
cidObject, _ := cid.Decode(cidString)
hashHex := cidObject.StringHash()
if internalError(ctx, err) {
return
}
download, err := files.Download(hashHex)
if internalError(ctx, err) {
return
}
err = ctx.StreamWriter(func(w io.Writer) error {
_, err = io.Copy(w, download)
_ = download.(io.Closer).Close()
return err
})
internalError(ctx, err)
}
func sendErrorCustom(ctx iris.Context, err error, customError error, irisError int) bool {
if err != nil {
if customError != nil {
err = customError
}
ctx.StopWithError(irisError, err)
return true
}
return false
}
func internalError(ctx iris.Context, err error) bool {
return sendErrorCustom(ctx, err, nil, iris.StatusInternalServerError)
}
func internalErrorCustom(ctx iris.Context, err error, customError error) bool {
return sendErrorCustom(ctx, err, customError, iris.StatusInternalServerError)
}
func sendError(ctx iris.Context, err error, irisError int) bool {
return sendErrorCustom(ctx, err, nil, irisError)
}

18
main.go
View File

@ -3,18 +3,16 @@ package main
import ( import (
"embed" "embed"
"git.lumeweb.com/LumeWeb/portal/config" "git.lumeweb.com/LumeWeb/portal/config"
"git.lumeweb.com/LumeWeb/portal/controller"
"git.lumeweb.com/LumeWeb/portal/db" "git.lumeweb.com/LumeWeb/portal/db"
_ "git.lumeweb.com/LumeWeb/portal/docs" _ "git.lumeweb.com/LumeWeb/portal/docs"
"git.lumeweb.com/LumeWeb/portal/renterd" "git.lumeweb.com/LumeWeb/portal/renterd"
"git.lumeweb.com/LumeWeb/portal/service/files" "git.lumeweb.com/LumeWeb/portal/service"
"git.lumeweb.com/LumeWeb/portal/validator" "git.lumeweb.com/LumeWeb/portal/validator"
"github.com/iris-contrib/swagger" "github.com/iris-contrib/swagger"
"github.com/iris-contrib/swagger/swaggerFiles" "github.com/iris-contrib/swagger/swaggerFiles"
"github.com/kataras/iris/v12" "github.com/kataras/iris/v12"
"github.com/kataras/iris/v12/mvc" "github.com/kataras/iris/v12/mvc"
"log" "log"
"net/http"
) )
// Embed a directory of static files for serving from the app's root path // Embed a directory of static files for serving from the app's root path
@ -47,7 +45,7 @@ func main() {
renterd.Ready() renterd.Ready()
files.Init() service.InitFiles()
// Create a new Iris app instance // Create a new Iris app instance
app := iris.New() app := iris.New()
@ -63,23 +61,19 @@ func main() {
api := app.Party("/api") api := app.Party("/api")
v1 := api.Party("/v1") v1 := api.Party("/v1")
// Register the AccountController with the MVC framework and attach it to the "/api/account" path // Register the AccountService with the MVC framework and attach it to the "/api/account" path
mvc.Configure(v1.Party("/account"), func(app *mvc.Application) { mvc.Configure(v1.Party("/account"), func(app *mvc.Application) {
app.Handle(new(controller.AccountController)) app.Handle(new(service.AccountService))
}) })
mvc.Configure(v1.Party("/auth"), func(app *mvc.Application) { mvc.Configure(v1.Party("/auth"), func(app *mvc.Application) {
app.Handle(new(controller.AuthController)) app.Handle(new(service.AuthService))
}) })
mvc.Configure(v1.Party("/files"), func(app *mvc.Application) { mvc.Configure(v1.Party("/files"), func(app *mvc.Application) {
app.Handle(new(controller.FilesController)) app.Handle(new(service.FilesService))
}) })
tus := initTus()
app.Any(API_PATH+"{fileparam:path}", iris.FromStd(http.StripPrefix(API_PATH, tus)))
swaggerConfig := swagger.Config{ swaggerConfig := swagger.Config{
// The url pointing to API definition. // The url pointing to API definition.
URL: "http://localhost:8080/swagger/doc.json", URL: "http://localhost:8080/swagger/doc.json",

View File

@ -1,4 +1,4 @@
package controller package service
import ( import (
"crypto/ed25519" "crypto/ed25519"
@ -14,7 +14,7 @@ import (
"reflect" "reflect"
) )
type AccountController struct { type AccountService struct {
Ctx iris.Context Ctx iris.Context
} }
@ -64,7 +64,7 @@ func hashPassword(password string) (string, error) {
return string(hashedPassword), nil return string(hashedPassword), nil
} }
func (a *AccountController) PostRegister() { func (a *AccountService) PostRegister() {
var r RegisterRequest var r RegisterRequest
if err := a.Ctx.ReadJSON(&r); err != nil { if err := a.Ctx.ReadJSON(&r); err != nil {

View File

@ -1,4 +1,4 @@
package controller package service
import ( import (
"crypto/ed25519" "crypto/ed25519"
@ -22,7 +22,7 @@ func init() {
blocklist = jwt.NewBlocklist(1 * time.Hour) blocklist = jwt.NewBlocklist(1 * time.Hour)
} }
type AuthController struct { type AuthService struct {
Ctx iris.Context Ctx iris.Context
} }
@ -132,7 +132,7 @@ func generateAndSaveChallengeToken(accountID uint, maxAge time.Duration) (string
} }
// PostLogin handles the POST /api/auth/login request to authenticate a user and return a JWT token. // PostLogin handles the POST /api/auth/login request to authenticate a user and return a JWT token.
func (a *AuthController) PostLogin() { func (a *AuthService) PostLogin() {
var r LoginRequest var r LoginRequest
// Read the login request from the client. // Read the login request from the client.
@ -169,7 +169,7 @@ func (a *AuthController) PostLogin() {
} }
// PostChallenge handles the POST /api/auth/pubkey/challenge request to generate a challenge for a user's public key. // PostChallenge handles the POST /api/auth/pubkey/challenge request to generate a challenge for a user's public key.
func (a *AuthController) PostPubkeyChallenge() { func (a *AuthService) PostPubkeyChallenge() {
var r LoginRequest var r LoginRequest
// Read the login request from the client. // Read the login request from the client.
@ -200,7 +200,7 @@ func (a *AuthController) PostPubkeyChallenge() {
} }
// PostKeyLogin handles the POST /api/auth/pubkey/login request to authenticate a user using a public key challenge and return a JWT token. // PostKeyLogin handles the POST /api/auth/pubkey/login request to authenticate a user using a public key challenge and return a JWT token.
func (a *AuthController) PostPubkeyLogin() { func (a *AuthService) PostPubkeyLogin() {
var r PubkeyLoginRequest var r PubkeyLoginRequest
// Read the key login request from the client. // Read the key login request from the client.
@ -268,7 +268,7 @@ func (a *AuthController) PostPubkeyLogin() {
} }
// PostLogout handles the POST /api/auth/logout request to invalidate a JWT token. // PostLogout handles the POST /api/auth/logout request to invalidate a JWT token.
func (a *AuthController) PostLogout() { func (a *AuthService) PostLogout() {
var r LogoutRequest var r LogoutRequest
// Read the logout request from the client. // Read the logout request from the client.

View File

@ -1,112 +0,0 @@
package files
import (
"bufio"
"encoding/hex"
"errors"
"fmt"
"git.lumeweb.com/LumeWeb/portal/bao"
"git.lumeweb.com/LumeWeb/portal/db"
"git.lumeweb.com/LumeWeb/portal/model"
"git.lumeweb.com/LumeWeb/portal/renterd"
"github.com/go-resty/resty/v2"
"io"
"lukechampine.com/blake3"
)
var client *resty.Client
func Init() {
client = resty.New()
client.SetBaseURL(renterd.GetApiAddr() + "/api")
client.SetBasicAuth("", renterd.GetAPIPassword())
client.SetDisableWarn(true)
}
func Upload(r io.ReadSeeker) (model.Upload, error) {
var upload model.Upload
hasher := blake3.New(0, nil)
_, err := io.Copy(hasher, r)
if err != nil {
return upload, err
}
hashBytes := hasher.Sum(nil)
hashHex := hex.EncodeToString(hashBytes[:])
if err != nil {
return upload, err
}
_, err = r.Seek(0, io.SeekStart)
if err != nil {
return upload, err
}
result := db.Get().Where("hash = ?", hashHex).First(&upload)
if (result.Error != nil && result.Error.Error() != "record not found") || result.RowsAffected > 0 {
err := result.Row().Scan(&upload)
if err != nil {
return upload, err
}
}
objectExistsResult, err := client.R().Get(fmt.Sprintf("/worker/objects/%s", hashHex))
if err != nil {
return upload, err
}
if objectExistsResult.StatusCode() != 404 {
return upload, errors.New("file already exists in network, but missing in database")
}
tree, err := bao.ComputeBaoTree(bufio.NewReader(r))
if err != nil {
return upload, err
}
_, err = r.Seek(0, io.SeekStart)
if err != nil {
return upload, err
}
ret, err := client.R().SetBody(r).Put(fmt.Sprintf("/worker/objects/%s", hashHex))
if ret.StatusCode() != 200 {
err = errors.New(string(ret.Body()))
return upload, err
}
ret, err = client.R().SetBody(tree).Put(fmt.Sprintf("/worker/objects/%s.obao", hashHex))
if ret.StatusCode() != 200 {
err = errors.New(string(ret.Body()))
return upload, err
}
upload = model.Upload{
Hash: hashHex,
}
if err = db.Get().Create(&upload).Error; err != nil {
return upload, err
}
return upload, nil
}
func Download(hash string) (io.Reader, error) {
result := db.Get().Table("uploads").Where("hash = ?", hash).Row()
if result.Err() != nil {
return nil, result.Err()
}
fetch, err := client.R().SetDoNotParseResponse(true).Get(fmt.Sprintf("/worker/objects/%s", hash))
if err != nil {
return nil, err
}
return fetch.RawBody(), nil
}

190
service/files_service.go Normal file
View File

@ -0,0 +1,190 @@
package service
import (
"bytes"
"encoding/hex"
"errors"
"fmt"
"git.lumeweb.com/LumeWeb/portal/bao"
"git.lumeweb.com/LumeWeb/portal/cid"
"git.lumeweb.com/LumeWeb/portal/db"
"git.lumeweb.com/LumeWeb/portal/model"
"git.lumeweb.com/LumeWeb/portal/renterd"
"github.com/go-resty/resty/v2"
"github.com/kataras/iris/v12"
"io"
"lukechampine.com/blake3"
)
type FilesService struct {
Ctx iris.Context
}
var client *resty.Client
type UploadResponse struct {
Cid string `json:"cid"`
}
func InitFiles() {
client = resty.New()
client.SetBaseURL(renterd.GetApiAddr() + "/api")
client.SetBasicAuth("", renterd.GetAPIPassword())
client.SetDisableWarn(true)
}
func (f *FilesService) PostUpload() {
ctx := f.Ctx
file, meta, err := f.Ctx.FormFile("file")
if internalErrorCustom(ctx, err, errors.New("invalid file data")) {
return
}
buf, err := io.ReadAll(file)
if internalError(ctx, err) {
return
}
if internalErrorCustom(ctx, err, errors.New("failed to read file data")) {
return
}
hashBytes := blake3.Sum256(buf)
hashHex := hex.EncodeToString(hashBytes[:])
fileCid, err := cid.Encode(hashBytes, uint64(meta.Size))
if internalError(ctx, err) {
return
}
_, err = file.Seek(0, io.SeekStart)
if internalError(ctx, err) {
return
}
var upload model.Upload
result := db.Get().Where("hash = ?", hashHex).First(&upload)
if (result.Error != nil && result.Error.Error() != "record not found") || result.RowsAffected > 0 {
ctx.JSON(&UploadResponse{Cid: fileCid})
return
}
_, err = file.Seek(0, io.SeekStart)
if internalError(ctx, err) {
return
}
tree, err := bao.ComputeBaoTree(bytes.NewReader(buf))
if internalError(ctx, err) {
return
}
objectExistsResult, err := client.R().Get(fmt.Sprintf("/worker/objects/%s", hashHex))
if internalError(ctx, err) {
return
}
if objectExistsResult.StatusCode() != 404 {
ctx.JSON(&UploadResponse{Cid: fileCid})
return
}
if internalError(ctx, err) {
return
}
ret, err := client.R().SetBody(buf).Put(fmt.Sprintf("/worker/objects/%s", hashHex))
if ret.StatusCode() != 200 {
err = errors.New(string(ret.Body()))
}
if internalError(ctx, err) {
return
}
ret, err = client.R().SetBody(tree).Put(fmt.Sprintf("/worker/objects/%s.obao", hashHex))
if ret.StatusCode() != 200 {
err = errors.New(string(ret.Body()))
}
if internalError(ctx, err) {
return
}
upload = model.Upload{
Hash: hashHex,
}
if err := db.Get().Create(&upload).Error; err != nil {
if internalError(ctx, err) {
return
}
}
ctx.JSON(&UploadResponse{Cid: fileCid})
}
func (f *FilesService) GetDownload() {
ctx := f.Ctx
cidString := ctx.URLParam("cid")
_, err := cid.Valid(cidString)
if sendError(ctx, err, iris.StatusBadRequest) {
return
}
cidObject, _ := cid.Decode(cidString)
hashHex := hex.EncodeToString(cidObject.Hash[:])
result := db.Get().Table("uploads").Where("hash = ?", hashHex).Row()
if result.Err() != nil {
sendError(ctx, result.Err(), iris.StatusNotFound)
return
}
fetch, err := client.R().SetDoNotParseResponse(true).Get(fmt.Sprintf("/worker/objects/%s", hashHex))
if err != nil {
if fetch.StatusCode() == 404 {
sendError(ctx, err, iris.StatusNotFound)
return
}
internalError(ctx, err)
return
}
ctx.Header("Transfer-Encoding", "chunked")
if internalError(ctx, err) {
return
}
err = ctx.StreamWriter(func(w io.Writer) error {
_, err = io.Copy(w, fetch.RawBody())
_ = fetch.RawBody().Close()
return err
})
internalError(ctx, err)
}
func sendErrorCustom(ctx iris.Context, err error, customError error, irisError int) bool {
if err != nil {
if customError != nil {
err = customError
}
ctx.StopWithError(irisError, err)
return true
}
return false
}
func internalError(ctx iris.Context, err error) bool {
return sendErrorCustom(ctx, err, nil, iris.StatusInternalServerError)
}
func internalErrorCustom(ctx iris.Context, err error, customError error) bool {
return sendErrorCustom(ctx, err, customError, iris.StatusInternalServerError)
}
func sendError(ctx iris.Context, err error, irisError int) bool {
return sendErrorCustom(ctx, err, nil, irisError)
}