From 2d2297bccc6cdcc774228d421121f89399eb9dd7 Mon Sep 17 00:00:00 2001 From: Kleissner Date: Fri, 10 Sep 2021 13:45:34 +0200 Subject: [PATCH] Refactor API into the core package. --- API.go | 545 +-------------------------------------------------------- API.md | 418 +------------------------------------------ go.mod | 4 +- go.sum | 9 +- 4 files changed, 14 insertions(+), 962 deletions(-) diff --git a/API.go b/API.go index 49346cc..9536228 100644 --- a/API.go +++ b/API.go @@ -8,18 +8,10 @@ package main import ( "bytes" - "crypto/tls" - "encoding/hex" - "encoding/json" - "errors" - "log" "net/http" - "strconv" "time" - "github.com/PeernetOfficial/core" - "github.com/google/uuid" - "github.com/gorilla/mux" + "github.com/PeernetOfficial/core/webapi" "github.com/gorilla/websocket" ) @@ -28,55 +20,8 @@ func startAPI() { return } - // by default allow all requests - wsUpgrader.CheckOrigin = func(r *http.Request) bool { - return true - } - - router := mux.NewRouter() - - router.HandleFunc("/test", apiTest).Methods("GET") - router.HandleFunc("/status", apiStatus).Methods("GET") - router.HandleFunc("/peer/self", apiPeerSelf).Methods("GET") - router.HandleFunc("/console", apiConsole).Methods("GET") - router.HandleFunc("/blockchain/self/header", apiBlockchainSelfHeader).Methods("GET") - router.HandleFunc("/blockchain/self/append", apiBlockchainSelfAppend).Methods("POST") - router.HandleFunc("/blockchain/self/read", apiBlockchainSelfRead).Methods("GET") - router.HandleFunc("/blockchain/self/add/file", apiBlockchainSelfAddFile).Methods("POST") - router.HandleFunc("/blockchain/self/list/file", apiBlockchainSelfListFile).Methods("GET") - router.HandleFunc("/profile/list", apiProfileList).Methods("GET") - router.HandleFunc("/profile/read", apiProfileRead).Methods("GET") - router.HandleFunc("/profile/write", apiProfileWrite).Methods("POST") - - for _, listen := range config.APIListen { - go startWebServer(listen, config.APIUseSSL, config.APICertificateFile, config.APICertificateKey, router, "API", parseDuration(config.APITimeoutRead), parseDuration(config.APITimeoutWrite)) - } -} - -// startWebServer starts a web-server with given parameters and logs the status. If may block forever and only returns if there is an error. -func startWebServer(WebListen string, UseSSL bool, CertificateFile, CertificateKey string, Handler http.Handler, Info string, ReadTimeout, WriteTimeout time.Duration) { - tlsConfig := &tls.Config{MinVersion: tls.VersionTLS12} // for security reasons disable TLS 1.0/1.1 - - server := &http.Server{ - Addr: WebListen, - Handler: Handler, - ReadTimeout: ReadTimeout, // ReadTimeout is the maximum duration for reading the entire request, including the body. - WriteTimeout: WriteTimeout, // WriteTimeout is the maximum duration before timing out writes of the response. This includes processing time and is therefore the max time any HTTP function may take. - //IdleTimeout: IdleTimeout, // IdleTimeout is the maximum amount of time to wait for the next request when keep-alives are enabled. - TLSConfig: tlsConfig, - } - - if UseSSL { - // HTTPS - if err := server.ListenAndServeTLS(CertificateFile, CertificateKey); err != nil { - log.Printf("Error listening on '%s': %v\n", WebListen, err) - } - } else { - // HTTP - if err := server.ListenAndServe(); err != nil { - log.Printf("Error listening on '%s': %v\n", WebListen, err) - } - } + webapi.Start(config.APIListen, config.APIUseSSL, config.APICertificateFile, config.APICertificateKey, parseDuration(config.APITimeoutRead), parseDuration(config.APITimeoutWrite)) + webapi.Router.HandleFunc("/console", apiConsole).Methods("GET") } // parseDuration is the same as time.ParseDuration without returning an error. Valid units are ms, s, m, h. For example "10s". @@ -85,91 +30,13 @@ func parseDuration(input string) (result time.Duration) { return } -func apiEncodeJSON(w http.ResponseWriter, r *http.Request, data interface{}) (err error) { - w.Header().Set("Content-Type", "application/json") - - err = json.NewEncoder(w).Encode(data) - if err != nil { - log.Printf("Error writing data for route '%s': %v\n", r.URL.Path, err) - } - - return err -} - -// apiDecodeJSON decodes input JSON data server side sent either via GET or POST. It does not limit the maximum amount to read. -// In case of error it will automatically send an error to the client. -func apiDecodeJSON(w http.ResponseWriter, r *http.Request, data interface{}) (err error) { - if r.Body == nil { - http.Error(w, "", http.StatusBadRequest) - return errors.New("no data") - } - - err = json.NewDecoder(r.Body).Decode(data) - if err != nil { - http.Error(w, "", http.StatusBadRequest) - return err - } - - return nil -} - -func apiTest(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusOK) - w.Write([]byte("ok")) -} - -type apiResponseStatus struct { - Status int `json:"status"` // Status code: 0 = Ok. - IsConnected bool `json:"isconnected"` // Whether connected to Peernet. - CountPeerList int `json:"countpeerlist"` // Count of peers in the peer list. Note that this contains peers that are considered inactive, but have not yet been removed from the list. - CountNetwork int `json:"countnetwork"` // Count of total peers in the network. - // This is usually a higher number than CountPeerList, which just represents the current number of connected peers. - // The CountNetwork number is going to be queried from root peers which may or may not have a limited view. -} - -/* apiStatus returns the current connectivity status to the network -Request: GET /status -Result: 200 with JSON structure Status -*/ -func apiStatus(w http.ResponseWriter, r *http.Request) { - status := apiResponseStatus{Status: 0, CountPeerList: core.PeerlistCount()} - status.CountNetwork = status.CountPeerList // For now always same as CountPeerList, until native Statistics message to root peers is available. - - // Connected: If at leat 2 peers. - // This metric needs to be improved in the future, as root peers never disconnect. - // Instead, the core should keep a count of "active peers". - status.IsConnected = status.CountPeerList >= 2 - - apiEncodeJSON(w, r, status) -} - -type apiResponsePeerSelf struct { - PeerID string `json:"peerid"` // Peer ID. This is derived from the public in compressed form. - NodeID string `json:"nodeid"` // Node ID. This is the blake3 hash of the peer ID and used in the DHT. -} - -/* apiPeerSelf provides information about the self peer details -Request: GET /peer/self -Result: 200 with JSON structure apiResponsePeerSelf -*/ -func apiPeerSelf(w http.ResponseWriter, r *http.Request) { - response := apiResponsePeerSelf{} - response.NodeID = hex.EncodeToString(core.SelfNodeID()) - - _, publicKey := core.ExportPrivateKey() - response.PeerID = hex.EncodeToString(publicKey.SerializeCompressed()) - - apiEncodeJSON(w, r, response) -} - -var wsUpgrader = websocket.Upgrader{} // use default options - -/* apiConsole provides a websocket to send/receive internal commands +/* +apiConsole provides a websocket to send/receive internal commands Request: GET /console Result: 200 with JSON structure apiResponsePeerSelf */ func apiConsole(w http.ResponseWriter, r *http.Request) { - c, err := wsUpgrader.Upgrade(w, r, nil) + c, err := webapi.WSUpgrader.Upgrade(w, r, nil) if err != nil { // May happen if request is simple HTTP request. return @@ -220,403 +87,3 @@ func apiConsole(w http.ResponseWriter, r *http.Request) { bufferR.Write(message) } } - -type apiBlockchainHeader struct { - PeerID string `json:"peerid"` // Peer ID hex encoded. - Version uint64 `json:"version"` // Current version number of the blockchain. - Height uint64 `json:"height"` // Height of the blockchain (number of blocks). If 0, no data exists. -} - -/* -apiBlockchainSelfHeader returns the current blockchain header information - -Request: GET /blockchain/self/header -Result: 200 with JSON structure apiResponsePeerSelf -*/ -func apiBlockchainSelfHeader(w http.ResponseWriter, r *http.Request) { - publicKey, height, version := core.UserBlockchainHeader() - - apiEncodeJSON(w, r, apiBlockchainHeader{Version: version, Height: height, PeerID: hex.EncodeToString(publicKey.SerializeCompressed())}) -} - -type apiBlockRecordRaw struct { - Type uint8 `json:"type"` // Record Type. See core.RecordTypeX. - Data []byte `json:"data"` // Data according to the type. -} - -// apiBlockchainBlockRaw contains a raw block of the blockchain via API -type apiBlockchainBlockRaw struct { - Records []apiBlockRecordRaw `json:"records"` // Block records in encoded raw format. -} - -type apiBlockchainBlockStatus struct { - Status int `json:"status"` // Status: 0 = Success, 1 = Error invalid data - Height uint64 `json:"height"` // New height of the blockchain (number of blocks). -} - -/* -apiBlockchainSelfAppend appends a block to the blockchain. This is a low-level function for already encoded blocks. -Do not use this function. Adding invalid data to the blockchain may corrupt it which might result in blacklisting by other peers. - -Request: POST /blockchain/self/append with JSON structure apiBlockchainBlockRaw -Response: 200 with JSON structure apiBlockchainBlockStatus -*/ -func apiBlockchainSelfAppend(w http.ResponseWriter, r *http.Request) { - var input apiBlockchainBlockRaw - if err := apiDecodeJSON(w, r, &input); err != nil { - return - } - - var records []core.BlockRecordRaw - - for _, record := range input.Records { - records = append(records, core.BlockRecordRaw{Type: record.Type, Data: record.Data}) - } - - newHeight, status := core.UserBlockchainAppend(records) - - apiEncodeJSON(w, r, apiBlockchainBlockStatus{Status: status, Height: newHeight}) -} - -type apiBlockchainBlock struct { - Status int `json:"status"` // Status: 0 = Success, 1 = Error block not found, 2 = Error block encoding (indicates that the blockchain is corrupt) - PeerID string `json:"peerid"` // Peer ID hex encoded. - LastBlockHash []byte `json:"lastblockhash"` // Hash of the last block. Blake3. - BlockchainVersion uint64 `json:"blockchainversion"` // Blockchain version - Number uint64 `json:"blocknumber"` // Block number - RecordsRaw []apiBlockRecordRaw `json:"recordsraw"` // Records raw. Successfully decoded records are parsed into the below fields. - RecordsDecoded []interface{} `json:"recordsdecoded"` // Records decoded. The encoding for each record depends on its type. -} - -// apiBlockRecordProfile contains end-user information. Any data is treated as untrusted and unverified by default. -type apiBlockRecordProfile struct { - Fields []apiBlockRecordProfileField `json:"fields"` // All fields - Blobs []apiBlockRecordProfileBlob `json:"blobs"` // Blobs -} - -// apiBlockRecordProfileField contains a single information about the end user. The data is always UTF8 text encoded. -// Note that all profile data is arbitrary and shall be considered untrusted and unverified. -// To establish trust, the user must load Certificates into the blockchain that validate certain data. -type apiBlockRecordProfileField struct { - Type uint16 `json:"type"` // See ProfileFieldX constants. - Text string `json:"text"` // The data -} - -// apiBlockRecordProfileBlob is similar to apiBlockRecordProfileField but contains binary objects instead of text. -// It can be used for example to store a profile picture on the blockchain. -type apiBlockRecordProfileBlob struct { - Type uint16 `json:"type"` // See ProfileBlobX constants. - Data []byte `json:"data"` // The data -} - -// apiFileMetadata describes recognized metadata that is decoded into text. -type apiFileMetadata struct { - Type uint16 `json:"type"` // See core.TagTypeX constants. - Name string `json:"name"` // User friendly name of the tag. Use the Type fields to identify the metadata as this name may change. - Value string `json:"value"` // Text value of the tag. -} - -// apiFileTagRaw describes a raw tag. This allows to support future metadata that is not yet defined in the core library. -type apiFileTagRaw struct { - Type uint16 `json:"type"` // See core.TagTypeX constants. - Data []byte `json:"data"` // Data -} - -// apiBlockRecordFile is the metadata of a file published on the blockchain -type apiBlockRecordFile struct { - ID uuid.UUID `json:"id"` // Unique ID. - Hash []byte `json:"hash"` // Blake3 hash of the file data - Type uint8 `json:"type"` // Type (low-level) - Format uint16 `json:"format"` // Format (high-level) - Size uint64 `json:"size"` // Size of the file - Folder string `json:"folder"` // Folder, optional - Name string `json:"name"` // Name of the file - Description string `json:"description"` // Description. This is expected to be multiline and contain hashtags! - Metadata []apiFileMetadata `json:"metadata"` // Metadata. These are decoded tags. - TagsRaw []apiFileTagRaw `json:"tagsraw"` // All tags encoded that were not recognized as metadata. - - // The following known tags from the core library are decoded into metadata or other fields in above structure; everything else is a raw tag: - // TagTypeName, TagTypeFolder, TagTypeDescription, TagTypeDateCreated - // The caller can specify its own metadata fields and fill the TagsRaw structure when creating a new file. It will be returned when reading the files' data. -} - -/* -apiBlockchainSelfRead reads a block and returns the decoded information. - -Request: GET /blockchain/self/read?block=[number] -Result: 200 with JSON structure apiBlockchainBlock -*/ -func apiBlockchainSelfRead(w http.ResponseWriter, r *http.Request) { - r.ParseForm() - blockN, err := strconv.Atoi(r.Form.Get("block")) - if err != nil || blockN < 0 { - http.Error(w, "", http.StatusBadRequest) - return - } - - block, status, _ := core.UserBlockchainRead(uint64(blockN)) - result := apiBlockchainBlock{Status: status} - - if status == 0 { - for _, record := range block.RecordsRaw { - result.RecordsRaw = append(result.RecordsRaw, apiBlockRecordRaw{Type: record.Type, Data: record.Data}) - } - - result.PeerID = hex.EncodeToString(block.OwnerPublicKey.SerializeCompressed()) - - for _, record := range block.RecordsDecoded { - switch v := record.(type) { - case core.BlockRecordFile: - result.RecordsDecoded = append(result.RecordsDecoded, blockRecordFileToAPI(v)) - - case core.BlockRecordProfile: - result.RecordsDecoded = append(result.RecordsDecoded, blockRecordProfileToAPI(v)) - - } - } - } - - apiEncodeJSON(w, r, result) -} - -// apiBlockAddFiles contains the file metadata to add to the blockchain -type apiBlockAddFiles struct { - Files []apiBlockRecordFile `json:"files"` // List of files - Status int `json:"status"` // Status of the operation, only used when this structure is returned from the API. -} - -/* -apiBlockchainSelfAddFile adds a file with the provided information to the blockchain. - -Request: POST /blockchain/self/add/file with JSON structure apiBlockAddFiles -Response: 200 with JSON structure apiBlockchainBlockStatus -*/ -func apiBlockchainSelfAddFile(w http.ResponseWriter, r *http.Request) { - var input apiBlockAddFiles - if err := apiDecodeJSON(w, r, &input); err != nil { - return - } - - var filesAdd []core.BlockRecordFile - - for _, file := range input.Files { - if file.ID == uuid.Nil { // if the ID is not provided by the caller, set it - file.ID = uuid.New() - } - - filesAdd = append(filesAdd, blockRecordFileFromAPI(file)) - } - - newHeight, status := core.UserBlockchainAddFiles(filesAdd) - - apiEncodeJSON(w, r, apiBlockchainBlockStatus{Status: status, Height: newHeight}) -} - -/* -apiBlockchainSelfListFile lists all files stored on the blockchain. - -Request: GET /blockchain/self/list/file -Response: 200 with JSON structure apiBlockAddFiles -*/ -func apiBlockchainSelfListFile(w http.ResponseWriter, r *http.Request) { - files, status := core.UserBlockchainListFiles() - - var result apiBlockAddFiles - - for _, file := range files { - result.Files = append(result.Files, blockRecordFileToAPI(file)) - } - - result.Status = status - - apiEncodeJSON(w, r, result) -} - -// --- Profile API --- - -// apiProfileData contains profile metadata stored on the blockchain. Any data is treated as untrusted and unverified by default. -type apiProfileData struct { - Fields []apiBlockRecordProfileField `json:"fields"` // All fields - Blobs []apiBlockRecordProfileBlob `json:"blobs"` // All blobs - Status int `json:"status"` // Status of the operation, only used when this structure is returned from the API. See core.BlockchainStatusX. -} - -/* -apiProfileList lists all users profile fields and blobs. - -Request: GET /profile/list -Response: 200 with JSON structure apiProfileData -*/ -func apiProfileList(w http.ResponseWriter, r *http.Request) { - fields, blobs, status := core.UserProfileList() - - result := apiProfileData{Status: status} - for n := range fields { - result.Fields = append(result.Fields, apiBlockRecordProfileField{Type: fields[n].Type, Text: fields[n].Text}) - } - for n := range blobs { - result.Blobs = append(result.Blobs, apiBlockRecordProfileBlob{Type: blobs[n].Type, Data: blobs[n].Data}) - } - - apiEncodeJSON(w, r, result) -} - -/* -apiProfileRead reads a specific users profile field or blob. -For the index see core.ProfileFieldX and core.ProfileBlobX constants. - -Request: GET /profile/read?field=[index] or &blob=[index] -Response: 200 with JSON structure apiProfileData -*/ -func apiProfileRead(w http.ResponseWriter, r *http.Request) { - r.ParseForm() - fieldN, err1 := strconv.Atoi(r.Form.Get("field")) - blobN, err2 := strconv.Atoi(r.Form.Get("blob")) - - if (err1 != nil && err2 != nil) || (err1 == nil && fieldN < 0) || (err2 == nil && blobN < 0) { - http.Error(w, "", http.StatusBadRequest) - return - } - - var result apiProfileData - - if err1 == nil { - var text string - if text, result.Status = core.UserProfileReadField(uint16(fieldN)); result.Status == core.BlockchainStatusOK { - result.Fields = append(result.Fields, apiBlockRecordProfileField{Type: uint16(fieldN), Text: text}) - } - } else { - var data []byte - if data, result.Status = core.UserProfileReadBlob(uint16(blobN)); result.Status == core.BlockchainStatusOK { - result.Blobs = append(result.Blobs, apiBlockRecordProfileBlob{Type: uint16(blobN), Data: data}) - } - } - - apiEncodeJSON(w, r, result) -} - -/* -apiProfileWrite writes a specific users profile field or blob. -For the index see core.ProfileFieldX and core.ProfileBlobX constants. - -Request: POST /profile/write with JSON structure apiProfileData -Response: 200 with JSON structure apiBlockchainBlockStatus -*/ -func apiProfileWrite(w http.ResponseWriter, r *http.Request) { - var input apiProfileData - if err := apiDecodeJSON(w, r, &input); err != nil { - return - } - - var profile core.BlockRecordProfile - - for n := range input.Fields { - profile.Fields = append(profile.Fields, core.BlockRecordProfileField{Type: input.Fields[n].Type, Text: input.Fields[n].Text}) - } - for n := range input.Blobs { - profile.Blobs = append(profile.Blobs, core.BlockRecordProfileBlob{Type: input.Blobs[n].Type, Data: input.Blobs[n].Data}) - } - - newHeight, status := core.UserProfileWrite(profile) - - apiEncodeJSON(w, r, apiBlockchainBlockStatus{Status: status, Height: newHeight}) -} - -// --- conversion from core to API data --- - -func isFileTagKnownMetadata(tagType uint16) bool { - switch tagType { - case core.TagTypeName, core.TagTypeFolder, core.TagTypeDescription, core.TagTypeDateCreated, core.TagTypeDateShared: - return true - - default: - return false - } -} - -func blockRecordFileToAPI(input core.BlockRecordFile) (output apiBlockRecordFile) { - output = apiBlockRecordFile{ID: input.ID, Hash: input.Hash, Type: input.Type, Format: input.Format, Size: input.Size, TagsRaw: []apiFileTagRaw{}, Metadata: []apiFileMetadata{}} - - // Copy all raw tags. This allows the API caller to decode any future tags that are not defined yet. - for n := range input.TagsRaw { - if !isFileTagKnownMetadata(input.TagsRaw[n].Type) { - output.TagsRaw = append(output.TagsRaw, apiFileTagRaw{Type: input.TagsRaw[n].Type, Data: input.TagsRaw[n].Data}) - } - } - - // Try to decode tags into known metadata. - for _, tagDecoded := range input.TagsDecoded { - switch v := tagDecoded.(type) { - case core.FileTagName: - output.Name = v.Name - - case core.FileTagFolder: - output.Folder = v.Name - - case core.FileTagDescription: - output.Description = v.Description - - case core.FileTagDateCreated: - output.Metadata = append(output.Metadata, apiFileMetadata{Type: core.TagTypeDateCreated, Name: "Date Created", Value: v.Date.Format(dateFormat)}) - - case core.FileTagDateShared: - output.Metadata = append(output.Metadata, apiFileMetadata{Type: core.TagTypeDateShared, Name: "Date Shared", Value: v.Date.Format(dateFormat)}) - - } - } - - return output -} - -func blockRecordFileFromAPI(input apiBlockRecordFile) (output core.BlockRecordFile) { - output = core.BlockRecordFile{ID: input.ID, Hash: input.Hash, Type: input.Type, Format: input.Format, Size: input.Size} - - if input.Name != "" { - output.TagsDecoded = append(output.TagsDecoded, core.FileTagName{Name: input.Name}) - } - if input.Folder != "" { - output.TagsDecoded = append(output.TagsDecoded, core.FileTagFolder{Name: input.Folder}) - } - if input.Description != "" { - output.TagsDecoded = append(output.TagsDecoded, core.FileTagDescription{Description: input.Description}) - } - - for _, tag := range input.Metadata { - switch tag.Type { - case core.TagTypeDateCreated: - if dateF, err := time.Parse(dateFormat, tag.Value); err == nil { - output.TagsDecoded = append(output.TagsDecoded, core.FileTagDateCreated{Date: dateF}) - } - } - } - - for n := range input.TagsRaw { - if !isFileTagKnownMetadata(input.TagsRaw[n].Type) { - output.TagsRaw = append(output.TagsRaw, core.BlockRecordFileTag{Type: input.TagsRaw[n].Type, Data: input.TagsRaw[n].Data}) - } - } - - return output -} - -func blockRecordProfileToAPI(input core.BlockRecordProfile) (output apiBlockRecordProfile) { - for n := range input.Fields { - output.Fields = append(output.Fields, apiBlockRecordProfileField{Type: input.Fields[n].Type, Text: input.Fields[n].Text}) - } - for n := range input.Blobs { - output.Blobs = append(output.Blobs, apiBlockRecordProfileBlob{Type: input.Blobs[n].Type, Data: input.Blobs[n].Data}) - } - - return output -} - -func blockRecordProfileFromAPI(input apiBlockRecordProfile) (output core.BlockRecordProfile) { - for n := range input.Fields { - output.Fields = append(output.Fields, core.BlockRecordProfileField{Type: input.Fields[n].Type, Text: input.Fields[n].Text}) - } - for n := range input.Blobs { - output.Blobs = append(output.Blobs, core.BlockRecordProfileBlob{Type: input.Blobs[n].Type, Data: input.Blobs[n].Data}) - } - - return output -} diff --git a/API.md b/API.md index bf21e75..1a9a847 100644 --- a/API.md +++ b/API.md @@ -1,96 +1,13 @@ # API -The API is intended to be used by a local full standalone client that provides a frontend for the end-user. The API allows to use Peernet effectively; it provides functions to share files, search for content and download files. +Please see https://github.com/PeernetOfficial/core/webapi for the API specification. -Note: This API code is likely to be moved into the core soon. - -## Use considerations - -It is not intended to be directly used in a legacy web browser, and shall not be exposed to the internet. It shall only run on a loopback IP such as `127.0.0.1` or `::1`. Special HTTP headers (including the Access-Control headers) are intentionally not set. - -The API is currently unauthenticated and intentionally provides direct access to the users blockchain. - -### Notes - -The API is still in development and endpoints are subject to change. The API should be currently only used for debugging and early phase development purposes. - -## Configuration - -The configuration file (default `Config.yaml`) contains settings for the API. To listen on `http://127.0.0.1:112/` add this line: - -```yaml -APIListen: ["127.0.0.1:112"] -``` - -## Overview - -These are the functions provided by the API: +This code extends the API by these function: ``` -/status Provides current connectivity status to the network -/peer/self Provides information about the self peer details -/share/list List all files and directories that are shared -/share/file Share a file via the peers blockchain - -/search Search Peernet for a file based on keywords or hash -/download/start Download a file based on a hash -/download/status Get the status of a download - -/status/ws Starts a websocket to receive updates on operations immediately (push instead of pull) /console Console provides a websocket to send/receive internal commands - -/blockchain/self/header Header of the blockchain -/blockchain/self/append Append a block to the blockchain -/blockchain/self/read Read a block of the blockchain -/blockchain/self/add/file Add file to the blockchain -/blockchain/self/list/file List all files stored on the blockchain - -/profile/list List all users profile fields and blobs -/profile/read Reads a specific users profile field or blob -/profile/write Writes a specific users profile field or blob ``` -The `/share` functions are providing high-level functionality to work with files. The `/blockchain` functions provide low-level functionality which is typically not needed. - -## Status - -This function informs about the current connection status of the client to the network. Additional fields will be added in the future. - -``` -Request: GET /status - -Response: 200 with JSON structure apiResponseStatus -``` - -```go -type apiResponseStatus struct { - Status int `json:"status"` // Status code: 0 = Ok. - IsConnected bool `json:"isconnected"` // Whether connected to Peernet. - CountPeerList int `json:"countpeerlist"` // Count of peers in the peer list. Note that this contains peers that are considered inactive, but have not yet been removed from the list. - CountNetwork int `json:"countnetwork"` // Count of total peers in the network. - // This is usually a higher number than CountPeerList, which just represents the current number of connected peers. - // The CountNetwork number is going to be queried from root peers which may or may not have a limited view into the network. -} -``` - -## Self Information - -This function returns information about the current peer. - -``` -Request: GET /peer/self - -Response: 200 with JSON structure apiResponsePeerSelf -``` - -The peer and node IDs are encoded as hex encoded strings. - -```go -type apiResponsePeerSelf struct { - PeerID string `json:"peerid"` // Peer ID. This is derived from the public in compressed form. - NodeID string `json:"nodeid"` // Node ID. This is the blake3 hash of the peer ID and used in the DHT. -} -``` ## Console @@ -99,334 +16,3 @@ The `/console` websocket allows to execute internal commands. This should be onl ``` Request: ws://127.0.0.1:112/console ``` - -## Blockchain Self Header - -This function returns information about the current peer. It is not required that a peer has a blockchain. If no data is shared, there are no blocks. The blockchain does not formally have a header as each block has the same structure. - -``` -Request: GET /blockchain/self/header - -Response: 200 with JSON structure apiBlockchainHeader -``` - -```go -type apiBlockchainHeader struct { - PeerID string `json:"peerid"` // Peer ID hex encoded. - Version uint64 `json:"version"` // Current version number of the blockchain. - Height uint64 `json:"height"` // Height of the blockchain (number of blocks). If 0, no data exists. -} -``` - -## Blockchain Append Block - -This appends a block to the blockchain. This is a low-level function for already encoded blocks. -Do not use this function. Adding invalid data to the blockchain may corrupt it which subsequently might result in blacklisting by other peers. - -``` -Request: POST /blockchain/self/append with JSON structure apiBlockchainBlockRaw - -Response: 200 with JSON structure apiBlockchainBlockStatus -``` - -```go -type apiBlockRecordRaw struct { - Type uint8 `json:"type"` // Record Type. See core.RecordTypeX. - Data []byte `json:"data"` // Data according to the type. -} - -type apiBlockchainBlockRaw struct { - Records []apiBlockRecordRaw `json:"records"` // Block records in encoded raw format. -} - -type apiBlockchainBlockStatus struct { - Status int `json:"status"` // Status: 0 = Success, 1 = Error invalid data - Version uint64 `json:"version"` // Current version number of the blockchain. - Height uint64 `json:"height"` // Height of the blockchain (number of blocks). -} -``` - -## Blockchain Read Block - -This reads a block of the current peer. - -``` -Request: GET /blockchain/self/read?block=[number] - -Response: 200 with JSON structure apiBlockchainBlock -``` - -```go -type apiBlockchainBlock struct { - Status int `json:"status"` // Status: 0 = Success, 1 = Error block not found, 2 = Error block encoding (indicates that the blockchain is corrupt) - PeerID string `json:"peerid"` // Peer ID hex encoded. - LastBlockHash []byte `json:"lastblockhash"` // Hash of the last block. Blake3. - BlockchainVersion uint64 `json:"blockchainversion"` // Blockchain version - Number uint64 `json:"blocknumber"` // Block number - RecordsRaw []apiBlockRecordRaw `json:"recordsraw"` // Records raw. Successfully decoded records are parsed into the below fields. - RecordsDecoded []interface{} `json:"recordsdecoded"` // Records decoded. The encoding for each record depends on its type. -} -``` - -The array `RecordsDecoded` will contain any present record of the following: -* Profile records, see `apiBlockRecordProfile` -* File records, see `apiBlockRecordFile` - -```go -type apiBlockRecordProfile struct { - Fields []apiBlockRecordProfileField `json:"fields"` // All fields - Blobs []apiBlockRecordProfileBlob `json:"blobs"` // Blobs -} - -type apiBlockRecordProfileField struct { - Type uint16 `json:"type"` // See ProfileFieldX constants. - Text string `json:"text"` // The data -} - -type apiBlockRecordProfileBlob struct { - Type uint16 `json:"type"` // See ProfileBlobX constants. - Data []byte `json:"data"` // The data -} -``` - -```go -type apiBlockRecordFile struct { - ID uuid.UUID `json:"id"` // Unique ID. - Hash []byte `json:"hash"` // Blake3 hash of the file data - Type uint8 `json:"type"` // Type (low-level) - Format uint16 `json:"format"` // Format (high-level) - Size uint64 `json:"size"` // Size of the file - Folder string `json:"folder"` // Folder, optional - Name string `json:"name"` // Name of the file - Description string `json:"description"` // Description. This is expected to be multiline and contain hashtags! - Metadata []apiFileMetadata `json:"metadata"` // Metadata. These are decoded tags. - TagsRaw []apiFileTagRaw `json:"tagsraw"` // All tags encoded that were not recognized as metadata. - - // The following known tags from the core library are decoded into metadata or other fields in above structure; everything else is a raw tag: - // TagTypeName, TagTypeFolder, TagTypeDescription, TagTypeDateCreated - // The caller can specify its own metadata fields and fill the TagsRaw structure when creating a new file. It will be returned when reading the files' data. -} - -type apiFileMetadata struct { - Type uint16 `json:"type"` // See core.TagTypeX constants. - Name string `json:"name"` // User friendly name of the tag. Use the Type fields to identify the metadata as this name may change. - Value string `json:"value"` // Text value of the tag. -} - -type apiFileTagRaw struct { - Type uint16 `json:"type"` // See core.TagTypeX constants. - Data []byte `json:"data"` // Data -} -``` - -## Blockchain Add File - -This adds a file with the provided information to the blockchain. - -``` -Request: POST /blockchain/self/add/file with JSON structure apiBlockAddFiles - -Response: 200 with JSON structure apiBlockchainBlockStatus -``` - -```go -type apiBlockAddFiles struct { - Files []apiBlockRecordFile `json:"files"` -} - -type apiBlockchainBlockStatus struct { - Status int `json:"status"` // Status: 0 = Success, 1 = Error invalid data - Height uint64 `json:"height"` // New height of the blockchain (number of blocks). -} -``` - -Example POST request to `http://127.0.0.1:112/blockchain/self/add/file`: - -```json -{ - "files": [{ - "id": "236de31d-f402-4389-bdd1-56463abdc309", - "hash": "aFad3zRACbk44dsOw5sVGxYmz+Rqh8ORDcGJNqIz+Ss=", - "type": 1, - "format": 10, - "size": 4, - "name": "Test.txt", - "folder": "sample directory/sub folder", - "description": "", - "metadata": [], - "tagsraw": [] - }] -} -``` - -Another example to create a new file but with a new arbitrary tag with type number 100 set to "test" and setting the metadata field "Date Created" (which is type 2 = `core.TagTypeDateCreated`): - -```json -{ - "files": [{ - "id": "bc32cbae-011d-4f0b-80a8-281ca93692e7", - "hash": "aFad3zRACbk44dsOw5sVGxYmz+Rqh8ORDcGJNqIz+Ss=", - "type": 1, - "format": 10, - "size": 4, - "name": "Test.txt", - "folder": "sample directory/sub folder", - "description": "Example description\nThis can be any text #newfile #2021.", - "metadata": [{ - "type": 2, - "value": "2021-08-28 00:00:00" - }], - "tagsraw": [{ - "type": 100, - "data": "dGVzdA==" - }] - }] -} -``` - -## Blockchain List Files - -This lists all files stored on the blockchain. - -``` -Request: GET /blockchain/self/list/file - -Response: 200 with JSON structure apiBlockAddFiles -``` - -Example output: - -```json -{ - "files": [{ - "id": "a59b6465-fe8c-4a61-9fcc-fe37cf711fd4", - "hash": "aFad3zRACbk44dsOw5sVGxYmz+Rqh8ORDcGJNqIz+Ss=", - "type": 1, - "format": 10, - "size": 4, - "folder": "sample directory/sub folder", - "name": "Test.txt", - "description": "", - "metadata": [{ - "type": 4, - "name": "Date Shared", - "value": "2021-08-27 16:59:13" - }], - "tagsraw": [] - }], - "status": 0 -} -``` - -## Profile List - -``` -Request: GET /profile/list - -Response: 200 with JSON structure apiProfileData -``` - -```go -type apiProfileData struct { - Fields []apiBlockRecordProfileField `json:"fields"` // All fields - Blobs []apiBlockRecordProfileBlob `json:"blobs"` // All blobs - Status int `json:"status"` // Status of the operation, only used when this structure is returned from the API. See core.BlockchainStatusX. -} - -const ( - BlockchainStatusOK = 0 // No problems in the blockchain detected. - BlockchainStatusBlockNotFound = 1 // Missing block in the blockchain. - BlockchainStatusCorruptBlock = 2 // Error block encoding - BlockchainStatusCorruptBlockRecord = 3 // Error block record encoding - BlockchainStatusDataNotFound = 4 // Requested data not available in the blockchain -) -``` - -Example request: `http://127.0.0.1:112/profile/list` - -Example response: - -```json -{ - "fields": [{ - "type": 0, - "text": "Test Username 2022" - }, { - "type": 1, - "text": "test@example.com" - }], - "blobs": null, - "status": 0 -} -``` - -## Profile Read - -This reads a specific users' profile field or blob. For the index see the `ProfileFieldX` and `ProfileBlobX` constants. - -``` -Request: GET /profile/read?field=[index] or &blob=[index] - -Response: 200 with JSON structure apiProfileData -``` - -```go -// ProfileFieldX constants define well known profile information -const ( - ProfileFieldName = 0 // Arbitrary username - ProfileFieldEmail = 1 // Email address - ProfileFieldWebsite = 2 // Website address - ProfileFieldTwitter = 3 // Twitter account without the @ - ProfileFieldYouTube = 4 // YouTube channel URL - ProfileFieldAddress = 5 // Physical address -) - -// ProfileBlobX constants define well known blobs -// Pictures should be in JPEG or PNG format. -const ( - ProfileBlobPicture = 0 // Profile picture, unspecified size -) -``` - -Example request to read the users username: `http://127.0.0.1:112/profile/read?field=0` - -Example response: - -```json -{ - "fields": [{ - "type": 0, - "text": "Test Username 2022" - }], - "blobs": null, - "status": 0 -} -``` - -## Profile Write - -``` -Request: POST /profile/write with JSON structure apiProfileData - -Response: 200 with JSON structure apiBlockchainBlockStatus -``` - -Example POST request to `http://127.0.0.1:112/profile/write`: - -```json -{ - "fields": [{ - "type": 0, - "text": "Test Username 2021" - }] -} -``` - -Example response: - -```json -{ - "status": 0, - "height": 1 -} -``` diff --git a/go.mod b/go.mod index 65346ea..e4b5d56 100644 --- a/go.mod +++ b/go.mod @@ -3,9 +3,7 @@ module github.com/PeernetOfficial/Cmd go 1.16 require ( - github.com/PeernetOfficial/core v0.0.0-20210903002426-928c16c492cf + github.com/PeernetOfficial/core v0.0.0-20210910114007-19e29b3a910a github.com/btcsuite/btcd v0.22.0-beta.0.20210625194946-86a17263b0ff - github.com/google/uuid v1.3.0 - github.com/gorilla/mux v1.8.1-0.20200912192056-d07530f46e1e github.com/gorilla/websocket v1.4.3-0.20210424162022-e8629af678b7 ) diff --git a/go.sum b/go.sum index 6bcc6c4..19ba261 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -github.com/PeernetOfficial/core v0.0.0-20210903002426-928c16c492cf h1:Hp+32gIAaXZBMPxVmHVY9JhHF9deLmmrnxtruWObP8s= -github.com/PeernetOfficial/core v0.0.0-20210903002426-928c16c492cf/go.mod h1:GCG3qjbCPEY+/PGIO76Dj/Dp6JvpMYy0B6T+CbmSk1A= +github.com/PeernetOfficial/core v0.0.0-20210910114007-19e29b3a910a h1:1ebpb3L6PGLI8lX5xkeXKOxoATOoedMUamAnl0elJ04= +github.com/PeernetOfficial/core v0.0.0-20210910114007-19e29b3a910a/go.mod h1:c2AGQxju2HdM8mrsgy6nFX3mW2TGs0/00kggbBqLB+o= github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= github.com/akrylysov/pogreb v0.10.1 h1:FqlR8VR7uCbJdfUob916tPM+idpKgeESDXOA1K0DK4w= github.com/akrylysov/pogreb v0.10.1/go.mod h1:pNs6QmpQ1UlTJKDezuRWmaqkgUE2TuU0YTWyqJZ7+lI= @@ -26,8 +26,9 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/gorilla/mux v1.8.1-0.20200912192056-d07530f46e1e h1:8+CIGbDW29nl9niCoMrpF8+ACFz3rLRpIjuqnx4rNKg= -github.com/gorilla/mux v1.8.1-0.20200912192056-d07530f46e1e/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= +github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.4.3-0.20210424162022-e8629af678b7 h1:L89uC9ATI61/V2eNgZYtQHyjjyjEplemB+aky4HdyzQ= github.com/gorilla/websocket v1.4.3-0.20210424162022-e8629af678b7/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=