feat: allow to fetch collections

This commit is contained in:
Alexander Navarro 2024-11-21 20:27:57 -03:00
parent 01086d12c9
commit 8c660053e5
7 changed files with 147 additions and 83 deletions

View file

@ -0,0 +1,11 @@
meta {
name: Get All Pokemons
type: http
seq: 2
}
get {
url: https://pokeapi.co/api/v2/pokemon/
body: none
auth: none
}

View file

@ -0,0 +1,9 @@
{
"version": "1",
"name": "PokeApi",
"type": "collection",
"ignore": [
"node_modules",
".git"
]
}

View file

@ -2,24 +2,80 @@ package main
import ( import (
"database/sql" "database/sql"
"encoding/json"
"fmt" "fmt"
"io"
"net/http"
"net/url"
"strconv"
_ "modernc.org/sqlite" _ "modernc.org/sqlite"
synchronizator "git.alecodes.page/alecodes/synchronizator/pkg" synchronizator "git.alecodes.page/alecodes/synchronizator/pkg"
) )
type PokeApi struct{} type PokeApiResponse[T any] struct {
Count int `json:"count"`
func (pokeApi *PokeApi) ToNode() (string, []byte, error) { Next string `json:"next"`
return "POKEAPI", nil, nil Previous string `json:"previous"`
Results []T `json:"results"`
} }
func (pokeApi *PokeApi) FromNode(_class string, name string, metadata []byte) error { type Pokedex struct {
if _class != "POKEAPI" { Name string `json:"name"`
return fmt.Errorf("invalid class %s", _class) Url string `json:"url"`
}
type Pokemon struct {
Id int `json:"id"`
Name string `json:"name"`
}
func getPokemons(
sync *synchronizator.DB,
pagination synchronizator.Pagination,
) ([]*synchronizator.Collection, synchronizator.Pagination, error) {
var collections []*synchronizator.Collection
params := url.Values{}
params.Add("offset", strconv.Itoa(pagination.Offset))
params.Add("limit", strconv.Itoa(pagination.Limit))
resp, err := http.Get("https://pokeapi.co/api/v2/pokedex?" + params.Encode())
if err != nil {
return nil, pagination, err
} }
return nil
body, err := io.ReadAll(resp.Body)
resp.Body.Close()
var data PokeApiResponse[Pokedex]
err = json.Unmarshal(body, &data)
if err != nil {
return nil, pagination, err
}
collections = make([]*synchronizator.Collection, 0, len(data.Results))
// TODO: this writes to the database in each collection creation
// add better strategies, like returning a collection to the platform and the
// platform do a final bulk update
for _, pokedex := range data.Results {
collection_name := "Pokedex_" + pokedex.Name
collection, err := sync.NewCollection(collection_name, nil)
if err != nil {
return nil, pagination, err
}
collections = append(collections, collection)
}
// fmt.Println(data)
pagination.Offset += pagination.Limit
pagination.HasMore = data.Next != ""
pagination.Total = data.Count
return collections, pagination, nil
} }
func main() { func main() {
@ -43,6 +99,17 @@ func main() {
return return
} }
pokeApi := &PokeApi{} pokeApi, err := sync.NewPlatform("pokeapi", nil)
sync.NewPlatform(pokeApi) if err != nil {
fmt.Println(err)
return
}
fmt.Println(pokeApi)
err = pokeApi.FetchCollections(getPokemons, synchronizator.StartPagination)
if err != nil {
fmt.Println(err)
return
}
} }

View file

@ -54,16 +54,3 @@ func (collection *Collection) AddChild(node *Node) error {
return nil return nil
} }
// Allows to retreive the saved information back into the user struct. This
// method will call the [NodeClass.FromNode] of the provided struct.
//
// Example:
//
// data := &Library{}
// if err := node.Unmarshall(data); err != nil {
// println(err)
// }
func (collection *Collection) Unmarshall(dst StandardNode) error {
return dst.FromNode("COLLECTION", collection.name, collection.metadata)
}

View file

