Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(launchpad): Add backend, service, indexer, db #1432

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
161 changes: 161 additions & 0 deletions api/launchpad/v1/launchpad.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
syntax = "proto3";

package launchpad.v1;
option go_package = "./launchpadpb";

service LaunchpadService {
rpc UploadMetadatas(UploadMetadatasRequest) returns (UploadMetadatasResponse);
rpc CalculateCollectionMerkleRoot(CalculateCollectionMerkleRootRequest) returns (CalculateCollectionMerkleRootResponse);
rpc TokenMetadata(TokenMetadataRequest) returns (TokenMetadataResponse);
rpc LaunchpadProjectsByCreator(LaunchpadProjectsByCreatorRequest) returns (LaunchpadProjectsByCreatorResponse);
rpc LaunchpadProjects(LaunchpadProjectsRequest) returns (LaunchpadProjectsResponse);
rpc LaunchpadProjectById(LaunchpadProjectByIdRequest) returns (LaunchpadProjectByIdResponse);
rpc LaunchpadProjectsCounts(LaunchpadProjectsCountsRequest) returns (LaunchpadProjectsCountsResponse);
rpc ProposeApproveProject(ProposeApproveProjectRequest) returns (ProposeApproveProjectResponse);
}

enum Sort {
SORT_UNSPECIFIED = 0;
MikaelVallenet marked this conversation as resolved.
Show resolved Hide resolved
SORT_COLLECTION_NAME = 1;
}

enum SortDirection {
SORT_DIRECTION_UNSPECIFIED = 0;
MikaelVallenet marked this conversation as resolved.
Show resolved Hide resolved
SORT_DIRECTION_ASCENDING = 1;
SORT_DIRECTION_DESCENDING = 2;
}

enum Status {
STATUS_UNSPECIFIED = 0;
MikaelVallenet marked this conversation as resolved.
Show resolved Hide resolved
STATUS_INCOMPLETE = 1;
STATUS_COMPLETE = 2;
STATUS_REVIEWING = 3;
STATUS_CONFIRMED = 4;
}

// -------------------------------

message LaunchpadProjectsByCreatorRequest {
string creator_id = 1;
string network_id = 2;
int32 limit = 3;
int32 offset = 4;
Sort sort = 5;
SortDirection sort_direction = 6;
optional Status status = 7;
}

message LaunchpadProjectsByCreatorResponse {
repeated LaunchpadProject projects = 1;
}

message LaunchpadProjectsRequest {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should not just add a optional creator id to refactor the same request above with just creator_id as addition?

string network_id = 1;
int32 limit = 2;
int32 offset = 3;
Sort sort = 4;
SortDirection sort_direction = 5;
optional Status status = 6;
}

message LaunchpadProjectsResponse {
repeated LaunchpadProject projects = 1;
}

message LaunchpadProjectByIdRequest {
string network_id = 1;
string project_id = 2;
}

message LaunchpadProjectByIdResponse {
LaunchpadProject project = 1;
}

message UploadMetadatasRequest {
string sender = 1;
string network_id = 2;
string project_id = 3;
repeated Metadata metadatas = 4;
optional string pinata_jwt = 5;
}

message UploadMetadatasResponse {
string merkle_root = 1;
}

message CalculateCollectionMerkleRootRequest {
string sender = 1;
repeated Metadata metadatas = 2;
}

message CalculateCollectionMerkleRootResponse {
string merkle_root = 1;
}

message TokenMetadataRequest {
string sender = 1;
string network_id = 2;
string project_id = 3;
uint32 token_id = 4;
}

message TokenMetadataResponse {
string merkle_root = 1;
Metadata metadata = 2;
repeated string merkle_proof = 3;
}

message LaunchpadProjectsCountsRequest {
string network_id = 1;
}

message LaunchpadProjectsCountsResponse {
repeated StatusCount status_counts = 1;
}

message ProposeApproveProjectRequest {
string sender = 1;
string network_id = 2;
string project_id = 3;
string proposal_id = 4;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is there a proposal id, if you want to create a proposal to approve a project request
or maybe i don't understand the purpose of this message

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if this is about approving a project request what about: ApproveProjectRequestProposal

}

message ProposeApproveProjectResponse {
bool approved = 1;
}

// -------------------------------

message StatusCount {
Status status = 1;
uint32 count = 2;
}

message LaunchpadProject {
string id = 1;
string network_id = 2;
string creator_id = 3;
string collection_data = 4;
Status status = 5;
optional string proposal_id = 6;
}

message Metadata {
optional string image = 1;
optional string image_data = 2;
optional string external_url = 3;
optional string description = 4;
optional string name = 5;
repeated Trait attributes = 6;
optional string background_color = 7;
optional string animation_url = 8;
optional string youtube_url = 9;
optional uint64 royalty_percentage = 10;
optional string royalty_payment_address = 11;
}

