mirror of
https://github.com/PeernetOfficial/Cmd.git
synced 2026-07-17 02:47:52 +01:00
Refactor API into the core package.
This commit is contained in:
545
API.go
545
API.go
@@ -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
418
API.md
@@ -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
4
go.mod
@@ -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
9
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-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=
|
||||||
|
|||||||
Reference in New Issue
Block a user