github.com/DapperCollectives/CAST/backend@v0.0.0-20230921221157-1350c8be7c96/main/server/app.go (about)

     1  package server
     2  
     3  import (
     4  	// "errors"
     5  
     6  	"context"
     7  	"encoding/json"
     8  	"flag"
     9  	"fmt"
    10  	"io/ioutil"
    11  	"net/http"
    12  	"os"
    13  	"strings"
    14  
    15  	"github.com/DapperCollectives/CAST/backend/main/middleware"
    16  	"github.com/DapperCollectives/CAST/backend/main/models"
    17  	"github.com/DapperCollectives/CAST/backend/main/shared"
    18  	"github.com/DapperCollectives/CAST/backend/main/strategies"
    19  	"github.com/axiomzen/envconfig"
    20  	"github.com/gorilla/mux"
    21  	"github.com/jackc/pgx/v4/pgxpool"
    22  
    23  	"github.com/rs/zerolog"
    24  	"github.com/rs/zerolog/log"
    25  )
    26  
    27  type Database shared.Database
    28  type IpfsClient shared.IpfsClient
    29  type Allowlist shared.Allowlist
    30  type Vote models.Vote
    31  type Proposal models.Proposal
    32  type Community models.Community
    33  type Balance models.Balance
    34  type List models.List
    35  type ListRequest models.ListPayload
    36  type ListUpdatePayload models.ListUpdatePayload
    37  type CommunityUser models.CommunityUser
    38  type CommunityUserPayload models.CommunityUserPayload
    39  type UserCommunity models.UserCommunity
    40  
    41  type TxOptionsAddresses []string
    42  
    43  type App struct {
    44  	Router      *mux.Router
    45  	DB          *shared.Database
    46  	IpfsClient  *shared.IpfsClient
    47  	FlowAdapter *shared.FlowAdapter
    48  
    49  	TxOptionsAddresses []string
    50  	Env                string
    51  	AdminAllowlist     shared.Allowlist
    52  	CommunityBlocklist shared.Allowlist
    53  	Config             shared.Config
    54  }
    55  
    56  type Strategy interface {
    57  	TallyVotes(votes []*models.VoteWithBalance, p *models.ProposalResults, proposal *models.Proposal) (models.ProposalResults, error)
    58  	GetVotes(votes []*models.VoteWithBalance, proposal *models.Proposal) ([]*models.VoteWithBalance, error)
    59  	GetVoteWeightForBalance(vote *models.VoteWithBalance, proposal *models.Proposal) (float64, error)
    60  	InitStrategy(f *shared.FlowAdapter, db *shared.Database)
    61  	FetchBalance(b *models.Balance, p *models.Proposal) (*models.Balance, error)
    62  	RequiresSnapshot() bool
    63  }
    64  
    65  var strategyMap = map[string]Strategy{
    66  	"token-weighted-default":        &strategies.TokenWeightedDefault{},
    67  	"total-token-weighted-default":  &strategies.TotalTokenWeightedDefault{},
    68  	"staked-token-weighted-default": &strategies.StakedTokenWeightedDefault{},
    69  	"one-address-one-vote":          &strategies.OneAddressOneVote{},
    70  	"balance-of-nfts":               &strategies.BalanceOfNfts{},
    71  	"float-nfts":                    &strategies.FloatNFTs{},
    72  	"custom-script":                 &strategies.CustomScript{},
    73  }
    74  
    75  var customScripts []shared.CustomScript
    76  
    77  var helpers Helpers
    78  
    79  //////////////////////
    80  // INSTANCE METHODS //
    81  //////////////////////
    82  
    83  func (a *App) Initialize() {
    84  	log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stdout})
    85  
    86  	// Env
    87  	env := os.Getenv("APP_ENV")
    88  	if flag.Lookup("test.v") != nil {
    89  		env = "TEST"
    90  		os.Setenv("APP_ENV", "TEST")
    91  	} else if len(env) == 0 {
    92  		env = "PROD"
    93  	}
    94  	a.Env = strings.TrimSpace(env)
    95  
    96  	// Set log level based on env
    97  	if a.Env == "PROD" {
    98  		log.Logger = log.Logger.Level(zerolog.InfoLevel)
    99  		log.Info().Msgf("Log level: %s for APP_ENV=%s", "INFO", a.Env)
   100  	} else {
   101  		log.Logger = log.Logger.Level(zerolog.DebugLevel)
   102  		log.Info().Msgf("Log level: %s for APP_ENV=%s", "DEBUG", a.Env)
   103  	}
   104  
   105  	// Set App-wide Config
   106  	err := envconfig.Process("FVT", &a.Config)
   107  	if err != nil {
   108  		log.Error().Err(err).Msg("Error Reading Configuration.")
   109  		os.Exit(1)
   110  	}
   111  
   112  	////////////
   113  	// Clients
   114  	////////////
   115  
   116  	// when running "make proposals" sets db to dev not test
   117  	arg := flag.String("db", "", "database type")
   118  	flag.Int("port", 5001, "port")
   119  	flag.Int("amount", 4, "Amount of proposals to create")
   120  
   121  	flag.Parse()
   122  	if *arg == "local" {
   123  		os.Setenv("APP_ENV", "DEV")
   124  	}
   125  
   126  	// Postgres
   127  	dbname := os.Getenv("DB_NAME")
   128  
   129  	// IPFS
   130  	if os.Getenv("APP_ENV") == "TEST" || os.Getenv("APP_ENV") == "DEV" {
   131  		flag.Bool("ipfs-override", true, "overrides ipfs call")
   132  	} else {
   133  		flag.Bool("ipfs-override", false, "overrides ipfs call")
   134  	}
   135  
   136  	// TEST Env
   137  	if os.Getenv("APP_ENV") == "TEST" {
   138  		dbname = os.Getenv("TEST_DB_NAME")
   139  	}
   140  
   141  	// Postgres
   142  	a.ConnectDB(
   143  		os.Getenv("DB_USERNAME"),
   144  		os.Getenv("DB_PASSWORD"),
   145  		os.Getenv("DB_HOST"),
   146  		os.Getenv("DB_PORT"),
   147  		dbname,
   148  	)
   149  
   150  	// IPFS
   151  	a.IpfsClient = shared.NewIpfsClient(os.Getenv("IPFS_KEY"), os.Getenv("IPFS_SECRET"))
   152  
   153  	// Flow
   154  
   155  	// Load custom scripts for strategies
   156  	scripts, err := ioutil.ReadFile("./main/cadence/scripts/custom/scripts.json")
   157  	if err != nil {
   158  		log.Error().Err(err).Msg("Error Reading Custom Strategy scripts.")
   159  	}
   160  
   161  	err = json.Unmarshal(scripts, &customScripts)
   162  	if err != nil {
   163  		log.Error().Err(err).Msg("Error during Unmarshalling custom scripts")
   164  	}
   165  
   166  	// Create Map for Flow Adaptor to look up when voting
   167  	var customScriptsMap = make(map[string]shared.CustomScript)
   168  	for _, script := range customScripts {
   169  		customScriptsMap[script.Key] = script
   170  	}
   171  
   172  	if os.Getenv("FLOW_ENV") == "" {
   173  		os.Setenv("FLOW_ENV", "emulator")
   174  	}
   175  	a.FlowAdapter = shared.NewFlowClient(os.Getenv("FLOW_ENV"), customScriptsMap)
   176  
   177  	// Snapshot
   178  	a.TxOptionsAddresses = strings.Fields(os.Getenv("TX_OPTIONS_ADDRS"))
   179  
   180  	// Router
   181  	a.Router = mux.NewRouter()
   182  	a.initializeRoutes()
   183  
   184  	// Middlewares
   185  	a.Router.Use(mux.CORSMethodMiddleware(a.Router))
   186  	a.Router.Use(middleware.Logger)
   187  	a.Router.Use(middleware.UseCors(a.Config))
   188  
   189  	helpers.Initialize(a)
   190  }
   191  
   192  func (a *App) Run() {
   193  	addr := fmt.Sprintf(":%s", os.Getenv("API_PORT"))
   194  	log.Info().Msgf("Starting server on %s ...", addr)
   195  	log.Fatal().Err(http.ListenAndServe(addr, a.Router)).Msgf("Server at %s crashed!", addr)
   196  }
   197  
   198  func (a *App) ConnectDB(username, password, host, port, dbname string) {
   199  	var database shared.Database
   200  	var err error
   201  
   202  	database.Context = context.Background()
   203  	database.Name = dbname
   204  
   205  	connectionString :=
   206  		fmt.Sprintf("postgres://%s:%s@%s:%s/%s", username, password, host, port, dbname)
   207  
   208  	pconf, confErr := pgxpool.ParseConfig(connectionString)
   209  	if confErr != nil {
   210  		log.Fatal().Err(err).Msg("Unable to parse database config url")
   211  	}
   212  
   213  	if os.Getenv("APP_ENV") == "TEST" {
   214  		log.Info().Msg("Setting MIN/MAX connections to 1")
   215  		pconf.MinConns = 1
   216  		pconf.MaxConns = 1
   217  	}
   218  
   219  	database.Conn, err = pgxpool.ConnectConfig(database.Context, pconf)
   220  
   221  	database.Env = &a.Env
   222  	if err != nil {
   223  		log.Fatal().Err(err).Msg("Error creating Postsgres conn pool")
   224  	} else {
   225  		a.DB = &database
   226  		log.Info().Msgf("Successfully created Postgres conn pool")
   227  	}
   228  }