### Setup and Build Project Source: https://github.com/davidwartell/go-mongo-apicursor/blob/main/README.md Commands to set up dependencies and build the project. 'make setup' typically installs necessary tools or dependencies, while 'make all' compiles the main application. ```bash make setup make all ``` -------------------------------- ### Install golangci-lint using Homebrew Source: https://github.com/davidwartell/go-mongo-apicursor/blob/main/README.md Installs the golangci-lint tool, a prerequisite for running linters within this project. This command uses Homebrew, a package manager for macOS. ```bash brew install golangci/tap/golangci-lint ``` -------------------------------- ### Setup and Build for Release Source: https://github.com/davidwartell/go-mongo-apicursor/blob/main/README.md Commands for preparing and building a release version of the project. This usually involves setting up the environment and then compiling optimized binaries. ```bash make setup make release all ``` -------------------------------- ### Complete GraphQL Resolver Example with API Cursor (Go) Source: https://context7.com/davidwartell/go-mongo-apicursor/llms.txt Demonstrates a full integration of the go-mongo-apicursor library within a GraphQL resolver function. This example showcases how to set up the cursor, build MongoDB queries with sorting and filtering, and return a paginated connection object. ```go package main import ( "context" "time" apicursor "github.com/davidwartell/go-mongo-apicursor" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/bson/primitive" "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/options" ) // Model type Person struct { Id primitive.ObjectID `bson:"_id" Name string `bson:"name" CreatedTime time.Time `bson:"createdTime" } // GraphQL input type for ordering type PersonOrder struct { Direction apicursor.OrderDirection `json:"direction" } // GraphQL resolver func (r *QueryResolver) Persons( ctx context.Context, after *string, before *string, first *int, last *int, orderBy *PersonOrder, ) (*PersonConnection, error) { // Initialize cursor cursor := apicursor.NewCursor() err := cursor.LoadFromAPIRequest(after, before, first, last, &PersonFactory{}) if err != nil { return nil, err } cursor.SetMarshaler(&PersonCursorMarshaler{}) // Build query filter := bson.M{} findOptions := &options.FindOptions{} findOptions.SetLimit(cursor.FindLimit()) sortDirection := apicursor.DESC.Int() if orderBy != nil { sortDirection = orderBy.Direction.Int() } findOptions.SetSort(bson.D{ {"createdTime", cursor.CursorFilterSortDirection(sortDirection)}, {"_id", cursor.CursorFilterSortDirection(sortDirection)}, }) // Get total count var collection *mongo.Collection // assume initialized totalCount, err := collection.CountDocuments(ctx, filter) if err != nil { return nil, err } // Apply cursor filters err = cursor.UnmarshalMongo(filter, sortDirection) if err != nil { return nil, err } // Execute query mongoCursor, err := collection.Find(ctx, filter, findOptions) if err != nil { return nil, err } defer mongoCursor.Close(ctx) // Build connection result connection, err := cursor.ConnectionFromMongoCursor(ctx, mongoCursor, totalCount) if err != nil { return nil, err } return connection.(*PersonConnection), nil } ``` -------------------------------- ### Implement PersonEdge for Node Wrapping (Go) Source: https://context7.com/davidwartell/go-mongo-apicursor/llms.txt Implements the edge interface to wrap individual nodes with their cursor position. It includes methods to get and set the cursor string and the node itself. ```go package main type PersonEdge struct { Cursor string Node *Person } func (pe *PersonEdge) GetCursor() string { return pe.Cursor } func (pe *PersonEdge) SetCursor(c string) { pe.Cursor = c } func (pe *PersonEdge) GetNode() interface{} { return pe.Node } func (pe *PersonEdge) SetNode(node interface{}) { pe.Node = node.(*Person) } ``` -------------------------------- ### Implement PersonEdge Interface (Go) Source: https://github.com/davidwartell/go-mongo-apicursor/blob/main/README.md Implements the 'apicursor.Edge' interface for 'PersonEdge', providing methods to get and set the cursor and the associated node. This is essential for constructing paginated results. ```Go type PersonEdge struct { // A cursor for use in pagination. Cursor string // The item at the end of the edge. Node *Person } func (de *PersonEdge) GetCursor() string { return de.Cursor } func (de *PersonEdge) SetCursor(c string) { de.Cursor = c } func (de *PersonEdge) GetNode() interface{} { return de.Node } func (de *PersonEdge) SetNode(node interface{}) { de.Node = node.(*Person) } ``` -------------------------------- ### Configure Sort Direction Constants (Go) Source: https://context7.com/davidwartell/go-mongo-apicursor/llms.txt Utilizes constants to specify ascending or descending sort order for pagination. It shows how to get the integer representation of these directions and how they are used with cursors. ```go package main import apicursor "github.com/davidwartell/go-mongo-apicursor" func configureSortDirection() { // Ascending order (1) ascDirection := apicursor.ASC.Int() // Returns 1 // Descending order (-1) descDirection := apicursor.DESC.Int() // Returns -1 // Use with cursor to get actual sort direction cursor := apicursor.NewCursor() // Forward pagination keeps natural sort direction // Backward pagination reverses it actualSort := cursor.CursorFilterSortDirection(descDirection) _ = ascDirection _ = actualSort } ``` -------------------------------- ### Define PersonConnection for Paginated Results (Go) Source: https://context7.com/davidwartell/go-mongo-apicursor/llms.txt Implements the connection interface to define a paginated result container. It holds edges, nodes, page info, and total count, with methods to set and get these properties. ```go package main import apicursor "github.com/davidwartell/go-mongo-apicursor" type PersonEdge struct { Cursor string Node *Person } func (pe *PersonEdge) GetCursor() string { return pe.Cursor } func (pe *PersonEdge) SetCursor(c string) { pe.Cursor = c } func (pe *PersonEdge) GetNode() interface{} { return pe.Node } func (pe *PersonEdge) SetNode(node interface{}) { pe.Node = node.(*Person) } ``` -------------------------------- ### Run Unit Tests Source: https://github.com/davidwartell/go-mongo-apicursor/blob/main/README.md Executes the unit tests for the Go project. This command ensures that individual components of the codebase function as expected. ```bash make test ``` -------------------------------- ### Run Go Linting Source: https://github.com/davidwartell/go-mongo-apicursor/blob/main/README.md Executes the golint linter to check Go source code for style issues and potential errors. This helps maintain code quality and consistency. ```bash make golint ``` -------------------------------- ### Implement API Cursor Pagination in Go Source: https://context7.com/davidwartell/go-mongo-apicursor/llms.txt Demonstrates how to use the APICursor interface to implement cursor-based pagination for MongoDB queries. It covers loading parameters, setting marshaler, building MongoDB queries with sort and filter options, and processing results from a MongoDB cursor. ```go package main import ( "context" apicursor "github.com/davidwartell/go-mongo-apicursor" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/mongo/options" ) func QueryPersons(ctx context.Context, after *string, before *string, first *int, last *int) (*PersonConnection, error) { // Create a new cursor instance cursor := apicursor.NewCursor() // Load pagination parameters from the API request // Parameters: after cursor, before cursor, first N items, last N items, model factory err := cursor.LoadFromAPIRequest(after, before, first, last, &PersonFactory{}) if err != nil { return nil, err } // Set the cursor marshaler for MongoDB operations cursor.SetMarshaler(&PersonCursorMarshaler{}) // Build the MongoDB query filter := bson.M{} findOptions := &options.FindOptions{} findOptions.SetLimit(cursor.FindLimit()) // Returns limit + 1 for hasNextPage detection sortDirection := apicursor.DESC.Int() findOptions.SetSort(bson.D{ {"createdTime", cursor.CursorFilterSortDirection(sortDirection)}, {"_id", cursor.CursorFilterSortDirection(sortDirection)}, }) // Apply cursor filters to the MongoDB query err = cursor.UnmarshalMongo(filter, sortDirection) if err != nil { return nil, err } // Execute query and build connection result mongoCursor, err := collection.Find(ctx, filter, findOptions) if err != nil { return nil, err } defer mongoCursor.Close(ctx) totalCount, _ := collection.CountDocuments(ctx, bson.M{}) connection, err := cursor.ConnectionFromMongoCursor(ctx, mongoCursor, totalCount) if err != nil { return nil, err } return connection.(*PersonConnection), nil } ``` -------------------------------- ### Run Security Linter Source: https://github.com/davidwartell/go-mongo-apicursor/blob/main/README.md Executes the gosec linter to identify security vulnerabilities in the Go code. This is crucial for ensuring the application is secure. ```bash make gosec ``` -------------------------------- ### Implement ModelFactory for Pagination Types in Go Source: https://context7.com/davidwartell/go-mongo-apicursor/llms.txt Shows how to implement the ModelFactory interface to define how your custom model, connection, and edge types are created during the pagination process. This is essential for the library to instantiate the correct types. ```go package main import apicursor "github.com/davidwartell/go-mongo-apicursor" // PersonFactory creates instances for pagination type PersonFactory struct{} // New returns a new instance of the model type func (pf *PersonFactory) New() interface{} { return &Person{} } // NewConnection returns a new connection container func (pf *PersonFactory) NewConnection() apicursor.Connection { return &PersonConnection{} } // NewEdge returns a new edge wrapper func (pf *PersonFactory) NewEdge() apicursor.Edge { return &PersonEdge{} } ``` -------------------------------- ### Implement PersonConnection Interface (Go) Source: https://github.com/davidwartell/go-mongo-apicursor/blob/main/README.md Implements the 'apicursor.Connection' interface for 'PersonConnection', providing methods to manage edges, nodes, page info, and total count. This enables structured data retrieval for API responses. ```Go type PersonConnection struct { // A list of edges. Edges []*PersonEdge // A list of nodes. Nodes []*Person // Information to aid in pagination. PageInfo *apicursor.PageInfo // Identifies the total count of items in the connection. TotalCount uint64 } func (dc *PersonConnection) GetEdges() []*PersonEdge { return dc.Edges } func (dc *PersonConnection) SetEdges(edges []apicursor.Edge) { dc.Edges = make([]*PersonEdge, len(edges)) for i, d := range edges { de := d.(*PersonEdge) dc.Edges[i] = de } } func (dc *PersonConnection) GetNodes() []*Person { return dc.Nodes } func (dc *PersonConnection) SetNodes(nodes []interface{}) { dc.Nodes = make([]*Person, len(nodes)) for i, d := range nodes { person := d.(*Person) dc.Nodes[i] = person } } func (dc *PersonConnection) GetPageInfo() *apicursor.PageInfo { return dc.PageInfo } func (dc *PersonConnection) SetPageInfo(pginfo *apicursor.PageInfo) { dc.PageInfo = pginfo } func (dc *PersonConnection) GetTotalCount() uint64 { return dc.TotalCount } func (dc *PersonConnection) SetTotalCount(count uint64) { dc.TotalCount = count } ``` -------------------------------- ### Handle Pagination Response Metadata (Go) Source: https://context7.com/davidwartell/go-mongo-apicursor/llms.txt Processes the PageInfo struct to extract pagination metadata like EndCursor and StartCursor. This allows for navigating to the next or previous pages based on available data. ```go package main import apicursor "github.com/davidwartell/go-mongo-apicursor" // PageInfo fields (automatically populated by ConnectionFromMongoCursor): // - EndCursor: cursor to continue forward pagination // - HasNextPage: true if more items exist after current page // - HasPreviousPage: true if more items exist before current page // - StartCursor: cursor to continue backward pagination func handlePaginationResponse(connection *PersonConnection) { pageInfo := connection.GetPageInfo() if pageInfo.HasNextPage && pageInfo.EndCursor != nil { // Use *pageInfo.EndCursor as the "after" parameter for next page nextPageAfter := *pageInfo.EndCursor _ = nextPageAfter } if pageInfo.HasPreviousPage && pageInfo.StartCursor != nil { // Use *pageInfo.StartCursor as the "before" parameter for previous page prevPageBefore := *pageInfo.StartCursor _ = prevPageBefore } } ``` -------------------------------- ### Find Persons with Pagination from MongoDB (Go) Source: https://github.com/davidwartell/go-mongo-apicursor/blob/main/README.md Implements the 'Find' method in 'personService' to query MongoDB with cursor-based pagination. It sets up the cursor, marshaler, filter, and find options, then executes the query and constructs the 'PersonConnection' result. ```Go func (s *personService) Find(ctx context.Context, queryCursor apicursor.APICursor, orderBy *PersonOrder) (personConnection *PersonConnection, err error) { if queryCursor == nil { queryCursor = apicursor.NewCursor() } queryCursor.SetMarshaler(&PersonCursorMarshaler{}) filter := bson.M{} findOptions := &options.FindOptions{} findOptions.SetLimit(queryCursor.FindLimit()) sortDirection := apicursor.DESC.Int() if orderBy != nil { sortDirection = orderBy.Direction.Int() } findOptions.SetSort(bson.D{ {"createdTime", queryCursor.CursorFilterSortDirection(sortDirection)}, {"_id", queryCursor.CursorFilterSortDirection(sortDirection)}, }) var countDocsResult int64 countDocsResult, err = collection.CountDocuments(ctx, filter) if err != nil { return } err = queryCursor.UnmarshalMongo(filter, sortDirection) if err != nil { return } var mongoCursor *mongo.Cursor mongoCursor, err = collection.Find(ctx, filter, findOptions) if err != nil { return } defer func() { if mongoCursor != nil { _ = mongoCursor.Close(ctx) } }() var connectionResult apicursor.Connection connectionResult, err = queryCursor.ConnectionFromMongoCursor(ctx, mongoCursor, countDocsResult) if err != nil { return } personConnection = connectionResult.(*PersonConnection) return } ``` -------------------------------- ### Handle Person API Request with Pagination (Go) Source: https://github.com/davidwartell/go-mongo-apicursor/blob/main/README.md Defines the 'Persons' resolver function which handles incoming API requests for persons, utilizing the apicursor to manage pagination parameters (after, before, first, last) and orderBy. It then calls the service to find and return the paginated results. ```Go // PersonOrder Ordering options for Person connections type PersonOrder struct { // The ordering direction. Direction apicursor.OrderDirection `json:"direction"` } func (r *queryResolver) Persons(ctx context.Context, after *string, before *string, first *int, last *int, orderBy *PersonOrder) (*PersonConnection, error) { cursor := apicursor.NewCursor() err = cursor.LoadFromAPIRequest(after, before, first, last, &PersonFactory{}) if err != nil { return err } return person.Instance().Find( ctx, cursor, orderBy, ) } ``` -------------------------------- ### Implement MongoDB Cursor Marshaler for Person (Go) Source: https://github.com/davidwartell/go-mongo-apicursor/blob/main/README.md Implements the 'apicursor.CursorMarshaler' interface for 'PersonCursorMarshaler', defining how to unmarshal MongoDB cursors and marshal object fields for pagination. It specifies the fields ('createdTime', '_id') and their types for cursor encoding. ```Go type PersonCursorMarshaler struct{} func (dcm PersonCursorMarshaler) UnmarshalMongo(c apicursor.APICursor, findFilter bson.M, naturalSortDirection int) (err error) { return c.AddCursorFilters(findFilter, naturalSortDirection, apicursor.CursorFilterField{ FieldName: "createdTime", FieldType: apicursor.CursorFieldTypeTime, }, apicursor.CursorFilterField{ FieldName: "_id", FieldType: apicursor.CursorFieldTypeMongoOid, }, ) } func (dcm PersonCursorMarshaler) Marshal(obj interface{}) (cursorFields map[string]string, err error) { person := obj.(*Person) cursorFields = make(map[string]string) cursorFields["createdTime"], err = apicursor.MarshalTimeField(person.CreatedTime) cursorFields["_id"] = apicursor.MarshalMongoOidField(person.Id) return } ``` -------------------------------- ### Define Person Data Structure and Factory (Go) Source: https://github.com/davidwartell/go-mongo-apicursor/blob/main/README.md Defines the 'Person' struct with BSON tags for MongoDB mapping and a 'PersonFactory' to create instances of Person and related connection/edge types. This is crucial for data serialization and deserialization. ```Go type Person struct { Id bson.ObjectId `bson:"_id"` Name string `bson:"name"` CreatedTime time.Time `bson:"createdTime"` } type PersonFactory struct { } func (df *PersonFactory) New() interface{} { return NewPerson() } func (df *PersonFactory) NewConnection() apicursor.Connection { doc := PersonConnection{} return &doc } func (df *PersonFactory) NewEdge() apicursor.Edge { doc := PersonEdge{} return &doc } ``` -------------------------------- ### Build Cursor Filters for MongoDB Queries (Go) Source: https://context7.com/davidwartell/go-mongo-apicursor/llms.txt Adds cursor-based filters to MongoDB queries, supporting single or multiple fields for compound cursors. This ensures stable pagination even with duplicate values. ```go package main import ( "time" apicursor "github.com/davidwartell/go-mongo-apicursor" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/bson/primitive" ) func buildCursorFilter() { // Single field cursor filter filter := make(bson.M) cursor := &apicursor.Cursor{ After: map[string]string{ "createdTime": "2024-01-15T10:30:00Z", }, } err := cursor.AddCursorFilters(filter, apicursor.ASC.Int(), apicursor.CursorFilterField{ FieldName: "createdTime", FieldType: apicursor.CursorFieldTypeTime, }, ) // Result: {"createdTime": {"$gt":