Refactor API into the core package.

This commit is contained in:
Kleissner
2021-09-10 13:45:34 +02:00
parent 4297b522c7
commit 2d2297bccc
4 changed files with 14 additions and 962 deletions

545
API.go
View File

@@ -8,18 +8,10 @@ package main
import ( import (
"bytes" "bytes"
"crypto/tls"
"encoding/hex"
"encoding/json"
"errors"
"log"
"net/http" "net/http"
"strconv"
"time" "time"
"github.com/PeernetOfficial/core" "github.com/PeernetOfficial/core/webapi"
"github.com/google/uuid"
"github.com/gorilla/mux"
"github.com/gorilla/websocket" "github.com/gorilla/websocket"
) )
@@ -28,55 +20,8 @@ func startAPI() {
return return
} }
// by default allow all requests webapi.Start(config.APIListen, config.APIUseSSL, config.APICertificateFile, config.APICertificateKey, parseDuration(config.APITimeoutRead), parseDuration(config.APITimeoutWrite))
wsUpgrader.CheckOrigin = func(r *http.Request) bool { webapi.Router.HandleFunc("/console", apiConsole).Methods("GET")
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)
}
}
} }
// parseDuration is the same as time.ParseDuration without returning an error. Valid units are ms, s, m, h. For example "10s". // 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 return
} }
func apiEncodeJSON(w http.ResponseWriter, r *http.Request, data interface{}) (err error) { /*
w.Header().Set("Content-Type", "application/json") apiConsole provides a websocket to send/receive internal commands
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
Request: GET /console Request: GET /console
Result: 200 with JSON structure apiResponsePeerSelf Result: 200 with JSON structure apiResponsePeerSelf
*/ */
func apiConsole(w http.ResponseWriter, r *http.Request) { 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 { if err != nil {
// May happen if request is simple HTTP request. // May happen if request is simple HTTP request.
return return
@@ -220,403 +87,3 @@ func apiConsole(w http.ResponseWriter, r *http.Request) {
bufferR.Write(message) 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
}

418
API.md
View File

@@ -1,96 +1,13 @@
# API # 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. This code extends the API by these function:
## 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:
``` ```
/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 /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 ## 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 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
}
```

4
go.mod
View File

@@ -3,9 +3,7 @@ module github.com/PeernetOfficial/Cmd
go 1.16 go 1.16
require ( 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/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 github.com/gorilla/websocket v1.4.3-0.20210424162022-e8629af678b7
) )

9
go.sum
View File

@@ -1,5 +1,5 @@
github.com/PeernetOfficial/core v0.0.0-20210903002426-928c16c492cf h1:Hp+32gIAaXZBMPxVmHVY9JhHF9deLmmrnxtruWObP8s= github.com/PeernetOfficial/core v0.0.0-20210910114007-19e29b3a910a h1:1ebpb3L6PGLI8lX5xkeXKOxoATOoedMUamAnl0elJ04=
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/go.mod h1:c2AGQxju2HdM8mrsgy6nFX3mW2TGs0/00kggbBqLB+o=
github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII= 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 h1:FqlR8VR7uCbJdfUob916tPM+idpKgeESDXOA1K0DK4w=
github.com/akrylysov/pogreb v0.10.1/go.mod h1:pNs6QmpQ1UlTJKDezuRWmaqkgUE2TuU0YTWyqJZ7+lI= 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/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 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 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.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
github.com/gorilla/mux v1.8.1-0.20200912192056-d07530f46e1e/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= 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 h1:L89uC9ATI61/V2eNgZYtQHyjjyjEplemB+aky4HdyzQ=
github.com/gorilla/websocket v1.4.3-0.20210424162022-e8629af678b7/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= 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= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=