Skip to content

Commit

Permalink
[HOTFIX] change list backets method from HEAD to POST. And simplify m…
Browse files Browse the repository at this point in the history
…igration on your server, example for languages (#14)

* change contract for list buckets. HEAD -> POST

* add example for go and python

* simplify selfhost
  • Loading branch information
Split174 authored May 25, 2024
1 parent 3351e69 commit 8c8c436
Show file tree
Hide file tree
Showing 11 changed files with 327 additions and 21 deletions.
5 changes: 4 additions & 1 deletion .dockerignore
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
/data/
/data/
.github/
/site/
/examples/
20 changes: 12 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ Allows the user to download their entire KV store as a BoltDB file. The bot will
#### Listing all buckets
<details>
<summary><code>HEAD</code> <code><b>/buckets</b></code></summary>
<summary><code>POST</code> <code><b>/buckets</b></code></summary>
##### Responses
Expand All @@ -118,7 +118,7 @@ Allows the user to download their entire KV store as a BoltDB file. The bot will
##### Example cURL
> ```shell
> curl -X HEAD -H "API-KEY: your_api_key" https://kvrest.dev/api/buckets
> curl -X POST -H "API-KEY: your_api_key" https://kvrest.dev/api/buckets
> ```
</details>
Expand Down Expand Up @@ -238,14 +238,18 @@ Allows the user to download their entire KV store as a BoltDB file. The bot will
## Migrate on your server
> ```shell
> git clone https://github.com/Split174/kvrest.git
> cd kvrest
> docker-compose up -d
> ```
Download db file from bot `/download_db`.
> ```shell
> docker cp YOURKV.DB container_id:/data/YOURKV.DB
> mkdir -p ./kvrest && cp /file/from-bot/file.db ./kvrest/
> cd ./kvrest/
> docker run --rm -it -p 8080:8080 -v ${PWD}:/app/data ghcr.io/split174/kvrest:1.0.2
> ```
Test:
> ```shell
> curl -X POST -H "API-KEY: DB-FILE-FROM-BOT" http://localhost:8080/api/buckets
> ```
## Project Structure
Expand Down
2 changes: 1 addition & 1 deletion api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,7 @@ func listKeys(w http.ResponseWriter, r *http.Request) {
func RegisterRoutes(r *mux.Router) {
r.HandleFunc("/{bucketName}", createBucket).Methods("PUT")
r.HandleFunc("/{bucketName}", deleteBucket).Methods("DELETE")
r.HandleFunc("/buckets", listBuckets).Methods("HEAD")
r.HandleFunc("/buckets", listBuckets).Methods("POST")
r.HandleFunc("/{bucketName}/{key}", setKey).Methods("PUT")
r.HandleFunc("/{bucketName}/{key}", getValue).Methods("GET")
r.HandleFunc("/{bucketName}/{key}", deleteKey).Methods("DELETE")
Expand Down
2 changes: 1 addition & 1 deletion api/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ func TestE2E(t *testing.T) {
}

// List buckets
req = httptest.NewRequest("HEAD", "/buckets", nil)
req = httptest.NewRequest("POST", "/buckets", nil)
req.Header.Set("API-KEY", apiKey)
w = httptest.NewRecorder()
routers.ServeHTTP(w, req)
Expand Down
5 changes: 5 additions & 0 deletions examples/golang/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
## Golang example

1. Change you api key ```apiKey := "CHANGE-ME"```
2. run ```go run ./examples/golang/main.go```

189 changes: 189 additions & 0 deletions examples/golang/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
package main

import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
)

type KVRestAPI struct {
APIKey string
BaseURL string
Client *http.Client
}

type BucketListResponse struct {
Buckets []string `json:"buckets"`
}

type KeyListResponse struct {
Keys []string `json:"keys"`
}

func NewKVRestAPI(apiKey string) *KVRestAPI {
return &KVRestAPI{
APIKey: apiKey,
BaseURL: "https://kvrest.dev/api",
Client: &http.Client{},
}
}

func (api *KVRestAPI) makeRequest(method, endpoint string, body interface{}) ([]byte, error) {
url := fmt.Sprintf("%s%s", api.BaseURL, endpoint)
var reqBody []byte

if body != nil {
var err error
reqBody, err = json.Marshal(body)
if err != nil {
return nil, err
}
}

req, err := http.NewRequest(method, url, bytes.NewBuffer(reqBody))
if err != nil {
return nil, err
}

req.Header.Set("API-KEY", api.APIKey)
req.Header.Set("Content-Type", "application/json")

resp, err := api.Client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()

respBody, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}

if resp.StatusCode < 200 || resp.StatusCode >= 300 {
return nil, fmt.Errorf("API request failed with status: %s, response: %s", resp.Status, string(respBody))
}

return respBody, nil
}

func (api *KVRestAPI) CreateBucket(bucketName string) error {
_, err := api.makeRequest("PUT", "/"+bucketName, nil)
return err
}

func (api *KVRestAPI) DeleteBucket(bucketName string) error {
_, err := api.makeRequest("DELETE", "/"+bucketName, nil)
return err
}

func (api *KVRestAPI) ListBuckets() ([]string, error) {
respBody, err := api.makeRequest("POST", "/buckets", nil)
if err != nil {
return nil, err
}

var bucketResponse BucketListResponse
err = json.Unmarshal(respBody, &bucketResponse)
if err != nil {
return nil, err
}

return bucketResponse.Buckets, nil
}

func (api *KVRestAPI) CreateOrUpdateKeyValue(bucketName, key string, value interface{}) error {
_, err := api.makeRequest("PUT", fmt.Sprintf("/%s/%s", bucketName, key), value)
return err
}

func (api *KVRestAPI) GetValue(bucketName, key string, target interface{}) error {
respBody, err := api.makeRequest("GET", fmt.Sprintf("/%s/%s", bucketName, key), nil)
if err != nil {
return err
}

err = json.Unmarshal(respBody, target)
return err
}

func (api *KVRestAPI) DeleteKeyValue(bucketName, key string) error {
_, err := api.makeRequest("DELETE", fmt.Sprintf("/%s/%s", bucketName, key), nil)
return err
}

func (api *KVRestAPI) ListKeys(bucketName string) ([]string, error) {
respBody, err := api.makeRequest("GET", "/"+bucketName, nil)
if err != nil {
return nil, err
}

// Assuming the response is a JSON array of keys
var keysResponse KeyListResponse
err = json.Unmarshal(respBody, &keysResponse)
if err != nil {
return nil, err
}

return keysResponse.Keys, nil
}

func main() {
apiKey := "CHANGE-ME" // Replace with your actual API key
api := NewKVRestAPI(apiKey)

bucketName := "my-test-bucket-go"
key := "my-key"
value := map[string]string{"message": "Hello from Go!"}

// Create a bucket
err := api.CreateBucket(bucketName)
if err != nil {
panic(err)
}
fmt.Printf("Bucket '%s' created successfully.\n", bucketName)

// Create a key-value pair
err = api.CreateOrUpdateKeyValue(bucketName, key, value)
if err != nil {
panic(err)
}
fmt.Printf("Key-value pair created/updated for key '%s'.\n", key)

// Retrieve the value
var retrievedValue map[string]string
err = api.GetValue(bucketName, key, &retrievedValue)
if err != nil {
panic(err)
}
fmt.Printf("Retrieved value for key '%s': %+v\n", key, retrievedValue)

// List all buckets
buckets, err := api.ListBuckets()
if err != nil {
panic(err)
}
fmt.Printf("All buckets: %+v\n", buckets)

// List keys in the bucket
keys, err := api.ListKeys(bucketName)
if err != nil {
panic(err)
}
fmt.Printf("Keys in bucket '%s': %+v\n", bucketName, keys)

// Delete the key-value pair
err = api.DeleteKeyValue(bucketName, key)
if err != nil {
panic(err)
}
fmt.Printf("Key-value pair with key '%s' deleted.\n", key)

// Delete the bucket
err = api.DeleteBucket(bucketName)
if err != nil {
panic(err)
}
fmt.Printf("Bucket '%s' deleted.\n", bucketName)
}
4 changes: 4 additions & 0 deletions examples/python/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
## Python example

1. Change you api key ```api_key = "CHANGE_ME"```
2. run ```python ./examples/python/main.py```
101 changes: 101 additions & 0 deletions examples/python/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import requests

class KVRestAPI:
def __init__(self, api_key, base_url="https://kvrest.dev/api"):
self.api_key = api_key
self.base_url = base_url
self.headers = {"API-KEY": self.api_key, "Content-Type": "application/json"}

def _make_request(self, method, endpoint, json=None):
url = f"{self.base_url}{endpoint}"
response = requests.request(method, url, headers=self.headers, json=json)

if response.status_code not in (200, 201):
response.raise_for_status() # Raise an exception for bad status codes

try:
if response.text: # Check if response has content
return response.json()
else:
return {} # Or return None, depending on how you want to handle it
except ValueError as e:
# Log the error and the raw response for debugging
print(f"Error parsing JSON response: {e}, Raw Response: {response.text}")
return None # Or raise the exception again if needed

def create_bucket(self, bucket_name):
"""Create a new bucket."""
endpoint = f"/{bucket_name}"
self._make_request("PUT", endpoint)

def delete_bucket(self, bucket_name):
"""Delete an existing bucket."""
endpoint = f"/{bucket_name}"
self._make_request("DELETE", endpoint)

def list_buckets(self):
"""List all buckets."""
endpoint = "/buckets"
return self._make_request("POST", endpoint)

def create_or_update_key_value(self, bucket_name, key, value):
"""Create or update a key-value pair."""
endpoint = f"/{bucket_name}/{key}"
self._make_request("PUT", endpoint, json=value)

def get_value(self, bucket_name, key):
"""Retrieve a value for a key."""
endpoint = f"/{bucket_name}/{key}"
return self._make_request("GET", endpoint)

def delete_key_value(self, bucket_name, key):
"""Delete a key-value pair."""
endpoint = f"/{bucket_name}/{key}"
self._make_request("DELETE", endpoint)

def list_keys(self, bucket_name):
"""List all keys in a bucket."""
endpoint = f"/{bucket_name}"
return self._make_request("GET", endpoint)


if __name__ == "__main__":
api_key = "CHANGE_ME" # Replace with your actual API key
api = KVRestAPI(api_key)

# Example usage:
try:
bucket_name = "my-test-bucket"
key = "my-key"
value = {"message": "Hello from the API!"}

# Create a bucket
api.create_bucket(bucket_name)
print(f"Bucket '{bucket_name}' created successfully.")

# Create a key-value pair
api.create_or_update_key_value(bucket_name, key, value)
print(f"Key-value pair created/updated for key '{key}'.")

# Retrieve the value
retrieved_value = api.get_value(bucket_name, key)
print(f"Retrieved value for key '{key}': {retrieved_value}")

# List all buckets
buckets = api.list_buckets()
print(f"All buckets: {buckets}")

# List keys in the bucket
keys = api.list_keys(bucket_name)
print(f"Keys in bucket '{bucket_name}': {keys}")

# Delete the key-value pair
api.delete_key_value(bucket_name, key)
print(f"Key-value pair with key '{key}' deleted.")

# Delete the bucket
api.delete_bucket(bucket_name)
print(f"Bucket '{bucket_name}' deleted.")

except requests.exceptions.RequestException as e:
print(f"An error occurred: {e}")
4 changes: 3 additions & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ func main() {
}

// Start the Telegram bot in a separate goroutine
go telegram_bot.StartBot()
if os.Getenv("BOT_TOKEN") != "" {
go telegram_bot.StartBot()
}

// Define the main router
router := mux.NewRouter()
Expand Down
Loading

0 comments on commit 8c8c436

Please sign in to comment.