diff --git a/go.mod b/go.mod index 70822dd..0007d49 100644 --- a/go.mod +++ b/go.mod @@ -3,10 +3,11 @@ module git.solsynth.dev/hypernet/paperclip go 1.23.2 require ( - git.solsynth.dev/hypernet/nexus v0.0.0-20241103165538-c0fec1084611 - git.solsynth.dev/hypernet/passport v0.0.0-20241102174750-808e7998dd1c + git.solsynth.dev/hypernet/nexus v0.0.0-20241123050605-25ab1371739b + git.solsynth.dev/hypernet/passport v0.0.0-20250128183757-09010d5867ed + git.solsynth.dev/hypernet/wallet v0.0.0-20250129103922-9b5f67e67788 github.com/barasher/go-exiftool v1.10.0 - github.com/dgraph-io/ristretto v0.1.1 + github.com/dgraph-io/ristretto v0.2.0 github.com/eko/gocache/lib/v4 v4.1.6 github.com/eko/gocache/store/ristretto/v4 v4.2.2 github.com/fatih/color v1.18.0 @@ -32,7 +33,7 @@ require ( require ( filippo.io/edwards25519 v1.1.0 // indirect - git.solsynth.dev/hypernet/pusher v0.0.0-20241026153052-cd2c326efa4e // indirect + git.solsynth.dev/hypernet/pusher v0.0.0-20241228030233-50ff8304e465 // indirect github.com/andybalholm/brotli v1.1.1 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect @@ -44,7 +45,6 @@ require ( github.com/go-sql-driver/mysql v1.8.1 // indirect github.com/goccy/go-json v0.10.3 // indirect github.com/golang-jwt/jwt/v5 v5.2.1 // indirect - github.com/golang/glog v1.2.2 // indirect github.com/golang/mock v1.6.0 // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/hashicorp/hcl v1.0.0 // indirect diff --git a/go.sum b/go.sum index bd82c61..61b221b 100644 --- a/go.sum +++ b/go.sum @@ -33,12 +33,14 @@ cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9 dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= -git.solsynth.dev/hypernet/nexus v0.0.0-20241103165538-c0fec1084611 h1:ZEzUDsO88X+amOaEKZOpnQHHNYm5iw3hCBdy138sQro= -git.solsynth.dev/hypernet/nexus v0.0.0-20241103165538-c0fec1084611/go.mod h1:PhLCv2lsNoscPVJbkWnxwQnJ141lc4RIEkVffrHwl4s= -git.solsynth.dev/hypernet/passport v0.0.0-20241102174750-808e7998dd1c h1:RVY0xsYGpb22ZuITxS+xpFu76XpPDrOpbIuSMDjW1+Q= -git.solsynth.dev/hypernet/passport v0.0.0-20241102174750-808e7998dd1c/go.mod h1:Gj8uWVKUeUxU3Z6v1sYLGHB79ln1nubQ5XfsJVUcvWM= -git.solsynth.dev/hypernet/pusher v0.0.0-20241026153052-cd2c326efa4e h1:DtHhMjgxS/spUt/KEdbRFtaVnepI6Vx8pbHdJaNH1hs= -git.solsynth.dev/hypernet/pusher v0.0.0-20241026153052-cd2c326efa4e/go.mod h1:XHTqFU/vBe4JiuAjl87GUcL8+w/IizSNoqH6n3WkQFc= +git.solsynth.dev/hypernet/nexus v0.0.0-20241123050605-25ab1371739b h1:8yB9kMwEMY/nIbmDDxrhH5sTypgmK5PIIiIfP5QXx4s= +git.solsynth.dev/hypernet/nexus v0.0.0-20241123050605-25ab1371739b/go.mod h1:PhLCv2lsNoscPVJbkWnxwQnJ141lc4RIEkVffrHwl4s= +git.solsynth.dev/hypernet/passport v0.0.0-20250128183757-09010d5867ed h1:+z84T2At6CbfZfo7zvvMQP4zv/+tGM5KlJlCSoIkB8Y= +git.solsynth.dev/hypernet/passport v0.0.0-20250128183757-09010d5867ed/go.mod h1:h78BjtyDuchDDlddLtk3HjvDI7DAK8Jzk/uVwqvv2cw= +git.solsynth.dev/hypernet/pusher v0.0.0-20241228030233-50ff8304e465 h1:KFtv9lF0JMUGsq1uHwQvop8PTyqdiLuUQuRrd5WmzPk= +git.solsynth.dev/hypernet/pusher v0.0.0-20241228030233-50ff8304e465/go.mod h1:XHTqFU/vBe4JiuAjl87GUcL8+w/IizSNoqH6n3WkQFc= +git.solsynth.dev/hypernet/wallet v0.0.0-20250129103922-9b5f67e67788 h1:jnZoRiRSFVTRl05mGSOynlbS5MIHAq3EUqVdkz8BjEI= +git.solsynth.dev/hypernet/wallet v0.0.0-20250129103922-9b5f67e67788/go.mod h1:jd1MTBI5NPHne22nq7nR7kyl4iYb9kV2A+tpXi7HOYY= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= @@ -69,11 +71,10 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dgraph-io/ristretto v0.1.1 h1:6CWw5tJNgpegArSHpNHJKldNeq03FQCwYvfMVWajOK8= -github.com/dgraph-io/ristretto v0.1.1/go.mod h1:S1GPSBCYCIhmVNfcth17y2zZtQT6wzkzgwUve0VDWWA= -github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczCTSixgIKmwPv6+wP5DGjqLYw5SUiA= -github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= -github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/dgraph-io/ristretto v0.2.0 h1:XAfl+7cmoUDWW/2Lx8TGZQjjxIQ2Ley9DSf52dru4WE= +github.com/dgraph-io/ristretto v0.2.0/go.mod h1:8uBHCU/PBV4Ag0CJrP47b9Ofby5dqWNh4FicAdoqFNU= +github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 h1:fAjc9m62+UWV/WAFKLNi6ZS0675eEUC9y3AlwSbQu1Y= +github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/eko/gocache/lib/v4 v4.1.6 h1:5WWIGISKhE7mfkyF+SJyWwqa4Dp2mkdX8QsZpnENqJI= @@ -128,8 +129,6 @@ github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2V github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A= github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/glog v1.2.2 h1:1+mZ9upx1Dh6FmUTFR1naJ77miKiXgALjWOZ3NVFPmY= -github.com/golang/glog v1.2.2/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -509,7 +508,6 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/pkg/internal/server/api/up_direct_api.go b/pkg/internal/server/api/up_direct_api.go index 5fa8eda..890fc53 100644 --- a/pkg/internal/server/api/up_direct_api.go +++ b/pkg/internal/server/api/up_direct_api.go @@ -32,8 +32,8 @@ func createAttachmentDirectly(c *fiber.Ctx) error { return err } - if !user.HasPermNode("CreateAttachments", file.Size) { - return fiber.NewError(fiber.StatusForbidden, "you are not permitted to create attachments like this large") + if !user.HasPermNode("CreateAttachments", true) { + return fiber.NewError(fiber.StatusForbidden, "you are not permitted to create attachments") } else if pool.Config.Data().MaxFileSize != nil && file.Size > *pool.Config.Data().MaxFileSize { return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("attachment pool %s doesn't allow file larger than %d", pool.Alias, *pool.Config.Data().MaxFileSize)) } @@ -62,6 +62,13 @@ func createAttachmentDirectly(c *fiber.Ctx) error { return fiber.NewError(fiber.StatusBadRequest, err.Error()) } + // If pool has no belongs to, it means it is shared pool, apply shared attachment discount + withDiscount := pool.AccountID == nil + if err := services.PlaceOrder(user.ID, file.Size, withDiscount); err != nil { + tx.Rollback() + return fiber.NewError(fiber.StatusPaymentRequired, err.Error()) + } + tx.Commit() metadata.Pool = &pool diff --git a/pkg/internal/server/api/up_multipart_api.go b/pkg/internal/server/api/up_multipart_api.go index e27d082..bf8c8c1 100644 --- a/pkg/internal/server/api/up_multipart_api.go +++ b/pkg/internal/server/api/up_multipart_api.go @@ -41,13 +41,15 @@ func createAttachmentFragment(c *fiber.Ctx) error { return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("unable to get attachment pool info: %v", err)) } - if !user.HasPermNode("CreateAttachments", data.Size) { - return fiber.NewError(fiber.StatusForbidden, "you are not permitted to create attachments like this large") + if !user.HasPermNode("CreateAttachments", true) { + return fiber.NewError(fiber.StatusForbidden, "you are not permitted to create attachments") } else if pool.Config.Data().MaxFileSize != nil && *pool.Config.Data().MaxFileSize > data.Size { return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("attachment pool %s doesn't allow file larger than %d", pool.Alias, *pool.Config.Data().MaxFileSize)) } - metadata, err := services.NewAttachmentFragment(database.C, user, models.AttachmentFragment{ + tx := database.C.Begin() + + metadata, err := services.NewAttachmentFragment(tx, user, models.AttachmentFragment{ Name: data.FileName, Size: data.Size, Alternative: data.Alternative, @@ -63,6 +65,15 @@ func createAttachmentFragment(c *fiber.Ctx) error { metadata.FileChunksMissing = services.FindFragmentMissingChunks(metadata) } + // If pool has no belongs to, it means it is shared pool, apply shared attachment discount + withDiscount := pool.AccountID == nil + if err := services.PlaceOrder(user.ID, data.Size, withDiscount); err != nil { + tx.Rollback() + return fiber.NewError(fiber.StatusPaymentRequired, err.Error()) + } + + tx.Commit() + return c.JSON(fiber.Map{ "chunk_size": viper.GetInt64("performance.file_chunk_size"), "chunk_count": len(metadata.FileChunks), diff --git a/pkg/internal/services/payment.go b/pkg/internal/services/payment.go new file mode 100644 index 0000000..b4076bd --- /dev/null +++ b/pkg/internal/services/payment.go @@ -0,0 +1,60 @@ +package services + +import ( + "context" + "fmt" + "time" + + "git.solsynth.dev/hypernet/paperclip/pkg/internal/gap" + wproto "git.solsynth.dev/hypernet/wallet/pkg/proto" + "github.com/rs/zerolog/log" + "github.com/samber/lo" +) + +const DiscountFileSize = 52428800 // 50 MiB + +// PlaceOrder create a transaction if needed for user +// Pricing according here: https://kb.solsynth.dev/solar-network/wallet#file-uploads +func PlaceOrder(user uint, filesize int64, withDiscount bool) error { + if filesize <= DiscountFileSize && withDiscount { + // Discount included + return nil + } + + var amount float64 + if withDiscount { + billableSize := filesize - DiscountFileSize + amount = float64(billableSize) / 1024 / 1024 * 1 + } else if filesize > DiscountFileSize { + amount = 50 + float64(filesize-DiscountFileSize)/1024/1024*5 + } else { + amount = float64(filesize) / 1024 / 1024 * 1 + } + + if !withDiscount { + amount += 10 // Service fee + } + + conn, err := gap.Nx.GetClientGrpcConn("wa") + if err != nil { + return fmt.Errorf("unable to connect wallet: %v", err) + } + + wc := wproto.NewPaymentServiceClient(conn) + ctx, cancel := context.WithTimeout(context.Background(), time.Second*3) + defer cancel() + resp, err := wc.MakeTransactionWithAccount(ctx, &wproto.MakeTransactionWithAccountRequest{ + PayerAccountId: lo.ToPtr(uint64(user)), + Amount: amount, + Remark: "File Uploading Fee", + }) + if err != nil { + return err + } + + log.Info(). + Uint64("transaction", resp.Id).Float64("amount", amount).Bool("discount", withDiscount). + Msg("Order placed for charge file uploading fee...") + + return nil +}