From 73f190ab914f5ff0bd0d565dc1cd679033518bbb Mon Sep 17 00:00:00 2001 From: "Tristan B. Velloza Kildaire" Date: Mon, 9 Jan 2023 12:29:40 +0200 Subject: [PATCH] - Added website --- .gitignore | 18 - LICENSE | 165 ---- README.md | 197 ----- branding/logo.xcf | Bin 13189 -> 0 bytes docs/index.md | 29 + {branding => docs}/logo.png | Bin docs/record management/auth collections.md | 29 + docs/record management/base collections.md | 45 + docs/server initialization.md | 8 + docs/translation/deserialization.md | 42 + docs/translation/serialization.md | 38 + dub.json | 16 - dummy.json | 89 -- mkdocs.yml | 5 + source/libpb/driver.d | 923 --------------------- source/libpb/exceptions.d | 72 -- source/libpb/package.d | 4 - 17 files changed, 196 insertions(+), 1484 deletions(-) delete mode 100644 .gitignore delete mode 100644 LICENSE delete mode 100644 README.md delete mode 100644 branding/logo.xcf create mode 100644 docs/index.md rename {branding => docs}/logo.png (100%) create mode 100644 docs/record management/auth collections.md create mode 100644 docs/record management/base collections.md create mode 100644 docs/server initialization.md create mode 100644 docs/translation/deserialization.md create mode 100644 docs/translation/serialization.md delete mode 100644 dub.json delete mode 100644 dummy.json create mode 100644 mkdocs.yml delete mode 100644 source/libpb/driver.d delete mode 100644 source/libpb/exceptions.d delete mode 100644 source/libpb/package.d diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 1a5f6b3..0000000 --- a/.gitignore +++ /dev/null @@ -1,18 +0,0 @@ -.dub -docs.json -__dummy.html -docs/ -/libpb -libpb.so -libpb.dylib -libpb.dll -libpb.a -libpb.lib -libpb-test-* -*.exe -*.pdb -*.o -*.obj -*.lst -liblibpb.a -dub.selections.json diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 0a04128..0000000 --- a/LICENSE +++ /dev/null @@ -1,165 +0,0 @@ - GNU LESSER GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - - This version of the GNU Lesser General Public License incorporates -the terms and conditions of version 3 of the GNU General Public -License, supplemented by the additional permissions listed below. - - 0. Additional Definitions. - - As used herein, "this License" refers to version 3 of the GNU Lesser -General Public License, and the "GNU GPL" refers to version 3 of the GNU -General Public License. - - "The Library" refers to a covered work governed by this License, -other than an Application or a Combined Work as defined below. - - An "Application" is any work that makes use of an interface provided -by the Library, but which is not otherwise based on the Library. -Defining a subclass of a class defined by the Library is deemed a mode -of using an interface provided by the Library. - - A "Combined Work" is a work produced by combining or linking an -Application with the Library. The particular version of the Library -with which the Combined Work was made is also called the "Linked -Version". - - The "Minimal Corresponding Source" for a Combined Work means the -Corresponding Source for the Combined Work, excluding any source code -for portions of the Combined Work that, considered in isolation, are -based on the Application, and not on the Linked Version. - - The "Corresponding Application Code" for a Combined Work means the -object code and/or source code for the Application, including any data -and utility programs needed for reproducing the Combined Work from the -Application, but excluding the System Libraries of the Combined Work. - - 1. Exception to Section 3 of the GNU GPL. - - You may convey a covered work under sections 3 and 4 of this License -without being bound by section 3 of the GNU GPL. - - 2. Conveying Modified Versions. - - If you modify a copy of the Library, and, in your modifications, a -facility refers to a function or data to be supplied by an Application -that uses the facility (other than as an argument passed when the -facility is invoked), then you may convey a copy of the modified -version: - - a) under this License, provided that you make a good faith effort to - ensure that, in the event an Application does not supply the - function or data, the facility still operates, and performs - whatever part of its purpose remains meaningful, or - - b) under the GNU GPL, with none of the additional permissions of - this License applicable to that copy. - - 3. Object Code Incorporating Material from Library Header Files. - - The object code form of an Application may incorporate material from -a header file that is part of the Library. You may convey such object -code under terms of your choice, provided that, if the incorporated -material is not limited to numerical parameters, data structure -layouts and accessors, or small macros, inline functions and templates -(ten or fewer lines in length), you do both of the following: - - a) Give prominent notice with each copy of the object code that the - Library is used in it and that the Library and its use are - covered by this License. - - b) Accompany the object code with a copy of the GNU GPL and this license - document. - - 4. Combined Works. - - You may convey a Combined Work under terms of your choice that, -taken together, effectively do not restrict modification of the -portions of the Library contained in the Combined Work and reverse -engineering for debugging such modifications, if you also do each of -the following: - - a) Give prominent notice with each copy of the Combined Work that - the Library is used in it and that the Library and its use are - covered by this License. - - b) Accompany the Combined Work with a copy of the GNU GPL and this license - document. - - c) For a Combined Work that displays copyright notices during - execution, include the copyright notice for the Library among - these notices, as well as a reference directing the user to the - copies of the GNU GPL and this license document. - - d) Do one of the following: - - 0) Convey the Minimal Corresponding Source under the terms of this - License, and the Corresponding Application Code in a form - suitable for, and under terms that permit, the user to - recombine or relink the Application with a modified version of - the Linked Version to produce a modified Combined Work, in the - manner specified by section 6 of the GNU GPL for conveying - Corresponding Source. - - 1) Use a suitable shared library mechanism for linking with the - Library. A suitable mechanism is one that (a) uses at run time - a copy of the Library already present on the user's computer - system, and (b) will operate properly with a modified version - of the Library that is interface-compatible with the Linked - Version. - - e) Provide Installation Information, but only if you would otherwise - be required to provide such information under section 6 of the - GNU GPL, and only to the extent that such information is - necessary to install and execute a modified version of the - Combined Work produced by recombining or relinking the - Application with a modified version of the Linked Version. (If - you use option 4d0, the Installation Information must accompany - the Minimal Corresponding Source and Corresponding Application - Code. If you use option 4d1, you must provide the Installation - Information in the manner specified by section 6 of the GNU GPL - for conveying Corresponding Source.) - - 5. Combined Libraries. - - You may place library facilities that are a work based on the -Library side by side in a single library together with other library -facilities that are not Applications and are not covered by this -License, and convey such a combined library under terms of your -choice, if you do both of the following: - - a) Accompany the combined library with a copy of the same work based - on the Library, uncombined with any other library facilities, - conveyed under the terms of this License. - - b) Give prominent notice with the combined library that part of it - is a work based on the Library, and explaining where to find the - accompanying uncombined form of the same work. - - 6. Revised Versions of the GNU Lesser General Public License. - - The Free Software Foundation may publish revised and/or new versions -of the GNU Lesser General Public License from time to time. Such new -versions will be similar in spirit to the present version, but may -differ in detail to address new problems or concerns. - - Each version is given a distinguishing version number. If the -Library as you received it specifies that a certain numbered version -of the GNU Lesser General Public License "or any later version" -applies to it, you have the option of following the terms and -conditions either of that published version or of any later version -published by the Free Software Foundation. If the Library as you -received it does not specify a version number of the GNU Lesser -General Public License, you may choose any version of the GNU Lesser -General Public License ever published by the Free Software Foundation. - - If the Library as you received it specifies that a proxy can decide -whether future versions of the GNU Lesser General Public License shall -apply, that proxy's public statement of acceptance of any version is -permanent authorization for you to choose that version for the -Library. diff --git a/README.md b/README.md deleted file mode 100644 index 86f78cf..0000000 --- a/README.md +++ /dev/null @@ -1,197 +0,0 @@ -![](branding/logo.png) - -libpb -===== - -#### _PocketBase wrapper with serializer/deserializer support_ - ----- - -## Example usage - -View the full API documentation (methods etc.) [here](https://libpb.dpldocs.info/libpb.html). - -### Server initiation - -Firstly we create a new PocketBase instance to manage our server: - -```d -PocketBase pb = new PocketBase("http://127.0.0.1:8090/api/"); -``` - -### Serialization - -This is just to show off the serialization method `serializeRecord(RecordType)` which returns a `JSONValue` struct: - -```d -import std.algorithm.searching : canFind; -import std.string : cmp; - -struct Person -{ - public string firstname, lastname; - public int age; - public string[] list; - public JSONValue extraJSON; - public EnumType eType; -} - -Person p1; -p1.firstname = "Tristan"; -p1.lastname = "Kildaire"; -p1.age = 23; -p1.list = ["1", "2", "3"]; -p1.extraJSON = parseJSON(`{"item":1, "items":[1,2,3]}`); -p1.eType = EnumType.CAT; - -JSONValue serialized = serializeRecord(p1); - -string[] keys = serialized.object().keys(); -assert(canFind(keys, "firstname") && cmp(serialized["firstname"].str(), "Tristan") == 0); -assert(canFind(keys, "lastname") && cmp(serialized["lastname"].str(), "Kildaire") == 0); -assert(canFind(keys, "age") && serialized["age"].integer() == 23); - -debug(dbg) -{ - writeln(serialized.toPrettyString()); -} -``` - -### Deserialization - -This is to show off deserialization method `fromJSON(RecordType)(JSONValue jsonIn)` which returns a struct of type `RecordType` (so far most features are implemented): - -```d -import std.string : cmp; - -struct Person -{ - public string firstname, lastname; - public int age; - public bool isMale; - public JSONValue obj; - public int[] list; -} - -JSONValue json = parseJSON(`{ -"firstname" : "Tristan", -"lastname": "Kildaire", -"age": 23, -"obj" : {"bruh":1}, -"isMale": true, -"list": [1,2,3] -} -`); - -Person person = fromJSON!(Person)(json); - -debug(dbg) -{ - writeln(person); -} - - -assert(cmp(person.firstname, "Tristan") == 0); -assert(cmp(person.lastname, "Kildaire") == 0); -assert(person.age == 23); -assert(person.isMale == true); -assert(person.obj["bruh"].integer() == 1); -//TODO: list test case -``` - -### Record management - -#### Normal collections - -Below we have a few calls like create and delete: - -```d -PocketBase pb = new PocketBase(); - -struct Person -{ - string id; - string name; - int age; -} - -Person p1 = Person(); -p1.name = "Tristan Gonzales"; -p1.age = 23; - -Person recordStored = pb.createRecord("dummy", p1); -pb.deleteRecord("dummy", recordStored.id); - - -recordStored = pb.createRecord("dummy", p1); -recordStored.age = 46; -recordStored = pb.updateRecord("dummy", recordStored); - -Person recordFetched = pb.viewRecord!(Person)("dummy", recordStored.id); - -pb.deleteRecord("dummy", recordStored); - -Person[] people = [Person(), Person()]; -people[0].name = "Abby"; -people[1].name = "Becky"; - -people[0] = pb.createRecord("dummy", people[0]); -people[1] = pb.createRecord("dummy", people[1]); - -Person[] returnedPeople = pb.listRecords!(Person)("dummy"); -foreach(Person returnedPerson; returnedPeople) -{ - writeln(returnedPerson); - pb.deleteRecord("dummy", returnedPerson); -} -``` - -#### `auth` collections - -Auth collections require that certain calls, such as `createRecord(table, record, isAuthCollection)` have the last argument se to `true`. - -```d -import core.thread : Thread, dur; -import std.string : cmp; - -PocketBase pb = new PocketBase(); - -struct Person -{ - string id; - string email; - string username; - string password; - string passwordConfirm; -} - -Person p1; -p1.email = "deavmi@redxen.eu"; -p1.username = "deavmi"; -p1.password = "bigbruh1111"; -p1.passwordConfirm = "bigbruh1111"; - -p1 = pb.createRecordAuth("dummy_auth", p1); -pb.deleteRecord("dummy_auth", p1); -``` - -## Development - -### Dependencies - -This requires that you have the `libcurl` libraries available for -linking against. - -### Unit tests - -To run tests you will want to enable the `pragma`s and `writeln`s. therefore pass the `dbg` flag in as such: - -```bash -dub test -ddbg -``` - -Run pocketbase on the default port and then use the schema provided as `dummy.json` to test with (in a collection named `dummy`). - -## License - -See [LICENSE](LICENSE) diff --git a/branding/logo.xcf b/branding/logo.xcf deleted file mode 100644 index 597795a217b902c2a0a46274c316db3507d6304c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13189 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 0) - { - // Then add the authaorization header - http.addRequestHeader("Authorization", pbInstance.getAuthToken()); - } - } - -} - -public class PocketBase -{ - private string pocketBaseURL; - private string authToken; - - /** - * Constructs a new PocketBase instance with - * the default settings - */ - this(string pocketBaseURL = "http://127.0.0.1:8090/api/", string authToken = "") - { - this.pocketBaseURL = pocketBaseURL; - this.authToken = authToken; - } - - public void setAuthToken(string authToken) - { - if(cmp(authToken, "") != 0) - { - this.authToken = authToken; - } - } - - public string getAuthToken() - { - return this.authToken; - } - - /** - * List all of the records in the given table (base collection) - * - * Params: - * table = the table to list from - * page = the page to look at (default is 1) - * perPage = the number of items to return per page (default is 30) - * filter = the predicate to filter by - * - * Returns: A list of type RecordType - */ - public RecordType[] listRecords(RecordType)(string table, ulong page = 1, ulong perPage = 30, string filter = "") - { - return listRecords_internal!(RecordType)(table, page, perPage, filter, false); - } - - /** - * List all of the records in the given table (auth collection) - * - * Params: - * table = the table to list from - * page = the page to look at (default is 1) - * perPage = the number of items to return per page (default is 30) - * filter = the predicate to filter by - * - * Returns: A list of type RecordType - */ - public RecordType[] listRecordsAuth(RecordType)(string table, ulong page = 1, ulong perPage = 30, string filter = "") - { - return listRecords_internal!(RecordType)(table, page, perPage, filter, true); - } - - /** - * List all of the records in the given table (internals) - * - * Params: - * table = the table to list from - * page = the page to look at (default is 1) - * perPage = the number of items to return per page (default is 30) - * filter = the predicate to filter by - * isAuthCollection = true if this is an auth collection, false - * for base collection - * - * Returns: A list of type RecordType - */ - private RecordType[] listRecords_internal(RecordType)(string table, ulong page, ulong perPage, string filter, bool isAuthCollection) - { - // Set authorization token if setup - HTTP httpSettings = HTTP(); - mixin AuthTokenHeader!(httpSettings, this); - InitializeAuthHeader(); - - RecordType[] recordsOut; - - // Compute the query string - string queryStr = "page="~to!(string)(page)~"&perPage="~to!(string)(perPage); - - // If there is a filter then perform the needed escaping - if(cmp(filter, "") != 0) - { - // For the filter, make sure to add URL escaping to the `filter` parameter - import etc.c.curl : curl_escape; - import std.string : toStringz, fromStringz; - char* escapedParameter = curl_escape(toStringz(filter), cast(int)filter.length); - if(escapedParameter is null) - { - debug(dbg) - { - writeln("Invalid return from curl_easy_escape"); - } - throw new NetworkException(); - } - - // Convert back to D-string (the filter) - filter = cast(string)fromStringz(escapedParameter); - } - - // Append the filter - queryStr ~= cmp(filter, "") == 0 ? "" : "&filter="~filter; - - try - { - string responseData = cast(string)get(pocketBaseURL~"collections/"~table~"/records?"~queryStr, httpSettings); - JSONValue responseJSON = parseJSON(responseData); - JSONValue[] returnedItems = responseJSON["items"].array(); - - foreach(JSONValue returnedItem; returnedItems) - { - // If this is an authable record (meaning it has email, password and passwordConfirm) - // well then the latter two will not be returned so fill them in. Secondly, the email - // will only be returned if `emailVisibility` is true. - if(isAuthCollection) - { - returnedItem["password"] = ""; - returnedItem["passwordConfirm"] = ""; - - // If email is invisible make a fake field to prevent crash - if(!returnedItem["emailVisibility"].boolean()) - { - returnedItem["email"] = ""; - } - } - - recordsOut ~= fromJSON!(RecordType)(returnedItem); - } - - return recordsOut; - } - catch(HTTPStatusException e) - { - if(e.status == 403) - { - throw new NotAuthorized(table, null); - } - else - { - throw new NetworkException(); - } - } - catch(CurlException e) - { - debug(dbg) - { - writeln("curl"); - writeln(e); - } - - throw new NetworkException(); - } - catch(JSONException e) - { - throw new PocketBaseParsingException(); - } - catch(SerializationError e) - { - throw new RemoteFieldMissing(); - } - } - - /** - * Creates a record in the given authentication table - * - * Params: - * table = the table to create the record in - * item = The Record to create - * - * Returns: An instance of the created RecordType - */ - public RecordType createRecordAuth(string, RecordType)(string table, RecordType item) - { - mixin isAuthable!(RecordType); - - return createRecord_internal(table, item, true); - } - - /** - * Creates a record in the given base table - * - * Params: - * table = the table to create the record in - * item = The Record to create - * - * Returns: An instance of the created RecordType - */ - public RecordType createRecord(string, RecordType)(string table, RecordType item) - { - return createRecord_internal(table, item, false); - } - - /** - * Creates a record in the given table (internal method) - * - * Params: - * table = the table to create the record in - * item = The Record to create - * isAuthCollection = whether or not this collection is auth or not (base) - * - * Returns: An instance of the created RecordType - */ - private RecordType createRecord_internal(string, RecordType)(string table, RecordType item, bool isAuthCollection) - { - idAbleCheck(item); - - RecordType recordOut; - - // Set authorization token if setup - HTTP httpSettings = HTTP(); - mixin AuthTokenHeader!(httpSettings, this); - InitializeAuthHeader(); - - // Set the content type - httpSettings.addRequestHeader("Content-Type", "application/json"); - - // Serialize the record instance - JSONValue serialized = serializeRecord(item); - - try - { - string responseData = cast(string)post(pocketBaseURL~"collections/"~table~"/records", serialized.toString(), httpSettings); - JSONValue responseJSON = parseJSON(responseData); - - // On creation of a record in an "auth" collection the email visibility - // will initially be false, therefore fill in a blank for it temporarily - // now as to not make `fromJSON` crash when it sees an email field in - // a struct and tries to look the the JSON key "email" when it isn't present - // - // A password is never returned (so `password` and `passwordConfirm` will be left out) - // - // The above are all assumed to be strings, if not then a runtime error will occur - // See (issue #3) - if(isAuthCollection) - { - responseJSON["email"] = ""; - responseJSON["password"] = ""; - responseJSON["passwordConfirm"] = ""; - } - - recordOut = fromJSON!(RecordType)(responseJSON); - - return recordOut; - } - catch(HTTPStatusException e) - { - debug(dbg) - { - writeln("createRecord_internal: "~e.toString()); - } - - if(e.status == 403) - { - throw new NotAuthorized(table, item.id); - } - else if(e.status == 400) - { - throw new ValidationRequired(table, item.id); - } - else - { - // TODO: Fix this - throw new NetworkException(); - } - } - catch(CurlException e) - { - throw new NetworkException(); - } - catch(JSONException e) - { - throw new PocketBaseParsingException(); - } - catch(SerializationError e) - { - throw new RemoteFieldMissing(); - } - } - - /** - * Authenticates on the given auth table with the provided - * credentials, returning a JWT token in the reference parameter. - * Finally returning the record of the authenticated user. - * - * Params: - * table = the auth collection to use - * identity = the user's identity - * password = the user's password - * token = the variable to return into - * - * Returns: An instance of `RecordType` - */ - public RecordType authWithPassword(RecordType)(string table, string identity, string password, ref string token) - { - mixin isAuthable!(RecordType); - - RecordType recordOut; - - // Set the content type - HTTP httpSettings = HTTP(); - httpSettings.addRequestHeader("Content-Type", "application/json"); - - // Construct the authentication record - JSONValue authRecord; - authRecord["identity"] = identity; - authRecord["password"] = password; - - try - { - string responseData = cast(string)post(pocketBaseURL~"collections/"~table~"/auth-with-password", authRecord.toString(), httpSettings); - JSONValue responseJSON = parseJSON(responseData); - JSONValue recordResponse = responseJSON["record"]; - - // In the case we are doing auth, we won't get password, passwordConfirm sent back - // set them to empty - recordResponse["password"] = ""; - recordResponse["passwordConfirm"] = ""; - - // If email is invisible make a fake field to prevent crash - if(!recordResponse["emailVisibility"].boolean()) - { - recordResponse["email"] = ""; - } - - recordOut = fromJSON!(RecordType)(recordResponse); - - // Store the token - token = responseJSON["token"].str(); - - return recordOut; - } - catch(HTTPStatusException e) - { - if(e.status == 400) - { - // TODO: Update this error - throw new NotAuthorized(table, null); - } - else - { - // TODO: Fix this - throw new NetworkException(); - } - } - catch(CurlException e) - { - throw new NetworkException(); - } - catch(JSONException e) - { - throw new PocketBaseParsingException(); - } - catch(SerializationError e) - { - throw new RemoteFieldMissing(); - } - } - - /** - * View the given record by id (base collections) - * - * Params: - * table = the table to lookup the record in - * id = the id to lookup the record by - * - * Returns: The found record of type RecordType - */ - public RecordType viewRecord(RecordType)(string table, string id) - { - return viewRecord_internal!(RecordType)(table, id, false); - } - - - /** - * View the given record by id (auth collections) - * - * Params: - * table = the table to lookup the record in - * id = the id to lookup the record by - * - * Returns: The found record of type RecordType - */ - public RecordType viewRecordAuth(RecordType)(string table, string id) - { - return viewRecord_internal!(RecordType)(table, id, true); - } - - /** - * View the given record by id (internal) - * - * Params: - * table = the table to lookup the record in - * id = the id to lookup the record by - * isAuthCollection = true if this is an auth collection, false - * for base collection - * - * Returns: The found record of type RecordType - */ - private RecordType viewRecord_internal(RecordType)(string table, string id, bool isAuthCollection) - { - RecordType recordOut; - - // Set authorization token if setup - HTTP httpSettings = HTTP(); - mixin AuthTokenHeader!(httpSettings, this); - InitializeAuthHeader(); - - try - { - string responseData = cast(string)get(pocketBaseURL~"collections/"~table~"/records/"~id, httpSettings); - JSONValue responseJSON = parseJSON(responseData); - - // If this is an authable record (meaning it has email, password and passwordConfirm) - // well then the latter two will not be returned so fill them in. Secondly, the email - // will only be returned if `emailVisibility` is true. - if(isAuthCollection) - { - responseJSON["password"] = ""; - responseJSON["passwordConfirm"] = ""; - - // If email is invisible make a fake field to prevent crash - if(!responseJSON["emailVisibility"].boolean()) - { - responseJSON["email"] = ""; - } - } - - recordOut = fromJSON!(RecordType)(responseJSON); - - return recordOut; - } - catch(HTTPStatusException e) - { - if(e.status == 404) - { - throw new RecordNotFoundException(table, id); - } - else - { - // TODO: Fix this - throw new NetworkException(); - } - } - catch(CurlException e) - { - throw new NetworkException(); - } - catch(JSONException e) - { - throw new PocketBaseParsingException(); - } - catch(SerializationError e) - { - throw new RemoteFieldMissing(); - } - } - - /** - * Updates the given record in the given table, returning the - * updated record (auth collections) - * - * Params: - * table = tabe table to update the record in - * item = the record of type RecordType to update - * - * Returns: The updated RecordType - */ - public RecordType updateRecordAuth(string, RecordType)(string table, RecordType item) - { - return updateRecord_internal(table, item, true); - } - - /** - * Updates the given record in the given table, returning the - * updated record (base collections) - * - * Params: - * table = tabe table to update the record in - * item = the record of type RecordType to update - * - * Returns: The updated RecordType - */ - public RecordType updateRecord(string, RecordType)(string table, RecordType item) - { - return updateRecord_internal(table, item, false); - } - - /** - * Updates the given record in the given table, returning the - * updated record (internal) - * - * Params: - * table = tabe table to update the record in - * item = the record of type RecordType to update - * isAuthCollection = true if this is an auth collection, false - * for base collection - * - * Returns: The updated RecordType - */ - private RecordType updateRecord_internal(string, RecordType)(string table, RecordType item, bool isAuthCollection) - { - idAbleCheck(item); - - RecordType recordOut; - - // Set authorization token if setup - HTTP httpSettings = HTTP(); - mixin AuthTokenHeader!(httpSettings, this); - InitializeAuthHeader(); - - // Set the content type - httpSettings.addRequestHeader("Content-Type", "application/json"); - - // Serialize the record instance - JSONValue serialized = serializeRecord(item); - - try - { - string responseData = cast(string)patch(pocketBaseURL~"collections/"~table~"/records/"~item.id, serialized.toString(), httpSettings); - JSONValue responseJSON = parseJSON(responseData); - - // If this is an authable record (meaning it has email, password and passwordConfirm) - // well then the latter two will not be returned so fill them in. Secondly, the email - // will only be returned if `emailVisibility` is true. - if(isAuthCollection) - { - responseJSON["password"] = ""; - responseJSON["passwordConfirm"] = ""; - - // If email is invisible make a fake field to prevent crash - if(!responseJSON["emailVisibility"].boolean()) - { - responseJSON["email"] = ""; - } - } - - recordOut = fromJSON!(RecordType)(responseJSON); - - return recordOut; - } - catch(HTTPStatusException e) - { - if(e.status == 404) - { - throw new RecordNotFoundException(table, item.id); - } - else if(e.status == 403) - { - throw new NotAuthorized(table, item.id); - } - else if(e.status == 400) - { - throw new ValidationRequired(table, item.id); - } - else - { - // TODO: Fix this - throw new NetworkException(); - } - } - catch(CurlException e) - { - throw new NetworkException(); - } - catch(JSONException e) - { - throw new PocketBaseParsingException(); - } - catch(SerializationError e) - { - throw new RemoteFieldMissing(); - } - } - - /** - * Deletes the provided record by id from the given table - * - * Params: - * table = the table to delete the record from - * id = the id of the record to delete - */ - public void deleteRecord(string table, string id) - { - // Set authorization token if setup - HTTP httpSettings = HTTP(); - mixin AuthTokenHeader!(httpSettings, this); - InitializeAuthHeader(); - - try - { - del(pocketBaseURL~"collections/"~table~"/records/"~id, httpSettings); - } - catch(HTTPStatusException e) - { - if(e.status == 404) - { - throw new RecordNotFoundException(table, id); - } - else - { - // TODO: Fix this - throw new NetworkException(); - } - } - catch(CurlException e) - { - throw new NetworkException(); - } - } - - /** - * Deletes the provided record from the given table - * - * Params: - * table = the table to delete from - * record = the record of type RecordType to delete - */ - public void deleteRecord(string, RecordType)(string table, RecordType record) - { - idAbleCheck(record); - deleteRecord(table, record.id); - } - - private mixin template MemberAndType(alias record, alias typeEnforce, string memberName) - { - static if(__traits(hasMember, record, memberName)) - { - static if(__traits(isSame, typeof(mixin("record."~memberName)), typeEnforce)) - { - - } - else - { - pragma(msg, "Member '"~memberName~"' not of type '"~typeEnforce~"'"); - static assert(false); - } - } - else - { - pragma(msg, "Record does not have member '"~memberName~"'"); - static assert(false); - } - } - - private static void isAuthable(RecordType)(RecordType record) - { - mixin MemberAndType!(record, string, "email"); - mixin MemberAndType!(record, string, "password"); - mixin MemberAndType!(record, string, "passwordConfirm"); - } - - private static void idAbleCheck(RecordType)(RecordType record) - { - static if(__traits(hasMember, record, "id")) - { - static if(__traits(isSame, typeof(record.id), string)) - { - // Do nothing as it is a-okay - } - else - { - // Must be a string - pragma(msg, "The `id` field of the record provided must be of type string"); - static assert(false); - } - } - else - { - // An id field is required (TODO: ensure not a function identifier) - pragma(msg, "The provided record must have a `id` field"); - static assert(false); - } - } - - // TODO: Implement the streaming functionality - private void stream(string table) - { - - } -} - -unittest -{ - import core.thread : Thread, dur; - import std.string : cmp; - - PocketBase pb = new PocketBase(); - - struct Person - { - string id; - string name; - int age; - } - - Person p1 = Person(); - p1.name = "Tristan Gonzales"; - p1.age = 23; - - Person recordStored = pb.createRecord("dummy", p1); - pb.deleteRecord("dummy", recordStored.id); - - - recordStored = pb.createRecord("dummy", p1); - Thread.sleep(dur!("seconds")(3)); - recordStored.age = 46; - recordStored = pb.updateRecord("dummy", recordStored); - assert(recordStored.age == 46); - Thread.sleep(dur!("seconds")(3)); - - Person recordFetched = pb.viewRecord!(Person)("dummy", recordStored.id); - assert(recordFetched.age == 46); - assert(cmp(recordFetched.name, "Tristan Gonzales") == 0); - assert(cmp(recordFetched.id, recordStored.id) == 0); - - pb.deleteRecord("dummy", recordStored); - - Person[] people = [Person(), Person()]; - people[0].name = "Abby"; - people[1].name = "Becky"; - - people[0] = pb.createRecord("dummy", people[0]); - people[1] = pb.createRecord("dummy", people[1]); - - Person[] returnedPeople = pb.listRecords!(Person)("dummy"); - foreach(Person returnedPerson; returnedPeople) - { - debug(dbg) - { - writeln(returnedPerson); - } - pb.deleteRecord("dummy", returnedPerson); - } - - try - { - recordFetched = pb.viewRecord!(Person)("dummy", people[0].id); - assert(false); - } - catch(RecordNotFoundException e) - { - assert(cmp(e.offendingTable, "dummy") == 0 && e.offendingId == people[0].id); - } - catch(Exception e) - { - assert(false); - } - - try - { - recordFetched = pb.updateRecord("dummy", people[0]); - assert(false); - } - catch(RecordNotFoundException e) - { - assert(cmp(e.offendingTable, "dummy") == 0 && e.offendingId == people[0].id); - } - catch(Exception e) - { - assert(false); - } - - try - { - pb.deleteRecord("dummy", people[0]); - assert(false); - } - catch(RecordNotFoundException e) - { - assert(cmp(e.offendingTable, "dummy") == 0 && e.offendingId == people[0].id); - } - catch(Exception e) - { - assert(false); - } -} - -unittest -{ - import core.thread : Thread, dur; - import std.string : cmp; - - PocketBase pb = new PocketBase(); - - struct Person - { - string id; - string email; - string username; - string password; - string passwordConfirm; - string name; - int age; - } - - // Set the password to use - string passwordToUse = "bigbruh1111"; - - Person p1; - p1.email = "deavmi@redxen.eu"; - p1.username = "deavmi"; - p1.password = passwordToUse; - p1.passwordConfirm = passwordToUse; - p1.name = "Tristaniha"; - p1.age = 29; - - p1 = pb.createRecordAuth("dummy_auth", p1); - - - Person[] people = pb.listRecordsAuth!(Person)("dummy_auth", 1, 30, "(id='"~p1.id~"')"); - assert(people.length == 1); - - // Ensure we get our person back - assert(cmp(people[0].name, p1.name) == 0); - assert(people[0].age == p1.age); - // assert(cmp(people[0].email, p1.email) == 0); - - - Person person = pb.viewRecordAuth!(Person)("dummy_auth", p1.id); - - // Ensure we get our person back - assert(cmp(people[0].name, p1.name) == 0); - assert(people[0].age == p1.age); - // assert(cmp(people[0].email, p1.email) == 0); - - - string newName = "Bababooey"; - person.name = newName; - person = pb.updateRecordAuth("dummy_auth", person); - assert(cmp(person.name, newName) == 0); - - - - string tokenIn; - Person authPerson = pb.authWithPassword!(Person)("dummy_auth", p1.username, passwordToUse, tokenIn); - - // Ensure a non-empty token - assert(cmp(tokenIn, "") != 0); - writeln("Token: "~tokenIn); - - // Ensure we get our person back - assert(cmp(authPerson.name, person.name) == 0); - assert(authPerson.age == person.age); - assert(cmp(authPerson.email, person.email) == 0); - - // Delete the record - pb.deleteRecord("dummy_auth", p1); -} - -unittest -{ - import core.thread : Thread, dur; - import std.string : cmp; - - PocketBase pb = new PocketBase(); - - struct Person - { - string id; - string name; - int age; - } - - Person p1 = Person(); - p1.name = "Tristan Gonzales"; - p1.age = 23; - - Person p2 = Person(); - p2.name = p1.name~"2"; - p2.age = p1.age; - - p1 = pb.createRecord("dummy", p1); - p2 = pb.createRecord("dummy", p2); - - Person[] people = pb.listRecords!(Person)("dummy", 1, 30, "(id='"~p1.id~"')"); - assert(people.length == 1); - assert(cmp(people[0].id, p1.id) == 0); - - pb.deleteRecord("dummy", p1); - people = pb.listRecords!(Person)("dummy", 1, 30, "(id='"~p1.id~"')"); - assert(people.length == 0); - - people = pb.listRecords!(Person)("dummy", 1, 30, "(id='"~p2.id~"' && age=24)"); - assert(people.length == 0); - - people = pb.listRecords!(Person)("dummy", 1, 30, "(id='"~p2.id~"' && age=23)"); - assert(people.length == 1 && cmp(people[0].id, p2.id) == 0); - - pb.deleteRecord("dummy", p2); -} \ No newline at end of file diff --git a/source/libpb/exceptions.d b/source/libpb/exceptions.d deleted file mode 100644 index 6744988..0000000 --- a/source/libpb/exceptions.d +++ /dev/null @@ -1,72 +0,0 @@ -module libpb.exceptions; - -public abstract class PBException : Exception -{ - this(string message = "") - { - super("PBException: "~message); - } -} - -public final class RecordNotFoundException : PBException -{ - public const string offendingTable; - public const string offendingId; - this(string table, string id) - { - this.offendingTable = table; - this.offendingId = id; - - super("Could not find record '"~id~"' in table '"~offendingTable~"'"); - } -} - -public final class NotAuthorized : PBException -{ - public const string offendingTable; - public const string offendingId; - this(string table, string id) - { - this.offendingTable = table; - this.offendingId = id; - } -} - -public final class ValidationRequired : PBException -{ - public const string offendingTable; - public const string offendingId; - this(string table, string id) - { - this.offendingTable = table; - this.offendingId = id; - } -} - - -/** - * NetworkException - * - * Thrown on an unhandled curl error - */ -public final class NetworkException : PBException -{ - this() - { - - } -} - -public final class PocketBaseParsingException : PBException -{ - -} - - -public final class RemoteFieldMissing : PBException -{ - this() - { - - } -} \ No newline at end of file diff --git a/source/libpb/package.d b/source/libpb/package.d deleted file mode 100644 index 7bf3fe1..0000000 --- a/source/libpb/package.d +++ /dev/null @@ -1,4 +0,0 @@ -module libpb; - -public import libpb.exceptions; -public import libpb.driver; \ No newline at end of file