diff --git a/client.go b/client.go index 36fb2f2..369f439 100644 --- a/client.go +++ b/client.go @@ -2,9 +2,8 @@ package appwrite import ( "encoding/json" - "io/ioutil" + "io/ioutil" "net/http" - "net/url" "strings" ) @@ -60,58 +59,47 @@ func (clt *Client) Call(method string, path string, headers map[string]interface // Allow self signed requests } - urlPath := clt.endpoint + path isGet := strings.ToUpper(method) == "GET" - var reqBody *strings.Reader - if !isGet { - frm := url.Values{} + urlPath := clt.endpoint + path + + var http_req *http.Request + var reqBody []byte + if isGet { + http_req, _ = http.NewRequest(strings.ToUpper(method), urlPath, strings.NewReader(string(reqBody))) + q := http_req.URL.Query() for key, val := range params { - frm.Add(key, ToString(val)) + q.Add(key, ToString(val)) } - reqBody = strings.NewReader(frm.Encode()) - } - - // Create and modify HTTP request before sending - req, err := http.NewRequest(method, urlPath, reqBody) - if err != nil { - return nil, err + http_req.URL.RawQuery = q.Encode() + } else { + reqBody, _ = json.Marshal(params) + http_req, _ = http.NewRequest(strings.ToUpper(method), urlPath, strings.NewReader(string(reqBody))) } - // Set Client headers + // set client headers for key, val := range clt.headers { - req.Header.Set(key, ToString(val)) + http_req.Header.Set(key, val) } - // Set Custom headers + // set custom headers for key, val := range headers { - req.Header.Set(key, ToString(val)) + http_req.Header.Set(key, ToString(val)) } - if isGet { - q := req.URL.Query() - for key, val := range params { - q.Add(key, ToString(val)) - } - req.URL.RawQuery = q.Encode() - } - - // Make request - response, err := clt.client.Do(req) + // submit the request + resp, err := clt.client.Do(http_req) if err != nil { return nil, err } + defer resp.Body.Close() - // Handle response - defer response.Body.Close() - - responseData, err := ioutil.ReadAll(response.Body) + // read the response data + respData, err := ioutil.ReadAll(resp.Body) if err != nil { return nil, err } - - var jsonResponse map[string]interface{} - json.Unmarshal(responseData, &jsonResponse) - - return jsonResponse, nil + var jsonResp map[string]interface{} + json.Unmarshal(respData, &jsonResp) + return jsonResp, nil } diff --git a/database.go b/database.go index 7e5f686..77d634e 100644 --- a/database.go +++ b/database.go @@ -1,6 +1,9 @@ package appwrite import ( + "fmt" + "reflect" + "regexp" "strings" ) @@ -9,12 +12,12 @@ type Database struct { client Client } -func NewDatabase(clt Client) Database { - service := Database{ +func NewDatabase(clt Client) Database { + service := Database{ client: clt, } - return service + return service } // ListCollections get a list of all the user collections. You can use the @@ -25,27 +28,36 @@ func (srv *Database) ListCollections(Search string, Limit int, Offset int, Order path := "/database/collections" params := map[string]interface{}{ - "search": Search, - "limit": Limit, - "offset": Offset, + "search": Search, + "limit": Limit, + "offset": Offset, "orderType": OrderType, } - return srv.client.Call("GET", path, nil, params) + header := map[string]interface{}{ + "content-type": "application/json", + } + + return srv.client.Call("GET", path, header, params) } // CreateCollection create a new Collection. -func (srv *Database) CreateCollection(Name string, Read []interface{}, Write []interface{}, Rules []interface{}) (map[string]interface{}, error) { +func (srv *Database) CreateCollection(CollectionId string, Name string, Permission string, Read []string, Write []string) (map[string]interface{}, error) { path := "/database/collections" params := map[string]interface{}{ - "name": Name, - "read": Read, - "write": Write, - "rules": Rules, + "collectionId": CollectionId, + "name": Name, + "read": Read, + "write": Write, + "permission": Permission, } - return srv.client.Call("POST", path, nil, params) + header := map[string]interface{}{ + "content-type": "application/json", + } + + return srv.client.Call("POST", path, header, params) } // GetCollection get collection by its unique ID. This endpoint response @@ -54,8 +66,7 @@ func (srv *Database) GetCollection(CollectionId string) (map[string]interface{}, r := strings.NewReplacer("{collectionId}", CollectionId) path := r.Replace("/database/collections/{collectionId}") - params := map[string]interface{}{ - } + params := map[string]interface{}{} return srv.client.Call("GET", path, nil, params) } @@ -66,8 +77,8 @@ func (srv *Database) UpdateCollection(CollectionId string, Name string, Read []i path := r.Replace("/database/collections/{collectionId}") params := map[string]interface{}{ - "name": Name, - "read": Read, + "name": Name, + "read": Read, "write": Write, "rules": Rules, } @@ -81,8 +92,7 @@ func (srv *Database) DeleteCollection(CollectionId string) (map[string]interface r := strings.NewReplacer("{collectionId}", CollectionId) path := r.Replace("/database/collections/{collectionId}") - params := map[string]interface{}{ - } + params := map[string]interface{}{} return srv.client.Call("DELETE", path, nil, params) } @@ -96,31 +106,31 @@ func (srv *Database) ListDocuments(CollectionId string, Filters []interface{}, O path := r.Replace("/database/collections/{collectionId}/documents") params := map[string]interface{}{ - "filters": Filters, - "offset": Offset, - "limit": Limit, + "filters": Filters, + "offset": Offset, + "limit": Limit, "order-field": OrderField, - "order-type": OrderType, - "order-cast": OrderCast, - "search": Search, - "first": First, - "last": Last, + "order-type": OrderType, + "order-cast": OrderCast, + "search": Search, + "first": First, + "last": Last, } return srv.client.Call("GET", path, nil, params) } // CreateDocument create a new Document. -func (srv *Database) CreateDocument(CollectionId string, Data object, Read []interface{}, Write []interface{}, ParentDocument string, ParentProperty string, ParentPropertyType string) (map[string]interface{}, error) { +func (srv *Database) CreateDocument(CollectionId string, Data interface{}, Read []interface{}, Write []interface{}, ParentDocument string, ParentProperty string, ParentPropertyType string) (map[string]interface{}, error) { r := strings.NewReplacer("{collectionId}", CollectionId) path := r.Replace("/database/collections/{collectionId}/documents") params := map[string]interface{}{ - "data": Data, - "read": Read, - "write": Write, - "parentDocument": ParentDocument, - "parentProperty": ParentProperty, + "data": Data, + "read": Read, + "write": Write, + "parentDocument": ParentDocument, + "parentProperty": ParentProperty, "parentPropertyType": ParentPropertyType, } @@ -133,20 +143,19 @@ func (srv *Database) GetDocument(CollectionId string, DocumentId string) (map[st r := strings.NewReplacer("{collectionId}", CollectionId, "{documentId}", DocumentId) path := r.Replace("/database/collections/{collectionId}/documents/{documentId}") - params := map[string]interface{}{ - } + params := map[string]interface{}{} return srv.client.Call("GET", path, nil, params) } // UpdateDocument -func (srv *Database) UpdateDocument(CollectionId string, DocumentId string, Data object, Read []interface{}, Write []interface{}) (map[string]interface{}, error) { +func (srv *Database) UpdateDocument(CollectionId string, DocumentId string, Data interface{}, Read []interface{}, Write []interface{}) (map[string]interface{}, error) { r := strings.NewReplacer("{collectionId}", CollectionId, "{documentId}", DocumentId) path := r.Replace("/database/collections/{collectionId}/documents/{documentId}") params := map[string]interface{}{ - "data": Data, - "read": Read, + "data": Data, + "read": Read, "write": Write, } @@ -160,8 +169,193 @@ func (srv *Database) DeleteDocument(CollectionId string, DocumentId string) (map r := strings.NewReplacer("{collectionId}", CollectionId, "{documentId}", DocumentId) path := r.Replace("/database/collections/{collectionId}/documents/{documentId}") + params := map[string]interface{}{} + + return srv.client.Call("DELETE", path, nil, params) +} + +func (srv *Database) CreateStringAttribute(CollectionId string, key string, size int, required bool, xdefault Optional[string], isArray Optional[bool]) (map[string]interface{}, error) { + Type := "string" + path := "/database/collections/{collectionId}/attributes/{type}" + r := strings.NewReplacer("{collectionId}", CollectionId, "{type}", Type) + path = r.Replace(path) + params := map[string]interface{}{ + "key": key, + "required": required, + "size": size, } + // optionals + if xdefault.Specified { + params["default"] = xdefault.Value + } + if isArray.Specified { + params["array"] = isArray.Value + } + headers := map[string]interface{}{ + "content-type": "application/json", + } + return srv.client.Call("POST", path, headers, params) +} + +func (srv *Database) CreateEmailAttribute(CollectionId string, key string, required bool, xdefault Optional[string], isArray Optional[bool]) (map[string]interface{}, error) { + Type := "email" + path := "/database/collections/{collectionId}/attributes/{type}" + r := strings.NewReplacer("{collectionId}", CollectionId, "{type}", Type) + path = r.Replace(path) + + params := map[string]interface{}{ + "key": key, + "required": required, + } + // optionals + if xdefault.Specified { + params["default"] = xdefault.Value + } + if isArray.Specified { + params["arrray"] = isArray.Value + } + + headers := map[string]interface{}{ + "content-type": "application/json", + } + return srv.client.Call("POST", path, headers, params) +} + +func (srv *Database) CreateEnumAttribute(CollectionId string, key string, elements []string, required bool, xdefault, isArray Optional[string]) (map[string]interface{}, error) { + Type := "enum" + path := "/database/collections/{collectionId}/attributes/{type}" + r := strings.NewReplacer("{collectionId}", CollectionId, "{type}", Type) + path = r.Replace(path) + + params := map[string]interface{}{ + "key": key, + "required": required, + "elements": elements, + } + // optionals + if xdefault.Specified { + if contains(elements, xdefault.Value) { + params["default"] = xdefault.Value + } else { + fmt.Println("❌ The default value is not contained in the array of elementes") + return nil, nil + } + } + if isArray.Specified { + params["array"] = isArray.Value + } + headers := map[string]interface{}{ + "content-type": "application/json", + } + return srv.client.Call("POST", path, headers, params) +} + +func (srv *Database) CreateIpAttribute(CollectionId string, key string, required bool, xdefault, isArray Optional[bool]) (map[string]interface{}, error) { + Type := "ip" + path := "/database/collections/{collectionId}/attributes/{type}" + r := strings.NewReplacer("{collectionId}", CollectionId, "{type}", Type) + path = r.Replace(path) + + params := map[string]interface{}{ + "key": key, + "required": required, + } + if xdefault.Specified { + params["default"] = xdefault.Value + } + if isArray.Specified { + params["array"] = isArray.Value + } + headers := map[string]interface{}{ + "content-type": "application/json", + } + return srv.client.Call("POST", path, headers, params) +} + +// Url Layout url://something +func (srv *Database) CreateUrlAttribute(CollectionId string, key string, required bool, xdefault Optional[string], isArray Optional[bool]) (map[string]interface{}, error) { + Type := "url" + path := "/database/collections/{collectionId}/attributes/{type}" + r := strings.NewReplacer("{collectionId}", CollectionId, "{type}", Type) + path = r.Replace(path) + + params := map[string]interface{}{ + "key": key, + "required": required, + } + // optionals + if xdefault.Specified { + params["default"] = xdefault.Value + } + if isArray.Specified { + params["array"] = isArray.Value + } + headers := map[string]interface{}{ + "content-type": "application/json", + } + return srv.client.Call("POST", path, headers, params) +} + +func (srv *Database) CreateIntegerAttribute(CollectionId string, key string, required bool, min, max, xdefault Optional[int], isArray Optional[bool]) (map[string]interface{}, error) { + return createNumAttribute(srv, CollectionId, key, required, isArray, min, max, xdefault) +} + +func (srv *Database) CreateFloatAttribute(CollectionId string, key string, required bool, min, max, xdefault Optional[float64], isArray Optional[bool]) (map[string]interface{}, error) { + return createNumAttribute(srv, CollectionId, key, required, isArray, min, max, xdefault) +} + +func createNumAttribute[T any](srv *Database, CollectionId string, key string, required bool, isArray Optional[bool], min, max, xdefault Optional[T]) (map[string]interface{}, error) { + reg, _ := regexp.Compile(`\D+`) + Type := reg.FindString(reflect.TypeOf(min.Value).String()) + if Type == "int" { + Type = "integer" + } + path := "/database/collections/{collectionId}/attributes/{type}" + r := strings.NewReplacer("{collectionId}", CollectionId, "{type}", Type) + path = r.Replace(path) + params := map[string]interface{}{ + "key": key, + "required": required, + } + // optionals + if min.Specified { + params["min"] = min.Value + } + if max.Specified { + params["max"] = max.Value + } + if xdefault.Specified { + params["default"] = xdefault.Value + } + if isArray.Specified { + params["array"] = isArray.Value + } + headers := map[string]interface{}{ + "content-type": "application/json", + } + return srv.client.Call("POST", path, headers, params) +} + +func (srv *Database) CreateBooleanAttribute(CollectionId string, key string, required bool, isArray, xdefault Optional[bool]) (map[string]interface{}, error) { + Type := "boolean" + path := "/database/collections/{collectionId}/attributes/{type}" + r := strings.NewReplacer("{collectionId}", CollectionId, "{type}", Type) + path = r.Replace(path) + + params := map[string]interface{}{ + "key": key, + "required": required, + } + if xdefault.Specified { + params["default"] = xdefault.Value + } + if isArray.Specified { + params["array"] = isArray.Value + } + headers := map[string]interface{}{ + "content-type": "application/json", + } + return srv.client.Call("POST", path, headers, params) - return srv.client.Call("DELETE", path, nil, params) } diff --git a/docs/examples/Database/create-attribute.md b/docs/examples/Database/create-attribute.md new file mode 100644 index 0000000..3f44ed7 --- /dev/null +++ b/docs/examples/Database/create-attribute.md @@ -0,0 +1,24 @@ +Usage of CreateAttribute methods + +Attributes that are available: +- string +- Enum +- Email +- Ip +- Url +- Integer +- Float +- Boolean + +Note: for the optional parameters, there is new type created (since GO doesn't have optional or default arguments). The Optional type is defined as a struct with fields: + +- Value -> this contains the value +- Specified -> wether or not it is specified (defaults to false) + +Optional[T]{} -> replace T with the desired type (string, int, ...) +empty optional = will see as "optional value not set" + +Example Use of the CreateAttribute functions: + +CreateStringAttribute("unique()", "StringName", 15, false, Optional[string]{Value: "MyDefaultString",Specified: false}, Optional[bool]{}) +This wil create a String attribute with name "StringName" that is max. 15 characters long and has default value "MyDefaultString". Since the isArray is an empty Optional[bool], the isArray bool will be false -> this is not an array diff --git a/docs/examples/Database/create-collection.md b/docs/examples/Database/create-collection.md index 947ce79..5335ac2 100644 --- a/docs/examples/Database/create-collection.md +++ b/docs/examples/Database/create-collection.md @@ -8,18 +8,12 @@ import ( func main() { var client := appwrite.Client{} + client.setEndpoint("url") // url to your appwrite instance client.SetProject("5df5acd0d48c2") // Your project ID client.SetKey("919c2d18fb5d4...a2ae413da83346ad2") // Your secret API key - var service := appwrite.Database{ - client: &client - } - - var response, error := service.CreateCollection("[NAME]", [], [], []) - - if error != nil { - panic(error) - } - + appwrite_db := appwrite.NewDatabase(client) + + response, error :=appwrite_db.CreateCollection("unique()", "Collection_name", "document", []string{"role:all"}, []string{"role:all"}) fmt.Println(response) } \ No newline at end of file diff --git a/main.go b/main.go index 13df7fc..1c415fb 100644 --- a/main.go +++ b/main.go @@ -2,5 +2,7 @@ package appwrite // NewClient initializes a new Appwrite client func NewClient() Client { - return Client{} + clt := Client{} + clt.headers = make(map[string]string) + return clt } diff --git a/users.go b/users.go index dc1b6be..4d148c6 100644 --- a/users.go +++ b/users.go @@ -9,12 +9,12 @@ type Users struct { client Client } -func NewUsers(clt Client) Users { - service := Users{ +func NewUsers(clt Client) Users { + service := Users{ client: clt, } - return service + return service } // List get a list of all the project users. You can use the query params to @@ -23,9 +23,9 @@ func (srv *Users) List(Search string, Limit int, Offset int, OrderType string) ( path := "/users" params := map[string]interface{}{ - "search": Search, - "limit": Limit, - "offset": Offset, + "search": Search, + "limit": Limit, + "offset": Offset, "orderType": OrderType, } @@ -37,9 +37,9 @@ func (srv *Users) Create(Email string, Password string, Name string) (map[string path := "/users" params := map[string]interface{}{ - "email": Email, + "email": Email, "password": Password, - "name": Name, + "name": Name, } return srv.client.Call("POST", path, nil, params) @@ -50,8 +50,7 @@ func (srv *Users) Get(UserId string) (map[string]interface{}, error) { r := strings.NewReplacer("{userId}", UserId) path := r.Replace("/users/{userId}") - params := map[string]interface{}{ - } + params := map[string]interface{}{} return srv.client.Call("GET", path, nil, params) } @@ -61,8 +60,7 @@ func (srv *Users) GetLogs(UserId string) (map[string]interface{}, error) { r := strings.NewReplacer("{userId}", UserId) path := r.Replace("/users/{userId}/logs") - params := map[string]interface{}{ - } + params := map[string]interface{}{} return srv.client.Call("GET", path, nil, params) } @@ -72,15 +70,14 @@ func (srv *Users) GetPrefs(UserId string) (map[string]interface{}, error) { r := strings.NewReplacer("{userId}", UserId) path := r.Replace("/users/{userId}/prefs") - params := map[string]interface{}{ - } + params := map[string]interface{}{} return srv.client.Call("GET", path, nil, params) } // UpdatePrefs update user preferences by its unique ID. You can pass only the // specific settings you wish to update. -func (srv *Users) UpdatePrefs(UserId string, Prefs object) (map[string]interface{}, error) { +func (srv *Users) UpdatePrefs(UserId string, Prefs []interface{}) (map[string]interface{}, error) { r := strings.NewReplacer("{userId}", UserId) path := r.Replace("/users/{userId}/prefs") @@ -96,8 +93,7 @@ func (srv *Users) GetSessions(UserId string) (map[string]interface{}, error) { r := strings.NewReplacer("{userId}", UserId) path := r.Replace("/users/{userId}/sessions") - params := map[string]interface{}{ - } + params := map[string]interface{}{} return srv.client.Call("GET", path, nil, params) } @@ -107,8 +103,7 @@ func (srv *Users) DeleteSessions(UserId string) (map[string]interface{}, error) r := strings.NewReplacer("{userId}", UserId) path := r.Replace("/users/{userId}/sessions") - params := map[string]interface{}{ - } + params := map[string]interface{}{} return srv.client.Call("DELETE", path, nil, params) } diff --git a/utils.go b/utils.go index ca36149..4d7fc8f 100644 --- a/utils.go +++ b/utils.go @@ -4,6 +4,7 @@ import ( "fmt" "reflect" "strconv" + "strings" ) // ToString changes arg to string @@ -28,9 +29,28 @@ func ToString(arg interface{}) string { return strconv.FormatFloat(v, 'f', -1, 64) case fmt.Stringer: return v.String() + case []string: + joined_string := strings.Join(v, `","`) + return "[" + joined_string + "]" case reflect.Value: return ToString(v.Interface()) default: + fmt.Println("⚠️ : Warning in utils ToString method: argument type is unknown returning empty string") return "" } } + +// returns if an element is contained in an array s +func contains[T comparable](s []T, element T) bool { + for _, el := range s { + if el == element { + return true + } + } + return false +} + +type Optional[T any] struct { + Value T + Specified bool +}