### Setup and Run Frontend Source: https://github.com/dan6erbond/revline/blob/main/README.md Commands to install frontend dependencies, generate GraphQL types, and start the Next.js development server. ```bash cd client npm install npm run codegen npm run dev ``` -------------------------------- ### Example Configuration File Source: https://github.com/dan6erbond/revline/blob/main/website/src/app/blog/posts/go-gqlgen-ent-uber-fx.mdx An example YAML file demonstrating how to provide configuration values for the application. These values can be overridden by environment variables. ```yaml environment: development # Other configuration variables ``` -------------------------------- ### Self-Hosting Setup Source: https://github.com/dan6erbond/revline/blob/main/README.md Command to start the Revline application in a self-hosted configuration using a sample Docker Compose file. ```bash docker-compose -f sample-docker-compose.yml up -d ``` -------------------------------- ### Configure Backend Source: https://github.com/dan6erbond/revline/blob/main/README.md Steps to configure the Go backend by copying an example configuration file to `server/config.yaml`. ```bash cp server/config.example.yaml server/config.yaml ``` -------------------------------- ### Run Development Server Source: https://github.com/dan6erbond/revline/blob/main/website/README.md Commands to start the Next.js development server. This allows for hot-reloading and immediate feedback as you edit your code. The server typically runs on http://localhost:3000. ```bash npm run dev # or yarn dev # or pnpm dev # or bun dev ``` -------------------------------- ### Basic Uber FX Application Setup Source: https://github.com/dan6erbond/revline/blob/main/website/src/app/blog/posts/go-gqlgen-ent-uber-fx.mdx A minimal Go program demonstrating the basic structure for an application using Uber's FX dependency injection framework. It initializes and runs the FX application. ```go import "go.uber.org/fx" func main() { fx.New().Run() } ``` -------------------------------- ### Run Revline Backend Server Source: https://github.com/dan6erbond/revline/blob/main/server/README.md Starts the Revline backend application. This command compiles and runs the main Go program, initializing the HTTP server, GraphQL endpoint, and other services. ```bash go run main.go ``` -------------------------------- ### Configure Frontend Environment Source: https://github.com/dan6erbond/revline/blob/main/README.md Instructions to set up the frontend environment variables by copying an example file to `client/.env.local`. ```bash cp client/.env.example client/.env.local ``` -------------------------------- ### Start Development Infrastructure Source: https://github.com/dan6erbond/revline/blob/main/README.md Command to start the necessary infrastructure services for local development, including Postgres and MinIO, using Docker Compose. ```bash docker-compose up ``` -------------------------------- ### Integrate HTTP Module in Main Application Source: https://github.com/dan6erbond/revline/blob/main/website/src/app/blog/posts/go-gqlgen-ent-uber-fx.mdx This Go snippet shows how to integrate the previously defined HTTP FX module into the main application. By calling `fx.New` with the `httpfx.Module` and then `Run()`, the application starts, and the HTTP server is initialized and launched via the FX lifecycle hooks. ```go fx.New( httpfx.Module, ).Run() ``` -------------------------------- ### Install Dependencies Source: https://github.com/dan6erbond/revline/blob/main/client/README.md Command to install project dependencies for the Revline frontend. This step is crucial after cloning the repository to ensure all necessary packages are downloaded and installed. ```bash cd client npm install ``` -------------------------------- ### Install Go Dependencies Source: https://github.com/dan6erbond/revline/blob/main/server/README.md Installs or updates project dependencies using Go modules. This command ensures all required libraries are fetched and managed correctly. ```bash go mod tidy ``` -------------------------------- ### GQLGen Handler Setup Source: https://github.com/dan6erbond/revline/blob/main/website/src/app/blog/posts/go-gqlgen-ent-uber-fx.mdx Defines custom HTTP route handlers for GQLGen's GraphQL endpoint and Playground, adhering to a familiar http.HandlerFunc pattern for integration with a custom HTTP server. ```go type NewServerResult struct { fx.Out Routes []httpfx.Route `group:"routes,flatten"` } type PlaygroundHandlerFunc func(http.ResponseWriter, *http.Request) func (f PlaygroundHandlerFunc) ServeHTTP(w http.ResponseWriter, r *http.Request) { f(w, r) } func (h PlaygroundHandlerFunc) Pattern() string { return "/playground" } func (h PlaygroundHandlerFunc) Methods() []string { return []string{"GET"} } type GraphqlHandlerFunc struct{ *handler.Server } func (h GraphqlHandlerFunc) Pattern() string { return "/graphql" } func (h GraphqlHandlerFunc) Methods() []string { return []string{} } func NewServer(resolver *graph.Resolver, entClient *ent.Client) NewServerResult { srv := handler.New(graph.NewExecutableSchema(graph.Config{ Resolvers: resolver, Directives: graph.DirectiveRoot{ LoggedIn: directives.LoggedIn(), }, })) srv.Use(entgql.Transactioner{TxOpener: entClient}) return NewServerResult{Routes: []httpfx.Route{ PlaygroundHandlerFunc(playground.Handler("Revline", "/graphql")), GraphqlHandlerFunc{srv}, }} } ``` -------------------------------- ### Start Development Server Source: https://github.com/dan6erbond/revline/blob/main/client/README.md Command to launch the local development server for the Revline frontend. This command compiles the application and makes it accessible at a specified local URL, typically http://localhost:3000. ```bash npm run dev ``` -------------------------------- ### Configure Environment Variables Source: https://github.com/dan6erbond/revline/blob/main/client/README.md Instructions to set up the local environment configuration for the Revline frontend. It involves copying an example environment file and configuring essential variables like the GraphQL endpoint and authentication providers. ```bash cp .env.example .env.local ``` -------------------------------- ### Create Chi Router with Middleware and Routes Source: https://github.com/dan6erbond/revline/blob/main/website/src/app/blog/posts/go-gqlgen-ent-uber-fx.mdx This Go function acts as an FX provider to create and configure a Chi router. It applies standard middleware like RequestID, RealIP, Recoverer, and Timeout, along with CORS configuration. It also registers custom routes based on the `Route` interface and starts the HTTP server on a configured host and port. ```go package httpfx import ( "fmt" "net/http" "time" "github.com/Dan6erbond/revline/internal" "github.com/go-chi/chi" "github.com/go-chi/chi/middleware" "github.com/go-chi/cors" "go.uber.org/fx" "go.uber.org/zap" ) var NewRouterParamTags = fx.ParamTags("", "", "", `group:"routes"`, `group:"middlewares"`) type Route interface { http.Handler Pattern() string Methods() []string } func NewRouter(lc fx.Lifecycle, logger *zap.Logger, config internal.Config, routes []Route, mws []func(http.Handler) http.Handler) *chi.Mux { outer := chi.NewRouter() router.Use(middleware.RequestID) router.Use(middleware.RealIP) router.Use(middleware.Recoverer) router.Use(middleware.Timeout(60 * time.Second)) router.Use(cors.Handler(cors.Options{ AllowOriginFunc: func(r *http.Request, origin string) bool { return true }, AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"}, AllowedHeaders: []string{"Accept", "Authorization", "Content-Type", "Content-Length", "X-CSRF-Token"}, ExposedHeaders: []string{"Link"}, AllowCredentials: true, MaxAge: 300, Debug: config.Environment == "development", })) router.Use(mws...) router.Get("/healthz", func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) w.Write([]byte("ok")) }) for _, route := range routes { if len(route.Methods()) == 0 { router.Handle(route.Pattern(), route) continue } for _, m := range route.Methods() { switch m { case "GET": router.Get(route.Pattern(), route.ServeHTTP) case "POST": router.Post(route.Pattern(), route.ServeHTTP) case "PUT": router.Put(route.Pattern(), route.ServeHTTP) case "DELETE": router.Delete(route.Pattern(), route.ServeHTTP) case "PATCH": router.Patch(route.Pattern(), route.ServeHTTP) case "OPTIONS": router.Options(route.Pattern(), route.ServeHTTP) case "HEAD": router.Head(route.Pattern(), route.ServeHTTP) default: logger.Warn("Unsupported method for route", zap.String("method", m)) } } } lc.Append(fx.StartHook(func() { go http.ListenAndServe(fmt.Sprintf("%s:%d", config.Host, config.Port), router) })) return router } ``` -------------------------------- ### React Frontend for Handling Stripe Onboarding Status Source: https://github.com/dan6erbond/revline/blob/main/website/src/app/blog/posts/stripe-affiliate-go-nextjs.mdx Manages user feedback after the Stripe onboarding process using React hooks and toasts. It checks for 'success' or 'canceled' query parameters to display appropriate messages to the user, guiding them on the next steps. ```typescript const router = useRouter(); const success = getQueryParam(router.query.success); const canceled = getQueryParam(router.query.canceled); useEffect(() => { if (success) { addToast({ title: "Onboarding complete", description: "Your payout account has been successfully linked. You're now ready to receive payments and manage transfers.", color: "success", }); } else if (canceled) { addToast({ title: "Onboarding canceled", description: "You didn't finish setting up your payout account. Without a connected account, you won’t be able to receive payments. Try again when you're ready.", color: "warning", }); } }, [canceled, success]); ``` -------------------------------- ### Initialize Go Module Source: https://github.com/dan6erbond/revline/blob/main/website/src/app/blog/posts/go-gqlgen-ent-uber-fx.mdx Initializes a new Go module for the project. This command sets up the go.mod file, which tracks dependencies for the Go project. ```shell go mod init github.com/dan6erbond/revline ``` -------------------------------- ### Configure and Supply App Configuration with FX Source: https://github.com/dan6erbond/revline/blob/main/website/src/app/blog/posts/go-gqlgen-ent-uber-fx.mdx Configures Viper to read application settings from a YAML file and environment variables, then unmarshals it into a Config struct. The configuration is then supplied to the FX dependency injection container. ```go func main() { var config internal.Config viper.SetConfigName("config") viper.SetConfigType("yaml") viper.AddConfigPath(".") viper.AutomaticEnv() internal.SetDefaults() err := viper.ReadInConfig() if err != nil { log.Fatalf("fatal error config file: %v", err) } if err = viper.Unmarshal(&config); err != nil { log.Fatalf("unable to decode into struct, %v", err) } fx.New( fx.Supply(config), ).Run() } ``` -------------------------------- ### Define HTTP FX Module Source: https://github.com/dan6erbond/revline/blob/main/website/src/app/blog/posts/go-gqlgen-ent-uber-fx.mdx This Go code defines an FX module for the HTTP server. It uses `fx.Provide` to register the `NewRouter` function as a dependency and `fx.Invoke` to ensure the router is created and its startup hook is registered within the FX application lifecycle. ```go package httpfx import ( "github.com/go-chi/chi" "go.uber.org/fx" ) var Module = fx.Module("http", fx.Provide( fx.Annotate( NewRouter, NewRouterParamTags, ), ), fx.Invoke(func(router *chi.Mux) {}), ) ``` -------------------------------- ### Run Backend Server Source: https://github.com/dan6erbond/revline/blob/main/README.md Command to compile and run the Go backend application from the `server/` directory. ```bash cd server go run main.go ``` -------------------------------- ### Implement Go Resolver for CreateCar Mutation Source: https://github.com/dan6erbond/revline/blob/main/website/src/app/blog/posts/go-gqlgen-ent-uber-fx.mdx Provides the Go implementation for the `createCar` GraphQL mutation. It retrieves user context, sets the owner ID from the context, and uses the Ent client to create and save a new car. ```go // CreateCar is the resolver for the createCar field. func (r *mutationResolver) CreateCar(ctx context.Context, input ent.CreateCarInput) (*ent.Car, error) { user := auth.ForContext(ctx) input.OwnerID = &user.ID return r.entClient.Car.Create().SetInput(input).Save(ctx) } ``` -------------------------------- ### Clone Revline Repository Source: https://github.com/dan6erbond/revline/blob/main/README.md Instructions to clone the Revline project from GitHub and navigate into the project directory. ```bash git clone https://github.com/dan6erbond/revline.git cd revline ``` -------------------------------- ### Define Configuration Struct and Viper Defaults Source: https://github.com/dan6erbond/revline/blob/main/website/src/app/blog/posts/go-gqlgen-ent-uber-fx.mdx Defines a Go struct to hold application configuration and sets default values using the Viper library. This struct is intended to be populated from environment variables or configuration files. ```go package internal import "github.com/spf13/viper" type Config struct { DatabaseURL string Host string Port uint Environment string // Other configuration variables } func SetDefaults() { viper.SetDefault("host", "localhost") viper.SetDefault("port", 4000) viper.SetDefault("environment", "development") } ``` -------------------------------- ### Ent Code Generation with entgql Source: https://github.com/dan6erbond/revline/blob/main/website/src/app/blog/posts/go-gqlgen-ent-uber-fx.mdx Configures Ent code generation using the entgql extension to generate GraphQL schemas, enable Relay specifications, and integrate with the Go backend. ```go package main import ( "log" "entgo.io/contrib/entgql" "entgo.io/ent/entc" "entgo.io/ent/entc/gen" ) func main() { ex, err := entgql.NewExtension( entgql.WithSchemaGenerator(), entgql.WithSchemaPath("../graph/ent.graphqls"), entgql.WithWhereInputs(true), entgql.WithRelaySpec(true), // entgql.WithNodeDescriptor(false), ) if err != nil { log.Fatalf("creating entgql extension: %v", err) } if err := entc.Generate("./schema", &gen.Config{}, entc.Extensions(ex), entc.FeatureNames("entql", "privacy", "schema/snapshot", "sql/execquery")); err != nil { log.Fatalf("running ent codegen: %v", err) } } ``` -------------------------------- ### GQLGen Module Definition Source: https://github.com/dan6erbond/revline/blob/main/website/src/app/blog/posts/go-gqlgen-ent-uber-fx.mdx Defines the fx.Module for GQLGen integration, providing the new resolver and server components to the Uber FX application. ```go package graphfx import ( "github.com/Dan6erbond/revline/graph" "go.uber.org/fx" ) var Module = fx.Module("graph", fx.Provide( graph.NewResolver, NewServer, ), ) ``` -------------------------------- ### Implement Go Resolver for CreateFuelUp Mutation Source: https://github.com/dan6erbond/revline/blob/main/website/src/app/blog/posts/go-gqlgen-ent-uber-fx.mdx Provides the Go implementation for the `CreateFuelUp` GraphQL mutation. It utilizes a transactional Ent client to ensure atomicity when creating a new fuel-up record, incorporating any extended input fields. ```go // CreateFuelUp is the resolver for the createFuelUp field. func (r *mutationResolver) CreateFuelUp(ctx context.Context, input ent.CreateFuelUpInput) (*ent.FuelUp, error) { c := ent.FromContext(ctx) return c.FuelUp.Create().SetInput(input).Save(ctx) } ``` -------------------------------- ### Extend GraphQL CreateFuelUpInput with OdometerKm Source: https://github.com/dan6erbond/revline/blob/main/website/src/app/blog/posts/go-gqlgen-ent-uber-fx.mdx Extends the Ent-generated `CreateFuelUpInput` type to include an optional `odometerKm` field. This allows capturing odometer readings directly within the fuel-up creation input. ```gql extend input CreateFuelUpInput { odometerKm: Float } ``` -------------------------------- ### Repository Structure Source: https://github.com/dan6erbond/revline/blob/main/README.md Outlines the directory structure of the Revline project, separating the Next.js frontend and Go backend, along with Docker configuration files. ```bash revline/ ├── client/ # Next.js frontend with Apollo Client ├── server/ # Go backend using Fx, gqlgen, ent ├── docker-compose.yml # Dev infra (Postgres, MinIO only) ├── sample-docker-compose.yml # Self-hosting reference └── README.md ``` -------------------------------- ### Implement Go Resolver for CreateFuelUpInput OdometerKm Field Source: https://github.com/dan6erbond/revline/blob/main/website/src/app/blog/posts/go-gqlgen-ent-uber-fx.mdx Implements the resolver for the extended `odometerKm` field within `CreateFuelUpInput`. If `odometerKm` is provided, it creates a corresponding `OdometerReading` record using the Ent client and associates its ID with the input. ```go // OdometerKm is the resolver for the odometerKm field. func (r *createFuelUpInputResolver) OdometerKm(ctx context.Context, obj *ent.CreateFuelUpInput, data *float64) error { if data != nil { c := ent.FromContext(ctx) or, err := c.OdometerReading.Create(). SetCarID(obj.CarID). SetReadingKm(*data). SetReadingTime(obj.OccurredAt). SetNotes("Created by fuel-up"). Save(ctx) if err != nil { return err } obj.OdometerReadingID = &or.ID return err } return nil } ``` -------------------------------- ### Revline Frontend Folder Layout Source: https://github.com/dan6erbond/revline/blob/main/client/README.md Provides an overview of the directory structure for the Revline frontend client application. It details the organization of assets, public files, and the main source code within the 'src' directory, including components, pages, and utility files. ```bash client/ ├── assets/ # Used in OG images and API routes ├── public/ # Logos, static placeholders, etc. └── src/ ├── apollo-client/ # Apollo setup, auth handling, upload link ├── app/ # App Router - used for public/marketing pages ├── auth/ # Auth.js configuration ├── components/ # Reusable UI components ├── contexts/ # React contexts ├── gql/ # Auto-generated GraphQL types ├── hooks/ # Custom hooks ├── literals/ # UI literals & constants ├── pages/ # Pages Router - main CSR app shell ├── styles/ # Global styles ├── utils/ # Helpers and utility functions └── middleware.ts # Auth protection for non-public routes ``` -------------------------------- ### Implement Go Resolver for Car AverageConsumption Field Source: https://github.com/dan6erbond/revline/blob/main/website/src/app/blog/posts/go-gqlgen-ent-uber-fx.mdx Provides a placeholder Go resolver function for the `averageConsumptionLitersPerKm` field added to the `Car` type. Currently, it panics indicating that the implementation is not yet complete. ```go // AverageConsumptionLitersPerKm is the resolver for the averageConsumptionLitersPerKm field. func (r *carResolver) AverageConsumptionLitersPerKm(ctx context.Context, obj *ent.Car) (float64, error) { panic("Not implemented: Car - averageConsumptionLitersPerKm") } ``` -------------------------------- ### MinIO Access Details Source: https://github.com/dan6erbond/revline/blob/main/README.md Credentials and URL for accessing the MinIO object storage console, used for local media storage. ```APIDOC MinIO Console: URL: http://localhost:9001 Access Key: minioadmin Secret Key: minioadmin ``` -------------------------------- ### React Frontend for Initiating Stripe Onboarding Source: https://github.com/dan6erbond/revline/blob/main/website/src/app/blog/posts/stripe-affiliate-go-nextjs.mdx Handles the user interaction for connecting a Stripe account in the React frontend. It orchestrates calls to `createConnectAccount` and `linkConnectAccount` mutations, redirecting the user to Stripe's onboarding flow upon successful account linking. ```typescript ``` -------------------------------- ### Generate GQLGen GraphQL Code Source: https://github.com/dan6erbond/revline/blob/main/server/README.md Executes the GQLGen code generation for the GraphQL API. This command generates Go code for GraphQL schema, resolvers, and models based on the defined `.graphqls` files. ```bash go generate ./graph ``` -------------------------------- ### GraphQL Mutations for Stripe Connect Source: https://github.com/dan6erbond/revline/blob/main/website/src/app/blog/posts/stripe-affiliate-go-nextjs.mdx Defines the GraphQL mutations required for managing Stripe Connect accounts within the Revline platform. These mutations handle account creation, linking, and generating login links for express onboarding. ```APIDOC extend type Mutation { createConnectAccount: User! @loggedIn linkConnectAccount: String! @loggedIn createExpressLoginLink: String! @loggedIn } ``` -------------------------------- ### GraphQL Query Schema Extension Source: https://github.com/dan6erbond/revline/blob/main/website/src/app/blog/posts/go-gqlgen-ent-uber-fx.mdx Extends the GraphQL schema to define a `car` query, specifying its input (ID) and output type. ```gql extend type Query { car(id: ID!): Car! } ``` -------------------------------- ### GraphQL Car Query Resolver Source: https://github.com/dan6erbond/revline/blob/main/website/src/app/blog/posts/go-gqlgen-ent-uber-fx.mdx Implements the `car` query resolver in Go, fetching a `Car` entity from the Ent client using a parsed UUID. ```go // Car is the resolver for the car field. func (r *queryResolver) Car(ctx context.Context, id string) (*ent.Car, error) { uid, err := uuid.Parse(id) if err != nil { return nil, err } return r.entClient.Car.Get(ctx, uid) } ``` -------------------------------- ### Go Resolver for Linking Stripe Connect Account Source: https://github.com/dan6erbond/revline/blob/main/website/src/app/blog/posts/stripe-affiliate-go-nextjs.mdx Implements the `linkConnectAccount` GraphQL mutation in Go. This function generates a Stripe Account Link URL, directing the user to Stripe's onboarding page. It uses the user's existing Stripe Account ID and specifies return URLs for success and cancellation. ```go // LinkConnectAccount is the resolver for the linkConnectAccount field. func (r *mutationResolver) LinkConnectAccount(ctx context.Context) (string, error) { user := auth.ForContext(ctx) baseURL, err := url.Parse(r.config.PublicURL) if err != nil { return "", err } var ( successURL = baseURL.JoinPath("/affiliate") cancelURL = baseURL.JoinPath("/affiliate") ) successURL.RawQuery = "success=true" cancelURL.RawQuery = "canceled=true" accountLink, err := accountlink.New(&stripe.AccountLinkParams{ Account: user.StripeAccountID, ReturnURL: stripe.String(successURL.String()), RefreshURL: stripe.String(cancelURL.String()), Type: stripe.String("account_onboarding"), }) if err != nil { return "", err } return accountLink.URL, err } ``` -------------------------------- ### Define GraphQL CreateCar Mutation Source: https://github.com/dan6erbond/revline/blob/main/website/src/app/blog/posts/go-gqlgen-ent-uber-fx.mdx Extends the GraphQL Mutation type to include a new mutation for creating a car. It specifies the input type and the return type, along with an authentication directive. ```gql extend type Mutation { createCar(input: CreateCarInput!): Car! @loggedIn } ``` -------------------------------- ### Handle Stripe Connect Webhook (Go) Source: https://github.com/dan6erbond/revline/blob/main/website/src/app/blog/posts/stripe-affiliate-go-nextjs.mdx This Go function sets up an HTTP handler to process Stripe Connect webhook events. It verifies the Stripe signature, reads and parses the event data, specifically handling the 'account.updated' event to update user capabilities in the database. Dependencies include Stripe Go SDK, Zap logger, and an ORM client (ent). ```go package main import ( "encoding/json" "io" "net/http" "go.uber.org/zap" "github.com/stripe/stripe-go/v74" "github.com/stripe/stripe-go/v74/webhook" "your_project/ent" "your_project/internal" ) func ConnectWebhook(config internal.Config, entClient *ent.Client, logger *zap.Logger) http.HandlerFunc { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { handleError := func(message string, code int, err error) { http.Error(w, err.Error(), code) logger.Warn(message, zap.Error(err)) } const MaxBodyBytes = int64(65536) r.Body = http.MaxBytesReader(w, r.Body, MaxBodyBytes) body, err := io.ReadAll(r.Body) if err != nil { handleError("Error reading request body", http.StatusBadRequest, err) return } event, err := webhook.ConstructEvent(body, r.Header.Get("Stripe-Signature"), config.Stripe.ConnectWebhookSecret) if err != nil { handleError("Error verifying webhook signature", http.StatusBadRequest, err) return } if event.Type == "account.updated" { var account stripe.Account if err := json.Unmarshal(event.Data.Raw, &account); err != nil { handleError("Error unmarshaling event data", http.StatusBadRequest, err) return } // Assuming User model has a StripeAccountID field and a SetStripeAccountCapabilities method user, err := entClient.User.Query().Where( user.StripeAccountID(account.ID), ).First(r.Context()) if err != nil { handleError("Error retrieving user by Stripe account ID", http.StatusInternalServerError, err) return } var capabilities map[string]string capabilitiesJSON, err := json.Marshal(account.Capabilities) if err != nil { handleError("Failed to marshal account capabilities", http.StatusInternalServerError, err) return } if err := json.Unmarshal(capabilitiesJSON, &capabilities); err != nil { handleError("Failed to unmarshal account capabilities", http.StatusInternalServerError, err) return } if _, err := user.Update().SetStripeAccountCapabilities(capabilities).Save(r.Context()); err != nil { handleError("Failed to update user Stripe account capabilities", http.StatusInternalServerError, err) return } } w.WriteHeader(http.StatusOK) }) } ``` -------------------------------- ### Generate Ent ORM Code Source: https://github.com/dan6erbond/revline/blob/main/server/README.md Runs the Ent ORM code generation process for the backend. This command generates Go code based on the Ent schema definitions, including database interactions and GraphQL extensions. ```bash go generate ./ent ``` -------------------------------- ### Ent Generate Directive Source: https://github.com/dan6erbond/revline/blob/main/website/src/app/blog/posts/go-gqlgen-ent-uber-fx.mdx Specifies the command to run the custom Ent code generation script (`entc.go`) using Go modules. ```go package ent //go:generate go run -mod=mod entc.go ``` -------------------------------- ### Process Stripe Webhook for Subscription Creation Source: https://github.com/dan6erbond/revline/blob/main/website/src/app/blog/posts/stripe-affiliate-go-nextjs.mdx This Go function handles Stripe's 'checkout.session.completed' webhook event. It parses the session, locks the subscriptions table, finds the associated checkout session, determines the subscription tier, and creates a new subscription record in the database, including affiliate codes. ```go func Webhook(config internal.Config, entClient *ent.Client, logger *zap.Logger) http.HandlerFunc { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // Parse and validate webhook payload switch event.Type { case "checkout.session.completed": tx, err := entClient.Tx(r.Context()) var session stripe.CheckoutSession if err := json.Unmarshal(event.Data.Raw, &session); err != nil { rollback(tx, "Error unmarshaling event data", http.StatusBadRequest, err) return } if _, err := tx.ExecContext(r.Context(), "LOCK TABLE subscriptions IN ACCESS EXCLUSIVE MODE"); err != nil { rollback(tx, "Error locking subscriptions table", http.StatusBadRequest, err) return } uid, err := uuid.Parse(session.ClientReferenceID) if err != nil { rollback(tx, "Error parsing checkout session reference UUID", http.StatusInternalServerError, err) return } checkoutSession, err := tx.CheckoutSession.Query(). Where(checkoutsession.Or( checkoutsession.IDEQ(uid), checkoutsession.StripeSessionIDEQ(session.ID), )). WithUser(). First(r.Context()) if err != nil { rollback(tx, "Error finding checkout session", http.StatusInternalServerError, err) return } var tier = subscriptionq.TierDiy if checkoutSession.StripePriceID == config.Stripe.Products.Enthusiast.Prices.Monthly.ID { tier = subscriptionq.TierEnthusiast } if _, err = tx.Subscription.Create(). SetCheckoutSession(checkoutSession). SetStripeSubscriptionID(session.Subscription.ID). SetStatus(subscriptionq.StatusActive). SetTier(tier). SetUser(checkoutSession.Edges.User). SetNillableAffiliate6moCode(checkoutSession.Affiliate6moCode). SetNillableAffiliate12moCode(checkoutSession.Affiliate12moCode). Save(r.Context()); err != nil { rollback(tx, "Error saving subscription", http.StatusInternalServerError, err) return } if err := tx.Commit(); err != nil { rollback(tx, "Error committing transaction", http.StatusInternalServerError, err) return } } }) } ``` -------------------------------- ### Go Resolver for Creating Stripe Connect Account Source: https://github.com/dan6erbond/revline/blob/main/website/src/app/blog/posts/stripe-affiliate-go-nextjs.mdx Implements the `createConnectAccount` GraphQL mutation in Go. This function creates a new Stripe Connect account of type 'express' for the logged-in user and updates the user record with the Stripe account ID and generated affiliate codes. ```go // CreateConnectAccount is the resolver for the createConnectAccount field. func (r *mutationResolver) CreateConnectAccount(ctx context.Context) (*ent.User, error) { user := auth.ForContext(ctx) account, err := account.New(&stripe.AccountParams{ Controller: &stripe.AccountControllerParams{ StripeDashboard: &stripe.AccountControllerStripeDashboardParams{ Type: stripe.String("express"), }, Fees: &stripe.AccountControllerFeesParams{ Payer: stripe.String("application"), }, Losses: &stripe.AccountControllerLossesParams{ Payments: stripe.String("application"), }, }, }) if err != nil { return nil, err } if _, err := user.Update(). SetStripeAccountID(account.ID). SetAffiliate6moCode(petname.Generate(3, "-")). SetAffiliate12moCode(petname.Generate(3, "-")). Save(ctx); err != nil { return nil, err } return user, err } ``` -------------------------------- ### Add GraphQL AverageConsumption Field to Car Type Source: https://github.com/dan6erbond/revline/blob/main/website/src/app/blog/posts/go-gqlgen-ent-uber-fx.mdx Extends the existing `Car` GraphQL type to include a new field, `averageConsumptionLitersPerKm`. This field is intended to be calculated at runtime. ```gql extend type Car { averageConsumptionLitersPerKm: Float! } ``` -------------------------------- ### Generate GraphQL Types Source: https://github.com/dan6erbond/revline/blob/main/client/README.md Command to generate TypeScript types for GraphQL queries and mutations. This process uses the GraphQL schema from the backend to create type-safe data structures, enhancing developer experience and reducing runtime errors. ```bash npm run codegen ``` -------------------------------- ### Handle Stripe Invoice Paid Event for Affiliate Payouts Source: https://github.com/dan6erbond/revline/blob/main/website/src/app/blog/posts/stripe-affiliate-go-nextjs.mdx Processes Stripe's 'invoice.paid' event to identify and pay affiliate commissions. It retrieves subscription details, checks for affiliate codes, fetches expanded invoice and payment intent data, calculates the commission amount (20% for 6-month codes, 30% for 12-month codes), and initiates a Stripe transfer to the affiliate's Connect account. ```go var invoice stripe.Invoice if err := json.Unmarshal(event.Data.Raw, &invoice); err != nil { handleError("Error unmarshaling event data", http.StatusBadRequest, err) return } id := invoice.Lines.Data[0].Parent.SubscriptionItemDetails.Subscription sub, err := entClient.Subscription.Query(). Where(subscriptionq.StripeSubscriptionID(id)). First(r.Context()) if err != nil { handleError("Error retrieving subscription by Stripe ID", http.StatusInternalServerError, err) return } if sub.Affiliate6moCode != nil || sub.Affiliate12moCode != nil { expandedInvoice, err := stripeInvoice.Get(invoice.ID, &stripe.InvoiceParams{ Expand: stripe.StringSlice([]string{"payments.data.payment.payment_intent"}), }) if err != nil { handleError("Error retrieving expanded invoice", http.StatusInternalServerError, err) return } paymentIntent, err := paymentintent.Get(expandedInvoice.Payments.Data[0].Payment.PaymentIntent.ID, &stripe.PaymentIntentParams{ Expand: stripe.StringSlice([]string{"latest_charge"}), }) if err != nil { handleError("Error retrieving expanded payment intent", http.StatusInternalServerError, err) return } var ( affiliatePartner *ent.User amount int64 ) if sub.Affiliate6moCode != nil { if time.Unix(invoice.Created, 0).After(sub.CreateTime.AddDate(0, 7, 0)) { w.WriteHeader(http.StatusOK) return } affiliatePartner, err = entClient.User.Query().Where( userq.Affiliate6moCodeEQ(*sub.Affiliate6moCode), ).First(r.Context()) amount = int64(float64(expandedInvoice.Lines.Data[0].Amount) * 0.2) } else { if time.Unix(invoice.Created, 0).After(sub.CreateTime.AddDate(0, 13, 0)) { w.WriteHeader(http.StatusOK) return } affiliatePartner, err = entClient.User.Query().Where( userq.Affiliate12moCodeEQ(*sub.Affiliate12moCode), ).First(r.Context()) amount = int64(float64(expandedInvoice.Lines.Data[0].Amount) * 0.3) } if err != nil { handleError("Error finding affiliate partner", http.StatusInternalServerError, err) return } params := &stripe.TransferParams{ Amount: stripe.Int64(amount), Currency: stripe.String(expandedInvoice.Lines.Data[0].Currency), Destination: affiliatePartner.StripeAccountID, SourceTransaction: &paymentIntent.LatestCharge.ID, } if _, err := transfer.New(params); err != nil { handleError("Failed to transfer affiliate payout", http.StatusInternalServerError, err) return } } w.WriteHeader(http.StatusOK) ``` -------------------------------- ### Check Account Capabilities in Frontend (TSX) Source: https://github.com/dan6erbond/revline/blob/main/website/src/app/blog/posts/stripe-affiliate-go-nextjs.mdx This TypeScript/React snippet demonstrates how to conditionally render UI elements based on fetched user data, specifically checking Stripe account capabilities. It accesses `stripeAccountCapabilities.transfers` to determine if the 'transfers' capability is 'active', influencing whether to show an affiliate code or an onboarding button. ```tsx data?.me.stripeAccountID && data.me.stripeAccountCapabilities?.transfers === "active" ``` -------------------------------- ### Validate Affiliate Code in Backend Request Source: https://github.com/dan6erbond/revline/blob/main/website/src/app/blog/posts/stripe-affiliate-go-nextjs.mdx Backend Go code to validate an incoming affiliate code against user records in the database. It queries for matching 6-month or 12-month affiliate codes and associates the found partner with the checkout session. ```go if input.Affiliate != nil { affiliatePartner, err = r.entClient.User.Query().Where( userq.Or( userq.Affiliate6moCodeEQ(*input.Affiliate), userq.Affiliate12moCodeEQ(*input.Affiliate), ), ).First(ctx) if err != nil { return "", err } if *affiliatePartner.Affiliate6moCode == *input.Affiliate { checkoutSessionCreate.SetAffiliate6moCode(*affiliatePartner.Affiliate6moCode) } else if *affiliatePartner.Affiliate12moCode == *input.Affiliate { checkoutSessionCreate.SetAffiliate12moCode(*affiliatePartner.Affiliate12moCode) } } ``` -------------------------------- ### Retrieve Affiliate Code from Cookie for Checkout Source: https://github.com/dan6erbond/revline/blob/main/website/src/app/blog/posts/stripe-affiliate-go-nextjs.mdx This function retrieves the affiliate code from the document's cookies and includes it in the checkout mutation variables. It handles decoding and parsing the cookie string. ```javascript const checkout = () => { const decodedCookie = decodeURIComponent(document.cookie); const ca = decodedCookie.split(";"); const affiliate = ca .find((c) => c.trim().startsWith("affiliate=")) ?.substring(11); mutate({ variables: { input: { tier: SubscriptionTier.Diy, affiliate }, }, }).then( ({ data }) => data?.createCheckoutSession && (window.location.href = data?.createCheckoutSession) ); } ``` -------------------------------- ### Set Affiliate Cookie in Next.js Pages Router Source: https://github.com/dan6erbond/revline/blob/main/website/src/app/blog/posts/stripe-affiliate-go-nextjs.mdx This snippet demonstrates how to capture an affiliate code from the router's query parameters and set it as a cookie, suitable for applications using the Next.js Pages Router. ```javascript const router = useRouter(); const href = useHref(); useEffect(() => { if (getQueryParam(router.query.affiliate)) { document.cookie = `affiliate=${router.query.affiliate}`; } }, [router.query]); ``` -------------------------------- ### Set Affiliate Cookie in Next.js App Router Source: https://github.com/dan6erbond/revline/blob/main/website/src/app/blog/posts/stripe-affiliate-go-nextjs.mdx This component reads the 'affiliate' query parameter from the URL and stores it in a document cookie. It's designed for Next.js App Router and should be placed in a high-level layout. ```tsx "use client"; import { useEffect } from "react"; import { useSearchParams } from "next/navigation"; export default function AffiliateCookie() { const query = useSearchParams(); useEffect(() => { if (query?.get("affiliate")) { document.cookie = `affiliate=${query.get("affiliate")}`; } }, [query]); return null; } ``` === COMPLETE CONTENT === This response contains all available snippets from this library. No additional content exists. Do not make further requests.