Something went wrong. Try again.
An ATProto Powered Reputation Network
Something went wrong. Try again.
8.6 kB · 197 lines
Go
at master
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198package appview
import ( "errors" "net/http" "net/url" "strconv" "strings"
"provenance/internal/domain" "provenance/internal/trust")
const openAPIVersion = "3.0.3"
type parameter struct { Name string `json:"name"` In string `json:"in"` Description string `json:"description,omitempty"` Required bool `json:"required"` Schema map[string]any `json:"schema"` Style string `json:"style,omitempty"` Explode bool `json:"explode,omitempty"`}
type endpoint struct { method, path string handler http.HandlerFunc private bool parameters []parameter response map[string]any body map[string]any responseMedia string}
func queryParameter(name, kind string) parameter { return parameter{Name: name, In: "query", Schema: map[string]any{"type": kind}}}
func reputationParams(descriptor trust.Descriptor, path string) []parameter { target := queryParameter("target", "string") target.Required = true target.Description = "Account handle or DID." params := []parameter{target, queryParameter("tag", "string"), queryParameter("mode", "string")} params[1].Schema["maxLength"] = domain.MaxTagLength params[2].Schema["enum"] = []string{string(domain.Default), string(domain.Tagged), string(domain.Fallback)} if path == "/v1/trust" || path == "/v1/reputation" { source := target source.Name = "source" params = append(params, source) } if path != "/v1/trusted" { params = append(params, queryParameter("include_path", "boolean")) } for _, option := range descriptor.Accounts { params = append(params, parameter{Name: option.Name, In: "query", Description: option.Description, Schema: map[string]any{"type": "array", "items": map[string]any{"type": "string"}, "minItems": 1, "maxItems": option.MaxItems}, Style: "form", Explode: true}) } return params}
func aggregateParams(descriptor trust.Descriptor, path string) []parameter { params := reputationParams(descriptor, path) filtered := make([]parameter, 0, len(params)) for _, p := range params { if p.Name == "include_path" { continue } filtered = append(filtered, p) } threshold := queryParameter("threshold", "number") threshold.Schema["minimum"], threshold.Schema["maximum"] = domain.MinScore, domain.MaxScore threshold.Description = "Minimum author trust. Authenticated queries use the private threshold by default. Public queries default to 1." return append(filtered, threshold)}
// The same parameter declarations control query acceptance and OpenAPI output.func validateQuery(values url.Values, params []parameter) (trust.Accounts, error) { allowed := map[string]parameter{} for _, p := range params { allowed[p.Name] = p if p.Required && values.Get(p.Name) == "" { return nil, errors.New("missing parameter: " + p.Name) } } options := trust.Accounts{} for name, entries := range values { p, ok := allowed[name] if !ok { return nil, errors.New("unsupported query parameter: " + name) } if p.Schema["type"] == "array" { if len(entries) == 0 || len(entries) > p.Schema["maxItems"].(int) { return nil, errors.New("too many accounts: " + name) } for _, value := range entries { if value == "" { return nil, errors.New("empty account: " + name) } } options[name] = entries continue } if len(entries) != 1 { return nil, errors.New("parameter must occur once: " + name) } value := entries[0] if p.Schema["type"] == "boolean" && value != "" && value != "true" && value != "false" { return nil, errors.New("invalid boolean: " + name) } if choices, ok := p.Schema["enum"].([]string); ok && value != "" { found := false for _, choice := range choices { if value == choice { found = true break } } if !found { return nil, errors.New("invalid parameter: " + name) } } } return options, nil}
func schemaRef(name string) map[string]any { return map[string]any{"$ref": "#/components/schemas/" + name}}func objectSchema(properties map[string]any, required ...string) map[string]any { return map[string]any{"type": "object", "properties": properties, "required": required}}func arraySchema(items map[string]any) map[string]any { return map[string]any{"type": "array", "items": items}}func scalarSchema(kind string) map[string]any { return map[string]any{"type": kind} }
func apiSchemas() map[string]any { stringSchema := scalarSchema("string") score := map[string]any{"type": "number", "nullable": true, "minimum": domain.Blocked, "maximum": domain.SelfScore} properties := map[string]any{ "status": map[string]any{"type": "string", "enum": []trust.Status{trust.UnknownStatus, trust.SelfStatus, trust.BlockedStatus, trust.CalculatedStatus}}, "score": score, "path": arraySchema(stringSchema), "path_scores": arraySchema(schemaRef("PathScore")), } evaluation := map[string]any{} for name, schema := range properties { evaluation[name] = schema } evaluation["trusted"] = scalarSchema("boolean") comment := map[string]any{"type": "string", "maxLength": domain.MaxCommentLength, "description": "Reference text from the incoming vouch. This text does not affect trust calculations."} return map[string]any{ "Result": objectSchema(properties, "status", "score"), "Reputation": objectSchema(map[string]any{ "score": map[string]any{"type": "number", "description": "Compatibility alias for absolute_score."}, "absolute_score": map[string]any{"type": "number", "description": "Signed sum of qualifying non-block vouch scores."}, "mean_score": map[string]any{"type": "number", "description": "Arithmetic mean of qualifying non-block vouch scores, including zeros. An empty set returns 0."}, }, "score", "absolute_score", "mean_score"), "Evaluation": objectSchema(evaluation, "status", "score", "trusted"), "PathScore": objectSchema(map[string]any{"did": stringSchema, "assertion": map[string]any{"type": "number", "nullable": true, "minimum": domain.Blocked, "maximum": domain.MaxScore}, "score": score, "comment": comment, "list_uri": stringSchema, "source": map[string]any{"type": "string", "enum": []string{domain.Collection, domain.FollowCollection, domain.BlockCollection, domain.ListBlockCollection}}}, "did", "assertion", "score"), "Error": objectSchema(map[string]any{"error": stringSchema}, "error"), "Vouch": objectSchema(map[string]any{"author": stringSchema, "subject": stringSchema, "score": map[string]any{"type": "integer", "minimum": domain.Blocked, "maximum": domain.MaxScore}, "tag": stringSchema, "createdAt": stringSchema, "comment": comment, "source": stringSchema, "record_key": stringSchema}, "author", "subject", "score", "tag"), "Participant": objectSchema(map[string]any{"did": stringSchema, "threshold": scalarSchema("number"), "active": scalarSchema("boolean"), "syncedAt": stringSchema, "bootstrapUs": scalarSchema("integer")}, "did", "threshold", "active", "syncedAt", "bootstrapUs"), }}
func apiDocument(endpoints []endpoint, descriptor trust.Descriptor) map[string]any { paths := map[string]any{} for _, e := range endpoints { media := e.responseMedia if media == "" { media = "application/json" } responses := map[string]any{strconv.Itoa(http.StatusOK): map[string]any{"description": "Success", "content": map[string]any{media: map[string]any{"schema": e.response}}}, "default": map[string]any{"description": "Request failed", "content": map[string]any{"application/json": map[string]any{"schema": schemaRef("Error")}}}} operation := map[string]any{"responses": responses} if len(e.parameters) > 0 { operation["parameters"] = e.parameters } if e.private { operation["security"] = []map[string][]string{{"serviceKey": {}, "identity": {}}} } if e.body != nil { operation["requestBody"] = map[string]any{"required": true, "content": map[string]any{"application/json": map[string]any{"schema": e.body}}} } methods, ok := paths[e.path].(map[string]any) if !ok { methods = map[string]any{} paths[e.path] = methods } methods[strings.ToLower(e.method)] = operation } return map[string]any{"openapi": openAPIVersion, "info": map[string]any{"title": "Provenance appview", "version": "1.0.0"}, "x-provenance-algorithm": descriptor.Name, "paths": paths, "components": map[string]any{"schemas": apiSchemas(), "securitySchemes": map[string]any{ "serviceKey": map[string]any{"type": "http", "scheme": "bearer"}, "identity": map[string]any{"type": "apiKey", "in": "header", "name": IdentityHeader}, }}}}