mirror of
https://github.com/hanzoai/base.git
synced 2026-10-21 03:41:50 +00:00
graph.New(app, router) renders the Base's collections as GraphQL and answers every field with a request on the Base's own router, made with the caller's headers, so the list and view rules, tenancy, rate limits and expand's view checks are the REST path's and there is no second authorization. zip's engine executes it: a host mounts g.Schema and g.Resolve with zip.MountGraphFor, so zip is bumped to v1.37.43. - A type per base and view collection, named from the collection (people: BasePerson, person, people, peopleGroupBy, createPerson, updatePerson, upsertPerson, deletePerson), with its fields, relations as objects, the records pointing at it as connections (opportunities_via_company) and its history. - A filter input per collection whose operators per field type are the where clause's: each is one leaf of core.CompileWhere, so and, or, not, @me, relative dates, back-relations and inSegment mean what they mean there, every value bound. - orderBy, cursor connections paged through Base's pages, a totalCount asked only when selected, and aggregate and GroupBy from the aggregate route. - Bounds: 100 a page, four relations deep refused before any request, 50 requests a root field, and zip's 10,000 resolutions. graph_test drives it through zip against a CRM Base: every filter type, paging both ways, the figures, the writes as REST reads them back, the refusals in REST's own words (a rule, a superuser-only collection, another org's token), the budget, and type names on every object. tests.IAM signs IAM tokens for any test, and apis' helper uses it.
83 lines
2.8 KiB
Go
83 lines
2.8 KiB
Go
package graph
|
|
|
|
import "github.com/hanzoai/base/core"
|
|
|
|
// composite is the shape of a composite field's value: the object it answers,
|
|
// the parts a filter, a sort and an input name, in the order its field names
|
|
// them (graph_test holds the two to each other).
|
|
type composite struct {
|
|
typ string // FullName: BaseFullName, BaseFullNameFilter, …
|
|
parts []part
|
|
write bool // takes a value in an Input; an actor is Base's to stamp
|
|
text bool // contains reads its text parts (the field is a core.TextParts)
|
|
}
|
|
|
|
// part is one named part of a composite value.
|
|
type part struct {
|
|
name string
|
|
kind kind // kText or kNumber; a list part is kJSON (only contains)
|
|
list bool // a list of values: answered whole, filtered by contains
|
|
elem *object // the object a list part holds, nil for a list of strings
|
|
}
|
|
|
|
// object is an object inside a composite part: a phone, a link.
|
|
type object struct {
|
|
typ string
|
|
fields []string // every one a String
|
|
}
|
|
|
|
var (
|
|
phone = &object{typ: "Phone", fields: []string{"number", "countryCode", "callingCode"}}
|
|
link = &object{typ: "Link", fields: []string{"url", "label"}}
|
|
)
|
|
|
|
func text(name string) part { return part{name: name, kind: kText} }
|
|
func number(name string) part { return part{name: name, kind: kNumber} }
|
|
|
|
// composites is every composite field type, by the field type it shapes.
|
|
var composites = map[string]*composite{
|
|
core.FieldTypeCurrency: {typ: "Currency", write: true, parts: []part{
|
|
number("amountMicros"), text("currencyCode"),
|
|
}},
|
|
core.FieldTypeEmails: {typ: "Emails", write: true, text: true, parts: []part{
|
|
text("primaryEmail"), {name: "additionalEmails", kind: kJSON, list: true},
|
|
}},
|
|
core.FieldTypePhones: {typ: "Phones", write: true, text: true, parts: []part{
|
|
text("primaryPhoneNumber"), text("primaryPhoneCountryCode"), text("primaryPhoneCallingCode"),
|
|
{name: "additionalPhones", kind: kJSON, list: true, elem: phone},
|
|
}},
|
|
core.FieldTypeLinks: {typ: "Links", write: true, text: true, parts: []part{
|
|
text("primaryLinkUrl"), text("primaryLinkLabel"),
|
|
{name: "secondaryLinks", kind: kJSON, list: true, elem: link},
|
|
}},
|
|
core.FieldTypeAddress: {typ: "Address", write: true, text: true, parts: []part{
|
|
text("addressStreet1"), text("addressStreet2"), text("addressCity"),
|
|
text("addressState"), text("addressPostcode"), text("addressCountry"),
|
|
number("addressLat"), number("addressLng"),
|
|
}},
|
|
core.FieldTypeFullName: {typ: "FullName", write: true, text: true, parts: []part{
|
|
text("firstName"), text("lastName"),
|
|
}},
|
|
core.FieldTypeActor: {typ: "Actor", text: true, parts: []part{
|
|
text("source"), text("id"), text("name"),
|
|
}},
|
|
}
|
|
|
|
func (c *composite) part(name string) *part {
|
|
for i := range c.parts {
|
|
if c.parts[i].name == name {
|
|
return &c.parts[i]
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *composite) sortable() bool {
|
|
for _, p := range c.parts {
|
|
if !p.list {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|