From 4297b522c71ff08b8f38c3f15ff346849e47d8cc Mon Sep 17 00:00:00 2001 From: Kleissner Date: Fri, 3 Sep 2021 02:33:28 +0200 Subject: [PATCH] New API functions /profile/list, /profile/read, /profile/write --- API.go | 93 ++++++++++++++++++++++++++++++++++++++++++++ API.md | 119 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++- go.mod | 2 +- go.sum | 4 +- 4 files changed, 214 insertions(+), 4 deletions(-) diff --git a/API.go b/API.go index e1d44b8..49346cc 100644 --- a/API.go +++ b/API.go @@ -44,6 +44,9 @@ func startAPI() { 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)) @@ -429,6 +432,96 @@ func apiBlockchainSelfListFile(w http.ResponseWriter, r *http.Request) { 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 { diff --git a/API.md b/API.md index b66b078..bf21e75 100644 --- a/API.md +++ b/API.md @@ -44,6 +44,10 @@ These are the functions provided by the API: /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. @@ -236,7 +240,7 @@ type apiBlockchainBlockStatus struct { } ``` -Example POST request data to `http://127.0.0.1:112/blockchain/self/add/file`: +Example POST request to `http://127.0.0.1:112/blockchain/self/add/file`: ```json { @@ -313,3 +317,116 @@ Example output: "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 f60a5ef..65346ea 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/PeernetOfficial/Cmd go 1.16 require ( - github.com/PeernetOfficial/core v0.0.0-20210828113738-22f21d187a38 + github.com/PeernetOfficial/core v0.0.0-20210903002426-928c16c492cf 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 diff --git a/go.sum b/go.sum index bfa1d07..6bcc6c4 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -github.com/PeernetOfficial/core v0.0.0-20210828113738-22f21d187a38 h1:WSqqgVAG5pekBrlrj4wpkAfEN/8GgpWDIQQc3/AbCVE= -github.com/PeernetOfficial/core v0.0.0-20210828113738-22f21d187a38/go.mod h1:GCG3qjbCPEY+/PGIO76Dj/Dp6JvpMYy0B6T+CbmSk1A= +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/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=