@ -1,29 +1,6 @@
package synchronizator package synchronizator
// A user representation of a node, this interface should be implemented by the user type StandardNode interface{}
// to provide the ability to parse the database node into a user defined
// struct that fulfills it's requirements.
//
// This interface is compatible with the [NodeClass] interface but it doesn't
// handle the class parameter as it's a static value provided by the
// StandardNode
type StandardNode interface {
// How to transform the struct into a node. It needs to return the class,
// name and a []byte representation of the metadata.
//
// - name: A user friendly name
// - metadata: Arbitrary data. This will be stored as a jsonb in the database
//
ToNode() (string, []byte, error)
// How to transform a node into the struct. This method should modify the
// struct directly as it receives a pointer.
//
// - class: The class of the node, should not be modified to avoid inconsistencies.
// - name: A user friendly name
// - metadata: Arbitrary data. This is stored as a jsonb in the database
FromNode(string, string, []byte) error
}
// A node in the database. // A node in the database.
// It adds some helper methods to easily manage the node. // It adds some helper methods to easily manage the node.
@ -112,19 +89,6 @@ func (node *Node) Delete() error {
return node._conn.DeleteNode(node.Id) return node._conn.DeleteNode(node.Id)
} }
// Allows to retreive the saved information back into the user struct. This
// method will call the [NodeClass.FromNode] of the provided struct.
//
// Example:
//
// data := &Library{}
// if err := node.Unmarshall(data); err != nil {
// println(err)
// }
func (node *Node) Unmarshall(dst StandardNode) error {
return dst.FromNode(node._class, node.name, node.metadata)
}
// Returns the class of the node // Returns the class of the node
func (relationship *Relationship) GetClass() string { func (relationship *Relationship) GetClass() string {
return relationship._class return relationship._class

View file

@ -23,3 +23,32 @@ type Platform struct {
Node // Underlaying node info Node // Underlaying node info
collections []*Collection // Child nodes collections []*Collection // Child nodes
} }
type Fetcher = func(conn *DB, pagination Pagination) ([]*Collection, Pagination, error)
type Pagination struct {
Total int
HasMore bool
Limit int
Offset int
}
var StartPagination = Pagination{
Total: 0,
HasMore: false,
Limit: 10,
Offset: 0,
}
func (platform *Platform) FetchCollections(fetcher Fetcher, start_pagination Pagination) error {
collections, pagination, err := fetcher(platform._conn, start_pagination)
if err != nil {
return err
}
platform.collections = collections
if pagination.HasMore {
return platform.FetchCollections(fetcher, pagination)
}
return nil
}

View file

@ -15,6 +15,7 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"os" "os"
"strings"
"time" "time"
) )
@ -168,23 +169,24 @@ func (conn *DB) Query(sql string, args ...any) (*sql.Rows, error) {
// A collection is only a Node wrapper with some extended functionality to // A collection is only a Node wrapper with some extended functionality to
// manage multiple nodes. For more information see [DB.NewNode] method and the // manage multiple nodes. For more information see [DB.NewNode] method and the
// [Platform] struct. // [Platform] struct.
func (conn *DB) NewPlatform(data StandardNode) (*Platform, error) { func (conn *DB) NewPlatform(name string, metadata []byte) (*Platform, error) {
var platform *Platform var platform *Platform
err := conn.withTx(func(tx *sql.Tx) error { err := conn.withTx(func(tx *sql.Tx) error {
node, err := conn.newNodewithTx(tx, "PLATFORM", data) node, err := conn.newNodewithTx(tx, name, "PLATFORM", metadata)
if err != nil { if err != nil {
return err return err
} }
collection, err := conn.newCollectionwithTx( collection, err := conn.newCollectionwithTx(
tx, tx,
&default_collection{platform_name: node.name}, strings.ToUpper(name)+"_DEFAULT",
nil,
) )
if err != nil { if err != nil {
return err return err
} }
platform := &Platform{ platform = &Platform{
Node: *node, Node: *node,
collections: []*Collection{collection}, collections: []*Collection{collection},
} }
@ -206,12 +208,12 @@ func (conn *DB) NewPlatform(data StandardNode) (*Platform, error) {
// [Collection] struct. // [Collection] struct.
// //
// The operation is ran in a database transaction. // The operation is ran in a database transaction.
func (conn *DB) NewCollection(data StandardNode) (*Collection, error) { func (conn *DB) NewCollection(name string, metadata []byte) (*Collection, error) {
var collection *Collection var collection *Collection
err := conn.withTx(func(tx *sql.Tx) error { err := conn.withTx(func(tx *sql.Tx) error {
var err error var err error
collection, err = conn.newCollectionwithTx(tx, data) collection, err = conn.newCollectionwithTx(tx, name, metadata)
return err return err
}) })
@ -223,8 +225,8 @@ func (conn *DB) NewCollection(data StandardNode) (*Collection, error) {
// A collection is only a Node wrapper with some extended functionality to // A collection is only a Node wrapper with some extended functionality to
// manage multiple nodes. For more information see [DB.NewNode] method and the // manage multiple nodes. For more information see [DB.NewNode] method and the
// [Collection] struct. // [Collection] struct.
func (conn *DB) newCollectionwithTx(tx *sql.Tx, data StandardNode) (*Collection, error) { func (conn *DB) newCollectionwithTx(tx *sql.Tx, name string, metadata []byte) (*Collection, error) {
node, err := conn.newNodewithTx(tx, "COLLECTION", data) node, err := conn.newNodewithTx(tx, name, "COLLECTION", metadata)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -240,12 +242,12 @@ func (conn *DB) newCollectionwithTx(tx *sql.Tx, data StandardNode) (*Collection,
// Creates a new node. // Creates a new node.
// //
// The operation is ran in a database transaction. // The operation is ran in a database transaction.
func (conn *DB) NewNode(class string, data StandardNode) (*Node, error) { func (conn *DB) NewNode(name string, class string, metadata []byte) (*Node, error) {
var node *Node var node *Node
err := conn.withTx(func(tx *sql.Tx) error { err := conn.withTx(func(tx *sql.Tx) error {
var err error var err error
node, err = conn.newNodewithTx(tx, class, data) node, err = conn.newNodewithTx(tx, name, class, metadata)
return err return err
}) })
@ -253,12 +255,12 @@ func (conn *DB) NewNode(class string, data StandardNode) (*Node, error) {
} }
// Creates a new node // Creates a new node
func (conn *DB) newNodewithTx(tx *sql.Tx, class string, data StandardNode) (*Node, error) { func (conn *DB) newNodewithTx(
name, metadata, err := data.ToNode() tx *sql.Tx,
if err != nil { name string,
return nil, err class string,
} metadata []byte,
) (*Node, error) {
node := Node{ node := Node{
_conn: conn, _conn: conn,
_class: class, _class: class,
@ -271,7 +273,7 @@ func (conn *DB) newNodewithTx(tx *sql.Tx, class string, data StandardNode) (*Nod
sql := "INSERT INTO nodes (_class, name, metadata) VALUES ($1, $2, $3) RETURNING id;" sql := "INSERT INTO nodes (_class, name, metadata) VALUES ($1, $2, $3) RETURNING id;"
err = tx.QueryRow(sql, node._class, node.name, metadata).Scan(&node.Id) err := tx.QueryRow(sql, node._class, node.name, metadata).Scan(&node.Id)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -280,12 +282,7 @@ func (conn *DB) newNodewithTx(tx *sql.Tx, class string, data StandardNode) (*Nod
} }
// Updates a node with the provided id and data // Updates a node with the provided id and data
func (conn *DB) UpdateNode(id int64, data StandardNode) (*Node, error) { func (conn *DB) UpdateNode(id int64, name string, metadata []byte) (*Node, error) {
name, metadata, err := data.ToNode()
if err != nil {
return nil, err
}
node := Node{ node := Node{
_conn: conn, _conn: conn,
name: name, name: name,