message Trait {
optional string display_type = 1;
string trait_type = 2;
string value = 3;
}
5 changes: 3 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ require (
github.com/go-co-op/gocron v1.18.0
github.com/gorilla/websocket v1.5.0
github.com/improbable-eng/grpc-web v0.15.0
github.com/ipfs/boxo v0.8.0
github.com/ipfs/go-cid v0.4.1
github.com/jackc/pgx/v5 v5.3.0
github.com/joho/godotenv v1.5.1
github.com/lib/pq v1.10.7
Expand Down Expand Up @@ -168,9 +170,8 @@ require (
github.com/holiman/uint256 v1.2.2-0.20230321075855-87b91420868c // indirect
github.com/huandu/skiplist v1.2.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/ipfs/boxo v0.8.0 // indirect
github.com/ipfs/go-cid v0.4.1 // indirect
github.com/ipfs/go-ipfs-api v0.6.0 // indirect
github.com/ipfs/go-log/v2 v2.5.1 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
github.com/jhump/protoreflect v1.15.1 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -852,6 +852,8 @@ github.com/ipfs/go-cid v0.4.1 h1:A/T3qGvxi4kpKWWcPC/PgbvDA2bjVLO7n4UeVwnbs/s=
github.com/ipfs/go-cid v0.4.1/go.mod h1:uQHwDeX4c6CtyrFwdqyhpNcxVewur1M7l7fNU7LKwZk=
github.com/ipfs/go-ipfs-api v0.6.0 h1:JARgG0VTbjyVhO5ZfesnbXv9wTcMvoKRBLF1SzJqzmg=
github.com/ipfs/go-ipfs-api v0.6.0/go.mod h1:iDC2VMwN9LUpQV/GzEeZ2zNqd8NUdRmWcFM+K/6odf0=
github.com/ipfs/go-log/v2 v2.5.1 h1:1XdUzF7048prq4aBjDQQ4SL5RxftpRGdXhNRwKSAlcY=
github.com/ipfs/go-log/v2 v2.5.1/go.mod h1:prSpmC1Gpllc9UYWxDiZDreBYw7zp4Iqp1kOLU9U5UI=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
Expand Down
146 changes: 146 additions & 0 deletions go/cmd/teritori-launchpad-backend/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
package main

import (
"context"
"flag"
"fmt"
"net"
"net/http"
"os"

"github.com/TERITORI/teritori-dapp/go/internal/indexerdb"
"github.com/TERITORI/teritori-dapp/go/pkg/launchpad"
"github.com/TERITORI/teritori-dapp/go/pkg/launchpadpb"
"github.com/TERITORI/teritori-dapp/go/pkg/networks"
"github.com/improbable-eng/grpc-web/go/grpcweb"
"github.com/peterbourgon/ff/v3"
"github.com/pkg/errors"
"go.uber.org/zap"
"google.golang.org/grpc"
"google.golang.org/grpc/reflection"
)

// FIXME: for now, I dont find how to add reflection on grpc wrapped by http server
// so to enable Postman reflection, I'm using this DEBUG const to switch on/off that capacity
const DEBUG = false

func main() {
fs := flag.NewFlagSet("teritori-dapp-backend", flag.ContinueOnError)
var (
enableTls = flag.Bool("enable_tls", false, "Use TLS - required for HTTP2.")
tlsCertFilePath = flag.String("tls_cert_file", "../../misc/localhost.crt", "Path to the CRT/PEM file.")
tlsKeyFilePath = flag.String("tls_key_file", "../../misc/localhost.key", "Path to the private key file.")
dbHost = fs.String("db-indexer-host", "", "host postgreSQL database")
dbPort = fs.String("db-indexer-port", "", "port for postgreSQL database")
dbPass = fs.String("postgres-password", "", "password for postgreSQL database")
dbName = fs.String("database-name", "", "database name for postgreSQL")
dbUser = fs.String("postgres-user", "", "username for postgreSQL")
networksFile = fs.String("networks-file", "networks.json", "path to networks config file")
pinataJWT = fs.String("pinata-jwt", "", "Pinata admin JWT token")
)
if err := ff.Parse(fs, os.Args[1:],
ff.WithEnvVars(),
ff.WithIgnoreUndefined(true),
ff.WithConfigFile(".env"),
ff.WithConfigFileParser(ff.EnvParser),
ff.WithAllowMissingConfigFile(true),
); err != nil {
panic(errors.Wrap(err, "failed to parse flags"))
}

logger, err := zap.NewDevelopment()
if err != nil {
panic(errors.Wrap(err, "failed to create logger"))
}

if *pinataJWT == "" {
logger.Warn("missing PINATA_JWT, feed pinning will be disabled")
}

// load networks
networksBytes, err := os.ReadFile(*networksFile)
if err != nil {
panic(errors.Wrap(err, "failed to read networks config file"))
}
netstore, err := networks.UnmarshalNetworkStore(networksBytes)
if err != nil {
panic(errors.Wrap(err, "failed to unmarshal networks config"))
}

var launchpadModels = []interface{}{
// users
&indexerdb.User{},

// launchpad
&LaunchpadProject{},
&LaunchpadToken{},
}

dataConnexion := fmt.Sprintf("host=%s user=%s password=%s dbname=%s port=%s",
*dbHost, *dbUser, *dbPass, *dbName, *dbPort)
launchpadDB, err := indexerdb.NewPostgresDB(dataConnexion)

if err != nil {
panic(errors.Wrap(err, "failed to access db"))
}
launchpadDB.AutoMigrate(launchpadModels...)

port := 9080
if *enableTls {
port = 9081
}

launchpadSvc := launchpad.NewLaunchpadService(context.Background(), &launchpad.Config{
Logger: logger,
IndexerDB: launchpadDB,
PinataJWT: *pinataJWT,
NetworkStore: netstore,
})

server := grpc.NewServer()
launchpadpb.RegisterLaunchpadServiceServer(server, launchpadSvc)

if DEBUG {
lis, err := net.Listen("tcp", fmt.Sprintf(":%d", port))
if err != nil {
panic(errors.Wrapf(err, "[DEBUG] failed to listen on port %d", port))
}

reflection.Register(server)

logger.Info(fmt.Sprintf("[DEBUG] gRPC server listening at: %s", lis.Addr().String()))
if err := server.Serve(lis); err != nil {
panic(errors.Errorf("failed to serve: %v", err))
}
}

wrappedServer := grpcweb.WrapServer(server,
grpcweb.WithWebsockets(true),
grpcweb.WithWebsocketOriginFunc(func(*http.Request) bool { return true }))

handler := func(resp http.ResponseWriter, req *http.Request) {
resp.Header().Set("Access-Control-Allow-Origin", "*")
resp.Header().Set("Access-Control-Allow-Headers", "*")
logger.Debug(fmt.Sprintf("Request: %v", req))
wrappedServer.ServeHTTP(resp, req)
}

httpServer := http.Server{
Addr: fmt.Sprintf(":%d", port),
Handler: http.HandlerFunc(handler),
}

reflection.Register(server)

logger.Info(fmt.Sprintf("Starting server. http port: %d, with TLS: %v", port, *enableTls))

if *enableTls {
if err := httpServer.ListenAndServeTLS(*tlsCertFilePath, *tlsKeyFilePath); err != nil {
panic(fmt.Errorf("failed starting http2 server: %v", err))
}
} else {
if err := httpServer.ListenAndServe(); err != nil {
panic(fmt.Errorf("failed starting http server: %v", err))
}
}
}
22 changes: 22 additions & 0 deletions go/cmd/teritori-launchpad-backend/models.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package main

import (
"github.com/TERITORI/teritori-dapp/go/pkg/launchpadpb"
"gorm.io/datatypes"
)

type LaunchpadProject struct {
NetworkID string `gorm:"primaryKey"`
ProjectID uint32 `gorm:"primaryKey"`

Status launchpadpb.Status
ProposalId string
}

type LaunchpadToken struct {
NetworkID string `gorm:"primaryKey"`
ProjectID uint32 `gorm:"primaryKey"`
TokenID uint32 `gorm:"primaryKey"`

Metadata datatypes.JSON
}
4 changes: 4 additions & 0 deletions go/internal/indexerdb/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,10 @@ var allModels = []interface{}{

// names
&Name{},

// launchpad
&LaunchpadProject{},
&LaunchpadToken{},
}

func NewSQLiteDB(path string) (*gorm.DB, error) {
Expand Down
26 changes: 26 additions & 0 deletions go/internal/indexerdb/launchpad.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package indexerdb

import (
"github.com/TERITORI/teritori-dapp/go/pkg/launchpadpb"
"github.com/TERITORI/teritori-dapp/go/pkg/networks"
"gorm.io/datatypes"
)

type LaunchpadProject struct {
NetworkID string `gorm:"primaryKey"`
ProjectID string `gorm:"primaryKey"`
CreatorID networks.UserID `gorm:"index"`

Status launchpadpb.Status
ProposalId string

CollectionData datatypes.JSON
}

type LaunchpadToken struct {
NetworkID string `gorm:"primaryKey"`
ProjectID string `gorm:"primaryKey"`
TokenID uint32 `gorm:"primaryKey"`

Metadata datatypes.JSON
}
Loading