From dedc94c3ad94dc4fde28c50bbd9020f8c35c2693 Mon Sep 17 00:00:00 2001 From: Steven Nguyen Date: Tue, 2 May 2023 14:02:58 -0700 Subject: [PATCH 1/3] Refactor Client.call() to use middlewares --- lib/appwrite.dart | 2 + lib/query.dart | 4 +- lib/services/account.dart | 2424 +++++++++-------- lib/services/avatars.dart | 422 +-- lib/services/databases.dart | 440 +-- lib/services/functions.dart | 245 +- lib/services/graphql.dart | 132 +- lib/services/locale.dart | 522 ++-- lib/services/storage.dart | 591 ++-- lib/services/teams.dart | 909 ++++--- lib/src/call_handlers/call_handler.dart | 12 + .../cookie_auth_call_handler.dart | 42 + .../fallback_auth_call_handler.dart | 31 + lib/src/call_handlers/http_call_handler.dart | 51 + .../call_handlers/offline_call_handler.dart | 139 + lib/src/call_params.dart | 23 + lib/src/client.dart | 19 +- lib/src/client_base.dart | 30 +- lib/src/client_browser.dart | 185 +- lib/src/client_io.dart | 210 +- lib/src/client_mixin.dart | 41 +- lib/src/client_offline_mixin.dart | 72 +- lib/src/offline/caller.dart | 4 + lib/src/response.dart | 3 +- 24 files changed, 3464 insertions(+), 3089 deletions(-) create mode 100644 lib/src/call_handlers/call_handler.dart create mode 100644 lib/src/call_handlers/cookie_auth_call_handler.dart create mode 100644 lib/src/call_handlers/fallback_auth_call_handler.dart create mode 100644 lib/src/call_handlers/http_call_handler.dart create mode 100644 lib/src/call_handlers/offline_call_handler.dart create mode 100644 lib/src/call_params.dart create mode 100644 lib/src/offline/caller.dart diff --git a/lib/appwrite.dart b/lib/appwrite.dart index 68c1d899..35f90d27 100644 --- a/lib/appwrite.dart +++ b/lib/appwrite.dart @@ -6,6 +6,8 @@ import 'dart:math'; import 'dart:typed_data'; import 'models.dart' as models; +import 'src/call_handlers/offline_call_handler.dart'; +import 'src/call_params.dart'; import 'src/enums.dart'; import 'src/input_file.dart'; import 'src/service.dart'; diff --git a/lib/query.dart b/lib/query.dart index 6dcca4a4..ab361b1c 100644 --- a/lib/query.dart +++ b/lib/query.dart @@ -47,8 +47,8 @@ class Query { static String parseValues(dynamic value) => (value is String) ? '"$value"' : '$value'; - String method; - List params; + final String method; + final List params; factory Query.parse(String query) { if (!query.contains('(') || !query.contains(')')) { diff --git a/lib/services/account.dart b/lib/services/account.dart index c58831b8..026b23a7 100644 --- a/lib/services/account.dart +++ b/lib/services/account.dart @@ -1,1165 +1,1265 @@ part of appwrite; - /// The Account service allows you to authenticate and manage a user account. +/// The Account service allows you to authenticate and manage a user account. class Account extends Service { - Account(super.client); - - /// Get Account - /// - /// Get currently logged in user data as JSON object. - /// - Future get() async { - const String path = '/account'; - - final Map params = { - }; - - final Map headers = { - 'content-type': 'application/json', - }; - - final cacheModel = '/account'; - final cacheKey = 'current'; - final cacheResponseIdKey = '\$id'; - final cacheResponseContainerKey = ''; - - final res = await client.call( - HttpMethod.get, - path: path, - params: params, - headers: headers, - cacheModel: cacheModel, - cacheKey: cacheKey, - cacheResponseIdKey: cacheResponseIdKey, - cacheResponseContainerKey: cacheResponseContainerKey, - ); - - return models.Account.fromMap(res.data); - - } - - /// Create Account - /// - /// Use this endpoint to allow a new user to register a new account in your - /// project. After the user registration completes successfully, you can use - /// the [/account/verfication](/docs/client/account#accountCreateVerification) - /// route to start verifying the user email address. To allow the new user to - /// login to their new account, you need to create a new [account - /// session](/docs/client/account#accountCreateSession). - /// - Future create({required String userId, required String email, required String password, String? name}) async { - const String path = '/account'; - - final Map params = { - 'userId': userId, - 'email': email, - 'password': password, - 'name': name, - }; - - final Map headers = { - 'content-type': 'application/json', - }; - - final cacheModel = ''; - final cacheKey = ''; - final cacheResponseIdKey = '\$id'; - final cacheResponseContainerKey = ''; - - final res = await client.call( - HttpMethod.post, - path: path, - params: params, - headers: headers, - cacheModel: cacheModel, - cacheKey: cacheKey, - cacheResponseIdKey: cacheResponseIdKey, - cacheResponseContainerKey: cacheResponseContainerKey, - ); - - return models.Account.fromMap(res.data); - - } - - /// Update Email - /// - /// Update currently logged in user account email address. After changing user - /// address, the user confirmation status will get reset. A new confirmation - /// email is not sent automatically however you can use the send confirmation - /// email endpoint again to send the confirmation email. For security measures, - /// user password is required to complete this request. - /// This endpoint can also be used to convert an anonymous account to a normal - /// one, by passing an email address and a new password. - /// - /// - Future updateEmail({required String email, required String password}) async { - const String path = '/account/email'; - - final Map params = { - 'email': email, - 'password': password, - }; - - final Map headers = { - 'content-type': 'application/json', - }; - - final cacheModel = '/account'; - final cacheKey = 'current'; - final cacheResponseIdKey = '\$id'; - final cacheResponseContainerKey = ''; - - final res = await client.call( - HttpMethod.patch, - path: path, - params: params, - headers: headers, - cacheModel: cacheModel, - cacheKey: cacheKey, - cacheResponseIdKey: cacheResponseIdKey, - cacheResponseContainerKey: cacheResponseContainerKey, - ); - - return models.Account.fromMap(res.data); - - } - - /// Create JWT - /// - /// Use this endpoint to create a JSON Web Token. You can use the resulting JWT - /// to authenticate on behalf of the current user when working with the - /// Appwrite server-side API and SDKs. The JWT secret is valid for 15 minutes - /// from its creation and will be invalid if the user will logout in that time - /// frame. - /// - Future createJWT() async { - const String path = '/account/jwt'; - - final Map params = { - }; - - final Map headers = { - 'content-type': 'application/json', - }; - - final cacheModel = ''; - final cacheKey = ''; - final cacheResponseIdKey = '\$id'; - final cacheResponseContainerKey = ''; - - final res = await client.call( - HttpMethod.post, - path: path, - params: params, - headers: headers, - cacheModel: cacheModel, - cacheKey: cacheKey, - cacheResponseIdKey: cacheResponseIdKey, - cacheResponseContainerKey: cacheResponseContainerKey, - ); - - return models.Jwt.fromMap(res.data); - - } - - /// List Logs - /// - /// Get currently logged in user list of latest security activity logs. Each - /// log returns user IP address, location and date and time of log. - /// - Future listLogs({List? queries}) async { - const String path = '/account/logs'; - - final Map params = { - 'queries': queries, - }; - - final Map headers = { - 'content-type': 'application/json', - }; - - final cacheModel = ''; - final cacheKey = ''; - final cacheResponseIdKey = '\$id'; - final cacheResponseContainerKey = 'logs'; - - final res = await client.call( - HttpMethod.get, - path: path, - params: params, - headers: headers, - cacheModel: cacheModel, - cacheKey: cacheKey, - cacheResponseIdKey: cacheResponseIdKey, - cacheResponseContainerKey: cacheResponseContainerKey, - ); - - return models.LogList.fromMap(res.data); - - } - - /// Update Name - /// - /// Update currently logged in user account name. - /// - Future updateName({required String name}) async { - const String path = '/account/name'; - - final Map params = { - 'name': name, - }; - - final Map headers = { - 'content-type': 'application/json', - }; - - final cacheModel = '/account'; - final cacheKey = 'current'; - final cacheResponseIdKey = '\$id'; - final cacheResponseContainerKey = ''; - - final res = await client.call( - HttpMethod.patch, - path: path, - params: params, - headers: headers, - cacheModel: cacheModel, - cacheKey: cacheKey, - cacheResponseIdKey: cacheResponseIdKey, - cacheResponseContainerKey: cacheResponseContainerKey, - ); - - return models.Account.fromMap(res.data); - - } - - /// Update Password - /// - /// Update currently logged in user password. For validation, user is required - /// to pass in the new password, and the old password. For users created with - /// OAuth, Team Invites and Magic URL, oldPassword is optional. - /// - Future updatePassword({required String password, String? oldPassword}) async { - const String path = '/account/password'; - - final Map params = { - 'password': password, - 'oldPassword': oldPassword, - }; - - final Map headers = { - 'content-type': 'application/json', - }; - - final cacheModel = '/account'; - final cacheKey = 'current'; - final cacheResponseIdKey = '\$id'; - final cacheResponseContainerKey = ''; - - final res = await client.call( - HttpMethod.patch, - path: path, - params: params, - headers: headers, - cacheModel: cacheModel, - cacheKey: cacheKey, - cacheResponseIdKey: cacheResponseIdKey, - cacheResponseContainerKey: cacheResponseContainerKey, - ); - - return models.Account.fromMap(res.data); - - } - - /// Update Phone - /// - /// Update the currently logged in user's phone number. After updating the - /// phone number, the phone verification status will be reset. A confirmation - /// SMS is not sent automatically, however you can use the [POST - /// /account/verification/phone](/docs/client/account#accountCreatePhoneVerification) - /// endpoint to send a confirmation SMS. - /// - Future updatePhone({required String phone, required String password}) async { - const String path = '/account/phone'; - - final Map params = { - 'phone': phone, - 'password': password, - }; - - final Map headers = { - 'content-type': 'application/json', - }; - - final cacheModel = '/account'; - final cacheKey = 'current'; - final cacheResponseIdKey = '\$id'; - final cacheResponseContainerKey = ''; - - final res = await client.call( - HttpMethod.patch, - path: path, - params: params, - headers: headers, - cacheModel: cacheModel, - cacheKey: cacheKey, - cacheResponseIdKey: cacheResponseIdKey, - cacheResponseContainerKey: cacheResponseContainerKey, - ); - - return models.Account.fromMap(res.data); - - } - - /// Get Account Preferences - /// - /// Get currently logged in user preferences as a key-value object. - /// - Future getPrefs() async { - const String path = '/account/prefs'; - - final Map params = { - }; - - final Map headers = { - 'content-type': 'application/json', - }; - - final cacheModel = '/account/prefs'; - final cacheKey = 'current'; - final cacheResponseIdKey = '\$id'; - final cacheResponseContainerKey = ''; - - final res = await client.call( - HttpMethod.get, - path: path, - params: params, - headers: headers, - cacheModel: cacheModel, - cacheKey: cacheKey, - cacheResponseIdKey: cacheResponseIdKey, - cacheResponseContainerKey: cacheResponseContainerKey, - ); - - return models.Preferences.fromMap(res.data); - - } - - /// Update Preferences - /// - /// Update currently logged in user account preferences. The object you pass is - /// stored as is, and replaces any previous value. The maximum allowed prefs - /// size is 64kB and throws error if exceeded. - /// - Future updatePrefs({required Map prefs}) async { - const String path = '/account/prefs'; - - final Map params = { - 'prefs': prefs, - }; - - final Map headers = { - 'content-type': 'application/json', - }; - - final cacheModel = '/account/prefs'; - final cacheKey = 'current'; - final cacheResponseIdKey = '\$id'; - final cacheResponseContainerKey = ''; - - final res = await client.call( - HttpMethod.patch, - path: path, - params: params, - headers: headers, - cacheModel: cacheModel, - cacheKey: cacheKey, - cacheResponseIdKey: cacheResponseIdKey, - cacheResponseContainerKey: cacheResponseContainerKey, - ); - - return models.Account.fromMap(res.data); - - } - - /// Create Password Recovery - /// - /// Sends the user an email with a temporary secret key for password reset. - /// When the user clicks the confirmation link he is redirected back to your - /// app password reset URL with the secret key and email address values - /// attached to the URL query string. Use the query string params to submit a - /// request to the [PUT - /// /account/recovery](/docs/client/account#accountUpdateRecovery) endpoint to - /// complete the process. The verification link sent to the user's email - /// address is valid for 1 hour. - /// - Future createRecovery({required String email, required String url}) async { - const String path = '/account/recovery'; - - final Map params = { - 'email': email, - 'url': url, - }; - - final Map headers = { - 'content-type': 'application/json', - }; - - final cacheModel = ''; - final cacheKey = ''; - final cacheResponseIdKey = '\$id'; - final cacheResponseContainerKey = ''; - - final res = await client.call( - HttpMethod.post, - path: path, - params: params, - headers: headers, - cacheModel: cacheModel, - cacheKey: cacheKey, - cacheResponseIdKey: cacheResponseIdKey, - cacheResponseContainerKey: cacheResponseContainerKey, - ); - - return models.Token.fromMap(res.data); - - } - - /// Create Password Recovery (confirmation) - /// - /// Use this endpoint to complete the user account password reset. Both the - /// **userId** and **secret** arguments will be passed as query parameters to - /// the redirect URL you have provided when sending your request to the [POST - /// /account/recovery](/docs/client/account#accountCreateRecovery) endpoint. - /// - /// Please note that in order to avoid a [Redirect - /// Attack](https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) - /// the only valid redirect URLs are the ones from domains you have set when - /// adding your platforms in the console interface. - /// - Future updateRecovery({required String userId, required String secret, required String password, required String passwordAgain}) async { - const String path = '/account/recovery'; - - final Map params = { - 'userId': userId, - 'secret': secret, - 'password': password, - 'passwordAgain': passwordAgain, - }; - - final Map headers = { - 'content-type': 'application/json', - }; - - final cacheModel = ''; - final cacheKey = ''; - final cacheResponseIdKey = '\$id'; - final cacheResponseContainerKey = ''; - - final res = await client.call( - HttpMethod.put, - path: path, - params: params, - headers: headers, - cacheModel: cacheModel, - cacheKey: cacheKey, - cacheResponseIdKey: cacheResponseIdKey, - cacheResponseContainerKey: cacheResponseContainerKey, - ); - - return models.Token.fromMap(res.data); - - } - - /// List Sessions - /// - /// Get currently logged in user list of active sessions across different - /// devices. - /// - Future listSessions() async { - const String path = '/account/sessions'; - - final Map params = { - }; - - final Map headers = { - 'content-type': 'application/json', - }; - - final cacheModel = '/account/sessions'; - final cacheKey = ''; - final cacheResponseIdKey = '\$id'; - final cacheResponseContainerKey = 'sessions'; - - final res = await client.call( - HttpMethod.get, - path: path, - params: params, - headers: headers, - cacheModel: cacheModel, - cacheKey: cacheKey, - cacheResponseIdKey: cacheResponseIdKey, - cacheResponseContainerKey: cacheResponseContainerKey, - ); - - return models.SessionList.fromMap(res.data); - - } - - /// Delete Sessions - /// - /// Delete all sessions from the user account and remove any sessions cookies - /// from the end client. - /// - Future deleteSessions() async { - const String path = '/account/sessions'; - - final Map params = { - }; - - final Map headers = { - 'content-type': 'application/json', - }; - - final cacheModel = ''; - final cacheKey = ''; - final cacheResponseIdKey = '\$id'; - final cacheResponseContainerKey = ''; - - final res = await client.call( - HttpMethod.delete, - path: path, - params: params, - headers: headers, - cacheModel: cacheModel, - cacheKey: cacheKey, - cacheResponseIdKey: cacheResponseIdKey, - cacheResponseContainerKey: cacheResponseContainerKey, - ); - - return res.data; - - } - - /// Create Anonymous Session - /// - /// Use this endpoint to allow a new user to register an anonymous account in - /// your project. This route will also create a new session for the user. To - /// allow the new user to convert an anonymous account to a normal account, you - /// need to update its [email and - /// password](/docs/client/account#accountUpdateEmail) or create an [OAuth2 - /// session](/docs/client/account#accountCreateOAuth2Session). - /// - Future createAnonymousSession() async { - const String path = '/account/sessions/anonymous'; - - final Map params = { - }; - - final Map headers = { - 'content-type': 'application/json', - }; - - final cacheModel = ''; - final cacheKey = ''; - final cacheResponseIdKey = '\$id'; - final cacheResponseContainerKey = ''; - - final res = await client.call( - HttpMethod.post, - path: path, - params: params, - headers: headers, - cacheModel: cacheModel, - cacheKey: cacheKey, - cacheResponseIdKey: cacheResponseIdKey, - cacheResponseContainerKey: cacheResponseContainerKey, - ); - - return models.Session.fromMap(res.data); - - } - - /// Create Email Session - /// - /// Allow the user to login into their account by providing a valid email and - /// password combination. This route will create a new session for the user. - /// - /// A user is limited to 10 active sessions at a time by default. [Learn more - /// about session limits](/docs/authentication#limits). - /// - Future createEmailSession({required String email, required String password}) async { - const String path = '/account/sessions/email'; - - final Map params = { - 'email': email, - 'password': password, - }; - - final Map headers = { - 'content-type': 'application/json', - }; - - final cacheModel = ''; - final cacheKey = ''; - final cacheResponseIdKey = '\$id'; - final cacheResponseContainerKey = ''; - - final res = await client.call( - HttpMethod.post, - path: path, - params: params, - headers: headers, - cacheModel: cacheModel, - cacheKey: cacheKey, - cacheResponseIdKey: cacheResponseIdKey, - cacheResponseContainerKey: cacheResponseContainerKey, - ); - - return models.Session.fromMap(res.data); - - } - - /// Create Magic URL session - /// - /// Sends the user an email with a secret key for creating a session. If the - /// provided user ID has not be registered, a new user will be created. When - /// the user clicks the link in the email, the user is redirected back to the - /// URL you provided with the secret key and userId values attached to the URL - /// query string. Use the query string parameters to submit a request to the - /// [PUT - /// /account/sessions/magic-url](/docs/client/account#accountUpdateMagicURLSession) - /// endpoint to complete the login process. The link sent to the user's email - /// address is valid for 1 hour. If you are on a mobile device you can leave - /// the URL parameter empty, so that the login completion will be handled by - /// your Appwrite instance by default. - /// - /// A user is limited to 10 active sessions at a time by default. [Learn more - /// about session limits](/docs/authentication#limits). - /// - Future createMagicURLSession({required String userId, required String email, String? url}) async { - const String path = '/account/sessions/magic-url'; - - final Map params = { - 'userId': userId, - 'email': email, - 'url': url, - }; - - final Map headers = { - 'content-type': 'application/json', - }; - - final cacheModel = ''; - final cacheKey = ''; - final cacheResponseIdKey = '\$id'; - final cacheResponseContainerKey = ''; - - final res = await client.call( - HttpMethod.post, - path: path, - params: params, - headers: headers, - cacheModel: cacheModel, - cacheKey: cacheKey, - cacheResponseIdKey: cacheResponseIdKey, - cacheResponseContainerKey: cacheResponseContainerKey, - ); - - return models.Token.fromMap(res.data); - - } - - /// Create Magic URL session (confirmation) - /// - /// Use this endpoint to complete creating the session with the Magic URL. Both - /// the **userId** and **secret** arguments will be passed as query parameters - /// to the redirect URL you have provided when sending your request to the - /// [POST - /// /account/sessions/magic-url](/docs/client/account#accountCreateMagicURLSession) - /// endpoint. - /// - /// Please note that in order to avoid a [Redirect - /// Attack](https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) - /// the only valid redirect URLs are the ones from domains you have set when - /// adding your platforms in the console interface. - /// - Future updateMagicURLSession({required String userId, required String secret}) async { - const String path = '/account/sessions/magic-url'; - - final Map params = { - 'userId': userId, - 'secret': secret, - }; - - final Map headers = { - 'content-type': 'application/json', - }; - - final cacheModel = ''; - final cacheKey = ''; - final cacheResponseIdKey = '\$id'; - final cacheResponseContainerKey = ''; - - final res = await client.call( - HttpMethod.put, - path: path, - params: params, - headers: headers, - cacheModel: cacheModel, - cacheKey: cacheKey, - cacheResponseIdKey: cacheResponseIdKey, - cacheResponseContainerKey: cacheResponseContainerKey, - ); - - return models.Session.fromMap(res.data); - - } - - /// Create OAuth2 Session - /// - /// Allow the user to login to their account using the OAuth2 provider of their - /// choice. Each OAuth2 provider should be enabled from the Appwrite console - /// first. Use the success and failure arguments to provide a redirect URL's - /// back to your app when login is completed. - /// - /// If there is already an active session, the new session will be attached to - /// the logged-in account. If there are no active sessions, the server will - /// attempt to look for a user with the same email address as the email - /// received from the OAuth2 provider and attach the new session to the - /// existing user. If no matching user is found - the server will create a new - /// user. - /// - /// A user is limited to 10 active sessions at a time by default. [Learn more - /// about session limits](/docs/authentication#limits). - /// - /// - Future createOAuth2Session({required String provider, String? success, String? failure, List? scopes}) async { - final String path = '/account/sessions/oauth2/{provider}'.replaceAll('{provider}', provider); - - final Map params = { - - 'success': success, - 'failure': failure, - 'scopes': scopes, - - 'project': client.config['project'], - }; - - final List query = []; - - params.forEach((key, value) { - if (value is List) { - for (var item in value) { - query.add(Uri.encodeComponent(key + '[]') + '=' + Uri.encodeComponent(item)); - } - } else if(value != null) { - query.add(Uri.encodeComponent(key) + '=' + Uri.encodeComponent(value)); - } - }); - - Uri endpoint = Uri.parse(client.endPoint); - Uri url = Uri(scheme: endpoint.scheme, - host: endpoint.host, - port: endpoint.port, - path: endpoint.path + path, - query: query.join('&') - ); - - return client.webAuth(url, callbackUrlScheme: success); - } - - /// Create Phone session - /// - /// Sends the user an SMS with a secret key for creating a session. If the - /// provided user ID has not be registered, a new user will be created. Use the - /// returned user ID and secret and submit a request to the [PUT - /// /account/sessions/phone](/docs/client/account#accountUpdatePhoneSession) - /// endpoint to complete the login process. The secret sent to the user's phone - /// is valid for 15 minutes. - /// - /// A user is limited to 10 active sessions at a time by default. [Learn more - /// about session limits](/docs/authentication#limits). - /// - Future createPhoneSession({required String userId, required String phone}) async { - const String path = '/account/sessions/phone'; - - final Map params = { - 'userId': userId, - 'phone': phone, - }; - - final Map headers = { - 'content-type': 'application/json', - }; - - final cacheModel = ''; - final cacheKey = ''; - final cacheResponseIdKey = '\$id'; - final cacheResponseContainerKey = ''; - - final res = await client.call( - HttpMethod.post, - path: path, - params: params, - headers: headers, - cacheModel: cacheModel, - cacheKey: cacheKey, - cacheResponseIdKey: cacheResponseIdKey, - cacheResponseContainerKey: cacheResponseContainerKey, - ); - - return models.Token.fromMap(res.data); - - } - - /// Create Phone Session (confirmation) - /// - /// Use this endpoint to complete creating a session with SMS. Use the - /// **userId** from the - /// [createPhoneSession](/docs/client/account#accountCreatePhoneSession) - /// endpoint and the **secret** received via SMS to successfully update and - /// confirm the phone session. - /// - Future updatePhoneSession({required String userId, required String secret}) async { - const String path = '/account/sessions/phone'; - - final Map params = { - 'userId': userId, - 'secret': secret, - }; - - final Map headers = { - 'content-type': 'application/json', - }; - - final cacheModel = ''; - final cacheKey = ''; - final cacheResponseIdKey = '\$id'; - final cacheResponseContainerKey = ''; - - final res = await client.call( - HttpMethod.put, - path: path, - params: params, - headers: headers, - cacheModel: cacheModel, - cacheKey: cacheKey, - cacheResponseIdKey: cacheResponseIdKey, - cacheResponseContainerKey: cacheResponseContainerKey, - ); - - return models.Session.fromMap(res.data); - - } - - /// Get Session - /// - /// Use this endpoint to get a logged in user's session using a Session ID. - /// Inputting 'current' will return the current session being used. - /// - Future getSession({required String sessionId}) async { - final String path = '/account/sessions/{sessionId}'.replaceAll('{sessionId}', sessionId); - - final Map params = { - }; - - final Map headers = { - 'content-type': 'application/json', - }; - - final cacheModel = '/account/sessions'.replaceAll('{sessionId}', sessionId); - final cacheKey = sessionId; - final cacheResponseIdKey = '\$id'; - final cacheResponseContainerKey = ''; - - final res = await client.call( - HttpMethod.get, - path: path, - params: params, - headers: headers, - cacheModel: cacheModel, - cacheKey: cacheKey, - cacheResponseIdKey: cacheResponseIdKey, - cacheResponseContainerKey: cacheResponseContainerKey, - ); - - return models.Session.fromMap(res.data); - - } - - /// Update OAuth Session (Refresh Tokens) - /// - /// Access tokens have limited lifespan and expire to mitigate security risks. - /// If session was created using an OAuth provider, this route can be used to - /// "refresh" the access token. - /// - Future updateSession({required String sessionId}) async { - final String path = '/account/sessions/{sessionId}'.replaceAll('{sessionId}', sessionId); - - final Map params = { - }; - - final Map headers = { - 'content-type': 'application/json', - }; - - final cacheModel = ''; - final cacheKey = ''; - final cacheResponseIdKey = '\$id'; - final cacheResponseContainerKey = ''; - - final res = await client.call( - HttpMethod.patch, - path: path, - params: params, - headers: headers, - cacheModel: cacheModel, - cacheKey: cacheKey, - cacheResponseIdKey: cacheResponseIdKey, - cacheResponseContainerKey: cacheResponseContainerKey, - ); - - return models.Session.fromMap(res.data); - - } - - /// Delete Session - /// - /// Use this endpoint to log out the currently logged in user from all their - /// account sessions across all of their different devices. When using the - /// Session ID argument, only the unique session ID provided is deleted. - /// - /// - Future deleteSession({required String sessionId}) async { - final String path = '/account/sessions/{sessionId}'.replaceAll('{sessionId}', sessionId); - - final Map params = { - }; - - final Map headers = { - 'content-type': 'application/json', - }; - - final cacheModel = ''; - final cacheKey = ''; - final cacheResponseIdKey = '\$id'; - final cacheResponseContainerKey = ''; - - final res = await client.call( - HttpMethod.delete, - path: path, - params: params, - headers: headers, - cacheModel: cacheModel, - cacheKey: cacheKey, - cacheResponseIdKey: cacheResponseIdKey, - cacheResponseContainerKey: cacheResponseContainerKey, - ); - - return res.data; - - } - - /// Update Status - /// - /// Block the currently logged in user account. Behind the scene, the user - /// record is not deleted but permanently blocked from any access. To - /// completely delete a user, use the Users API instead. - /// - Future updateStatus() async { - const String path = '/account/status'; - - final Map params = { - }; - - final Map headers = { - 'content-type': 'application/json', - }; - - final cacheModel = ''; - final cacheKey = ''; - final cacheResponseIdKey = '\$id'; - final cacheResponseContainerKey = ''; - - final res = await client.call( - HttpMethod.patch, - path: path, - params: params, - headers: headers, - cacheModel: cacheModel, - cacheKey: cacheKey, - cacheResponseIdKey: cacheResponseIdKey, - cacheResponseContainerKey: cacheResponseContainerKey, - ); - - return models.Account.fromMap(res.data); - - } - - /// Create Email Verification - /// - /// Use this endpoint to send a verification message to your user email address - /// to confirm they are the valid owners of that address. Both the **userId** - /// and **secret** arguments will be passed as query parameters to the URL you - /// have provided to be attached to the verification email. The provided URL - /// should redirect the user back to your app and allow you to complete the - /// verification process by verifying both the **userId** and **secret** - /// parameters. Learn more about how to [complete the verification - /// process](/docs/client/account#accountUpdateEmailVerification). The - /// verification link sent to the user's email address is valid for 7 days. - /// - /// Please note that in order to avoid a [Redirect - /// Attack](https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md), - /// the only valid redirect URLs are the ones from domains you have set when - /// adding your platforms in the console interface. - /// - /// - Future createVerification({required String url}) async { - const String path = '/account/verification'; - - final Map params = { - 'url': url, - }; - - final Map headers = { - 'content-type': 'application/json', - }; - - final cacheModel = ''; - final cacheKey = ''; - final cacheResponseIdKey = '\$id'; - final cacheResponseContainerKey = ''; - - final res = await client.call( - HttpMethod.post, - path: path, - params: params, - headers: headers, - cacheModel: cacheModel, - cacheKey: cacheKey, - cacheResponseIdKey: cacheResponseIdKey, - cacheResponseContainerKey: cacheResponseContainerKey, - ); - - return models.Token.fromMap(res.data); - - } - - /// Create Email Verification (confirmation) - /// - /// Use this endpoint to complete the user email verification process. Use both - /// the **userId** and **secret** parameters that were attached to your app URL - /// to verify the user email ownership. If confirmed this route will return a - /// 200 status code. - /// - Future updateVerification({required String userId, required String secret}) async { - const String path = '/account/verification'; - - final Map params = { - 'userId': userId, - 'secret': secret, - }; - - final Map headers = { - 'content-type': 'application/json', - }; - - final cacheModel = ''; - final cacheKey = ''; - final cacheResponseIdKey = '\$id'; - final cacheResponseContainerKey = ''; - - final res = await client.call( - HttpMethod.put, - path: path, - params: params, - headers: headers, - cacheModel: cacheModel, - cacheKey: cacheKey, - cacheResponseIdKey: cacheResponseIdKey, - cacheResponseContainerKey: cacheResponseContainerKey, - ); - - return models.Token.fromMap(res.data); - - } - - /// Create Phone Verification - /// - /// Use this endpoint to send a verification SMS to the currently logged in - /// user. This endpoint is meant for use after updating a user's phone number - /// using the [accountUpdatePhone](/docs/client/account#accountUpdatePhone) - /// endpoint. Learn more about how to [complete the verification - /// process](/docs/client/account#accountUpdatePhoneVerification). The - /// verification code sent to the user's phone number is valid for 15 minutes. - /// - Future createPhoneVerification() async { - const String path = '/account/verification/phone'; - - final Map params = { - }; - - final Map headers = { - 'content-type': 'application/json', - }; - - final cacheModel = ''; - final cacheKey = ''; - final cacheResponseIdKey = '\$id'; - final cacheResponseContainerKey = ''; - - final res = await client.call( - HttpMethod.post, - path: path, - params: params, - headers: headers, - cacheModel: cacheModel, - cacheKey: cacheKey, - cacheResponseIdKey: cacheResponseIdKey, - cacheResponseContainerKey: cacheResponseContainerKey, - ); - - return models.Token.fromMap(res.data); - - } - - /// Create Phone Verification (confirmation) - /// - /// Use this endpoint to complete the user phone verification process. Use the - /// **userId** and **secret** that were sent to your user's phone number to - /// verify the user email ownership. If confirmed this route will return a 200 - /// status code. - /// - Future updatePhoneVerification({required String userId, required String secret}) async { - const String path = '/account/verification/phone'; - - final Map params = { - 'userId': userId, - 'secret': secret, - }; - - final Map headers = { - 'content-type': 'application/json', - }; - - final cacheModel = ''; - final cacheKey = ''; - final cacheResponseIdKey = '\$id'; - final cacheResponseContainerKey = ''; - - final res = await client.call( - HttpMethod.put, - path: path, - params: params, - headers: headers, - cacheModel: cacheModel, - cacheKey: cacheKey, - cacheResponseIdKey: cacheResponseIdKey, - cacheResponseContainerKey: cacheResponseContainerKey, - ); - - return models.Token.fromMap(res.data); - - } -} \ No newline at end of file + Account(super.client); + + /// Get Account + /// + /// Get currently logged in user data as JSON object. + /// + Future get() async { + const String path = '/account'; + + final Map params = {}; + + final Map headers = { + 'content-type': 'application/json', + }; + + final cacheModel = '/account'; + final cacheKey = 'current'; + final cacheResponseIdKey = '\$id'; + final cacheResponseContainerKey = ''; + + final res = await client.call(withCacheParams( + CallParams( + HttpMethod.get, + path, + params: params, + headers: headers, + ), + CacheParams( + model: cacheModel, + key: cacheKey, + responseIdKey: cacheResponseIdKey, + responseContainerKey: cacheResponseContainerKey, + ), + )); + + return models.Account.fromMap(res.data); + } + + /// Create Account + /// + /// Use this endpoint to allow a new user to register a new account in your + /// project. After the user registration completes successfully, you can use + /// the [/account/verfication](/docs/client/account#accountCreateVerification) + /// route to start verifying the user email address. To allow the new user to + /// login to their new account, you need to create a new [account + /// session](/docs/client/account#accountCreateSession). + /// + Future create( + {required String userId, + required String email, + required String password, + String? name}) async { + const String path = '/account'; + + final Map params = { + 'userId': userId, + 'email': email, + 'password': password, + 'name': name, + }; + + final Map headers = { + 'content-type': 'application/json', + }; + + final cacheModel = ''; + final cacheKey = ''; + final cacheResponseIdKey = '\$id'; + final cacheResponseContainerKey = ''; + + final res = await client.call(withCacheParams( + CallParams( + HttpMethod.post, + path, + params: params, + headers: headers, + ), + CacheParams( + model: cacheModel, + key: cacheKey, + responseIdKey: cacheResponseIdKey, + responseContainerKey: cacheResponseContainerKey, + ), + )); + + return models.Account.fromMap(res.data); + } + + /// Update Email + /// + /// Update currently logged in user account email address. After changing user + /// address, the user confirmation status will get reset. A new confirmation + /// email is not sent automatically however you can use the send confirmation + /// email endpoint again to send the confirmation email. For security measures, + /// user password is required to complete this request. + /// This endpoint can also be used to convert an anonymous account to a normal + /// one, by passing an email address and a new password. + /// + /// + Future updateEmail( + {required String email, required String password}) async { + const String path = '/account/email'; + + final Map params = { + 'email': email, + 'password': password, + }; + + final Map headers = { + 'content-type': 'application/json', + }; + + final cacheModel = '/account'; + final cacheKey = 'current'; + final cacheResponseIdKey = '\$id'; + final cacheResponseContainerKey = ''; + + final res = await client.call(withCacheParams( + CallParams( + HttpMethod.patch, + path, + params: params, + headers: headers, + ), + CacheParams( + model: cacheModel, + key: cacheKey, + responseIdKey: cacheResponseIdKey, + responseContainerKey: cacheResponseContainerKey, + ), + )); + + return models.Account.fromMap(res.data); + } + + /// Create JWT + /// + /// Use this endpoint to create a JSON Web Token. You can use the resulting JWT + /// to authenticate on behalf of the current user when working with the + /// Appwrite server-side API and SDKs. The JWT secret is valid for 15 minutes + /// from its creation and will be invalid if the user will logout in that time + /// frame. + /// + Future createJWT() async { + const String path = '/account/jwt'; + + final Map params = {}; + + final Map headers = { + 'content-type': 'application/json', + }; + + final cacheModel = ''; + final cacheKey = ''; + final cacheResponseIdKey = '\$id'; + final cacheResponseContainerKey = ''; + + final res = await client.call(withCacheParams( + CallParams( + HttpMethod.post, + path, + params: params, + headers: headers, + ), + CacheParams( + model: cacheModel, + key: cacheKey, + responseIdKey: cacheResponseIdKey, + responseContainerKey: cacheResponseContainerKey, + ), + )); + + return models.Jwt.fromMap(res.data); + } + + /// List Logs + /// + /// Get currently logged in user list of latest security activity logs. Each + /// log returns user IP address, location and date and time of log. + /// + Future listLogs({List? queries}) async { + const String path = '/account/logs'; + + final Map params = { + 'queries': queries, + }; + + final Map headers = { + 'content-type': 'application/json', + }; + + final cacheModel = ''; + final cacheKey = ''; + final cacheResponseIdKey = '\$id'; + final cacheResponseContainerKey = 'logs'; + + final res = await client.call(withCacheParams( + CallParams( + HttpMethod.get, + path, + params: params, + headers: headers, + ), + CacheParams( + model: cacheModel, + key: cacheKey, + responseIdKey: cacheResponseIdKey, + responseContainerKey: cacheResponseContainerKey, + ), + )); + + return models.LogList.fromMap(res.data); + } + + /// Update Name + /// + /// Update currently logged in user account name. + /// + Future updateName({required String name}) async { + const String path = '/account/name'; + + final Map params = { + 'name': name, + }; + + final Map headers = { + 'content-type': 'application/json', + }; + + final cacheModel = '/account'; + final cacheKey = 'current'; + final cacheResponseIdKey = '\$id'; + final cacheResponseContainerKey = ''; + + final res = await client.call(withCacheParams( + CallParams( + HttpMethod.patch, + path, + params: params, + headers: headers, + ), + CacheParams( + model: cacheModel, + key: cacheKey, + responseIdKey: cacheResponseIdKey, + responseContainerKey: cacheResponseContainerKey, + ), + )); + + return models.Account.fromMap(res.data); + } + + /// Update Password + /// + /// Update currently logged in user password. For validation, user is required + /// to pass in the new password, and the old password. For users created with + /// OAuth, Team Invites and Magic URL, oldPassword is optional. + /// + Future updatePassword( + {required String password, String? oldPassword}) async { + const String path = '/account/password'; + + final Map params = { + 'password': password, + 'oldPassword': oldPassword, + }; + + final Map headers = { + 'content-type': 'application/json', + }; + + final cacheModel = '/account'; + final cacheKey = 'current'; + final cacheResponseIdKey = '\$id'; + final cacheResponseContainerKey = ''; + + final res = await client.call(withCacheParams( + CallParams( + HttpMethod.patch, + path, + params: params, + headers: headers, + ), + CacheParams( + model: cacheModel, + key: cacheKey, + responseIdKey: cacheResponseIdKey, + responseContainerKey: cacheResponseContainerKey, + ), + )); + + return models.Account.fromMap(res.data); + } + + /// Update Phone + /// + /// Update the currently logged in user's phone number. After updating the + /// phone number, the phone verification status will be reset. A confirmation + /// SMS is not sent automatically, however you can use the [POST + /// /account/verification/phone](/docs/client/account#accountCreatePhoneVerification) + /// endpoint to send a confirmation SMS. + /// + Future updatePhone( + {required String phone, required String password}) async { + const String path = '/account/phone'; + + final Map params = { + 'phone': phone, + 'password': password, + }; + + final Map headers = { + 'content-type': 'application/json', + }; + + final cacheModel = '/account'; + final cacheKey = 'current'; + final cacheResponseIdKey = '\$id'; + final cacheResponseContainerKey = ''; + + final res = await client.call(withCacheParams( + CallParams( + HttpMethod.patch, + path, + params: params, + headers: headers, + ), + CacheParams( + model: cacheModel, + key: cacheKey, + responseIdKey: cacheResponseIdKey, + responseContainerKey: cacheResponseContainerKey, + ), + )); + + return models.Account.fromMap(res.data); + } + + /// Get Account Preferences + /// + /// Get currently logged in user preferences as a key-value object. + /// + Future getPrefs() async { + const String path = '/account/prefs'; + + final Map params = {}; + + final Map headers = { + 'content-type': 'application/json', + }; + + final cacheModel = '/account/prefs'; + final cacheKey = 'current'; + final cacheResponseIdKey = '\$id'; + final cacheResponseContainerKey = ''; + + final res = await client.call(withCacheParams( + CallParams( + HttpMethod.get, + path, + params: params, + headers: headers, + ), + CacheParams( + model: cacheModel, + key: cacheKey, + responseIdKey: cacheResponseIdKey, + responseContainerKey: cacheResponseContainerKey, + ), + )); + + return models.Preferences.fromMap(res.data); + } + + /// Update Preferences + /// + /// Update currently logged in user account preferences. The object you pass is + /// stored as is, and replaces any previous value. The maximum allowed prefs + /// size is 64kB and throws error if exceeded. + /// + Future updatePrefs({required Map prefs}) async { + const String path = '/account/prefs'; + + final Map params = { + 'prefs': prefs, + }; + + final Map headers = { + 'content-type': 'application/json', + }; + + final cacheModel = '/account/prefs'; + final cacheKey = 'current'; + final cacheResponseIdKey = '\$id'; + final cacheResponseContainerKey = ''; + + final res = await client.call(withCacheParams( + CallParams( + HttpMethod.patch, + path, + params: params, + headers: headers, + ), + CacheParams( + model: cacheModel, + key: cacheKey, + responseIdKey: cacheResponseIdKey, + responseContainerKey: cacheResponseContainerKey, + ), + )); + + return models.Account.fromMap(res.data); + } + + /// Create Password Recovery + /// + /// Sends the user an email with a temporary secret key for password reset. + /// When the user clicks the confirmation link he is redirected back to your + /// app password reset URL with the secret key and email address values + /// attached to the URL query string. Use the query string params to submit a + /// request to the [PUT + /// /account/recovery](/docs/client/account#accountUpdateRecovery) endpoint to + /// complete the process. The verification link sent to the user's email + /// address is valid for 1 hour. + /// + Future createRecovery( + {required String email, required String url}) async { + const String path = '/account/recovery'; + + final Map params = { + 'email': email, + 'url': url, + }; + + final Map headers = { + 'content-type': 'application/json', + }; + + final cacheModel = ''; + final cacheKey = ''; + final cacheResponseIdKey = '\$id'; + final cacheResponseContainerKey = ''; + + final res = await client.call(withCacheParams( + CallParams( + HttpMethod.post, + path, + params: params, + headers: headers, + ), + CacheParams( + model: cacheModel, + key: cacheKey, + responseIdKey: cacheResponseIdKey, + responseContainerKey: cacheResponseContainerKey, + ), + )); + + return models.Token.fromMap(res.data); + } + + /// Create Password Recovery (confirmation) + /// + /// Use this endpoint to complete the user account password reset. Both the + /// **userId** and **secret** arguments will be passed as query parameters to + /// the redirect URL you have provided when sending your request to the [POST + /// /account/recovery](/docs/client/account#accountCreateRecovery) endpoint. + /// + /// Please note that in order to avoid a [Redirect + /// Attack](https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) + /// the only valid redirect URLs are the ones from domains you have set when + /// adding your platforms in the console interface. + /// + Future updateRecovery( + {required String userId, + required String secret, + required String password, + required String passwordAgain}) async { + const String path = '/account/recovery'; + + final Map params = { + 'userId': userId, + 'secret': secret, + 'password': password, + 'passwordAgain': passwordAgain, + }; + + final Map headers = { + 'content-type': 'application/json', + }; + + final cacheModel = ''; + final cacheKey = ''; + final cacheResponseIdKey = '\$id'; + final cacheResponseContainerKey = ''; + + final res = await client.call(withCacheParams( + CallParams( + HttpMethod.put, + path, + params: params, + headers: headers, + ), + CacheParams( + model: cacheModel, + key: cacheKey, + responseIdKey: cacheResponseIdKey, + responseContainerKey: cacheResponseContainerKey, + ), + )); + + return models.Token.fromMap(res.data); + } + + /// List Sessions + /// + /// Get currently logged in user list of active sessions across different + /// devices. + /// + Future listSessions() async { + const String path = '/account/sessions'; + + final Map params = {}; + + final Map headers = { + 'content-type': 'application/json', + }; + + final cacheModel = '/account/sessions'; + final cacheKey = ''; + final cacheResponseIdKey = '\$id'; + final cacheResponseContainerKey = 'sessions'; + + final res = await client.call(withCacheParams( + CallParams( + HttpMethod.get, + path, + params: params, + headers: headers, + ), + CacheParams( + model: cacheModel, + key: cacheKey, + responseIdKey: cacheResponseIdKey, + responseContainerKey: cacheResponseContainerKey, + ), + )); + + return models.SessionList.fromMap(res.data); + } + + /// Delete Sessions + /// + /// Delete all sessions from the user account and remove any sessions cookies + /// from the end client. + /// + Future deleteSessions() async { + const String path = '/account/sessions'; + + final Map params = {}; + + final Map headers = { + 'content-type': 'application/json', + }; + + final cacheModel = ''; + final cacheKey = ''; + final cacheResponseIdKey = '\$id'; + final cacheResponseContainerKey = ''; + + final res = await client.call(withCacheParams( + CallParams( + HttpMethod.delete, + path, + params: params, + headers: headers, + ), + CacheParams( + model: cacheModel, + key: cacheKey, + responseIdKey: cacheResponseIdKey, + responseContainerKey: cacheResponseContainerKey, + ), + )); + + return res.data; + } + + /// Create Anonymous Session + /// + /// Use this endpoint to allow a new user to register an anonymous account in + /// your project. This route will also create a new session for the user. To + /// allow the new user to convert an anonymous account to a normal account, you + /// need to update its [email and + /// password](/docs/client/account#accountUpdateEmail) or create an [OAuth2 + /// session](/docs/client/account#accountCreateOAuth2Session). + /// + Future createAnonymousSession() async { + const String path = '/account/sessions/anonymous'; + + final Map params = {}; + + final Map headers = { + 'content-type': 'application/json', + }; + + final cacheModel = ''; + final cacheKey = ''; + final cacheResponseIdKey = '\$id'; + final cacheResponseContainerKey = ''; + + final res = await client.call(withCacheParams( + CallParams( + HttpMethod.post, + path, + params: params, + headers: headers, + ), + CacheParams( + model: cacheModel, + key: cacheKey, + responseIdKey: cacheResponseIdKey, + responseContainerKey: cacheResponseContainerKey, + ), + )); + + return models.Session.fromMap(res.data); + } + + /// Create Email Session + /// + /// Allow the user to login into their account by providing a valid email and + /// password combination. This route will create a new session for the user. + /// + /// A user is limited to 10 active sessions at a time by default. [Learn more + /// about session limits](/docs/authentication#limits). + /// + Future createEmailSession( + {required String email, required String password}) async { + const String path = '/account/sessions/email'; + + final Map params = { + 'email': email, + 'password': password, + }; + + final Map headers = { + 'content-type': 'application/json', + }; + + final cacheModel = ''; + final cacheKey = ''; + final cacheResponseIdKey = '\$id'; + final cacheResponseContainerKey = ''; + + final res = await client.call(withCacheParams( + CallParams( + HttpMethod.post, + path, + params: params, + headers: headers, + ), + CacheParams( + model: cacheModel, + key: cacheKey, + responseIdKey: cacheResponseIdKey, + responseContainerKey: cacheResponseContainerKey, + ), + )); + + return models.Session.fromMap(res.data); + } + + /// Create Magic URL session + /// + /// Sends the user an email with a secret key for creating a session. If the + /// provided user ID has not be registered, a new user will be created. When + /// the user clicks the link in the email, the user is redirected back to the + /// URL you provided with the secret key and userId values attached to the URL + /// query string. Use the query string parameters to submit a request to the + /// [PUT + /// /account/sessions/magic-url](/docs/client/account#accountUpdateMagicURLSession) + /// endpoint to complete the login process. The link sent to the user's email + /// address is valid for 1 hour. If you are on a mobile device you can leave + /// the URL parameter empty, so that the login completion will be handled by + /// your Appwrite instance by default. + /// + /// A user is limited to 10 active sessions at a time by default. [Learn more + /// about session limits](/docs/authentication#limits). + /// + Future createMagicURLSession( + {required String userId, required String email, String? url}) async { + const String path = '/account/sessions/magic-url'; + + final Map params = { + 'userId': userId, + 'email': email, + 'url': url, + }; + + final Map headers = { + 'content-type': 'application/json', + }; + + final cacheModel = ''; + final cacheKey = ''; + final cacheResponseIdKey = '\$id'; + final cacheResponseContainerKey = ''; + + final res = await client.call(withCacheParams( + CallParams( + HttpMethod.post, + path, + params: params, + headers: headers, + ), + CacheParams( + model: cacheModel, + key: cacheKey, + responseIdKey: cacheResponseIdKey, + responseContainerKey: cacheResponseContainerKey, + ), + )); + + return models.Token.fromMap(res.data); + } + + /// Create Magic URL session (confirmation) + /// + /// Use this endpoint to complete creating the session with the Magic URL. Both + /// the **userId** and **secret** arguments will be passed as query parameters + /// to the redirect URL you have provided when sending your request to the + /// [POST + /// /account/sessions/magic-url](/docs/client/account#accountCreateMagicURLSession) + /// endpoint. + /// + /// Please note that in order to avoid a [Redirect + /// Attack](https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) + /// the only valid redirect URLs are the ones from domains you have set when + /// adding your platforms in the console interface. + /// + Future updateMagicURLSession( + {required String userId, required String secret}) async { + const String path = '/account/sessions/magic-url'; + + final Map params = { + 'userId': userId, + 'secret': secret, + }; + + final Map headers = { + 'content-type': 'application/json', + }; + + final cacheModel = ''; + final cacheKey = ''; + final cacheResponseIdKey = '\$id'; + final cacheResponseContainerKey = ''; + + final res = await client.call(withCacheParams( + CallParams( + HttpMethod.put, + path, + params: params, + headers: headers, + ), + CacheParams( + model: cacheModel, + key: cacheKey, + responseIdKey: cacheResponseIdKey, + responseContainerKey: cacheResponseContainerKey, + ), + )); + + return models.Session.fromMap(res.data); + } + + /// Create OAuth2 Session + /// + /// Allow the user to login to their account using the OAuth2 provider of their + /// choice. Each OAuth2 provider should be enabled from the Appwrite console + /// first. Use the success and failure arguments to provide a redirect URL's + /// back to your app when login is completed. + /// + /// If there is already an active session, the new session will be attached to + /// the logged-in account. If there are no active sessions, the server will + /// attempt to look for a user with the same email address as the email + /// received from the OAuth2 provider and attach the new session to the + /// existing user. If no matching user is found - the server will create a new + /// user. + /// + /// A user is limited to 10 active sessions at a time by default. [Learn more + /// about session limits](/docs/authentication#limits). + /// + /// + Future createOAuth2Session( + {required String provider, + String? success, + String? failure, + List? scopes}) async { + final String path = '/account/sessions/oauth2/{provider}' + .replaceAll('{provider}', provider); + + final Map params = { + 'success': success, + 'failure': failure, + 'scopes': scopes, + 'project': client.config['project'], + }; + + final List query = []; + + params.forEach((key, value) { + if (value is List) { + for (var item in value) { + query.add(Uri.encodeComponent(key + '[]') + + '=' + + Uri.encodeComponent(item)); + } + } else if (value != null) { + query.add(Uri.encodeComponent(key) + '=' + Uri.encodeComponent(value)); + } + }); + + Uri endpoint = Uri.parse(client.endPoint); + Uri url = Uri( + scheme: endpoint.scheme, + host: endpoint.host, + port: endpoint.port, + path: endpoint.path + path, + query: query.join('&')); + + return client.webAuth(url, callbackUrlScheme: success); + } + + /// Create Phone session + /// + /// Sends the user an SMS with a secret key for creating a session. If the + /// provided user ID has not be registered, a new user will be created. Use the + /// returned user ID and secret and submit a request to the [PUT + /// /account/sessions/phone](/docs/client/account#accountUpdatePhoneSession) + /// endpoint to complete the login process. The secret sent to the user's phone + /// is valid for 15 minutes. + /// + /// A user is limited to 10 active sessions at a time by default. [Learn more + /// about session limits](/docs/authentication#limits). + /// + Future createPhoneSession( + {required String userId, required String phone}) async { + const String path = '/account/sessions/phone'; + + final Map params = { + 'userId': userId, + 'phone': phone, + }; + + final Map headers = { + 'content-type': 'application/json', + }; + + final cacheModel = ''; + final cacheKey = ''; + final cacheResponseIdKey = '\$id'; + final cacheResponseContainerKey = ''; + + final res = await client.call(withCacheParams( + CallParams( + HttpMethod.post, + path, + params: params, + headers: headers, + ), + CacheParams( + model: cacheModel, + key: cacheKey, + responseIdKey: cacheResponseIdKey, + responseContainerKey: cacheResponseContainerKey, + ), + )); + + return models.Token.fromMap(res.data); + } + + /// Create Phone Session (confirmation) + /// + /// Use this endpoint to complete creating a session with SMS. Use the + /// **userId** from the + /// [createPhoneSession](/docs/client/account#accountCreatePhoneSession) + /// endpoint and the **secret** received via SMS to successfully update and + /// confirm the phone session. + /// + Future updatePhoneSession( + {required String userId, required String secret}) async { + const String path = '/account/sessions/phone'; + + final Map params = { + 'userId': userId, + 'secret': secret, + }; + + final Map headers = { + 'content-type': 'application/json', + }; + + final cacheModel = ''; + final cacheKey = ''; + final cacheResponseIdKey = '\$id'; + final cacheResponseContainerKey = ''; + + final res = await client.call(withCacheParams( + CallParams( + HttpMethod.put, + path, + params: params, + headers: headers, + ), + CacheParams( + model: cacheModel, + key: cacheKey, + responseIdKey: cacheResponseIdKey, + responseContainerKey: cacheResponseContainerKey, + ), + )); + + return models.Session.fromMap(res.data); + } + + /// Get Session + /// + /// Use this endpoint to get a logged in user's session using a Session ID. + /// Inputting 'current' will return the current session being used. + /// + Future getSession({required String sessionId}) async { + final String path = + '/account/sessions/{sessionId}'.replaceAll('{sessionId}', sessionId); + + final Map params = {}; + + final Map headers = { + 'content-type': 'application/json', + }; + + final cacheModel = '/account/sessions'.replaceAll('{sessionId}', sessionId); + final cacheKey = sessionId; + final cacheResponseIdKey = '\$id'; + final cacheResponseContainerKey = ''; + + final res = await client.call(withCacheParams( + CallParams( + HttpMethod.get, + path, + params: params, + headers: headers, + ), + CacheParams( + model: cacheModel, + key: cacheKey, + responseIdKey: cacheResponseIdKey, + responseContainerKey: cacheResponseContainerKey, + ), + )); + + return models.Session.fromMap(res.data); + } + + /// Update OAuth Session (Refresh Tokens) + /// + /// Access tokens have limited lifespan and expire to mitigate security risks. + /// If session was created using an OAuth provider, this route can be used to + /// "refresh" the access token. + /// + Future updateSession({required String sessionId}) async { + final String path = + '/account/sessions/{sessionId}'.replaceAll('{sessionId}', sessionId); + + final Map params = {}; + + final Map headers = { + 'content-type': 'application/json', + }; + + final cacheModel = ''; + final cacheKey = ''; + final cacheResponseIdKey = '\$id'; + final cacheResponseContainerKey = ''; + + final res = await client.call(withCacheParams( + CallParams( + HttpMethod.patch, + path, + params: params, + headers: headers, + ), + CacheParams( + model: cacheModel, + key: cacheKey, + responseIdKey: cacheResponseIdKey, + responseContainerKey: cacheResponseContainerKey, + ), + )); + + return models.Session.fromMap(res.data); + } + + /// Delete Session + /// + /// Use this endpoint to log out the currently logged in user from all their + /// account sessions across all of their different devices. When using the + /// Session ID argument, only the unique session ID provided is deleted. + /// + /// + Future deleteSession({required String sessionId}) async { + final String path = + '/account/sessions/{sessionId}'.replaceAll('{sessionId}', sessionId); + + final Map params = {}; + + final Map headers = { + 'content-type': 'application/json', + }; + + final cacheModel = ''; + final cacheKey = ''; + final cacheResponseIdKey = '\$id'; + final cacheResponseContainerKey = ''; + + final res = await client.call(withCacheParams( + CallParams( + HttpMethod.delete, + path, + params: params, + headers: headers, + ), + CacheParams( + model: cacheModel, + key: cacheKey, + responseIdKey: cacheResponseIdKey, + responseContainerKey: cacheResponseContainerKey, + ), + )); + + return res.data; + } + + /// Update Status + /// + /// Block the currently logged in user account. Behind the scene, the user + /// record is not deleted but permanently blocked from any access. To + /// completely delete a user, use the Users API instead. + /// + Future updateStatus() async { + const String path = '/account/status'; + + final Map params = {}; + + final Map headers = { + 'content-type': 'application/json', + }; + + final cacheModel = ''; + final cacheKey = ''; + final cacheResponseIdKey = '\$id'; + final cacheResponseContainerKey = ''; + + final res = await client.call(withCacheParams( + CallParams( + HttpMethod.patch, + path, + params: params, + headers: headers, + ), + CacheParams( + model: cacheModel, + key: cacheKey, + responseIdKey: cacheResponseIdKey, + responseContainerKey: cacheResponseContainerKey, + ), + )); + + return models.Account.fromMap(res.data); + } + + /// Create Email Verification + /// + /// Use this endpoint to send a verification message to your user email address + /// to confirm they are the valid owners of that address. Both the **userId** + /// and **secret** arguments will be passed as query parameters to the URL you + /// have provided to be attached to the verification email. The provided URL + /// should redirect the user back to your app and allow you to complete the + /// verification process by verifying both the **userId** and **secret** + /// parameters. Learn more about how to [complete the verification + /// process](/docs/client/account#accountUpdateEmailVerification). The + /// verification link sent to the user's email address is valid for 7 days. + /// + /// Please note that in order to avoid a [Redirect + /// Attack](https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md), + /// the only valid redirect URLs are the ones from domains you have set when + /// adding your platforms in the console interface. + /// + /// + Future createVerification({required String url}) async { + const String path = '/account/verification'; + + final Map params = { + 'url': url, + }; + + final Map headers = { + 'content-type': 'application/json', + }; + + final cacheModel = ''; + final cacheKey = ''; + final cacheResponseIdKey = '\$id'; + final cacheResponseContainerKey = ''; + + final res = await client.call(withCacheParams( + CallParams( + HttpMethod.post, + path, + params: params, + headers: headers, + ), + CacheParams( + model: cacheModel, + key: cacheKey, + responseIdKey: cacheResponseIdKey, + responseContainerKey: cacheResponseContainerKey, + ), + )); + + return models.Token.fromMap(res.data); + } + + /// Create Email Verification (confirmation) + /// + /// Use this endpoint to complete the user email verification process. Use both + /// the **userId** and **secret** parameters that were attached to your app URL + /// to verify the user email ownership. If confirmed this route will return a + /// 200 status code. + /// + Future updateVerification( + {required String userId, required String secret}) async { + const String path = '/account/verification'; + + final Map params = { + 'userId': userId, + 'secret': secret, + }; + + final Map headers = { + 'content-type': 'application/json', + }; + + final cacheModel = ''; + final cacheKey = ''; + final cacheResponseIdKey = '\$id'; + final cacheResponseContainerKey = ''; + + final res = await client.call(withCacheParams( + CallParams( + HttpMethod.put, + path, + params: params, + headers: headers, + ), + CacheParams( + model: cacheModel, + key: cacheKey, + responseIdKey: cacheResponseIdKey, + responseContainerKey: cacheResponseContainerKey, + ), + )); + + return models.Token.fromMap(res.data); + } + + /// Create Phone Verification + /// + /// Use this endpoint to send a verification SMS to the currently logged in + /// user. This endpoint is meant for use after updating a user's phone number + /// using the [accountUpdatePhone](/docs/client/account#accountUpdatePhone) + /// endpoint. Learn more about how to [complete the verification + /// process](/docs/client/account#accountUpdatePhoneVerification). The + /// verification code sent to the user's phone number is valid for 15 minutes. + /// + Future createPhoneVerification() async { + const String path = '/account/verification/phone'; + + final Map params = {}; + + final Map headers = { + 'content-type': 'application/json', + }; + + final cacheModel = ''; + final cacheKey = ''; + final cacheResponseIdKey = '\$id'; + final cacheResponseContainerKey = ''; + + final res = await client.call(withCacheParams( + CallParams( + HttpMethod.post, + path, + params: params, + headers: headers, + ), + CacheParams( + model: cacheModel, + key: cacheKey, + responseIdKey: cacheResponseIdKey, + responseContainerKey: cacheResponseContainerKey, + ), + )); + + return models.Token.fromMap(res.data); + } + + /// Create Phone Verification (confirmation) + /// + /// Use this endpoint to complete the user phone verification process. Use the + /// **userId** and **secret** that were sent to your user's phone number to + /// verify the user email ownership. If confirmed this route will return a 200 + /// status code. + /// + Future updatePhoneVerification( + {required String userId, required String secret}) async { + const String path = '/account/verification/phone'; + + final Map params = { + 'userId': userId, + 'secret': secret, + }; + + final Map headers = { + 'content-type': 'application/json', + }; + + final cacheModel = ''; + final cacheKey = ''; + final cacheResponseIdKey = '\$id'; + final cacheResponseContainerKey = ''; + + final res = await client.call(withCacheParams( + CallParams( + HttpMethod.put, + path, + params: params, + headers: headers, + ), + CacheParams( + model: cacheModel, + key: cacheKey, + responseIdKey: cacheResponseIdKey, + responseContainerKey: cacheResponseContainerKey, + ), + )); + + return models.Token.fromMap(res.data); + } +} diff --git a/lib/services/avatars.dart b/lib/services/avatars.dart index 6962ff5f..3d35e9fa 100644 --- a/lib/services/avatars.dart +++ b/lib/services/avatars.dart @@ -1,200 +1,228 @@ part of appwrite; - /// The Avatars service aims to help you complete everyday tasks related to - /// your app image, icons, and avatars. +/// The Avatars service aims to help you complete everyday tasks related to +/// your app image, icons, and avatars. class Avatars extends Service { - Avatars(super.client); - - /// Get Browser Icon - /// - /// You can use this endpoint to show different browser icons to your users. - /// The code argument receives the browser code as it appears in your user [GET - /// /account/sessions](/docs/client/account#accountGetSessions) endpoint. Use - /// width, height and quality arguments to change the output settings. - /// - /// When one dimension is specified and the other is 0, the image is scaled - /// with preserved aspect ratio. If both dimensions are 0, the API provides an - /// image at source quality. If dimensions are not specified, the default size - /// of image returned is 100x100px. - /// - Future getBrowser({required String code, int? width, int? height, int? quality}) async { - final String path = '/avatars/browsers/{code}'.replaceAll('{code}', code); - - final Map params = { - - 'width': width, - 'height': height, - 'quality': quality, - - 'project': client.config['project'], - }; - - final res = await client.call(HttpMethod.get, path: path, params: params, responseType: ResponseType.bytes); - return res.data; - } - - /// Get Credit Card Icon - /// - /// The credit card endpoint will return you the icon of the credit card - /// provider you need. Use width, height and quality arguments to change the - /// output settings. - /// - /// When one dimension is specified and the other is 0, the image is scaled - /// with preserved aspect ratio. If both dimensions are 0, the API provides an - /// image at source quality. If dimensions are not specified, the default size - /// of image returned is 100x100px. - /// - /// - Future getCreditCard({required String code, int? width, int? height, int? quality}) async { - final String path = '/avatars/credit-cards/{code}'.replaceAll('{code}', code); - - final Map params = { - - 'width': width, - 'height': height, - 'quality': quality, - - 'project': client.config['project'], - }; - - final res = await client.call(HttpMethod.get, path: path, params: params, responseType: ResponseType.bytes); - return res.data; - } - - /// Get Favicon - /// - /// Use this endpoint to fetch the favorite icon (AKA favicon) of any remote - /// website URL. - /// - /// - Future getFavicon({required String url}) async { - const String path = '/avatars/favicon'; - - final Map params = { - - 'url': url, - - 'project': client.config['project'], - }; - - final res = await client.call(HttpMethod.get, path: path, params: params, responseType: ResponseType.bytes); - return res.data; - } - - /// Get Country Flag - /// - /// You can use this endpoint to show different country flags icons to your - /// users. The code argument receives the 2 letter country code. Use width, - /// height and quality arguments to change the output settings. Country codes - /// follow the [ISO 3166-1](http://en.wikipedia.org/wiki/ISO_3166-1) standard. - /// - /// When one dimension is specified and the other is 0, the image is scaled - /// with preserved aspect ratio. If both dimensions are 0, the API provides an - /// image at source quality. If dimensions are not specified, the default size - /// of image returned is 100x100px. - /// - /// - Future getFlag({required String code, int? width, int? height, int? quality}) async { - final String path = '/avatars/flags/{code}'.replaceAll('{code}', code); - - final Map params = { - - 'width': width, - 'height': height, - 'quality': quality, - - 'project': client.config['project'], - }; - - final res = await client.call(HttpMethod.get, path: path, params: params, responseType: ResponseType.bytes); - return res.data; - } - - /// Get Image from URL - /// - /// Use this endpoint to fetch a remote image URL and crop it to any image size - /// you want. This endpoint is very useful if you need to crop and display - /// remote images in your app or in case you want to make sure a 3rd party - /// image is properly served using a TLS protocol. - /// - /// When one dimension is specified and the other is 0, the image is scaled - /// with preserved aspect ratio. If both dimensions are 0, the API provides an - /// image at source quality. If dimensions are not specified, the default size - /// of image returned is 400x400px. - /// - /// - Future getImage({required String url, int? width, int? height}) async { - const String path = '/avatars/image'; - - final Map params = { - - 'url': url, - 'width': width, - 'height': height, - - 'project': client.config['project'], - }; - - final res = await client.call(HttpMethod.get, path: path, params: params, responseType: ResponseType.bytes); - return res.data; - } - - /// Get User Initials - /// - /// Use this endpoint to show your user initials avatar icon on your website or - /// app. By default, this route will try to print your logged-in user name or - /// email initials. You can also overwrite the user name if you pass the 'name' - /// parameter. If no name is given and no user is logged, an empty avatar will - /// be returned. - /// - /// You can use the color and background params to change the avatar colors. By - /// default, a random theme will be selected. The random theme will persist for - /// the user's initials when reloading the same theme will always return for - /// the same initials. - /// - /// When one dimension is specified and the other is 0, the image is scaled - /// with preserved aspect ratio. If both dimensions are 0, the API provides an - /// image at source quality. If dimensions are not specified, the default size - /// of image returned is 100x100px. - /// - /// - Future getInitials({String? name, int? width, int? height, String? background}) async { - const String path = '/avatars/initials'; - - final Map params = { - - 'name': name, - 'width': width, - 'height': height, - 'background': background, - - 'project': client.config['project'], - }; - - final res = await client.call(HttpMethod.get, path: path, params: params, responseType: ResponseType.bytes); - return res.data; - } - - /// Get QR Code - /// - /// Converts a given plain text to a QR code image. You can use the query - /// parameters to change the size and style of the resulting image. - /// - /// - Future getQR({required String text, int? size, int? margin, bool? download}) async { - const String path = '/avatars/qr'; - - final Map params = { - - 'text': text, - 'size': size, - 'margin': margin, - 'download': download, - - 'project': client.config['project'], - }; - - final res = await client.call(HttpMethod.get, path: path, params: params, responseType: ResponseType.bytes); - return res.data; - } -} \ No newline at end of file + Avatars(super.client); + + /// Get Browser Icon + /// + /// You can use this endpoint to show different browser icons to your users. + /// The code argument receives the browser code as it appears in your user [GET + /// /account/sessions](/docs/client/account#accountGetSessions) endpoint. Use + /// width, height and quality arguments to change the output settings. + /// + /// When one dimension is specified and the other is 0, the image is scaled + /// with preserved aspect ratio. If both dimensions are 0, the API provides an + /// image at source quality. If dimensions are not specified, the default size + /// of image returned is 100x100px. + /// + Future getBrowser( + {required String code, int? width, int? height, int? quality}) async { + final String path = '/avatars/browsers/{code}'.replaceAll('{code}', code); + + final Map params = { + 'width': width, + 'height': height, + 'quality': quality, + 'project': client.config['project'], + }; + + final res = await client.call(CallParams( + HttpMethod.get, + path, + params: params, + responseType: ResponseType.bytes, + )); + return res.data; + } + + /// Get Credit Card Icon + /// + /// The credit card endpoint will return you the icon of the credit card + /// provider you need. Use width, height and quality arguments to change the + /// output settings. + /// + /// When one dimension is specified and the other is 0, the image is scaled + /// with preserved aspect ratio. If both dimensions are 0, the API provides an + /// image at source quality. If dimensions are not specified, the default size + /// of image returned is 100x100px. + /// + /// + Future getCreditCard( + {required String code, int? width, int? height, int? quality}) async { + final String path = + '/avatars/credit-cards/{code}'.replaceAll('{code}', code); + + final Map params = { + 'width': width, + 'height': height, + 'quality': quality, + 'project': client.config['project'], + }; + + final res = await client.call(CallParams( + HttpMethod.get, + path, + params: params, + responseType: ResponseType.bytes, + )); + return res.data; + } + + /// Get Favicon + /// + /// Use this endpoint to fetch the favorite icon (AKA favicon) of any remote + /// website URL. + /// + /// + Future getFavicon({required String url}) async { + const String path = '/avatars/favicon'; + + final Map params = { + 'url': url, + 'project': client.config['project'], + }; + + final res = await client.call(CallParams( + HttpMethod.get, + path, + params: params, + responseType: ResponseType.bytes, + )); + return res.data; + } + + /// Get Country Flag + /// + /// You can use this endpoint to show different country flags icons to your + /// users. The code argument receives the 2 letter country code. Use width, + /// height and quality arguments to change the output settings. Country codes + /// follow the [ISO 3166-1](http://en.wikipedia.org/wiki/ISO_3166-1) standard. + /// + /// When one dimension is specified and the other is 0, the image is scaled + /// with preserved aspect ratio. If both dimensions are 0, the API provides an + /// image at source quality. If dimensions are not specified, the default size + /// of image returned is 100x100px. + /// + /// + Future getFlag( + {required String code, int? width, int? height, int? quality}) async { + final String path = '/avatars/flags/{code}'.replaceAll('{code}', code); + + final Map params = { + 'width': width, + 'height': height, + 'quality': quality, + 'project': client.config['project'], + }; + + final res = await client.call(CallParams( + HttpMethod.get, + path, + params: params, + responseType: ResponseType.bytes, + )); + return res.data; + } + + /// Get Image from URL + /// + /// Use this endpoint to fetch a remote image URL and crop it to any image size + /// you want. This endpoint is very useful if you need to crop and display + /// remote images in your app or in case you want to make sure a 3rd party + /// image is properly served using a TLS protocol. + /// + /// When one dimension is specified and the other is 0, the image is scaled + /// with preserved aspect ratio. If both dimensions are 0, the API provides an + /// image at source quality. If dimensions are not specified, the default size + /// of image returned is 400x400px. + /// + /// + Future getImage( + {required String url, int? width, int? height}) async { + const String path = '/avatars/image'; + + final Map params = { + 'url': url, + 'width': width, + 'height': height, + 'project': client.config['project'], + }; + + final res = await client.call(CallParams( + HttpMethod.get, + path, + params: params, + responseType: ResponseType.bytes, + )); + return res.data; + } + + /// Get User Initials + /// + /// Use this endpoint to show your user initials avatar icon on your website or + /// app. By default, this route will try to print your logged-in user name or + /// email initials. You can also overwrite the user name if you pass the 'name' + /// parameter. If no name is given and no user is logged, an empty avatar will + /// be returned. + /// + /// You can use the color and background params to change the avatar colors. By + /// default, a random theme will be selected. The random theme will persist for + /// the user's initials when reloading the same theme will always return for + /// the same initials. + /// + /// When one dimension is specified and the other is 0, the image is scaled + /// with preserved aspect ratio. If both dimensions are 0, the API provides an + /// image at source quality. If dimensions are not specified, the default size + /// of image returned is 100x100px. + /// + /// + Future getInitials( + {String? name, int? width, int? height, String? background}) async { + const String path = '/avatars/initials'; + + final Map params = { + 'name': name, + 'width': width, + 'height': height, + 'background': background, + 'project': client.config['project'], + }; + + final res = await client.call(CallParams( + HttpMethod.get, + path, + params: params, + responseType: ResponseType.bytes, + )); + return res.data; + } + + /// Get QR Code + /// + /// Converts a given plain text to a QR code image. You can use the query + /// parameters to change the size and style of the resulting image. + /// + /// + Future getQR( + {required String text, int? size, int? margin, bool? download}) async { + const String path = '/avatars/qr'; + + final Map params = { + 'text': text, + 'size': size, + 'margin': margin, + 'download': download, + 'project': client.config['project'], + }; + + final res = await client.call(CallParams( + HttpMethod.get, + path, + params: params, + responseType: ResponseType.bytes, + )); + return res.data; + } +} diff --git a/lib/services/databases.dart b/lib/services/databases.dart index d33661a1..ccd97ed8 100644 --- a/lib/services/databases.dart +++ b/lib/services/databases.dart @@ -1,189 +1,257 @@ part of appwrite; - /// The Databases service allows you to create structured collections of - /// documents, query and filter lists of documents +/// The Databases service allows you to create structured collections of +/// documents, query and filter lists of documents class Databases extends Service { - Databases(super.client); - - /// List Documents - /// - /// Get a list of all the user's documents in a given collection. You can use - /// the query params to filter your results. - /// - Future listDocuments({required String databaseId, required String collectionId, List? queries}) async { - final String path = '/databases/{databaseId}/collections/{collectionId}/documents'.replaceAll('{databaseId}', databaseId).replaceAll('{collectionId}', collectionId); - - final Map params = { - 'queries': queries, - }; - - final Map headers = { - 'content-type': 'application/json', - }; - - final cacheModel = '/databases/{databaseId}/collections/{collectionId}/documents'.replaceAll('{databaseId}', databaseId).replaceAll('{collectionId}', collectionId); - final cacheKey = ''; - final cacheResponseIdKey = '\$id'; - final cacheResponseContainerKey = 'documents'; - - final res = await client.call( - HttpMethod.get, - path: path, - params: params, - headers: headers, - cacheModel: cacheModel, - cacheKey: cacheKey, - cacheResponseIdKey: cacheResponseIdKey, - cacheResponseContainerKey: cacheResponseContainerKey, - ); - - return models.DocumentList.fromMap(res.data); - - } - - /// Create Document - /// - /// Create a new Document. Before using this route, you should create a new - /// collection resource using either a [server - /// integration](/docs/server/databases#databasesCreateCollection) API or - /// directly from your database console. - /// - Future createDocument({required String databaseId, required String collectionId, required String documentId, required Map data, List? permissions}) async { - final String path = '/databases/{databaseId}/collections/{collectionId}/documents'.replaceAll('{databaseId}', databaseId).replaceAll('{collectionId}', collectionId); - - final Map params = { - 'documentId': documentId, - 'data': data, - 'permissions': permissions, - }; - - final Map headers = { - 'content-type': 'application/json', - }; - - final cacheModel = '/databases/{databaseId}/collections/{collectionId}/documents'.replaceAll('{databaseId}', databaseId).replaceAll('{collectionId}', collectionId); - final cacheKey = documentId; - final cacheResponseIdKey = '\$id'; - final cacheResponseContainerKey = ''; - - final res = await client.call( - HttpMethod.post, - path: path, - params: params, - headers: headers, - cacheModel: cacheModel, - cacheKey: cacheKey, - cacheResponseIdKey: cacheResponseIdKey, - cacheResponseContainerKey: cacheResponseContainerKey, - ); - - return models.Document.fromMap(res.data); - - } - - /// Get Document - /// - /// Get a document by its unique ID. This endpoint response returns a JSON - /// object with the document data. - /// - Future getDocument({required String databaseId, required String collectionId, required String documentId}) async { - final String path = '/databases/{databaseId}/collections/{collectionId}/documents/{documentId}'.replaceAll('{databaseId}', databaseId).replaceAll('{collectionId}', collectionId).replaceAll('{documentId}', documentId); - - final Map params = { - }; - - final Map headers = { - 'content-type': 'application/json', - }; - - final cacheModel = '/databases/{databaseId}/collections/{collectionId}/documents'.replaceAll('{databaseId}', databaseId).replaceAll('{collectionId}', collectionId).replaceAll('{documentId}', documentId); - final cacheKey = documentId; - final cacheResponseIdKey = '\$id'; - final cacheResponseContainerKey = ''; - - final res = await client.call( - HttpMethod.get, - path: path, - params: params, - headers: headers, - cacheModel: cacheModel, - cacheKey: cacheKey, - cacheResponseIdKey: cacheResponseIdKey, - cacheResponseContainerKey: cacheResponseContainerKey, - ); - - return models.Document.fromMap(res.data); - - } - - /// Update Document - /// - /// Update a document by its unique ID. Using the patch method you can pass - /// only specific fields that will get updated. - /// - Future updateDocument({required String databaseId, required String collectionId, required String documentId, Map? data, List? permissions}) async { - final String path = '/databases/{databaseId}/collections/{collectionId}/documents/{documentId}'.replaceAll('{databaseId}', databaseId).replaceAll('{collectionId}', collectionId).replaceAll('{documentId}', documentId); - - final Map params = { - 'data': data, - 'permissions': permissions, - }; - - final Map headers = { - 'content-type': 'application/json', - }; - - final cacheModel = '/databases/{databaseId}/collections/{collectionId}/documents'.replaceAll('{databaseId}', databaseId).replaceAll('{collectionId}', collectionId).replaceAll('{documentId}', documentId); - final cacheKey = documentId; - final cacheResponseIdKey = '\$id'; - final cacheResponseContainerKey = ''; - - final res = await client.call( - HttpMethod.patch, - path: path, - params: params, - headers: headers, - cacheModel: cacheModel, - cacheKey: cacheKey, - cacheResponseIdKey: cacheResponseIdKey, - cacheResponseContainerKey: cacheResponseContainerKey, - ); - - return models.Document.fromMap(res.data); - - } - - /// Delete Document - /// - /// Delete a document by its unique ID. - /// - Future deleteDocument({required String databaseId, required String collectionId, required String documentId}) async { - final String path = '/databases/{databaseId}/collections/{collectionId}/documents/{documentId}'.replaceAll('{databaseId}', databaseId).replaceAll('{collectionId}', collectionId).replaceAll('{documentId}', documentId); - - final Map params = { - }; - - final Map headers = { - 'content-type': 'application/json', - }; - - final cacheModel = '/databases/{databaseId}/collections/{collectionId}/documents'.replaceAll('{databaseId}', databaseId).replaceAll('{collectionId}', collectionId).replaceAll('{documentId}', documentId); - final cacheKey = documentId; - final cacheResponseIdKey = '\$id'; - final cacheResponseContainerKey = ''; - - final res = await client.call( - HttpMethod.delete, - path: path, - params: params, - headers: headers, - cacheModel: cacheModel, - cacheKey: cacheKey, - cacheResponseIdKey: cacheResponseIdKey, - cacheResponseContainerKey: cacheResponseContainerKey, - ); - - return res.data; - - } -} \ No newline at end of file + Databases(super.client); + + /// List Documents + /// + /// Get a list of all the user's documents in a given collection. You can use + /// the query params to filter your results. + /// + Future listDocuments( + {required String databaseId, + required String collectionId, + List? queries}) async { + final String path = + '/databases/{databaseId}/collections/{collectionId}/documents' + .replaceAll('{databaseId}', databaseId) + .replaceAll('{collectionId}', collectionId); + + final Map params = { + 'queries': queries, + }; + + final Map headers = { + 'content-type': 'application/json', + }; + + final cacheModel = + '/databases/{databaseId}/collections/{collectionId}/documents' + .replaceAll('{databaseId}', databaseId) + .replaceAll('{collectionId}', collectionId); + final cacheKey = ''; + final cacheResponseIdKey = '\$id'; + final cacheResponseContainerKey = 'documents'; + + final res = await client.call(withCacheParams( + CallParams( + HttpMethod.get, + path, + params: params, + headers: headers, + ), + CacheParams( + model: cacheModel, + key: cacheKey, + responseIdKey: cacheResponseIdKey, + responseContainerKey: cacheResponseContainerKey, + ), + )); + + return models.DocumentList.fromMap(res.data); + } + + /// Create Document + /// + /// Create a new Document. Before using this route, you should create a new + /// collection resource using either a [server + /// integration](/docs/server/databases#databasesCreateCollection) API or + /// directly from your database console. + /// + Future createDocument( + {required String databaseId, + required String collectionId, + required String documentId, + required Map data, + List? permissions}) async { + final String path = + '/databases/{databaseId}/collections/{collectionId}/documents' + .replaceAll('{databaseId}', databaseId) + .replaceAll('{collectionId}', collectionId); + + final Map params = { + 'documentId': documentId, + 'data': data, + 'permissions': permissions, + }; + + final Map headers = { + 'content-type': 'application/json', + }; + + final cacheModel = + '/databases/{databaseId}/collections/{collectionId}/documents' + .replaceAll('{databaseId}', databaseId) + .replaceAll('{collectionId}', collectionId); + final cacheKey = documentId; + final cacheResponseIdKey = '\$id'; + final cacheResponseContainerKey = ''; + + final res = await client.call(withCacheParams( + CallParams( + HttpMethod.post, + path, + params: params, + headers: headers, + ), + CacheParams( + model: cacheModel, + key: cacheKey, + responseIdKey: cacheResponseIdKey, + responseContainerKey: cacheResponseContainerKey, + ), + )); + + return models.Document.fromMap(res.data); + } + + /// Get Document + /// + /// Get a document by its unique ID. This endpoint response returns a JSON + /// object with the document data. + /// + Future getDocument( + {required String databaseId, + required String collectionId, + required String documentId}) async { + final String path = + '/databases/{databaseId}/collections/{collectionId}/documents/{documentId}' + .replaceAll('{databaseId}', databaseId) + .replaceAll('{collectionId}', collectionId) + .replaceAll('{documentId}', documentId); + + final Map params = {}; + + final Map headers = { + 'content-type': 'application/json', + }; + + final cacheModel = + '/databases/{databaseId}/collections/{collectionId}/documents' + .replaceAll('{databaseId}', databaseId) + .replaceAll('{collectionId}', collectionId) + .replaceAll('{documentId}', documentId); + final cacheKey = documentId; + final cacheResponseIdKey = '\$id'; + final cacheResponseContainerKey = ''; + + final res = await client.call(withCacheParams( + CallParams( + HttpMethod.get, + path, + params: params, + headers: headers, + ), + CacheParams( + model: cacheModel, + key: cacheKey, + responseIdKey: cacheResponseIdKey, + responseContainerKey: cacheResponseContainerKey, + ), + )); + + return models.Document.fromMap(res.data); + } + + /// Update Document + /// + /// Update a document by its unique ID. Using the patch method you can pass + /// only specific fields that will get updated. + /// + Future updateDocument( + {required String databaseId, + required String collectionId, + required String documentId, + Map? data, + List? permissions}) async { + final String path = + '/databases/{databaseId}/collections/{collectionId}/documents/{documentId}' + .replaceAll('{databaseId}', databaseId) + .replaceAll('{collectionId}', collectionId) + .replaceAll('{documentId}', documentId); + + final Map params = { + 'data': data, + 'permissions': permissions, + }; + + final Map headers = { + 'content-type': 'application/json', + }; + + final cacheModel = + '/databases/{databaseId}/collections/{collectionId}/documents' + .replaceAll('{databaseId}', databaseId) + .replaceAll('{collectionId}', collectionId) + .replaceAll('{documentId}', documentId); + final cacheKey = documentId; + final cacheResponseIdKey = '\$id'; + final cacheResponseContainerKey = ''; + + final res = await client.call(withCacheParams( + CallParams( + HttpMethod.patch, + path, + params: params, + headers: headers, + ), + CacheParams( + model: cacheModel, + key: cacheKey, + responseIdKey: cacheResponseIdKey, + responseContainerKey: cacheResponseContainerKey, + ), + )); + + return models.Document.fromMap(res.data); + } + + /// Delete Document + /// + /// Delete a document by its unique ID. + /// + Future deleteDocument( + {required String databaseId, + required String collectionId, + required String documentId}) async { + final String path = + '/databases/{databaseId}/collections/{collectionId}/documents/{documentId}' + .replaceAll('{databaseId}', databaseId) + .replaceAll('{collectionId}', collectionId) + .replaceAll('{documentId}', documentId); + + final Map params = {}; + + final Map headers = { + 'content-type': 'application/json', + }; + + final cacheModel = + '/databases/{databaseId}/collections/{collectionId}/documents' + .replaceAll('{databaseId}', databaseId) + .replaceAll('{collectionId}', collectionId) + .replaceAll('{documentId}', documentId); + final cacheKey = documentId; + final cacheResponseIdKey = '\$id'; + final cacheResponseContainerKey = ''; + + final res = await client.call(withCacheParams( + CallParams( + HttpMethod.delete, + path, + params: params, + headers: headers, + ), + CacheParams( + model: cacheModel, + key: cacheKey, + responseIdKey: cacheResponseIdKey, + responseContainerKey: cacheResponseContainerKey, + ), + )); + + return res.data; + } +} diff --git a/lib/services/functions.dart b/lib/services/functions.dart index 1810f1b5..875c2b6c 100644 --- a/lib/services/functions.dart +++ b/lib/services/functions.dart @@ -1,117 +1,134 @@ part of appwrite; - /// The Functions Service allows you view, create and manage your Cloud - /// Functions. +/// The Functions Service allows you view, create and manage your Cloud +/// Functions. class Functions extends Service { - Functions(super.client); - - /// List Executions - /// - /// Get a list of all the current user function execution logs. You can use the - /// query params to filter your results. - /// - Future listExecutions({required String functionId, List? queries, String? search}) async { - final String path = '/functions/{functionId}/executions'.replaceAll('{functionId}', functionId); - - final Map params = { - 'queries': queries, - 'search': search, - }; - - final Map headers = { - 'content-type': 'application/json', - }; - - final cacheModel = ''; - final cacheKey = ''; - final cacheResponseIdKey = '\$id'; - final cacheResponseContainerKey = 'executions'; - - final res = await client.call( - HttpMethod.get, - path: path, - params: params, - headers: headers, - cacheModel: cacheModel, - cacheKey: cacheKey, - cacheResponseIdKey: cacheResponseIdKey, - cacheResponseContainerKey: cacheResponseContainerKey, - ); - - return models.ExecutionList.fromMap(res.data); - - } - - /// Create Execution - /// - /// Trigger a function execution. The returned object will return you the - /// current execution status. You can ping the `Get Execution` endpoint to get - /// updates on the current execution status. Once this endpoint is called, your - /// function execution process will start asynchronously. - /// - Future createExecution({required String functionId, String? data, bool? xasync}) async { - final String path = '/functions/{functionId}/executions'.replaceAll('{functionId}', functionId); - - final Map params = { - 'data': data, - 'async': xasync, - }; - - final Map headers = { - 'content-type': 'application/json', - }; - - final cacheModel = ''; - final cacheKey = ''; - final cacheResponseIdKey = '\$id'; - final cacheResponseContainerKey = ''; - - final res = await client.call( - HttpMethod.post, - path: path, - params: params, - headers: headers, - cacheModel: cacheModel, - cacheKey: cacheKey, - cacheResponseIdKey: cacheResponseIdKey, - cacheResponseContainerKey: cacheResponseContainerKey, - ); - - return models.Execution.fromMap(res.data); - - } - - /// Get Execution - /// - /// Get a function execution log by its unique ID. - /// - Future getExecution({required String functionId, required String executionId}) async { - final String path = '/functions/{functionId}/executions/{executionId}'.replaceAll('{functionId}', functionId).replaceAll('{executionId}', executionId); - - final Map params = { - }; - - final Map headers = { - 'content-type': 'application/json', - }; - - final cacheModel = ''; - final cacheKey = ''; - final cacheResponseIdKey = '\$id'; - final cacheResponseContainerKey = ''; - - final res = await client.call( - HttpMethod.get, - path: path, - params: params, - headers: headers, - cacheModel: cacheModel, - cacheKey: cacheKey, - cacheResponseIdKey: cacheResponseIdKey, - cacheResponseContainerKey: cacheResponseContainerKey, - ); - - return models.Execution.fromMap(res.data); - - } -} \ No newline at end of file + Functions(super.client); + + /// List Executions + /// + /// Get a list of all the current user function execution logs. You can use the + /// query params to filter your results. + /// + Future listExecutions( + {required String functionId, + List? queries, + String? search}) async { + final String path = '/functions/{functionId}/executions' + .replaceAll('{functionId}', functionId); + + final Map params = { + 'queries': queries, + 'search': search, + }; + + final Map headers = { + 'content-type': 'application/json', + }; + + final cacheModel = ''; + final cacheKey = ''; + final cacheResponseIdKey = '\$id'; + final cacheResponseContainerKey = 'executions'; + + final res = await client.call(withCacheParams( + CallParams( + HttpMethod.get, + path, + params: params, + headers: headers, + ), + CacheParams( + model: cacheModel, + key: cacheKey, + responseIdKey: cacheResponseIdKey, + responseContainerKey: cacheResponseContainerKey, + ), + )); + + return models.ExecutionList.fromMap(res.data); + } + + /// Create Execution + /// + /// Trigger a function execution. The returned object will return you the + /// current execution status. You can ping the `Get Execution` endpoint to get + /// updates on the current execution status. Once this endpoint is called, your + /// function execution process will start asynchronously. + /// + Future createExecution( + {required String functionId, String? data, bool? xasync}) async { + final String path = '/functions/{functionId}/executions' + .replaceAll('{functionId}', functionId); + + final Map params = { + 'data': data, + 'async': xasync, + }; + + final Map headers = { + 'content-type': 'application/json', + }; + + final cacheModel = ''; + final cacheKey = ''; + final cacheResponseIdKey = '\$id'; + final cacheResponseContainerKey = ''; + + final res = await client.call(withCacheParams( + CallParams( + HttpMethod.post, + path, + params: params, + headers: headers, + ), + CacheParams( + model: cacheModel, + key: cacheKey, + responseIdKey: cacheResponseIdKey, + responseContainerKey: cacheResponseContainerKey, + ), + )); + + return models.Execution.fromMap(res.data); + } + + /// Get Execution + /// + /// Get a function execution log by its unique ID. + /// + Future getExecution( + {required String functionId, required String executionId}) async { + final String path = '/functions/{functionId}/executions/{executionId}' + .replaceAll('{functionId}', functionId) + .replaceAll('{executionId}', executionId); + + final Map params = {}; + + final Map headers = { + 'content-type': 'application/json', + }; + + final cacheModel = ''; + final cacheKey = ''; + final cacheResponseIdKey = '\$id'; + final cacheResponseContainerKey = ''; + + final res = await client.call(withCacheParams( + CallParams( + HttpMethod.get, + path, + params: params, + headers: headers, + ), + CacheParams( + model: cacheModel, + key: cacheKey, + responseIdKey: cacheResponseIdKey, + responseContainerKey: cacheResponseContainerKey, + ), + )); + + return models.Execution.fromMap(res.data); + } +} diff --git a/lib/services/graphql.dart b/lib/services/graphql.dart index 2f92eaf1..c36a96d5 100644 --- a/lib/services/graphql.dart +++ b/lib/services/graphql.dart @@ -1,77 +1,85 @@ part of appwrite; - /// The GraphQL API allows you to query and mutate your Appwrite server using - /// GraphQL. +/// The GraphQL API allows you to query and mutate your Appwrite server using +/// GraphQL. class Graphql extends Service { - Graphql(super.client); + Graphql(super.client); - /// GraphQL Endpoint - /// - /// Execute a GraphQL mutation. - /// - Future query({required Map query}) async { - const String path = '/graphql'; + /// GraphQL Endpoint + /// + /// Execute a GraphQL mutation. + /// + Future query({required Map query}) async { + const String path = '/graphql'; - final Map params = { - 'query': query, - }; + final Map params = { + 'query': query, + }; - final Map headers = { - 'x-sdk-graphql': 'true', 'content-type': 'application/json', - }; + final Map headers = { + 'x-sdk-graphql': 'true', + 'content-type': 'application/json', + }; - final cacheModel = ''; - final cacheKey = ''; - final cacheResponseIdKey = '\$id'; - final cacheResponseContainerKey = ''; + final cacheModel = ''; + final cacheKey = ''; + final cacheResponseIdKey = '\$id'; + final cacheResponseContainerKey = ''; - final res = await client.call( - HttpMethod.post, - path: path, - params: params, - headers: headers, - cacheModel: cacheModel, - cacheKey: cacheKey, - cacheResponseIdKey: cacheResponseIdKey, - cacheResponseContainerKey: cacheResponseContainerKey, - ); + final res = await client.call(withCacheParams( + CallParams( + HttpMethod.post, + path, + params: params, + headers: headers, + ), + CacheParams( + model: cacheModel, + key: cacheKey, + responseIdKey: cacheResponseIdKey, + responseContainerKey: cacheResponseContainerKey, + ), + )); - return res.data; + return res.data; + } - } + /// GraphQL Endpoint + /// + /// Execute a GraphQL mutation. + /// + Future mutation({required Map query}) async { + const String path = '/graphql/mutation'; - /// GraphQL Endpoint - /// - /// Execute a GraphQL mutation. - /// - Future mutation({required Map query}) async { - const String path = '/graphql/mutation'; + final Map params = { + 'query': query, + }; - final Map params = { - 'query': query, - }; + final Map headers = { + 'x-sdk-graphql': 'true', + 'content-type': 'application/json', + }; - final Map headers = { - 'x-sdk-graphql': 'true', 'content-type': 'application/json', - }; + final cacheModel = ''; + final cacheKey = ''; + final cacheResponseIdKey = '\$id'; + final cacheResponseContainerKey = ''; - final cacheModel = ''; - final cacheKey = ''; - final cacheResponseIdKey = '\$id'; - final cacheResponseContainerKey = ''; + final res = await client.call(withCacheParams( + CallParams( + HttpMethod.post, + path, + params: params, + headers: headers, + ), + CacheParams( + model: cacheModel, + key: cacheKey, + responseIdKey: cacheResponseIdKey, + responseContainerKey: cacheResponseContainerKey, + ), + )); - final res = await client.call( - HttpMethod.post, - path: path, - params: params, - headers: headers, - cacheModel: cacheModel, - cacheKey: cacheKey, - cacheResponseIdKey: cacheResponseIdKey, - cacheResponseContainerKey: cacheResponseContainerKey, - ); - - return res.data; - - } -} \ No newline at end of file + return res.data; + } +} diff --git a/lib/services/locale.dart b/lib/services/locale.dart index 2bc60905..2623932a 100644 --- a/lib/services/locale.dart +++ b/lib/services/locale.dart @@ -1,257 +1,271 @@ part of appwrite; - /// The Locale service allows you to customize your app based on your users' - /// location. +/// The Locale service allows you to customize your app based on your users' +/// location. class Locale extends Service { - Locale(super.client); - - /// Get User Locale - /// - /// Get the current user location based on IP. Returns an object with user - /// country code, country name, continent name, continent code, ip address and - /// suggested currency. You can use the locale header to get the data in a - /// supported language. - /// - /// ([IP Geolocation by DB-IP](https://db-ip.com)) - /// - Future get() async { - const String path = '/locale'; - - final Map params = { - }; - - final Map headers = { - 'content-type': 'application/json', - }; - - final cacheModel = '/locale'; - final cacheKey = 'current'; - final cacheResponseIdKey = '\$id'; - final cacheResponseContainerKey = ''; - - final res = await client.call( - HttpMethod.get, - path: path, - params: params, - headers: headers, - cacheModel: cacheModel, - cacheKey: cacheKey, - cacheResponseIdKey: cacheResponseIdKey, - cacheResponseContainerKey: cacheResponseContainerKey, - ); - - return models.Locale.fromMap(res.data); - - } - - /// List Continents - /// - /// List of all continents. You can use the locale header to get the data in a - /// supported language. - /// - Future listContinents() async { - const String path = '/locale/continents'; - - final Map params = { - }; - - final Map headers = { - 'content-type': 'application/json', - }; - - final cacheModel = '/locale/continents'; - final cacheKey = ''; - final cacheResponseIdKey = 'code'; - final cacheResponseContainerKey = 'continents'; - - final res = await client.call( - HttpMethod.get, - path: path, - params: params, - headers: headers, - cacheModel: cacheModel, - cacheKey: cacheKey, - cacheResponseIdKey: cacheResponseIdKey, - cacheResponseContainerKey: cacheResponseContainerKey, - ); - - return models.ContinentList.fromMap(res.data); - - } - - /// List Countries - /// - /// List of all countries. You can use the locale header to get the data in a - /// supported language. - /// - Future listCountries() async { - const String path = '/locale/countries'; - - final Map params = { - }; - - final Map headers = { - 'content-type': 'application/json', - }; - - final cacheModel = '/locale/countries'; - final cacheKey = ''; - final cacheResponseIdKey = 'code'; - final cacheResponseContainerKey = 'countries'; - - final res = await client.call( - HttpMethod.get, - path: path, - params: params, - headers: headers, - cacheModel: cacheModel, - cacheKey: cacheKey, - cacheResponseIdKey: cacheResponseIdKey, - cacheResponseContainerKey: cacheResponseContainerKey, - ); - - return models.CountryList.fromMap(res.data); - - } - - /// List EU Countries - /// - /// List of all countries that are currently members of the EU. You can use the - /// locale header to get the data in a supported language. - /// - Future listCountriesEU() async { - const String path = '/locale/countries/eu'; - - final Map params = { - }; - - final Map headers = { - 'content-type': 'application/json', - }; - - final cacheModel = '/locale/countries/eu'; - final cacheKey = ''; - final cacheResponseIdKey = 'code'; - final cacheResponseContainerKey = 'countries'; - - final res = await client.call( - HttpMethod.get, - path: path, - params: params, - headers: headers, - cacheModel: cacheModel, - cacheKey: cacheKey, - cacheResponseIdKey: cacheResponseIdKey, - cacheResponseContainerKey: cacheResponseContainerKey, - ); - - return models.CountryList.fromMap(res.data); - - } - - /// List Countries Phone Codes - /// - /// List of all countries phone codes. You can use the locale header to get the - /// data in a supported language. - /// - Future listCountriesPhones() async { - const String path = '/locale/countries/phones'; - - final Map params = { - }; - - final Map headers = { - 'content-type': 'application/json', - }; - - final cacheModel = '/locale/countries/phones'; - final cacheKey = ''; - final cacheResponseIdKey = 'countryCode'; - final cacheResponseContainerKey = 'phones'; - - final res = await client.call( - HttpMethod.get, - path: path, - params: params, - headers: headers, - cacheModel: cacheModel, - cacheKey: cacheKey, - cacheResponseIdKey: cacheResponseIdKey, - cacheResponseContainerKey: cacheResponseContainerKey, - ); - - return models.PhoneList.fromMap(res.data); - - } - - /// List Currencies - /// - /// List of all currencies, including currency symbol, name, plural, and - /// decimal digits for all major and minor currencies. You can use the locale - /// header to get the data in a supported language. - /// - Future listCurrencies() async { - const String path = '/locale/currencies'; - - final Map params = { - }; - - final Map headers = { - 'content-type': 'application/json', - }; - - final cacheModel = '/locale/currencies'; - final cacheKey = ''; - final cacheResponseIdKey = 'code'; - final cacheResponseContainerKey = 'currencies'; - - final res = await client.call( - HttpMethod.get, - path: path, - params: params, - headers: headers, - cacheModel: cacheModel, - cacheKey: cacheKey, - cacheResponseIdKey: cacheResponseIdKey, - cacheResponseContainerKey: cacheResponseContainerKey, - ); - - return models.CurrencyList.fromMap(res.data); - - } - - /// List Languages - /// - /// List of all languages classified by ISO 639-1 including 2-letter code, name - /// in English, and name in the respective language. - /// - Future listLanguages() async { - const String path = '/locale/languages'; - - final Map params = { - }; - - final Map headers = { - 'content-type': 'application/json', - }; - - final cacheModel = '/locale/languages'; - final cacheKey = ''; - final cacheResponseIdKey = 'code'; - final cacheResponseContainerKey = 'languages'; - - final res = await client.call( - HttpMethod.get, - path: path, - params: params, - headers: headers, - cacheModel: cacheModel, - cacheKey: cacheKey, - cacheResponseIdKey: cacheResponseIdKey, - cacheResponseContainerKey: cacheResponseContainerKey, - ); - - return models.LanguageList.fromMap(res.data); - - } -} \ No newline at end of file + Locale(super.client); + + /// Get User Locale + /// + /// Get the current user location based on IP. Returns an object with user + /// country code, country name, continent name, continent code, ip address and + /// suggested currency. You can use the locale header to get the data in a + /// supported language. + /// + /// ([IP Geolocation by DB-IP](https://db-ip.com)) + /// + Future get() async { + const String path = '/locale'; + + final Map params = {}; + + final Map headers = { + 'content-type': 'application/json', + }; + + final cacheModel = '/locale'; + final cacheKey = 'current'; + final cacheResponseIdKey = '\$id'; + final cacheResponseContainerKey = ''; + + final res = await client.call(withCacheParams( + CallParams( + HttpMethod.get, + path, + params: params, + headers: headers, + ), + CacheParams( + model: cacheModel, + key: cacheKey, + responseIdKey: cacheResponseIdKey, + responseContainerKey: cacheResponseContainerKey, + ), + )); + + return models.Locale.fromMap(res.data); + } + + /// List Continents + /// + /// List of all continents. You can use the locale header to get the data in a + /// supported language. + /// + Future listContinents() async { + const String path = '/locale/continents'; + + final Map params = {}; + + final Map headers = { + 'content-type': 'application/json', + }; + + final cacheModel = '/locale/continents'; + final cacheKey = ''; + final cacheResponseIdKey = 'code'; + final cacheResponseContainerKey = 'continents'; + + final res = await client.call(withCacheParams( + CallParams( + HttpMethod.get, + path, + params: params, + headers: headers, + ), + CacheParams( + model: cacheModel, + key: cacheKey, + responseIdKey: cacheResponseIdKey, + responseContainerKey: cacheResponseContainerKey, + ), + )); + + return models.ContinentList.fromMap(res.data); + } + + /// List Countries + /// + /// List of all countries. You can use the locale header to get the data in a + /// supported language. + /// + Future listCountries() async { + const String path = '/locale/countries'; + + final Map params = {}; + + final Map headers = { + 'content-type': 'application/json', + }; + + final cacheModel = '/locale/countries'; + final cacheKey = ''; + final cacheResponseIdKey = 'code'; + final cacheResponseContainerKey = 'countries'; + + final res = await client.call(withCacheParams( + CallParams( + HttpMethod.get, + path, + params: params, + headers: headers, + ), + CacheParams( + model: cacheModel, + key: cacheKey, + responseIdKey: cacheResponseIdKey, + responseContainerKey: cacheResponseContainerKey, + ), + )); + + return models.CountryList.fromMap(res.data); + } + + /// List EU Countries + /// + /// List of all countries that are currently members of the EU. You can use the + /// locale header to get the data in a supported language. + /// + Future listCountriesEU() async { + const String path = '/locale/countries/eu'; + + final Map params = {}; + + final Map headers = { + 'content-type': 'application/json', + }; + + final cacheModel = '/locale/countries/eu'; + final cacheKey = ''; + final cacheResponseIdKey = 'code'; + final cacheResponseContainerKey = 'countries'; + + final res = await client.call(withCacheParams( + CallParams( + HttpMethod.get, + path, + params: params, + headers: headers, + ), + CacheParams( + model: cacheModel, + key: cacheKey, + responseIdKey: cacheResponseIdKey, + responseContainerKey: cacheResponseContainerKey, + ), + )); + + return models.CountryList.fromMap(res.data); + } + + /// List Countries Phone Codes + /// + /// List of all countries phone codes. You can use the locale header to get the + /// data in a supported language. + /// + Future listCountriesPhones() async { + const String path = '/locale/countries/phones'; + + final Map params = {}; + + final Map headers = { + 'content-type': 'application/json', + }; + + final cacheModel = '/locale/countries/phones'; + final cacheKey = ''; + final cacheResponseIdKey = 'countryCode'; + final cacheResponseContainerKey = 'phones'; + + final res = await client.call(withCacheParams( + CallParams( + HttpMethod.get, + path, + params: params, + headers: headers, + ), + CacheParams( + model: cacheModel, + key: cacheKey, + responseIdKey: cacheResponseIdKey, + responseContainerKey: cacheResponseContainerKey, + ), + )); + + return models.PhoneList.fromMap(res.data); + } + + /// List Currencies + /// + /// List of all currencies, including currency symbol, name, plural, and + /// decimal digits for all major and minor currencies. You can use the locale + /// header to get the data in a supported language. + /// + Future listCurrencies() async { + const String path = '/locale/currencies'; + + final Map params = {}; + + final Map headers = { + 'content-type': 'application/json', + }; + + final cacheModel = '/locale/currencies'; + final cacheKey = ''; + final cacheResponseIdKey = 'code'; + final cacheResponseContainerKey = 'currencies'; + + final res = await client.call(withCacheParams( + CallParams( + HttpMethod.get, + path, + params: params, + headers: headers, + ), + CacheParams( + model: cacheModel, + key: cacheKey, + responseIdKey: cacheResponseIdKey, + responseContainerKey: cacheResponseContainerKey, + ), + )); + + return models.CurrencyList.fromMap(res.data); + } + + /// List Languages + /// + /// List of all languages classified by ISO 639-1 including 2-letter code, name + /// in English, and name in the respective language. + /// + Future listLanguages() async { + const String path = '/locale/languages'; + + final Map params = {}; + + final Map headers = { + 'content-type': 'application/json', + }; + + final cacheModel = '/locale/languages'; + final cacheKey = ''; + final cacheResponseIdKey = 'code'; + final cacheResponseContainerKey = 'languages'; + + final res = await client.call(withCacheParams( + CallParams( + HttpMethod.get, + path, + params: params, + headers: headers, + ), + CacheParams( + model: cacheModel, + key: cacheKey, + responseIdKey: cacheResponseIdKey, + responseContainerKey: cacheResponseContainerKey, + ), + )); + + return models.LanguageList.fromMap(res.data); + } +} diff --git a/lib/services/storage.dart b/lib/services/storage.dart index d91754dd..c1723a75 100644 --- a/lib/services/storage.dart +++ b/lib/services/storage.dart @@ -1,271 +1,326 @@ part of appwrite; - /// The Storage service allows you to manage your project files. +/// The Storage service allows you to manage your project files. class Storage extends Service { - Storage(super.client); - - /// List Files - /// - /// Get a list of all the user files. You can use the query params to filter - /// your results. - /// - Future listFiles({required String bucketId, List? queries, String? search}) async { - final String path = '/storage/buckets/{bucketId}/files'.replaceAll('{bucketId}', bucketId); - - final Map params = { - 'queries': queries, - 'search': search, - }; - - final Map headers = { - 'content-type': 'application/json', - }; - - final cacheModel = ''; - final cacheKey = ''; - final cacheResponseIdKey = '\$id'; - final cacheResponseContainerKey = 'files'; - - final res = await client.call( - HttpMethod.get, - path: path, - params: params, - headers: headers, - cacheModel: cacheModel, - cacheKey: cacheKey, - cacheResponseIdKey: cacheResponseIdKey, - cacheResponseContainerKey: cacheResponseContainerKey, - ); - - return models.FileList.fromMap(res.data); - - } - - /// Create File - /// - /// Create a new file. Before using this route, you should create a new bucket - /// resource using either a [server - /// integration](/docs/server/storage#storageCreateBucket) API or directly from - /// your Appwrite console. - /// - /// Larger files should be uploaded using multiple requests with the - /// [content-range](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Range) - /// header to send a partial request with a maximum supported chunk of `5MB`. - /// The `content-range` header values should always be in bytes. - /// - /// When the first request is sent, the server will return the **File** object, - /// and the subsequent part request must include the file's **id** in - /// `x-appwrite-id` header to allow the server to know that the partial upload - /// is for the existing file and not for a new one. - /// - /// If you're creating a new file using one of the Appwrite SDKs, all the - /// chunking logic will be managed by the SDK internally. - /// - /// - Future createFile({required String bucketId, required String fileId, required InputFile file, List? permissions, Function(UploadProgress)? onProgress}) async { - final String path = '/storage/buckets/{bucketId}/files'.replaceAll('{bucketId}', bucketId); - - final Map params = { - - - 'fileId': fileId, - 'file': file, - 'permissions': permissions, - }; - - final Map headers = { - 'content-type': 'multipart/form-data', - }; - - String idParamName = ''; - idParamName = 'fileId'; - final paramName = 'file'; - final res = await client.chunkedUpload( - path: path, - params: params, - paramName: paramName, - idParamName: idParamName, - headers: headers, - onProgress: onProgress, - ); - - return models.File.fromMap(res.data); - - } - - /// Get File - /// - /// Get a file by its unique ID. This endpoint response returns a JSON object - /// with the file metadata. - /// - Future getFile({required String bucketId, required String fileId}) async { - final String path = '/storage/buckets/{bucketId}/files/{fileId}'.replaceAll('{bucketId}', bucketId).replaceAll('{fileId}', fileId); - - final Map params = { - }; - - final Map headers = { - 'content-type': 'application/json', - }; - - final cacheModel = ''; - final cacheKey = ''; - final cacheResponseIdKey = '\$id'; - final cacheResponseContainerKey = ''; - - final res = await client.call( - HttpMethod.get, - path: path, - params: params, - headers: headers, - cacheModel: cacheModel, - cacheKey: cacheKey, - cacheResponseIdKey: cacheResponseIdKey, - cacheResponseContainerKey: cacheResponseContainerKey, - ); - - return models.File.fromMap(res.data); - - } - - /// Update File - /// - /// Update a file by its unique ID. Only users with write permissions have - /// access to update this resource. - /// - Future updateFile({required String bucketId, required String fileId, List? permissions}) async { - final String path = '/storage/buckets/{bucketId}/files/{fileId}'.replaceAll('{bucketId}', bucketId).replaceAll('{fileId}', fileId); - - final Map params = { - 'permissions': permissions, - }; - - final Map headers = { - 'content-type': 'application/json', - }; - - final cacheModel = ''; - final cacheKey = ''; - final cacheResponseIdKey = '\$id'; - final cacheResponseContainerKey = ''; - - final res = await client.call( - HttpMethod.put, - path: path, - params: params, - headers: headers, - cacheModel: cacheModel, - cacheKey: cacheKey, - cacheResponseIdKey: cacheResponseIdKey, - cacheResponseContainerKey: cacheResponseContainerKey, - ); - - return models.File.fromMap(res.data); - - } - - /// Delete File - /// - /// Delete a file by its unique ID. Only users with write permissions have - /// access to delete this resource. - /// - Future deleteFile({required String bucketId, required String fileId}) async { - final String path = '/storage/buckets/{bucketId}/files/{fileId}'.replaceAll('{bucketId}', bucketId).replaceAll('{fileId}', fileId); - - final Map params = { - }; - - final Map headers = { - 'content-type': 'application/json', - }; - - final cacheModel = ''; - final cacheKey = ''; - final cacheResponseIdKey = '\$id'; - final cacheResponseContainerKey = ''; - - final res = await client.call( - HttpMethod.delete, - path: path, - params: params, - headers: headers, - cacheModel: cacheModel, - cacheKey: cacheKey, - cacheResponseIdKey: cacheResponseIdKey, - cacheResponseContainerKey: cacheResponseContainerKey, - ); - - return res.data; - - } - - /// Get File for Download - /// - /// Get a file content by its unique ID. The endpoint response return with a - /// 'Content-Disposition: attachment' header that tells the browser to start - /// downloading the file to user downloads directory. - /// - Future getFileDownload({required String bucketId, required String fileId}) async { - final String path = '/storage/buckets/{bucketId}/files/{fileId}/download'.replaceAll('{bucketId}', bucketId).replaceAll('{fileId}', fileId); - - final Map params = { - - - 'project': client.config['project'], - }; - - final res = await client.call(HttpMethod.get, path: path, params: params, responseType: ResponseType.bytes); - return res.data; - } - - /// Get File Preview - /// - /// Get a file preview image. Currently, this method supports preview for image - /// files (jpg, png, and gif), other supported formats, like pdf, docs, slides, - /// and spreadsheets, will return the file icon image. You can also pass query - /// string arguments for cutting and resizing your preview image. Preview is - /// supported only for image files smaller than 10MB. - /// - Future getFilePreview({required String bucketId, required String fileId, int? width, int? height, String? gravity, int? quality, int? borderWidth, String? borderColor, int? borderRadius, double? opacity, int? rotation, String? background, String? output}) async { - final String path = '/storage/buckets/{bucketId}/files/{fileId}/preview'.replaceAll('{bucketId}', bucketId).replaceAll('{fileId}', fileId); - - final Map params = { - - 'width': width, - 'height': height, - 'gravity': gravity, - 'quality': quality, - 'borderWidth': borderWidth, - 'borderColor': borderColor, - 'borderRadius': borderRadius, - 'opacity': opacity, - 'rotation': rotation, - 'background': background, - 'output': output, - - 'project': client.config['project'], - }; - - final res = await client.call(HttpMethod.get, path: path, params: params, responseType: ResponseType.bytes); - return res.data; - } - - /// Get File for View - /// - /// Get a file content by its unique ID. This endpoint is similar to the - /// download method but returns with no 'Content-Disposition: attachment' - /// header. - /// - Future getFileView({required String bucketId, required String fileId}) async { - final String path = '/storage/buckets/{bucketId}/files/{fileId}/view'.replaceAll('{bucketId}', bucketId).replaceAll('{fileId}', fileId); - - final Map params = { - - - 'project': client.config['project'], - }; - - final res = await client.call(HttpMethod.get, path: path, params: params, responseType: ResponseType.bytes); - return res.data; - } -} \ No newline at end of file + Storage(super.client); + + /// List Files + /// + /// Get a list of all the user files. You can use the query params to filter + /// your results. + /// + Future listFiles( + {required String bucketId, List? queries, String? search}) async { + final String path = + '/storage/buckets/{bucketId}/files'.replaceAll('{bucketId}', bucketId); + + final Map params = { + 'queries': queries, + 'search': search, + }; + + final Map headers = { + 'content-type': 'application/json', + }; + + final cacheModel = ''; + final cacheKey = ''; + final cacheResponseIdKey = '\$id'; + final cacheResponseContainerKey = 'files'; + + final res = await client.call(withCacheParams( + CallParams( + HttpMethod.get, + path, + params: params, + headers: headers, + ), + CacheParams( + model: cacheModel, + key: cacheKey, + responseIdKey: cacheResponseIdKey, + responseContainerKey: cacheResponseContainerKey, + ), + )); + + return models.FileList.fromMap(res.data); + } + + /// Create File + /// + /// Create a new file. Before using this route, you should create a new bucket + /// resource using either a [server + /// integration](/docs/server/storage#storageCreateBucket) API or directly from + /// your Appwrite console. + /// + /// Larger files should be uploaded using multiple requests with the + /// [content-range](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Range) + /// header to send a partial request with a maximum supported chunk of `5MB`. + /// The `content-range` header values should always be in bytes. + /// + /// When the first request is sent, the server will return the **File** object, + /// and the subsequent part request must include the file's **id** in + /// `x-appwrite-id` header to allow the server to know that the partial upload + /// is for the existing file and not for a new one. + /// + /// If you're creating a new file using one of the Appwrite SDKs, all the + /// chunking logic will be managed by the SDK internally. + /// + /// + Future createFile( + {required String bucketId, + required String fileId, + required InputFile file, + List? permissions, + Function(UploadProgress)? onProgress}) async { + final String path = + '/storage/buckets/{bucketId}/files'.replaceAll('{bucketId}', bucketId); + + final Map params = { + 'fileId': fileId, + 'file': file, + 'permissions': permissions, + }; + + final Map headers = { + 'content-type': 'multipart/form-data', + }; + + String idParamName = ''; + idParamName = 'fileId'; + final paramName = 'file'; + final res = await client.chunkedUpload( + path: path, + params: params, + paramName: paramName, + idParamName: idParamName, + headers: headers, + onProgress: onProgress, + ); + + return models.File.fromMap(res.data); + } + + /// Get File + /// + /// Get a file by its unique ID. This endpoint response returns a JSON object + /// with the file metadata. + /// + Future getFile( + {required String bucketId, required String fileId}) async { + final String path = '/storage/buckets/{bucketId}/files/{fileId}' + .replaceAll('{bucketId}', bucketId) + .replaceAll('{fileId}', fileId); + + final Map params = {}; + + final Map headers = { + 'content-type': 'application/json', + }; + + final cacheModel = ''; + final cacheKey = ''; + final cacheResponseIdKey = '\$id'; + final cacheResponseContainerKey = ''; + + final res = await client.call(withCacheParams( + CallParams( + HttpMethod.get, + path, + params: params, + headers: headers, + ), + CacheParams( + model: cacheModel, + key: cacheKey, + responseIdKey: cacheResponseIdKey, + responseContainerKey: cacheResponseContainerKey, + ), + )); + + return models.File.fromMap(res.data); + } + + /// Update File + /// + /// Update a file by its unique ID. Only users with write permissions have + /// access to update this resource. + /// + Future updateFile( + {required String bucketId, + required String fileId, + List? permissions}) async { + final String path = '/storage/buckets/{bucketId}/files/{fileId}' + .replaceAll('{bucketId}', bucketId) + .replaceAll('{fileId}', fileId); + + final Map params = { + 'permissions': permissions, + }; + + final Map headers = { + 'content-type': 'application/json', + }; + + final cacheModel = ''; + final cacheKey = ''; + final cacheResponseIdKey = '\$id'; + final cacheResponseContainerKey = ''; + + final res = await client.call(withCacheParams( + CallParams( + HttpMethod.put, + path, + params: params, + headers: headers, + ), + CacheParams( + model: cacheModel, + key: cacheKey, + responseIdKey: cacheResponseIdKey, + responseContainerKey: cacheResponseContainerKey, + ), + )); + + return models.File.fromMap(res.data); + } + + /// Delete File + /// + /// Delete a file by its unique ID. Only users with write permissions have + /// access to delete this resource. + /// + Future deleteFile({required String bucketId, required String fileId}) async { + final String path = '/storage/buckets/{bucketId}/files/{fileId}' + .replaceAll('{bucketId}', bucketId) + .replaceAll('{fileId}', fileId); + + final Map params = {}; + + final Map headers = { + 'content-type': 'application/json', + }; + + final cacheModel = ''; + final cacheKey = ''; + final cacheResponseIdKey = '\$id'; + final cacheResponseContainerKey = ''; + + final res = await client.call(withCacheParams( + CallParams( + HttpMethod.delete, + path, + params: params, + headers: headers, + ), + CacheParams( + model: cacheModel, + key: cacheKey, + responseIdKey: cacheResponseIdKey, + responseContainerKey: cacheResponseContainerKey, + ), + )); + + return res.data; + } + + /// Get File for Download + /// + /// Get a file content by its unique ID. The endpoint response return with a + /// 'Content-Disposition: attachment' header that tells the browser to start + /// downloading the file to user downloads directory. + /// + Future getFileDownload( + {required String bucketId, required String fileId}) async { + final String path = '/storage/buckets/{bucketId}/files/{fileId}/download' + .replaceAll('{bucketId}', bucketId) + .replaceAll('{fileId}', fileId); + + final Map params = { + 'project': client.config['project'], + }; + + final res = await client.call(CallParams( + HttpMethod.get, + path, + params: params, + responseType: ResponseType.bytes, + )); + return res.data; + } + + /// Get File Preview + /// + /// Get a file preview image. Currently, this method supports preview for image + /// files (jpg, png, and gif), other supported formats, like pdf, docs, slides, + /// and spreadsheets, will return the file icon image. You can also pass query + /// string arguments for cutting and resizing your preview image. Preview is + /// supported only for image files smaller than 10MB. + /// + Future getFilePreview( + {required String bucketId, + required String fileId, + int? width, + int? height, + String? gravity, + int? quality, + int? borderWidth, + String? borderColor, + int? borderRadius, + double? opacity, + int? rotation, + String? background, + String? output}) async { + final String path = '/storage/buckets/{bucketId}/files/{fileId}/preview' + .replaceAll('{bucketId}', bucketId) + .replaceAll('{fileId}', fileId); + + final Map params = { + 'width': width, + 'height': height, + 'gravity': gravity, + 'quality': quality, + 'borderWidth': borderWidth, + 'borderColor': borderColor, + 'borderRadius': borderRadius, + 'opacity': opacity, + 'rotation': rotation, + 'background': background, + 'output': output, + 'project': client.config['project'], + }; + + final res = await client.call(CallParams( + HttpMethod.get, + path, + params: params, + responseType: ResponseType.bytes, + )); + return res.data; + } + + /// Get File for View + /// + /// Get a file content by its unique ID. This endpoint is similar to the + /// download method but returns with no 'Content-Disposition: attachment' + /// header. + /// + Future getFileView( + {required String bucketId, required String fileId}) async { + final String path = '/storage/buckets/{bucketId}/files/{fileId}/view' + .replaceAll('{bucketId}', bucketId) + .replaceAll('{fileId}', fileId); + + final Map params = { + 'project': client.config['project'], + }; + + final res = await client.call(CallParams( + HttpMethod.get, + path, + params: params, + responseType: ResponseType.bytes, + )); + return res.data; + } +} diff --git a/lib/services/teams.dart b/lib/services/teams.dart index 9532380a..dfd73e0b 100644 --- a/lib/services/teams.dart +++ b/lib/services/teams.dart @@ -1,427 +1,488 @@ part of appwrite; - /// The Teams service allows you to group users of your project and to enable - /// them to share read and write access to your project resources +/// The Teams service allows you to group users of your project and to enable +/// them to share read and write access to your project resources class Teams extends Service { - Teams(super.client); - - /// List Teams - /// - /// Get a list of all the teams in which the current user is a member. You can - /// use the parameters to filter your results. - /// - Future list({List? queries, String? search}) async { - const String path = '/teams'; - - final Map params = { - 'queries': queries, - 'search': search, - }; - - final Map headers = { - 'content-type': 'application/json', - }; - - final cacheModel = '/teams'; - final cacheKey = ''; - final cacheResponseIdKey = '\$id'; - final cacheResponseContainerKey = 'teams'; - - final res = await client.call( - HttpMethod.get, - path: path, - params: params, - headers: headers, - cacheModel: cacheModel, - cacheKey: cacheKey, - cacheResponseIdKey: cacheResponseIdKey, - cacheResponseContainerKey: cacheResponseContainerKey, - ); - - return models.TeamList.fromMap(res.data); - - } - - /// Create Team - /// - /// Create a new team. The user who creates the team will automatically be - /// assigned as the owner of the team. Only the users with the owner role can - /// invite new members, add new owners and delete or update the team. - /// - Future create({required String teamId, required String name, List? roles}) async { - const String path = '/teams'; - - final Map params = { - 'teamId': teamId, - 'name': name, - 'roles': roles, - }; - - final Map headers = { - 'content-type': 'application/json', - }; - - final cacheModel = ''; - final cacheKey = ''; - final cacheResponseIdKey = '\$id'; - final cacheResponseContainerKey = ''; - - final res = await client.call( - HttpMethod.post, - path: path, - params: params, - headers: headers, - cacheModel: cacheModel, - cacheKey: cacheKey, - cacheResponseIdKey: cacheResponseIdKey, - cacheResponseContainerKey: cacheResponseContainerKey, - ); - - return models.Team.fromMap(res.data); - - } - - /// Get Team - /// - /// Get a team by its ID. All team members have read access for this resource. - /// - Future get({required String teamId}) async { - final String path = '/teams/{teamId}'.replaceAll('{teamId}', teamId); - - final Map params = { - }; - - final Map headers = { - 'content-type': 'application/json', - }; - - final cacheModel = '/teams'.replaceAll('{teamId}', teamId); - final cacheKey = teamId; - final cacheResponseIdKey = '\$id'; - final cacheResponseContainerKey = ''; - - final res = await client.call( - HttpMethod.get, - path: path, - params: params, - headers: headers, - cacheModel: cacheModel, - cacheKey: cacheKey, - cacheResponseIdKey: cacheResponseIdKey, - cacheResponseContainerKey: cacheResponseContainerKey, - ); - - return models.Team.fromMap(res.data); - - } - - /// Update Team - /// - /// Update a team using its ID. Only members with the owner role can update the - /// team. - /// - Future update({required String teamId, required String name}) async { - final String path = '/teams/{teamId}'.replaceAll('{teamId}', teamId); - - final Map params = { - 'name': name, - }; - - final Map headers = { - 'content-type': 'application/json', - }; - - final cacheModel = '/teams'.replaceAll('{teamId}', teamId); - final cacheKey = teamId; - final cacheResponseIdKey = '\$id'; - final cacheResponseContainerKey = ''; - - final res = await client.call( - HttpMethod.put, - path: path, - params: params, - headers: headers, - cacheModel: cacheModel, - cacheKey: cacheKey, - cacheResponseIdKey: cacheResponseIdKey, - cacheResponseContainerKey: cacheResponseContainerKey, - ); - - return models.Team.fromMap(res.data); - - } - - /// Delete Team - /// - /// Delete a team using its ID. Only team members with the owner role can - /// delete the team. - /// - Future delete({required String teamId}) async { - final String path = '/teams/{teamId}'.replaceAll('{teamId}', teamId); - - final Map params = { - }; - - final Map headers = { - 'content-type': 'application/json', - }; - - final cacheModel = ''; - final cacheKey = ''; - final cacheResponseIdKey = '\$id'; - final cacheResponseContainerKey = ''; - - final res = await client.call( - HttpMethod.delete, - path: path, - params: params, - headers: headers, - cacheModel: cacheModel, - cacheKey: cacheKey, - cacheResponseIdKey: cacheResponseIdKey, - cacheResponseContainerKey: cacheResponseContainerKey, - ); - - return res.data; - - } - - /// List Team Memberships - /// - /// Use this endpoint to list a team's members using the team's ID. All team - /// members have read access to this endpoint. - /// - Future listMemberships({required String teamId, List? queries, String? search}) async { - final String path = '/teams/{teamId}/memberships'.replaceAll('{teamId}', teamId); - - final Map params = { - 'queries': queries, - 'search': search, - }; - - final Map headers = { - 'content-type': 'application/json', - }; - - final cacheModel = '/teams/{teamId}/memberships'.replaceAll('{teamId}', teamId); - final cacheKey = ''; - final cacheResponseIdKey = '\$id'; - final cacheResponseContainerKey = 'memberships'; - - final res = await client.call( - HttpMethod.get, - path: path, - params: params, - headers: headers, - cacheModel: cacheModel, - cacheKey: cacheKey, - cacheResponseIdKey: cacheResponseIdKey, - cacheResponseContainerKey: cacheResponseContainerKey, - ); - - return models.MembershipList.fromMap(res.data); - - } - - /// Create Team Membership - /// - /// Invite a new member to join your team. If initiated from the client SDK, an - /// email with a link to join the team will be sent to the member's email - /// address and an account will be created for them should they not be signed - /// up already. If initiated from server-side SDKs, the new member will - /// automatically be added to the team. - /// - /// Use the 'url' parameter to redirect the user from the invitation email back - /// to your app. When the user is redirected, use the [Update Team Membership - /// Status](/docs/client/teams#teamsUpdateMembershipStatus) endpoint to allow - /// the user to accept the invitation to the team. - /// - /// Please note that to avoid a [Redirect - /// Attack](https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) - /// the only valid redirect URL's are the once from domains you have set when - /// adding your platforms in the console interface. - /// - Future createMembership({required String teamId, required String email, required List roles, required String url, String? name}) async { - final String path = '/teams/{teamId}/memberships'.replaceAll('{teamId}', teamId); - - final Map params = { - 'email': email, - 'roles': roles, - 'url': url, - 'name': name, - }; - - final Map headers = { - 'content-type': 'application/json', - }; - - final cacheModel = ''; - final cacheKey = ''; - final cacheResponseIdKey = '\$id'; - final cacheResponseContainerKey = ''; - - final res = await client.call( - HttpMethod.post, - path: path, - params: params, - headers: headers, - cacheModel: cacheModel, - cacheKey: cacheKey, - cacheResponseIdKey: cacheResponseIdKey, - cacheResponseContainerKey: cacheResponseContainerKey, - ); - - return models.Membership.fromMap(res.data); - - } - - /// Get Team Membership - /// - /// Get a team member by the membership unique id. All team members have read - /// access for this resource. - /// - Future getMembership({required String teamId, required String membershipId}) async { - final String path = '/teams/{teamId}/memberships/{membershipId}'.replaceAll('{teamId}', teamId).replaceAll('{membershipId}', membershipId); - - final Map params = { - }; - - final Map headers = { - 'content-type': 'application/json', - }; - - final cacheModel = '/teams/{teamId}/memberships'.replaceAll('{teamId}', teamId).replaceAll('{membershipId}', membershipId); - final cacheKey = membershipId; - final cacheResponseIdKey = '\$id'; - final cacheResponseContainerKey = ''; - - final res = await client.call( - HttpMethod.get, - path: path, - params: params, - headers: headers, - cacheModel: cacheModel, - cacheKey: cacheKey, - cacheResponseIdKey: cacheResponseIdKey, - cacheResponseContainerKey: cacheResponseContainerKey, - ); - - return models.Membership.fromMap(res.data); - - } - - /// Update Membership Roles - /// - /// Modify the roles of a team member. Only team members with the owner role - /// have access to this endpoint. Learn more about [roles and - /// permissions](/docs/permissions). - /// - Future updateMembershipRoles({required String teamId, required String membershipId, required List roles}) async { - final String path = '/teams/{teamId}/memberships/{membershipId}'.replaceAll('{teamId}', teamId).replaceAll('{membershipId}', membershipId); - - final Map params = { - 'roles': roles, - }; - - final Map headers = { - 'content-type': 'application/json', - }; - - final cacheModel = ''; - final cacheKey = ''; - final cacheResponseIdKey = '\$id'; - final cacheResponseContainerKey = ''; - - final res = await client.call( - HttpMethod.patch, - path: path, - params: params, - headers: headers, - cacheModel: cacheModel, - cacheKey: cacheKey, - cacheResponseIdKey: cacheResponseIdKey, - cacheResponseContainerKey: cacheResponseContainerKey, - ); - - return models.Membership.fromMap(res.data); - - } - - /// Delete Team Membership - /// - /// This endpoint allows a user to leave a team or for a team owner to delete - /// the membership of any other team member. You can also use this endpoint to - /// delete a user membership even if it is not accepted. - /// - Future deleteMembership({required String teamId, required String membershipId}) async { - final String path = '/teams/{teamId}/memberships/{membershipId}'.replaceAll('{teamId}', teamId).replaceAll('{membershipId}', membershipId); - - final Map params = { - }; - - final Map headers = { - 'content-type': 'application/json', - }; - - final cacheModel = ''; - final cacheKey = ''; - final cacheResponseIdKey = '\$id'; - final cacheResponseContainerKey = ''; - - final res = await client.call( - HttpMethod.delete, - path: path, - params: params, - headers: headers, - cacheModel: cacheModel, - cacheKey: cacheKey, - cacheResponseIdKey: cacheResponseIdKey, - cacheResponseContainerKey: cacheResponseContainerKey, - ); - - return res.data; - - } - - /// Update Team Membership Status - /// - /// Use this endpoint to allow a user to accept an invitation to join a team - /// after being redirected back to your app from the invitation email received - /// by the user. - /// - /// If the request is successful, a session for the user is automatically - /// created. - /// - /// - Future updateMembershipStatus({required String teamId, required String membershipId, required String userId, required String secret}) async { - final String path = '/teams/{teamId}/memberships/{membershipId}/status'.replaceAll('{teamId}', teamId).replaceAll('{membershipId}', membershipId); - - final Map params = { - 'userId': userId, - 'secret': secret, - }; - - final Map headers = { - 'content-type': 'application/json', - }; - - final cacheModel = ''; - final cacheKey = ''; - final cacheResponseIdKey = '\$id'; - final cacheResponseContainerKey = ''; - - final res = await client.call( - HttpMethod.patch, - path: path, - params: params, - headers: headers, - cacheModel: cacheModel, - cacheKey: cacheKey, - cacheResponseIdKey: cacheResponseIdKey, - cacheResponseContainerKey: cacheResponseContainerKey, - ); - - return models.Membership.fromMap(res.data); - - } -} \ No newline at end of file + Teams(super.client); + + /// List Teams + /// + /// Get a list of all the teams in which the current user is a member. You can + /// use the parameters to filter your results. + /// + Future list({List? queries, String? search}) async { + const String path = '/teams'; + + final Map params = { + 'queries': queries, + 'search': search, + }; + + final Map headers = { + 'content-type': 'application/json', + }; + + final cacheModel = '/teams'; + final cacheKey = ''; + final cacheResponseIdKey = '\$id'; + final cacheResponseContainerKey = 'teams'; + + final res = await client.call(withCacheParams( + CallParams( + HttpMethod.get, + path, + params: params, + headers: headers, + ), + CacheParams( + model: cacheModel, + key: cacheKey, + responseIdKey: cacheResponseIdKey, + responseContainerKey: cacheResponseContainerKey, + ), + )); + + return models.TeamList.fromMap(res.data); + } + + /// Create Team + /// + /// Create a new team. The user who creates the team will automatically be + /// assigned as the owner of the team. Only the users with the owner role can + /// invite new members, add new owners and delete or update the team. + /// + Future create( + {required String teamId, + required String name, + List? roles}) async { + const String path = '/teams'; + + final Map params = { + 'teamId': teamId, + 'name': name, + 'roles': roles, + }; + + final Map headers = { + 'content-type': 'application/json', + }; + + final cacheModel = ''; + final cacheKey = ''; + final cacheResponseIdKey = '\$id'; + final cacheResponseContainerKey = ''; + + final res = await client.call(withCacheParams( + CallParams( + HttpMethod.post, + path, + params: params, + headers: headers, + ), + CacheParams( + model: cacheModel, + key: cacheKey, + responseIdKey: cacheResponseIdKey, + responseContainerKey: cacheResponseContainerKey, + ), + )); + + return models.Team.fromMap(res.data); + } + + /// Get Team + /// + /// Get a team by its ID. All team members have read access for this resource. + /// + Future get({required String teamId}) async { + final String path = '/teams/{teamId}'.replaceAll('{teamId}', teamId); + + final Map params = {}; + + final Map headers = { + 'content-type': 'application/json', + }; + + final cacheModel = '/teams'.replaceAll('{teamId}', teamId); + final cacheKey = teamId; + final cacheResponseIdKey = '\$id'; + final cacheResponseContainerKey = ''; + + final res = await client.call(withCacheParams( + CallParams( + HttpMethod.get, + path, + params: params, + headers: headers, + ), + CacheParams( + model: cacheModel, + key: cacheKey, + responseIdKey: cacheResponseIdKey, + responseContainerKey: cacheResponseContainerKey, + ), + )); + + return models.Team.fromMap(res.data); + } + + /// Update Team + /// + /// Update a team using its ID. Only members with the owner role can update the + /// team. + /// + Future update( + {required String teamId, required String name}) async { + final String path = '/teams/{teamId}'.replaceAll('{teamId}', teamId); + + final Map params = { + 'name': name, + }; + + final Map headers = { + 'content-type': 'application/json', + }; + + final cacheModel = '/teams'.replaceAll('{teamId}', teamId); + final cacheKey = teamId; + final cacheResponseIdKey = '\$id'; + final cacheResponseContainerKey = ''; + + final res = await client.call(withCacheParams( + CallParams( + HttpMethod.put, + path, + params: params, + headers: headers, + ), + CacheParams( + model: cacheModel, + key: cacheKey, + responseIdKey: cacheResponseIdKey, + responseContainerKey: cacheResponseContainerKey, + ), + )); + + return models.Team.fromMap(res.data); + } + + /// Delete Team + /// + /// Delete a team using its ID. Only team members with the owner role can + /// delete the team. + /// + Future delete({required String teamId}) async { + final String path = '/teams/{teamId}'.replaceAll('{teamId}', teamId); + + final Map params = {}; + + final Map headers = { + 'content-type': 'application/json', + }; + + final cacheModel = ''; + final cacheKey = ''; + final cacheResponseIdKey = '\$id'; + final cacheResponseContainerKey = ''; + + final res = await client.call(withCacheParams( + CallParams( + HttpMethod.delete, + path, + params: params, + headers: headers, + ), + CacheParams( + model: cacheModel, + key: cacheKey, + responseIdKey: cacheResponseIdKey, + responseContainerKey: cacheResponseContainerKey, + ), + )); + + return res.data; + } + + /// List Team Memberships + /// + /// Use this endpoint to list a team's members using the team's ID. All team + /// members have read access to this endpoint. + /// + Future listMemberships( + {required String teamId, List? queries, String? search}) async { + final String path = + '/teams/{teamId}/memberships'.replaceAll('{teamId}', teamId); + + final Map params = { + 'queries': queries, + 'search': search, + }; + + final Map headers = { + 'content-type': 'application/json', + }; + + final cacheModel = + '/teams/{teamId}/memberships'.replaceAll('{teamId}', teamId); + final cacheKey = ''; + final cacheResponseIdKey = '\$id'; + final cacheResponseContainerKey = 'memberships'; + + final res = await client.call(withCacheParams( + CallParams( + HttpMethod.get, + path, + params: params, + headers: headers, + ), + CacheParams( + model: cacheModel, + key: cacheKey, + responseIdKey: cacheResponseIdKey, + responseContainerKey: cacheResponseContainerKey, + ), + )); + + return models.MembershipList.fromMap(res.data); + } + + /// Create Team Membership + /// + /// Invite a new member to join your team. If initiated from the client SDK, an + /// email with a link to join the team will be sent to the member's email + /// address and an account will be created for them should they not be signed + /// up already. If initiated from server-side SDKs, the new member will + /// automatically be added to the team. + /// + /// Use the 'url' parameter to redirect the user from the invitation email back + /// to your app. When the user is redirected, use the [Update Team Membership + /// Status](/docs/client/teams#teamsUpdateMembershipStatus) endpoint to allow + /// the user to accept the invitation to the team. + /// + /// Please note that to avoid a [Redirect + /// Attack](https://github.com/OWASP/CheatSheetSeries/blob/master/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.md) + /// the only valid redirect URL's are the once from domains you have set when + /// adding your platforms in the console interface. + /// + Future createMembership( + {required String teamId, + required String email, + required List roles, + required String url, + String? name}) async { + final String path = + '/teams/{teamId}/memberships'.replaceAll('{teamId}', teamId); + + final Map params = { + 'email': email, + 'roles': roles, + 'url': url, + 'name': name, + }; + + final Map headers = { + 'content-type': 'application/json', + }; + + final cacheModel = ''; + final cacheKey = ''; + final cacheResponseIdKey = '\$id'; + final cacheResponseContainerKey = ''; + + final res = await client.call(withCacheParams( + CallParams( + HttpMethod.post, + path, + params: params, + headers: headers, + ), + CacheParams( + model: cacheModel, + key: cacheKey, + responseIdKey: cacheResponseIdKey, + responseContainerKey: cacheResponseContainerKey, + ), + )); + + return models.Membership.fromMap(res.data); + } + + /// Get Team Membership + /// + /// Get a team member by the membership unique id. All team members have read + /// access for this resource. + /// + Future getMembership( + {required String teamId, required String membershipId}) async { + final String path = '/teams/{teamId}/memberships/{membershipId}' + .replaceAll('{teamId}', teamId) + .replaceAll('{membershipId}', membershipId); + + final Map params = {}; + + final Map headers = { + 'content-type': 'application/json', + }; + + final cacheModel = '/teams/{teamId}/memberships' + .replaceAll('{teamId}', teamId) + .replaceAll('{membershipId}', membershipId); + final cacheKey = membershipId; + final cacheResponseIdKey = '\$id'; + final cacheResponseContainerKey = ''; + + final res = await client.call(withCacheParams( + CallParams( + HttpMethod.get, + path, + params: params, + headers: headers, + ), + CacheParams( + model: cacheModel, + key: cacheKey, + responseIdKey: cacheResponseIdKey, + responseContainerKey: cacheResponseContainerKey, + ), + )); + + return models.Membership.fromMap(res.data); + } + + /// Update Membership Roles + /// + /// Modify the roles of a team member. Only team members with the owner role + /// have access to this endpoint. Learn more about [roles and + /// permissions](/docs/permissions). + /// + Future updateMembershipRoles( + {required String teamId, + required String membershipId, + required List roles}) async { + final String path = '/teams/{teamId}/memberships/{membershipId}' + .replaceAll('{teamId}', teamId) + .replaceAll('{membershipId}', membershipId); + + final Map params = { + 'roles': roles, + }; + + final Map headers = { + 'content-type': 'application/json', + }; + + final cacheModel = ''; + final cacheKey = ''; + final cacheResponseIdKey = '\$id'; + final cacheResponseContainerKey = ''; + + final res = await client.call(withCacheParams( + CallParams( + HttpMethod.patch, + path, + params: params, + headers: headers, + ), + CacheParams( + model: cacheModel, + key: cacheKey, + responseIdKey: cacheResponseIdKey, + responseContainerKey: cacheResponseContainerKey, + ), + )); + + return models.Membership.fromMap(res.data); + } + + /// Delete Team Membership + /// + /// This endpoint allows a user to leave a team or for a team owner to delete + /// the membership of any other team member. You can also use this endpoint to + /// delete a user membership even if it is not accepted. + /// + Future deleteMembership( + {required String teamId, required String membershipId}) async { + final String path = '/teams/{teamId}/memberships/{membershipId}' + .replaceAll('{teamId}', teamId) + .replaceAll('{membershipId}', membershipId); + + final Map params = {}; + + final Map headers = { + 'content-type': 'application/json', + }; + + final cacheModel = ''; + final cacheKey = ''; + final cacheResponseIdKey = '\$id'; + final cacheResponseContainerKey = ''; + + final res = await client.call(withCacheParams( + CallParams( + HttpMethod.delete, + path, + params: params, + headers: headers, + ), + CacheParams( + model: cacheModel, + key: cacheKey, + responseIdKey: cacheResponseIdKey, + responseContainerKey: cacheResponseContainerKey, + ), + )); + + return res.data; + } + + /// Update Team Membership Status + /// + /// Use this endpoint to allow a user to accept an invitation to join a team + /// after being redirected back to your app from the invitation email received + /// by the user. + /// + /// If the request is successful, a session for the user is automatically + /// created. + /// + /// + Future updateMembershipStatus( + {required String teamId, + required String membershipId, + required String userId, + required String secret}) async { + final String path = '/teams/{teamId}/memberships/{membershipId}/status' + .replaceAll('{teamId}', teamId) + .replaceAll('{membershipId}', membershipId); + + final Map params = { + 'userId': userId, + 'secret': secret, + }; + + final Map headers = { + 'content-type': 'application/json', + }; + + final cacheModel = ''; + final cacheKey = ''; + final cacheResponseIdKey = '\$id'; + final cacheResponseContainerKey = ''; + + final res = await client.call(withCacheParams( + CallParams( + HttpMethod.patch, + path, + params: params, + headers: headers, + ), + CacheParams( + model: cacheModel, + key: cacheKey, + responseIdKey: cacheResponseIdKey, + responseContainerKey: cacheResponseContainerKey, + ), + )); + + return models.Membership.fromMap(res.data); + } +} diff --git a/lib/src/call_handlers/call_handler.dart b/lib/src/call_handlers/call_handler.dart new file mode 100644 index 00000000..1e6fb404 --- /dev/null +++ b/lib/src/call_handlers/call_handler.dart @@ -0,0 +1,12 @@ +import '../call_params.dart'; +import '../response.dart'; + +abstract class CallHandler { + late CallHandler next; + + void setNext(CallHandler next) { + this.next = next; + } + + Future handleCall(CallParams params); +} diff --git a/lib/src/call_handlers/cookie_auth_call_handler.dart b/lib/src/call_handlers/cookie_auth_call_handler.dart new file mode 100644 index 00000000..e2455fef --- /dev/null +++ b/lib/src/call_handlers/cookie_auth_call_handler.dart @@ -0,0 +1,42 @@ +import 'dart:io'; + +import 'package:cookie_jar/cookie_jar.dart'; + +import '../call_params.dart'; +import '../response.dart'; +import 'call_handler.dart'; +import 'http_call_handler.dart'; + +class CookieAuthCallHandler extends CallHandler { + final CookieJar cookieJar; + + CookieAuthCallHandler(this.cookieJar); + + @override + Future handleCall(CallParams params) async { + final endpoint = getEndpoint(params); + final uri = Uri.parse(endpoint + params.path); + try { + final cookies = await cookieJar.loadForRequest(uri); + var cookie = + cookies.map((cookie) => '${cookie.name}=${cookie.value}').join('; '); + if (cookie.isNotEmpty) { + params.headers.addAll({HttpHeaders.cookieHeader: cookie}); + } + } catch (_) {} + + final response = await next.handleCall(params); + + final cookie = response.headers[HttpHeaders.setCookieHeader]; + if (cookie == null) return response; + + var exp = RegExp(r',(?=[^ ])'); + var cookies = cookie.split(exp); + await cookieJar.saveFromResponse( + Uri(scheme: uri.scheme, host: uri.host), + cookies.map((str) => Cookie.fromSetCookieValue(str)).toList(), + ); + + return response; + } +} diff --git a/lib/src/call_handlers/fallback_auth_call_handler.dart b/lib/src/call_handlers/fallback_auth_call_handler.dart new file mode 100644 index 00000000..837e999c --- /dev/null +++ b/lib/src/call_handlers/fallback_auth_call_handler.dart @@ -0,0 +1,31 @@ +import 'package:flutter/foundation.dart'; +import 'package:universal_html/html.dart' as html; + +import '../call_params.dart'; +import '../response.dart'; +import 'call_handler.dart'; + +const _cookieFallbackKey = 'cookieFallback'; + +class FallbackAuthCallHandler extends CallHandler { + FallbackAuthCallHandler(); + + @override + Future handleCall(CallParams params) async { + if (html.window.localStorage.keys.contains(_cookieFallbackKey)) { + final cookieFallback = html.window.localStorage[_cookieFallbackKey]; + params.headers.addAll({'x-fallback-cookies': cookieFallback!}); + } + + final response = await next.handleCall(params); + + final cookieFallback = response.headers['x-fallback-cookies']; + if (cookieFallback != null) { + debugPrint( + 'Appwrite is using localStorage for session management. Increase your security by adding a custom domain as your API endpoint.'); + html.window.localStorage[_cookieFallbackKey] = cookieFallback; + } + + return response; + } +} diff --git a/lib/src/call_handlers/http_call_handler.dart b/lib/src/call_handlers/http_call_handler.dart new file mode 100644 index 00000000..b72648a6 --- /dev/null +++ b/lib/src/call_handlers/http_call_handler.dart @@ -0,0 +1,51 @@ +import 'package:http/http.dart' as http; + +import '../call_params.dart'; +import '../client_mixin.dart'; +import '../exception.dart'; +import '../response.dart'; +import 'call_handler.dart'; + +const endpointKey = 'endpoint'; + +String getEndpoint(CallParams params) { + final endpoint = params.context[endpointKey]; + if (endpoint == null) return 'https://HOSTNAME/v1'; + return endpoint as String; +} + +CallParams withEndpoint(CallParams params, String endpoint) { + params.context[endpointKey] = endpoint; + return params; +} + +class HttpCallHandler extends CallHandler with ClientMixin { + final http.Client client; + + HttpCallHandler(this.client); + + @override + Future handleCall(CallParams params) async { + final endpoint = getEndpoint(params); + + late http.Response res; + http.BaseRequest request = prepareRequest( + params.method, + uri: Uri.parse(endpoint + params.path), + headers: params.headers, + params: params.params, + ); + + try { + final streamedResponse = await client.send(request); + res = await toResponse(streamedResponse); + + return prepareResponse(res, responseType: params.responseType); + } catch (e) { + if (e is AppwriteException) { + rethrow; + } + throw AppwriteException(e.toString()); + } + } +} diff --git a/lib/src/call_handlers/offline_call_handler.dart b/lib/src/call_handlers/offline_call_handler.dart new file mode 100644 index 00000000..3f5c87e9 --- /dev/null +++ b/lib/src/call_handlers/offline_call_handler.dart @@ -0,0 +1,139 @@ +import 'dart:io'; + +import 'package:http/http.dart' as http; + +import '../call_params.dart'; +import '../client_mixin.dart'; +import '../client_offline_mixin.dart'; +import '../enums.dart'; +import '../exception.dart'; +import '../offline/caller.dart'; +import '../response.dart'; +import 'call_handler.dart'; +import 'http_call_handler.dart'; + +const offlinePersistencyKey = 'offlinePersistency'; + +bool getOfflinePersistency(CallParams params) { + final offlinePersistency = params.context[offlinePersistencyKey]; + if (offlinePersistency == null) return false; + return offlinePersistency as bool; +} + +CallParams withOfflinePersistency(CallParams params, bool status) { + params.context[offlinePersistencyKey] = status; + return params; +} + +class CacheParams { + final String model; + final String key; + final String responseIdKey; + final String responseContainerKey; + final Map? previous; + + CacheParams({ + this.model = '', + this.key = '', + this.responseIdKey = '', + this.responseContainerKey = '', + this.previous, + }); +} + +const cacheParamsKey = 'cacheParams'; + +CacheParams getCacheParams(CallParams params) { + final cacheParams = params.context[cacheParamsKey]; + if (cacheParams == null) return CacheParams(); + return cacheParams as CacheParams; +} + +CallParams withCacheParams(CallParams params, CacheParams cacheParams) { + params.context[cacheParamsKey] = cacheParams; + return params; +} + +class OfflineCallHandler extends CallHandler + with ClientMixin, ClientOfflineMixin { + final Caller _call; + + OfflineCallHandler(this._call); + + @override + Future handleCall(CallParams params) async { + final endpoint = getEndpoint(params); + final offlinePersistency = getOfflinePersistency(params); + + while (true) { + try { + // if offline, do offline stuff + print('checking offline status...'); + + final uri = Uri.parse(endpoint + params.path); + + http.BaseRequest request = prepareRequest( + params.method, + uri: Uri.parse(endpoint + params.path), + headers: params.headers, + params: params.params, + ); + + if (offlinePersistency && !isOnline.value) { + await checkOnlineStatus(); + } + + final cacheParams = getCacheParams(params); + + if (cacheParams.model.isNotEmpty && + offlinePersistency && + !isOnline.value && + params.responseType != ResponseType.bytes) { + return handleOfflineRequest( + uri: uri, + method: params.method, + call: this._call, + path: params.path, + headers: params.headers, + params: params.params, + responseType: params.responseType, + cacheModel: cacheParams.model, + cacheKey: cacheParams.key, + cacheResponseIdKey: cacheParams.responseIdKey, + cacheResponseContainerKey: cacheParams.responseContainerKey, + ); + } + + final response = await next.handleCall(params); + + // cache stuff + print('cached stuff...'); + if (offlinePersistency) { + cacheResponse( + cacheModel: cacheParams.model, + cacheKey: cacheParams.key, + cacheResponseIdKey: cacheParams.responseIdKey, + request: request, + response: response, + ); + } + + return response; + } on AppwriteException catch (e) { + if ((e.message != "Network is unreachable" && + !(e.message?.contains("Failed host lookup") ?? false)) || + !offlinePersistency) { + rethrow; + } + isOnline.value = false; + } on SocketException catch (_) { + if (!offlinePersistency) { + rethrow; + } + isOnline.value = false; + } catch (e) { + throw AppwriteException(e.toString()); + } + } + } +} diff --git a/lib/src/call_params.dart b/lib/src/call_params.dart new file mode 100644 index 00000000..baa171d6 --- /dev/null +++ b/lib/src/call_params.dart @@ -0,0 +1,23 @@ +import 'enums.dart'; + +class CallParams { + final HttpMethod method; + final String path; + final Map headers = {}; + final Map params = {}; + final ResponseType? responseType; + final Map context = {}; + + CallParams( + this.method, + this.path, { + this.responseType, + Map headers = const {}, + Map params = const {}, + Map context = const {}, + }) { + this.headers.addAll(headers); + this.params.addAll(params); + this.context.addAll(context); + } +} diff --git a/lib/src/client.dart b/lib/src/client.dart index 88cb62d7..cf9aa2b1 100644 --- a/lib/src/client.dart +++ b/lib/src/client.dart @@ -1,7 +1,8 @@ +import 'call_handlers/call_handler.dart'; +import 'call_params.dart'; import 'client_stub.dart' if (dart.library.html) 'client_browser.dart' if (dart.library.io) 'client_io.dart'; -import 'enums.dart'; import 'response.dart'; import 'upload_progress.dart'; @@ -39,24 +40,14 @@ abstract class Client { /// Your project ID Client setProject(value); + /// Your secret JSON Web Token Client setJWT(value); Client setLocale(value); Client addHeader(String key, String value); - Future call( - HttpMethod method, { - String path = '', - Map headers = const {}, - Map params = const {}, - ResponseType? responseType, - String cacheModel = '', - String cacheKey = '', - String cacheResponseIdKey = '', - String cacheResponseContainerKey = '', - Map? previous, - }); + Future call(CallParams params); Future setOfflinePersistency({ bool status = true, @@ -68,4 +59,6 @@ abstract class Client { Client setOfflineCacheSize(int kbytes); int getOfflineCacheSize(); + + Client addHandler(CallHandler handler); } diff --git a/lib/src/client_base.dart b/lib/src/client_base.dart index cdfe8a82..375f0a96 100644 --- a/lib/src/client_base.dart +++ b/lib/src/client_base.dart @@ -1,57 +1,37 @@ +import 'call_handlers/call_handler.dart'; +import 'call_params.dart'; import 'client.dart'; -import 'enums.dart'; import 'response.dart'; abstract class ClientBase implements Client { /// Your project ID - @override ClientBase setProject(value); /// Your secret JSON Web Token - @override ClientBase setJWT(value); - @override ClientBase setLocale(value); - @override ClientBase setSelfSigned({bool status = true}); - @override ClientBase setEndpoint(String endPoint); - @override Client setEndPointRealtime(String endPoint); - @override ClientBase addHeader(String key, String value); - @override - Future call( - HttpMethod method, { - String path = '', - Map headers = const {}, - Map params = const {}, - ResponseType? responseType, - String cacheModel = '', - String cacheKey = '', - String cacheResponseIdKey = '', - String cacheResponseContainerKey = '', - Map? previous, - }); + Future call(CallParams params); - @override Future setOfflinePersistency({ bool status = true, void Function(Object)? onWriteQueueError, }); - @override bool getOfflinePersistency(); - @override ClientBase setOfflineCacheSize(int kbytes); - @override int getOfflineCacheSize(); + + ClientBase addHandler(CallHandler handler); } diff --git a/lib/src/client_browser.dart b/lib/src/client_browser.dart index 79db3dbd..83054ee9 100644 --- a/lib/src/client_browser.dart +++ b/lib/src/client_browser.dart @@ -1,15 +1,16 @@ -import 'dart:io'; import 'dart:math'; -import 'package:flutter/foundation.dart'; import 'package:flutter_web_auth_2/flutter_web_auth_2.dart'; import 'package:http/browser_client.dart'; import 'package:http/http.dart' as http; -import 'package:universal_html/html.dart' as html; +import 'call_handlers/call_handler.dart'; +import 'call_handlers/fallback_auth_call_handler.dart'; +import 'call_handlers/http_call_handler.dart'; +import 'call_handlers/offline_call_handler.dart'; +import 'call_params.dart'; import 'client_base.dart'; import 'client_mixin.dart'; -import 'client_offline_mixin.dart'; import 'enums.dart'; import 'exception.dart'; import 'input_file.dart'; @@ -22,7 +23,7 @@ ClientBase createClient({ }) => ClientBrowser(endPoint: endPoint, selfSigned: selfSigned); -class ClientBrowser extends ClientBase with ClientMixin, ClientOfflineMixin { +class ClientBrowser extends ClientBase with ClientMixin { static const int CHUNK_SIZE = 5 * 1024 * 1024; String _endPoint; Map? _headers; @@ -30,6 +31,8 @@ class ClientBrowser extends ClientBase with ClientMixin, ClientOfflineMixin { late Map config; late BrowserClient _httpClient; String? _endPointRealtime; + late CallHandler _handler; + late OfflineCallHandler _offlineHandler; bool _offlinePersistency = false; int _maxCacheSize = 40000; // 40MB @@ -57,6 +60,9 @@ class ClientBrowser extends ClientBase with ClientMixin, ClientOfflineMixin { assert(_endPoint.startsWith(RegExp("http://|https://")), "endPoint $_endPoint must start with 'http'"); + _handler = HttpCallHandler(_httpClient); + _offlineHandler = OfflineCallHandler(call); + init(); } @@ -116,7 +122,7 @@ class ClientBrowser extends ClientBase with ClientMixin, ClientOfflineMixin { _offlinePersistency = status; if (_offlinePersistency) { - await initOffline( + await _offlineHandler.initOffline( call: call, onWriteQueueError: onWriteQueueError, getOfflineCacheSize: getOfflineCacheSize, @@ -146,10 +152,8 @@ class ClientBrowser extends ClientBase with ClientMixin, ClientOfflineMixin { } Future init() async { - if (html.window.localStorage.keys.contains('cookieFallback')) { - addHeader('x-fallback-cookies', - html.window.localStorage['cookieFallback'] ?? ''); - } + addHandler(FallbackAuthCallHandler()); + addHandler(_offlineHandler); _httpClient.withCredentials = true; } @@ -176,23 +180,23 @@ class ClientBrowser extends ClientBase with ClientMixin, ClientOfflineMixin { file.bytes!, filename: file.filename, ); - return call( + return call(CallParams( HttpMethod.post, - path: path, + path, params: params, headers: headers, - ); + )); } var offset = 0; if (idParamName.isNotEmpty && params[idParamName] != 'unique()') { //make a request to check if a file already exists try { - res = await call( + res = await call(CallParams( HttpMethod.get, - path: path + '/' + params[idParamName], + path + '/' + params[idParamName], headers: headers, - ); + )); final int chunksUploaded = res.data['chunksUploaded'] as int; offset = min(size, chunksUploaded * CHUNK_SIZE); } on AppwriteException catch (_) {} @@ -209,8 +213,12 @@ class ClientBrowser extends ClientBase with ClientMixin, ClientOfflineMixin { ); headers['content-range'] = 'bytes $offset-${min(((offset + CHUNK_SIZE) - 1), size)}/$size'; - res = await call(HttpMethod.post, - path: path, headers: headers, params: params); + res = await call(CallParams( + HttpMethod.post, + path, + headers: headers, + params: params, + )); offset += CHUNK_SIZE; if (offset < size) { headers['x-appwrite-id'] = res.data['\$id']; @@ -228,132 +236,16 @@ class ClientBrowser extends ClientBase with ClientMixin, ClientOfflineMixin { } @override - Future call( - HttpMethod method, { - String path = '', - Map headers = const {}, - Map params = const {}, - ResponseType? responseType, - String cacheModel = '', - String cacheKey = '', - String cacheResponseIdKey = '', - String cacheResponseContainerKey = '', - Map? previous, - }) async { - while (true) { - final uri = Uri.parse(endPoint + path); - - http.BaseRequest request = prepareRequest( - method, - uri: uri, - headers: {..._headers!, ...headers}, - params: params, - ); - - if (getOfflinePersistency() && !isOnline.value) { - await checkOnlineStatus(); - } - - if (cacheModel.isNotEmpty && - getOfflinePersistency() && - !isOnline.value && - responseType != ResponseType.bytes) { - return handleOfflineRequest( - uri: uri, - method: method, - call: call, - path: path, - headers: headers, - params: params, - responseType: responseType, - cacheModel: cacheModel, - cacheKey: cacheKey, - cacheResponseIdKey: cacheResponseIdKey, - cacheResponseContainerKey: cacheResponseContainerKey, - ); - } - - try { - final response = await send( - method, - path: path, - headers: headers, - params: params, - responseType: responseType, - cacheModel: cacheModel, - cacheKey: cacheKey, - cacheResponseIdKey: cacheResponseIdKey, - cacheResponseContainerKey: cacheResponseContainerKey, - ); - - if (getOfflinePersistency()) { - cacheResponse( - cacheModel: cacheModel, - cacheKey: cacheKey, - cacheResponseIdKey: cacheResponseIdKey, - request: request, - response: response, - ); - } - - return response; - } on AppwriteException catch (e) { - if ((e.message != "Network is unreachable" && - !(e.message?.contains("Failed host lookup") ?? false)) || - !getOfflinePersistency()) { - rethrow; - } - isOnline.value = false; - } on SocketException catch (_) { - if (!getOfflinePersistency()) { - rethrow; - } - isOnline.value = false; - } catch (e) { - throw AppwriteException(e.toString()); - } - } - } - - Future send( - HttpMethod method, { - String path = '', - Map headers = const {}, - Map params = const {}, - ResponseType? responseType, - String cacheModel = '', - String cacheKey = '', - String cacheResponseIdKey = '', - String cacheResponseContainerKey = '', - Map? previous, - }) async { - await init(); - - late http.Response res; - http.BaseRequest request = prepareRequest( - method, - uri: Uri.parse(_endPoint + path), - headers: {..._headers!, ...headers}, - params: params, + Future call(CallParams params) async { + params.headers.addAll(this._headers!); + final response = await _handler.handleCall( + withOfflinePersistency( + withEndpoint(params, endPoint), + getOfflinePersistency(), + ), ); - try { - final streamedResponse = await _httpClient.send(request); - res = await toResponse(streamedResponse); - - final cookieFallback = res.headers['x-fallback-cookies']; - if (cookieFallback != null) { - debugPrint( - 'Appwrite is using localStorage for session management. Increase your security by adding a custom domain as your API endpoint.'); - addHeader('X-Fallback-Cookies', cookieFallback); - html.window.localStorage['cookieFallback'] = cookieFallback; - } - return prepareResponse(res, responseType: responseType); - } catch (e) { - if (e is AppwriteException) { - rethrow; - } - throw AppwriteException(e.toString()); - } + + return response; } @override @@ -363,4 +255,11 @@ class ClientBrowser extends ClientBase with ClientMixin, ClientOfflineMixin { callbackUrlScheme: "appwrite-callback-" + config['project']!, ); } + + @override + ClientBrowser addHandler(CallHandler handler) { + handler.setNext(_handler); + _handler = handler; + return this; + } } diff --git a/lib/src/client_io.dart b/lib/src/client_io.dart index 3b29c760..7f10b48e 100644 --- a/lib/src/client_io.dart +++ b/lib/src/client_io.dart @@ -11,14 +11,16 @@ import 'package:http/io_client.dart'; import 'package:package_info_plus/package_info_plus.dart'; import 'package:path_provider/path_provider.dart'; +import 'call_handlers/call_handler.dart'; +import 'call_handlers/cookie_auth_call_handler.dart'; +import 'call_handlers/http_call_handler.dart'; +import 'call_handlers/offline_call_handler.dart'; +import 'call_params.dart'; import 'client_base.dart'; import 'client_mixin.dart'; -import 'client_offline_mixin.dart'; -import 'cookie_manager.dart'; import 'enums.dart'; import 'exception.dart'; import 'input_file.dart'; -import 'interceptor.dart'; import 'response.dart'; import 'upload_progress.dart'; @@ -31,7 +33,7 @@ ClientBase createClient({ selfSigned: selfSigned, ); -class ClientIO extends ClientBase with ClientMixin, ClientOfflineMixin { +class ClientIO extends ClientBase with ClientMixin { static const int CHUNK_SIZE = 5 * 1024 * 1024; String _endPoint; Map? _headers; @@ -44,7 +46,8 @@ class ClientIO extends ClientBase with ClientMixin, ClientOfflineMixin { late http.Client _httpClient; late HttpClient _nativeClient; late CookieJar _cookieJar; - final List _interceptors = []; + late CallHandler _handler; + late OfflineCallHandler _offlineHandler; bool get initProgress => _initProgress; bool get initialized => _initialized; @@ -78,6 +81,10 @@ class ClientIO extends ClientBase with ClientMixin, ClientOfflineMixin { assert(_endPoint.startsWith(RegExp("http://|https://")), "endPoint $_endPoint must start with 'http'"); + + _handler = HttpCallHandler(_httpClient); + _offlineHandler = OfflineCallHandler(call); + init(); } @@ -148,7 +155,7 @@ class ClientIO extends ClientBase with ClientMixin, ClientOfflineMixin { _offlinePersistency = status; if (_offlinePersistency) { - await initOffline( + await _offlineHandler.initOffline( call: call, onWriteQueueError: onWriteQueueError, getOfflineCacheSize: getOfflineCacheSize, @@ -182,7 +189,8 @@ class ClientIO extends ClientBase with ClientMixin, ClientOfflineMixin { _initProgress = true; final Directory cookieDir = await _getCookiePath(); _cookieJar = PersistCookieJar(storage: FileStorage(cookieDir.path)); - _interceptors.add(CookieManager(_cookieJar)); + addHandler(CookieAuthCallHandler(_cookieJar)); + addHandler(_offlineHandler); var device = ''; try { @@ -230,35 +238,6 @@ class ClientIO extends ClientBase with ClientMixin, ClientOfflineMixin { _initProgress = false; } - Future _interceptRequest(http.BaseRequest request) async { - final body = (request is http.Request) ? request.body : ''; - for (final i in _interceptors) { - request = await i.onRequest(request); - } - - if (request is http.Request) { - assert( - body == request.body, - 'Interceptors should not transform the body of the request' - 'Use Request converter instead', - ); - } - return request; - } - - Future _interceptResponse(http.Response response) async { - final body = response.body; - for (final i in _interceptors) { - response = await i.onResponse(response); - } - - assert( - body == response.body, - 'Interceptors should not transform the body of the response', - ); - return response; - } - @override Future chunkedUpload({ required String path, @@ -300,23 +279,23 @@ class ClientIO extends ClientBase with ClientMixin, ClientOfflineMixin { filename: file.filename, ); } - return call( + return call(CallParams( HttpMethod.post, - path: path, + path, params: params, headers: headers, - ); + )); } var offset = 0; if (idParamName.isNotEmpty && params[idParamName] != 'unique()') { //make a request to check if a file already exists try { - res = await call( + res = await call(CallParams( HttpMethod.get, - path: path + '/' + params[idParamName], + path + '/' + params[idParamName], headers: headers, - ); + )); final int chunksUploaded = res.data['chunksUploaded'] as int; offset = min(size, chunksUploaded * CHUNK_SIZE); } on AppwriteException catch (_) {} @@ -344,12 +323,12 @@ class ClientIO extends ClientBase with ClientMixin, ClientOfflineMixin { ); headers['content-range'] = 'bytes $offset-${min(((offset + CHUNK_SIZE) - 1), size)}/$size'; - res = await call( + res = await call(CallParams( HttpMethod.post, - path: path, + path, headers: headers, params: params, - ); + )); offset += CHUNK_SIZE; if (offset < size) { headers['x-appwrite-id'] = res.data['\$id']; @@ -393,138 +372,23 @@ class ClientIO extends ClientBase with ClientMixin, ClientOfflineMixin { }); } - Future send( - HttpMethod method, { - String path = '', - Map headers = const {}, - Map params = const {}, - ResponseType? responseType, - String cacheModel = '', - String cacheKey = '', - String cacheResponseIdKey = '', - String cacheResponseContainerKey = '', - Map? previous, - }) async { - while (!_initialized && _initProgress) { - await Future.delayed(Duration(milliseconds: 10)); - } - if (!_initialized) { - await init(); - } - - final uri = Uri.parse(_endPoint + path); - http.BaseRequest request = prepareRequest( - method, - uri: uri, - headers: {..._headers!, ...headers}, - params: params, + @override + Future call(CallParams params) async { + params.headers.addAll(this._headers!); + final response = await _handler.handleCall( + withOfflinePersistency( + withEndpoint(params, endPoint), + getOfflinePersistency(), + ), ); - try { - request = await _interceptRequest(request); - final streamedResponse = await _httpClient.send(request); - http.Response res = await toResponse(streamedResponse); - res = await _interceptResponse(res); - - final response = prepareResponse( - res, - responseType: responseType, - ); - - return response; - } catch (e) { - if (e is AppwriteException) { - rethrow; - } - throw AppwriteException(e.toString()); - } + return response; } @override - Future call( - HttpMethod method, { - String path = '', - Map headers = const {}, - Map params = const {}, - ResponseType? responseType, - String cacheModel = '', - String cacheKey = '', - String cacheResponseIdKey = '', - String cacheResponseContainerKey = '', - Map? previous, - }) async { - while (true) { - final uri = Uri.parse(endPoint + path); - - http.BaseRequest request = prepareRequest( - method, - uri: uri, - headers: {..._headers!, ...headers}, - params: params, - ); - - if (getOfflinePersistency() && !isOnline.value) { - await checkOnlineStatus(); - } - - if (cacheModel.isNotEmpty && - getOfflinePersistency() && - !isOnline.value && - responseType != ResponseType.bytes) { - return handleOfflineRequest( - uri: uri, - method: method, - call: call, - path: path, - headers: headers, - params: params, - responseType: responseType, - cacheModel: cacheModel, - cacheKey: cacheKey, - cacheResponseIdKey: cacheResponseIdKey, - cacheResponseContainerKey: cacheResponseContainerKey, - ); - } - - try { - final response = await send( - method, - path: path, - headers: headers, - params: params, - responseType: responseType, - cacheModel: cacheModel, - cacheKey: cacheKey, - cacheResponseIdKey: cacheResponseIdKey, - cacheResponseContainerKey: cacheResponseContainerKey, - ); - - if (getOfflinePersistency()) { - cacheResponse( - cacheModel: cacheModel, - cacheKey: cacheKey, - cacheResponseIdKey: cacheResponseIdKey, - request: request, - response: response, - ); - } - - return response; - } on AppwriteException catch (e) { - if ((e.message != "Network is unreachable" && - !(e.message?.contains("Failed host lookup") ?? false)) || - !getOfflinePersistency()) { - rethrow; - } - isOnline.value = false; - } on SocketException catch (_) { - if (!getOfflinePersistency()) { - rethrow; - } - isOnline.value = false; - } catch (e) { - throw AppwriteException(e.toString()); - } - } + ClientBase addHandler(CallHandler handler) { + handler.setNext(_handler); + _handler = handler; + return this; } } diff --git a/lib/src/client_mixin.dart b/lib/src/client_mixin.dart index 82fede58..d9926b29 100644 --- a/lib/src/client_mixin.dart +++ b/lib/src/client_mixin.dart @@ -1,8 +1,10 @@ +import 'dart:convert'; + import 'package:http/http.dart' as http; + +import 'enums.dart'; import 'exception.dart'; import 'response.dart'; -import 'dart:convert'; -import 'enums.dart'; class ClientMixin { http.BaseRequest prepareRequest( @@ -39,7 +41,7 @@ class ClientMixin { } } else if (method == HttpMethod.get) { if (params.isNotEmpty) { - params = params.map((key, value){ + params = params.map((key, value) { if (value is int || value is double) { return MapEntry(key, value.toString()); } @@ -96,21 +98,26 @@ class ClientMixin { data = res.body; } } - return Response(data: data); + return Response(headers: res.headers, data: data); } - Future toResponse(http.StreamedResponse streamedResponse) async { - if(streamedResponse.statusCode == 204) { - return http.Response('', - streamedResponse.statusCode, - headers: streamedResponse.headers.map((k,v) => k.toLowerCase()=='content-type' ? MapEntry(k, 'text/plain') : MapEntry(k,v)), - request: streamedResponse.request, - isRedirect: streamedResponse.isRedirect, - persistentConnection: streamedResponse.persistentConnection, - reasonPhrase: streamedResponse.reasonPhrase, - ); - } else { - return await http.Response.fromStream(streamedResponse); - } + Future toResponse( + http.StreamedResponse streamedResponse) async { + if (streamedResponse.statusCode == 204) { + return http.Response( + '', + streamedResponse.statusCode, + headers: streamedResponse.headers.map((k, v) => + k.toLowerCase() == 'content-type' + ? MapEntry(k, 'text/plain') + : MapEntry(k, v)), + request: streamedResponse.request, + isRedirect: streamedResponse.isRedirect, + persistentConnection: streamedResponse.persistentConnection, + reasonPhrase: streamedResponse.reasonPhrase, + ); + } else { + return await http.Response.fromStream(streamedResponse); + } } } diff --git a/lib/src/client_offline_mixin.dart b/lib/src/client_offline_mixin.dart index b1a1d4eb..6af74937 100644 --- a/lib/src/client_offline_mixin.dart +++ b/lib/src/client_offline_mixin.dart @@ -5,8 +5,11 @@ import 'package:flutter/foundation.dart'; import 'package:http/http.dart' as http; import 'package:sembast/utils/value_utils.dart'; +import 'call_handlers/offline_call_handler.dart'; +import 'call_params.dart'; import 'enums.dart'; import 'exception.dart'; +import 'offline/caller.dart'; import 'offline/services/accessed_at.dart'; import 'offline/services/cache_size.dart'; import 'offline/services/model_data.dart'; @@ -25,18 +28,7 @@ class ClientOfflineMixin { late QueuedWrites _queuedWrites; Future initOffline({ - required Future> Function( - HttpMethod, { - String path, - Map headers, - Map params, - ResponseType? responseType, - String cacheModel, - String cacheKey, - String cacheResponseIdKey, - String cacheResponseContainerKey, - }) - call, + required Caller call, void Function(Object)? onWriteQueueError, required int Function() getOfflineCacheSize, }) async { @@ -64,20 +56,7 @@ class ClientOfflineMixin { _queuedWrites = QueuedWrites(db); } - Future processWriteQueue( - Future> Function( - HttpMethod, { - String path, - Map headers, - Map params, - ResponseType? responseType, - String cacheModel, - String cacheKey, - String cacheResponseIdKey, - String cacheResponseContainerKey, - }) - call, - {void Function(Object e)? onError}) async { + Future processWriteQueue(Caller call, {Function? onError}) async { if (!isOnline.value) return; final queuedWrites = await _queuedWrites.list(); for (final queuedWrite in queuedWrites) { @@ -85,16 +64,22 @@ class ClientOfflineMixin { final method = HttpMethod.values .where((v) => v.name() == queuedWrite.method) .first; - final res = await call( - method, - path: queuedWrite.path, - headers: queuedWrite.headers, - params: queuedWrite.params, - cacheModel: queuedWrite.cacheModel, - cacheKey: queuedWrite.cacheKey, - cacheResponseContainerKey: queuedWrite.cacheResponseContainerKey, - cacheResponseIdKey: queuedWrite.cacheResponseIdKey, + + final params = withCacheParams( + CallParams( + method, + queuedWrite.path, + headers: queuedWrite.headers, + params: queuedWrite.params, + ), + CacheParams( + model: queuedWrite.cacheModel, + key: queuedWrite.cacheKey, + responseContainerKey: queuedWrite.cacheResponseContainerKey, + responseIdKey: queuedWrite.cacheResponseIdKey, + ), ); + final res = await call(params); if (method == HttpMethod.post) { await _modelData.upsert( @@ -160,16 +145,7 @@ class ClientOfflineMixin { Future handleOfflineRequest({ required Uri uri, required HttpMethod method, - required Future> Function(HttpMethod, - {String path, - Map headers, - Map params, - ResponseType? responseType, - String cacheModel, - String cacheKey, - String cacheResponseIdKey, - String cacheResponseContainerKey}) - call, + required Caller call, String path = '', Map headers = const {}, Map params = const {}, @@ -320,13 +296,13 @@ class ClientOfflineMixin { } try { - final res = await call( + final res = await call(CallParams( method, + path, headers: headers, params: params, - path: path, responseType: responseType, - ); + )); final futures = []; if (method == HttpMethod.post) { diff --git a/lib/src/offline/caller.dart b/lib/src/offline/caller.dart new file mode 100644 index 00000000..8eb6a0f9 --- /dev/null +++ b/lib/src/offline/caller.dart @@ -0,0 +1,4 @@ +import '../call_params.dart'; +import '../response.dart'; + +typedef Caller = Future> Function(CallParams params); diff --git a/lib/src/response.dart b/lib/src/response.dart index bf4eea3f..306bbff3 100644 --- a/lib/src/response.dart +++ b/lib/src/response.dart @@ -1,8 +1,9 @@ import 'dart:convert'; class Response { - Response({this.data}); + Response({this.headers = const {}, this.data}); + final Map headers; T? data; @override From 3153f928b06943f9fcd3cae13c7a20648b9d6873 Mon Sep 17 00:00:00 2001 From: Steven Nguyen Date: Fri, 5 May 2023 15:13:53 -0700 Subject: [PATCH 2/3] Add a router and route mapping file Previously, we added various caching metadata in the services and passed those to the Client so that the Client knew where to cache the data. This was a little messy and decentralized the offline support code. By adding a router, we can match against the path the Client receives and determine where to cache the data. --- lib/services/account.dart | 644 ++++-------------- lib/services/databases.dart | 133 +--- lib/services/functions.dart | 69 +- lib/services/graphql.dart | 46 +- lib/services/locale.dart | 161 +---- lib/services/storage.dart | 92 +-- lib/services/teams.dart | 256 ++----- .../call_handlers/offline_call_handler.dart | 47 +- lib/src/offline/route.dart | 47 ++ lib/src/offline/route_mapping.dart | 80 +++ lib/src/offline/router.dart | 126 ++++ 11 files changed, 596 insertions(+), 1105 deletions(-) create mode 100644 lib/src/offline/route.dart create mode 100644 lib/src/offline/route_mapping.dart create mode 100644 lib/src/offline/router.dart diff --git a/lib/services/account.dart b/lib/services/account.dart index 026b23a7..d5597a2b 100644 --- a/lib/services/account.dart +++ b/lib/services/account.dart @@ -17,24 +17,11 @@ class Account extends Service { 'content-type': 'application/json', }; - final cacheModel = '/account'; - final cacheKey = 'current'; - final cacheResponseIdKey = '\$id'; - final cacheResponseContainerKey = ''; - - final res = await client.call(withCacheParams( - CallParams( - HttpMethod.get, - path, - params: params, - headers: headers, - ), - CacheParams( - model: cacheModel, - key: cacheKey, - responseIdKey: cacheResponseIdKey, - responseContainerKey: cacheResponseContainerKey, - ), + final res = await client.call(CallParams( + HttpMethod.get, + path, + params: params, + headers: headers, )); return models.Account.fromMap(res.data); @@ -67,24 +54,11 @@ class Account extends Service { 'content-type': 'application/json', }; - final cacheModel = ''; - final cacheKey = ''; - final cacheResponseIdKey = '\$id'; - final cacheResponseContainerKey = ''; - - final res = await client.call(withCacheParams( - CallParams( - HttpMethod.post, - path, - params: params, - headers: headers, - ), - CacheParams( - model: cacheModel, - key: cacheKey, - responseIdKey: cacheResponseIdKey, - responseContainerKey: cacheResponseContainerKey, - ), + final res = await client.call(CallParams( + HttpMethod.post, + path, + params: params, + headers: headers, )); return models.Account.fromMap(res.data); @@ -114,24 +88,11 @@ class Account extends Service { 'content-type': 'application/json', }; - final cacheModel = '/account'; - final cacheKey = 'current'; - final cacheResponseIdKey = '\$id'; - final cacheResponseContainerKey = ''; - - final res = await client.call(withCacheParams( - CallParams( - HttpMethod.patch, - path, - params: params, - headers: headers, - ), - CacheParams( - model: cacheModel, - key: cacheKey, - responseIdKey: cacheResponseIdKey, - responseContainerKey: cacheResponseContainerKey, - ), + final res = await client.call(CallParams( + HttpMethod.patch, + path, + params: params, + headers: headers, )); return models.Account.fromMap(res.data); @@ -154,24 +115,11 @@ class Account extends Service { 'content-type': 'application/json', }; - final cacheModel = ''; - final cacheKey = ''; - final cacheResponseIdKey = '\$id'; - final cacheResponseContainerKey = ''; - - final res = await client.call(withCacheParams( - CallParams( - HttpMethod.post, - path, - params: params, - headers: headers, - ), - CacheParams( - model: cacheModel, - key: cacheKey, - responseIdKey: cacheResponseIdKey, - responseContainerKey: cacheResponseContainerKey, - ), + final res = await client.call(CallParams( + HttpMethod.post, + path, + params: params, + headers: headers, )); return models.Jwt.fromMap(res.data); @@ -193,24 +141,11 @@ class Account extends Service { 'content-type': 'application/json', }; - final cacheModel = ''; - final cacheKey = ''; - final cacheResponseIdKey = '\$id'; - final cacheResponseContainerKey = 'logs'; - - final res = await client.call(withCacheParams( - CallParams( - HttpMethod.get, - path, - params: params, - headers: headers, - ), - CacheParams( - model: cacheModel, - key: cacheKey, - responseIdKey: cacheResponseIdKey, - responseContainerKey: cacheResponseContainerKey, - ), + final res = await client.call(CallParams( + HttpMethod.get, + path, + params: params, + headers: headers, )); return models.LogList.fromMap(res.data); @@ -231,24 +166,11 @@ class Account extends Service { 'content-type': 'application/json', }; - final cacheModel = '/account'; - final cacheKey = 'current'; - final cacheResponseIdKey = '\$id'; - final cacheResponseContainerKey = ''; - - final res = await client.call(withCacheParams( - CallParams( - HttpMethod.patch, - path, - params: params, - headers: headers, - ), - CacheParams( - model: cacheModel, - key: cacheKey, - responseIdKey: cacheResponseIdKey, - responseContainerKey: cacheResponseContainerKey, - ), + final res = await client.call(CallParams( + HttpMethod.patch, + path, + params: params, + headers: headers, )); return models.Account.fromMap(res.data); @@ -273,24 +195,11 @@ class Account extends Service { 'content-type': 'application/json', }; - final cacheModel = '/account'; - final cacheKey = 'current'; - final cacheResponseIdKey = '\$id'; - final cacheResponseContainerKey = ''; - - final res = await client.call(withCacheParams( - CallParams( - HttpMethod.patch, - path, - params: params, - headers: headers, - ), - CacheParams( - model: cacheModel, - key: cacheKey, - responseIdKey: cacheResponseIdKey, - responseContainerKey: cacheResponseContainerKey, - ), + final res = await client.call(CallParams( + HttpMethod.patch, + path, + params: params, + headers: headers, )); return models.Account.fromMap(res.data); @@ -317,24 +226,11 @@ class Account extends Service { 'content-type': 'application/json', }; - final cacheModel = '/account'; - final cacheKey = 'current'; - final cacheResponseIdKey = '\$id'; - final cacheResponseContainerKey = ''; - - final res = await client.call(withCacheParams( - CallParams( - HttpMethod.patch, - path, - params: params, - headers: headers, - ), - CacheParams( - model: cacheModel, - key: cacheKey, - responseIdKey: cacheResponseIdKey, - responseContainerKey: cacheResponseContainerKey, - ), + final res = await client.call(CallParams( + HttpMethod.patch, + path, + params: params, + headers: headers, )); return models.Account.fromMap(res.data); @@ -353,24 +249,11 @@ class Account extends Service { 'content-type': 'application/json', }; - final cacheModel = '/account/prefs'; - final cacheKey = 'current'; - final cacheResponseIdKey = '\$id'; - final cacheResponseContainerKey = ''; - - final res = await client.call(withCacheParams( - CallParams( - HttpMethod.get, - path, - params: params, - headers: headers, - ), - CacheParams( - model: cacheModel, - key: cacheKey, - responseIdKey: cacheResponseIdKey, - responseContainerKey: cacheResponseContainerKey, - ), + final res = await client.call(CallParams( + HttpMethod.get, + path, + params: params, + headers: headers, )); return models.Preferences.fromMap(res.data); @@ -393,24 +276,11 @@ class Account extends Service { 'content-type': 'application/json', }; - final cacheModel = '/account/prefs'; - final cacheKey = 'current'; - final cacheResponseIdKey = '\$id'; - final cacheResponseContainerKey = ''; - - final res = await client.call(withCacheParams( - CallParams( - HttpMethod.patch, - path, - params: params, - headers: headers, - ), - CacheParams( - model: cacheModel, - key: cacheKey, - responseIdKey: cacheResponseIdKey, - responseContainerKey: cacheResponseContainerKey, - ), + final res = await client.call(CallParams( + HttpMethod.patch, + path, + params: params, + headers: headers, )); return models.Account.fromMap(res.data); @@ -440,24 +310,11 @@ class Account extends Service { 'content-type': 'application/json', }; - final cacheModel = ''; - final cacheKey = ''; - final cacheResponseIdKey = '\$id'; - final cacheResponseContainerKey = ''; - - final res = await client.call(withCacheParams( - CallParams( - HttpMethod.post, - path, - params: params, - headers: headers, - ), - CacheParams( - model: cacheModel, - key: cacheKey, - responseIdKey: cacheResponseIdKey, - responseContainerKey: cacheResponseContainerKey, - ), + final res = await client.call(CallParams( + HttpMethod.post, + path, + params: params, + headers: headers, )); return models.Token.fromMap(res.data); @@ -493,24 +350,11 @@ class Account extends Service { 'content-type': 'application/json', }; - final cacheModel = ''; - final cacheKey = ''; - final cacheResponseIdKey = '\$id'; - final cacheResponseContainerKey = ''; - - final res = await client.call(withCacheParams( - CallParams( - HttpMethod.put, - path, - params: params, - headers: headers, - ), - CacheParams( - model: cacheModel, - key: cacheKey, - responseIdKey: cacheResponseIdKey, - responseContainerKey: cacheResponseContainerKey, - ), + final res = await client.call(CallParams( + HttpMethod.put, + path, + params: params, + headers: headers, )); return models.Token.fromMap(res.data); @@ -530,24 +374,11 @@ class Account extends Service { 'content-type': 'application/json', }; - final cacheModel = '/account/sessions'; - final cacheKey = ''; - final cacheResponseIdKey = '\$id'; - final cacheResponseContainerKey = 'sessions'; - - final res = await client.call(withCacheParams( - CallParams( - HttpMethod.get, - path, - params: params, - headers: headers, - ), - CacheParams( - model: cacheModel, - key: cacheKey, - responseIdKey: cacheResponseIdKey, - responseContainerKey: cacheResponseContainerKey, - ), + final res = await client.call(CallParams( + HttpMethod.get, + path, + params: params, + headers: headers, )); return models.SessionList.fromMap(res.data); @@ -567,24 +398,11 @@ class Account extends Service { 'content-type': 'application/json', }; - final cacheModel = ''; - final cacheKey = ''; - final cacheResponseIdKey = '\$id'; - final cacheResponseContainerKey = ''; - - final res = await client.call(withCacheParams( - CallParams( - HttpMethod.delete, - path, - params: params, - headers: headers, - ), - CacheParams( - model: cacheModel, - key: cacheKey, - responseIdKey: cacheResponseIdKey, - responseContainerKey: cacheResponseContainerKey, - ), + final res = await client.call(CallParams( + HttpMethod.delete, + path, + params: params, + headers: headers, )); return res.data; @@ -608,24 +426,11 @@ class Account extends Service { 'content-type': 'application/json', }; - final cacheModel = ''; - final cacheKey = ''; - final cacheResponseIdKey = '\$id'; - final cacheResponseContainerKey = ''; - - final res = await client.call(withCacheParams( - CallParams( - HttpMethod.post, - path, - params: params, - headers: headers, - ), - CacheParams( - model: cacheModel, - key: cacheKey, - responseIdKey: cacheResponseIdKey, - responseContainerKey: cacheResponseContainerKey, - ), + final res = await client.call(CallParams( + HttpMethod.post, + path, + params: params, + headers: headers, )); return models.Session.fromMap(res.data); @@ -652,24 +457,11 @@ class Account extends Service { 'content-type': 'application/json', }; - final cacheModel = ''; - final cacheKey = ''; - final cacheResponseIdKey = '\$id'; - final cacheResponseContainerKey = ''; - - final res = await client.call(withCacheParams( - CallParams( - HttpMethod.post, - path, - params: params, - headers: headers, - ), - CacheParams( - model: cacheModel, - key: cacheKey, - responseIdKey: cacheResponseIdKey, - responseContainerKey: cacheResponseContainerKey, - ), + final res = await client.call(CallParams( + HttpMethod.post, + path, + params: params, + headers: headers, )); return models.Session.fromMap(res.data); @@ -706,24 +498,11 @@ class Account extends Service { 'content-type': 'application/json', }; - final cacheModel = ''; - final cacheKey = ''; - final cacheResponseIdKey = '\$id'; - final cacheResponseContainerKey = ''; - - final res = await client.call(withCacheParams( - CallParams( - HttpMethod.post, - path, - params: params, - headers: headers, - ), - CacheParams( - model: cacheModel, - key: cacheKey, - responseIdKey: cacheResponseIdKey, - responseContainerKey: cacheResponseContainerKey, - ), + final res = await client.call(CallParams( + HttpMethod.post, + path, + params: params, + headers: headers, )); return models.Token.fromMap(res.data); @@ -756,24 +535,11 @@ class Account extends Service { 'content-type': 'application/json', }; - final cacheModel = ''; - final cacheKey = ''; - final cacheResponseIdKey = '\$id'; - final cacheResponseContainerKey = ''; - - final res = await client.call(withCacheParams( - CallParams( - HttpMethod.put, - path, - params: params, - headers: headers, - ), - CacheParams( - model: cacheModel, - key: cacheKey, - responseIdKey: cacheResponseIdKey, - responseContainerKey: cacheResponseContainerKey, - ), + final res = await client.call(CallParams( + HttpMethod.put, + path, + params: params, + headers: headers, )); return models.Session.fromMap(res.data); @@ -862,24 +628,11 @@ class Account extends Service { 'content-type': 'application/json', }; - final cacheModel = ''; - final cacheKey = ''; - final cacheResponseIdKey = '\$id'; - final cacheResponseContainerKey = ''; - - final res = await client.call(withCacheParams( - CallParams( - HttpMethod.post, - path, - params: params, - headers: headers, - ), - CacheParams( - model: cacheModel, - key: cacheKey, - responseIdKey: cacheResponseIdKey, - responseContainerKey: cacheResponseContainerKey, - ), + final res = await client.call(CallParams( + HttpMethod.post, + path, + params: params, + headers: headers, )); return models.Token.fromMap(res.data); @@ -906,24 +659,11 @@ class Account extends Service { 'content-type': 'application/json', }; - final cacheModel = ''; - final cacheKey = ''; - final cacheResponseIdKey = '\$id'; - final cacheResponseContainerKey = ''; - - final res = await client.call(withCacheParams( - CallParams( - HttpMethod.put, - path, - params: params, - headers: headers, - ), - CacheParams( - model: cacheModel, - key: cacheKey, - responseIdKey: cacheResponseIdKey, - responseContainerKey: cacheResponseContainerKey, - ), + final res = await client.call(CallParams( + HttpMethod.put, + path, + params: params, + headers: headers, )); return models.Session.fromMap(res.data); @@ -944,24 +684,11 @@ class Account extends Service { 'content-type': 'application/json', }; - final cacheModel = '/account/sessions'.replaceAll('{sessionId}', sessionId); - final cacheKey = sessionId; - final cacheResponseIdKey = '\$id'; - final cacheResponseContainerKey = ''; - - final res = await client.call(withCacheParams( - CallParams( - HttpMethod.get, - path, - params: params, - headers: headers, - ), - CacheParams( - model: cacheModel, - key: cacheKey, - responseIdKey: cacheResponseIdKey, - responseContainerKey: cacheResponseContainerKey, - ), + final res = await client.call(CallParams( + HttpMethod.get, + path, + params: params, + headers: headers, )); return models.Session.fromMap(res.data); @@ -983,24 +710,11 @@ class Account extends Service { 'content-type': 'application/json', }; - final cacheModel = ''; - final cacheKey = ''; - final cacheResponseIdKey = '\$id'; - final cacheResponseContainerKey = ''; - - final res = await client.call(withCacheParams( - CallParams( - HttpMethod.patch, - path, - params: params, - headers: headers, - ), - CacheParams( - model: cacheModel, - key: cacheKey, - responseIdKey: cacheResponseIdKey, - responseContainerKey: cacheResponseContainerKey, - ), + final res = await client.call(CallParams( + HttpMethod.patch, + path, + params: params, + headers: headers, )); return models.Session.fromMap(res.data); @@ -1023,24 +737,11 @@ class Account extends Service { 'content-type': 'application/json', }; - final cacheModel = ''; - final cacheKey = ''; - final cacheResponseIdKey = '\$id'; - final cacheResponseContainerKey = ''; - - final res = await client.call(withCacheParams( - CallParams( - HttpMethod.delete, - path, - params: params, - headers: headers, - ), - CacheParams( - model: cacheModel, - key: cacheKey, - responseIdKey: cacheResponseIdKey, - responseContainerKey: cacheResponseContainerKey, - ), + final res = await client.call(CallParams( + HttpMethod.delete, + path, + params: params, + headers: headers, )); return res.data; @@ -1061,24 +762,11 @@ class Account extends Service { 'content-type': 'application/json', }; - final cacheModel = ''; - final cacheKey = ''; - final cacheResponseIdKey = '\$id'; - final cacheResponseContainerKey = ''; - - final res = await client.call(withCacheParams( - CallParams( - HttpMethod.patch, - path, - params: params, - headers: headers, - ), - CacheParams( - model: cacheModel, - key: cacheKey, - responseIdKey: cacheResponseIdKey, - responseContainerKey: cacheResponseContainerKey, - ), + final res = await client.call(CallParams( + HttpMethod.patch, + path, + params: params, + headers: headers, )); return models.Account.fromMap(res.data); @@ -1113,24 +801,11 @@ class Account extends Service { 'content-type': 'application/json', }; - final cacheModel = ''; - final cacheKey = ''; - final cacheResponseIdKey = '\$id'; - final cacheResponseContainerKey = ''; - - final res = await client.call(withCacheParams( - CallParams( - HttpMethod.post, - path, - params: params, - headers: headers, - ), - CacheParams( - model: cacheModel, - key: cacheKey, - responseIdKey: cacheResponseIdKey, - responseContainerKey: cacheResponseContainerKey, - ), + final res = await client.call(CallParams( + HttpMethod.post, + path, + params: params, + headers: headers, )); return models.Token.fromMap(res.data); @@ -1156,24 +831,11 @@ class Account extends Service { 'content-type': 'application/json', }; - final cacheModel = ''; - final cacheKey = ''; - final cacheResponseIdKey = '\$id'; - final cacheResponseContainerKey = ''; - - final res = await client.call(withCacheParams( - CallParams( - HttpMethod.put, - path, - params: params, - headers: headers, - ), - CacheParams( - model: cacheModel, - key: cacheKey, - responseIdKey: cacheResponseIdKey, - responseContainerKey: cacheResponseContainerKey, - ), + final res = await client.call(CallParams( + HttpMethod.put, + path, + params: params, + headers: headers, )); return models.Token.fromMap(res.data); @@ -1197,24 +859,11 @@ class Account extends Service { 'content-type': 'application/json', }; - final cacheModel = ''; - final cacheKey = ''; - final cacheResponseIdKey = '\$id'; - final cacheResponseContainerKey = ''; - - final res = await client.call(withCacheParams( - CallParams( - HttpMethod.post, - path, - params: params, - headers: headers, - ), - CacheParams( - model: cacheModel, - key: cacheKey, - responseIdKey: cacheResponseIdKey, - responseContainerKey: cacheResponseContainerKey, - ), + final res = await client.call(CallParams( + HttpMethod.post, + path, + params: params, + headers: headers, )); return models.Token.fromMap(res.data); @@ -1240,24 +889,11 @@ class Account extends Service { 'content-type': 'application/json', }; - final cacheModel = ''; - final cacheKey = ''; - final cacheResponseIdKey = '\$id'; - final cacheResponseContainerKey = ''; - - final res = await client.call(withCacheParams( - CallParams( - HttpMethod.put, - path, - params: params, - headers: headers, - ), - CacheParams( - model: cacheModel, - key: cacheKey, - responseIdKey: cacheResponseIdKey, - responseContainerKey: cacheResponseContainerKey, - ), + final res = await client.call(CallParams( + HttpMethod.put, + path, + params: params, + headers: headers, )); return models.Token.fromMap(res.data); diff --git a/lib/services/databases.dart b/lib/services/databases.dart index ccd97ed8..a1c03f3d 100644 --- a/lib/services/databases.dart +++ b/lib/services/databases.dart @@ -27,27 +27,11 @@ class Databases extends Service { 'content-type': 'application/json', }; - final cacheModel = - '/databases/{databaseId}/collections/{collectionId}/documents' - .replaceAll('{databaseId}', databaseId) - .replaceAll('{collectionId}', collectionId); - final cacheKey = ''; - final cacheResponseIdKey = '\$id'; - final cacheResponseContainerKey = 'documents'; - - final res = await client.call(withCacheParams( - CallParams( - HttpMethod.get, - path, - params: params, - headers: headers, - ), - CacheParams( - model: cacheModel, - key: cacheKey, - responseIdKey: cacheResponseIdKey, - responseContainerKey: cacheResponseContainerKey, - ), + final res = await client.call(CallParams( + HttpMethod.get, + path, + params: params, + headers: headers, )); return models.DocumentList.fromMap(res.data); @@ -81,27 +65,11 @@ class Databases extends Service { 'content-type': 'application/json', }; - final cacheModel = - '/databases/{databaseId}/collections/{collectionId}/documents' - .replaceAll('{databaseId}', databaseId) - .replaceAll('{collectionId}', collectionId); - final cacheKey = documentId; - final cacheResponseIdKey = '\$id'; - final cacheResponseContainerKey = ''; - - final res = await client.call(withCacheParams( - CallParams( - HttpMethod.post, - path, - params: params, - headers: headers, - ), - CacheParams( - model: cacheModel, - key: cacheKey, - responseIdKey: cacheResponseIdKey, - responseContainerKey: cacheResponseContainerKey, - ), + final res = await client.call(CallParams( + HttpMethod.post, + path, + params: params, + headers: headers, )); return models.Document.fromMap(res.data); @@ -128,28 +96,11 @@ class Databases extends Service { 'content-type': 'application/json', }; - final cacheModel = - '/databases/{databaseId}/collections/{collectionId}/documents' - .replaceAll('{databaseId}', databaseId) - .replaceAll('{collectionId}', collectionId) - .replaceAll('{documentId}', documentId); - final cacheKey = documentId; - final cacheResponseIdKey = '\$id'; - final cacheResponseContainerKey = ''; - - final res = await client.call(withCacheParams( - CallParams( - HttpMethod.get, - path, - params: params, - headers: headers, - ), - CacheParams( - model: cacheModel, - key: cacheKey, - responseIdKey: cacheResponseIdKey, - responseContainerKey: cacheResponseContainerKey, - ), + final res = await client.call(CallParams( + HttpMethod.get, + path, + params: params, + headers: headers, )); return models.Document.fromMap(res.data); @@ -181,28 +132,11 @@ class Databases extends Service { 'content-type': 'application/json', }; - final cacheModel = - '/databases/{databaseId}/collections/{collectionId}/documents' - .replaceAll('{databaseId}', databaseId) - .replaceAll('{collectionId}', collectionId) - .replaceAll('{documentId}', documentId); - final cacheKey = documentId; - final cacheResponseIdKey = '\$id'; - final cacheResponseContainerKey = ''; - - final res = await client.call(withCacheParams( - CallParams( - HttpMethod.patch, - path, - params: params, - headers: headers, - ), - CacheParams( - model: cacheModel, - key: cacheKey, - responseIdKey: cacheResponseIdKey, - responseContainerKey: cacheResponseContainerKey, - ), + final res = await client.call(CallParams( + HttpMethod.patch, + path, + params: params, + headers: headers, )); return models.Document.fromMap(res.data); @@ -228,28 +162,11 @@ class Databases extends Service { 'content-type': 'application/json', }; - final cacheModel = - '/databases/{databaseId}/collections/{collectionId}/documents' - .replaceAll('{databaseId}', databaseId) - .replaceAll('{collectionId}', collectionId) - .replaceAll('{documentId}', documentId); - final cacheKey = documentId; - final cacheResponseIdKey = '\$id'; - final cacheResponseContainerKey = ''; - - final res = await client.call(withCacheParams( - CallParams( - HttpMethod.delete, - path, - params: params, - headers: headers, - ), - CacheParams( - model: cacheModel, - key: cacheKey, - responseIdKey: cacheResponseIdKey, - responseContainerKey: cacheResponseContainerKey, - ), + final res = await client.call(CallParams( + HttpMethod.delete, + path, + params: params, + headers: headers, )); return res.data; diff --git a/lib/services/functions.dart b/lib/services/functions.dart index 875c2b6c..37afdc2d 100644 --- a/lib/services/functions.dart +++ b/lib/services/functions.dart @@ -26,24 +26,11 @@ class Functions extends Service { 'content-type': 'application/json', }; - final cacheModel = ''; - final cacheKey = ''; - final cacheResponseIdKey = '\$id'; - final cacheResponseContainerKey = 'executions'; - - final res = await client.call(withCacheParams( - CallParams( - HttpMethod.get, - path, - params: params, - headers: headers, - ), - CacheParams( - model: cacheModel, - key: cacheKey, - responseIdKey: cacheResponseIdKey, - responseContainerKey: cacheResponseContainerKey, - ), + final res = await client.call(CallParams( + HttpMethod.get, + path, + params: params, + headers: headers, )); return models.ExecutionList.fromMap(res.data); @@ -70,24 +57,11 @@ class Functions extends Service { 'content-type': 'application/json', }; - final cacheModel = ''; - final cacheKey = ''; - final cacheResponseIdKey = '\$id'; - final cacheResponseContainerKey = ''; - - final res = await client.call(withCacheParams( - CallParams( - HttpMethod.post, - path, - params: params, - headers: headers, - ), - CacheParams( - model: cacheModel, - key: cacheKey, - responseIdKey: cacheResponseIdKey, - responseContainerKey: cacheResponseContainerKey, - ), + final res = await client.call(CallParams( + HttpMethod.post, + path, + params: params, + headers: headers, )); return models.Execution.fromMap(res.data); @@ -109,24 +83,11 @@ class Functions extends Service { 'content-type': 'application/json', }; - final cacheModel = ''; - final cacheKey = ''; - final cacheResponseIdKey = '\$id'; - final cacheResponseContainerKey = ''; - - final res = await client.call(withCacheParams( - CallParams( - HttpMethod.get, - path, - params: params, - headers: headers, - ), - CacheParams( - model: cacheModel, - key: cacheKey, - responseIdKey: cacheResponseIdKey, - responseContainerKey: cacheResponseContainerKey, - ), + final res = await client.call(CallParams( + HttpMethod.get, + path, + params: params, + headers: headers, )); return models.Execution.fromMap(res.data); diff --git a/lib/services/graphql.dart b/lib/services/graphql.dart index c36a96d5..96804d88 100644 --- a/lib/services/graphql.dart +++ b/lib/services/graphql.dart @@ -21,24 +21,11 @@ class Graphql extends Service { 'content-type': 'application/json', }; - final cacheModel = ''; - final cacheKey = ''; - final cacheResponseIdKey = '\$id'; - final cacheResponseContainerKey = ''; - - final res = await client.call(withCacheParams( - CallParams( - HttpMethod.post, - path, - params: params, - headers: headers, - ), - CacheParams( - model: cacheModel, - key: cacheKey, - responseIdKey: cacheResponseIdKey, - responseContainerKey: cacheResponseContainerKey, - ), + final res = await client.call(CallParams( + HttpMethod.post, + path, + params: params, + headers: headers, )); return res.data; @@ -60,24 +47,11 @@ class Graphql extends Service { 'content-type': 'application/json', }; - final cacheModel = ''; - final cacheKey = ''; - final cacheResponseIdKey = '\$id'; - final cacheResponseContainerKey = ''; - - final res = await client.call(withCacheParams( - CallParams( - HttpMethod.post, - path, - params: params, - headers: headers, - ), - CacheParams( - model: cacheModel, - key: cacheKey, - responseIdKey: cacheResponseIdKey, - responseContainerKey: cacheResponseContainerKey, - ), + final res = await client.call(CallParams( + HttpMethod.post, + path, + params: params, + headers: headers, )); return res.data; diff --git a/lib/services/locale.dart b/lib/services/locale.dart index 2623932a..14fafba6 100644 --- a/lib/services/locale.dart +++ b/lib/services/locale.dart @@ -23,24 +23,11 @@ class Locale extends Service { 'content-type': 'application/json', }; - final cacheModel = '/locale'; - final cacheKey = 'current'; - final cacheResponseIdKey = '\$id'; - final cacheResponseContainerKey = ''; - - final res = await client.call(withCacheParams( - CallParams( - HttpMethod.get, - path, - params: params, - headers: headers, - ), - CacheParams( - model: cacheModel, - key: cacheKey, - responseIdKey: cacheResponseIdKey, - responseContainerKey: cacheResponseContainerKey, - ), + final res = await client.call(CallParams( + HttpMethod.get, + path, + params: params, + headers: headers, )); return models.Locale.fromMap(res.data); @@ -60,24 +47,11 @@ class Locale extends Service { 'content-type': 'application/json', }; - final cacheModel = '/locale/continents'; - final cacheKey = ''; - final cacheResponseIdKey = 'code'; - final cacheResponseContainerKey = 'continents'; - - final res = await client.call(withCacheParams( - CallParams( - HttpMethod.get, - path, - params: params, - headers: headers, - ), - CacheParams( - model: cacheModel, - key: cacheKey, - responseIdKey: cacheResponseIdKey, - responseContainerKey: cacheResponseContainerKey, - ), + final res = await client.call(CallParams( + HttpMethod.get, + path, + params: params, + headers: headers, )); return models.ContinentList.fromMap(res.data); @@ -97,24 +71,11 @@ class Locale extends Service { 'content-type': 'application/json', }; - final cacheModel = '/locale/countries'; - final cacheKey = ''; - final cacheResponseIdKey = 'code'; - final cacheResponseContainerKey = 'countries'; - - final res = await client.call(withCacheParams( - CallParams( - HttpMethod.get, - path, - params: params, - headers: headers, - ), - CacheParams( - model: cacheModel, - key: cacheKey, - responseIdKey: cacheResponseIdKey, - responseContainerKey: cacheResponseContainerKey, - ), + final res = await client.call(CallParams( + HttpMethod.get, + path, + params: params, + headers: headers, )); return models.CountryList.fromMap(res.data); @@ -134,24 +95,11 @@ class Locale extends Service { 'content-type': 'application/json', }; - final cacheModel = '/locale/countries/eu'; - final cacheKey = ''; - final cacheResponseIdKey = 'code'; - final cacheResponseContainerKey = 'countries'; - - final res = await client.call(withCacheParams( - CallParams( - HttpMethod.get, - path, - params: params, - headers: headers, - ), - CacheParams( - model: cacheModel, - key: cacheKey, - responseIdKey: cacheResponseIdKey, - responseContainerKey: cacheResponseContainerKey, - ), + final res = await client.call(CallParams( + HttpMethod.get, + path, + params: params, + headers: headers, )); return models.CountryList.fromMap(res.data); @@ -171,24 +119,11 @@ class Locale extends Service { 'content-type': 'application/json', }; - final cacheModel = '/locale/countries/phones'; - final cacheKey = ''; - final cacheResponseIdKey = 'countryCode'; - final cacheResponseContainerKey = 'phones'; - - final res = await client.call(withCacheParams( - CallParams( - HttpMethod.get, - path, - params: params, - headers: headers, - ), - CacheParams( - model: cacheModel, - key: cacheKey, - responseIdKey: cacheResponseIdKey, - responseContainerKey: cacheResponseContainerKey, - ), + final res = await client.call(CallParams( + HttpMethod.get, + path, + params: params, + headers: headers, )); return models.PhoneList.fromMap(res.data); @@ -209,24 +144,11 @@ class Locale extends Service { 'content-type': 'application/json', }; - final cacheModel = '/locale/currencies'; - final cacheKey = ''; - final cacheResponseIdKey = 'code'; - final cacheResponseContainerKey = 'currencies'; - - final res = await client.call(withCacheParams( - CallParams( - HttpMethod.get, - path, - params: params, - headers: headers, - ), - CacheParams( - model: cacheModel, - key: cacheKey, - responseIdKey: cacheResponseIdKey, - responseContainerKey: cacheResponseContainerKey, - ), + final res = await client.call(CallParams( + HttpMethod.get, + path, + params: params, + headers: headers, )); return models.CurrencyList.fromMap(res.data); @@ -246,24 +168,11 @@ class Locale extends Service { 'content-type': 'application/json', }; - final cacheModel = '/locale/languages'; - final cacheKey = ''; - final cacheResponseIdKey = 'code'; - final cacheResponseContainerKey = 'languages'; - - final res = await client.call(withCacheParams( - CallParams( - HttpMethod.get, - path, - params: params, - headers: headers, - ), - CacheParams( - model: cacheModel, - key: cacheKey, - responseIdKey: cacheResponseIdKey, - responseContainerKey: cacheResponseContainerKey, - ), + final res = await client.call(CallParams( + HttpMethod.get, + path, + params: params, + headers: headers, )); return models.LanguageList.fromMap(res.data); diff --git a/lib/services/storage.dart b/lib/services/storage.dart index c1723a75..2cd86836 100644 --- a/lib/services/storage.dart +++ b/lib/services/storage.dart @@ -23,24 +23,11 @@ class Storage extends Service { 'content-type': 'application/json', }; - final cacheModel = ''; - final cacheKey = ''; - final cacheResponseIdKey = '\$id'; - final cacheResponseContainerKey = 'files'; - - final res = await client.call(withCacheParams( - CallParams( - HttpMethod.get, - path, - params: params, - headers: headers, - ), - CacheParams( - model: cacheModel, - key: cacheKey, - responseIdKey: cacheResponseIdKey, - responseContainerKey: cacheResponseContainerKey, - ), + final res = await client.call(CallParams( + HttpMethod.get, + path, + params: params, + headers: headers, )); return models.FileList.fromMap(res.data); @@ -118,24 +105,11 @@ class Storage extends Service { 'content-type': 'application/json', }; - final cacheModel = ''; - final cacheKey = ''; - final cacheResponseIdKey = '\$id'; - final cacheResponseContainerKey = ''; - - final res = await client.call(withCacheParams( - CallParams( - HttpMethod.get, - path, - params: params, - headers: headers, - ), - CacheParams( - model: cacheModel, - key: cacheKey, - responseIdKey: cacheResponseIdKey, - responseContainerKey: cacheResponseContainerKey, - ), + final res = await client.call(CallParams( + HttpMethod.get, + path, + params: params, + headers: headers, )); return models.File.fromMap(res.data); @@ -162,24 +136,11 @@ class Storage extends Service { 'content-type': 'application/json', }; - final cacheModel = ''; - final cacheKey = ''; - final cacheResponseIdKey = '\$id'; - final cacheResponseContainerKey = ''; - - final res = await client.call(withCacheParams( - CallParams( - HttpMethod.put, - path, - params: params, - headers: headers, - ), - CacheParams( - model: cacheModel, - key: cacheKey, - responseIdKey: cacheResponseIdKey, - responseContainerKey: cacheResponseContainerKey, - ), + final res = await client.call(CallParams( + HttpMethod.put, + path, + params: params, + headers: headers, )); return models.File.fromMap(res.data); @@ -201,24 +162,11 @@ class Storage extends Service { 'content-type': 'application/json', }; - final cacheModel = ''; - final cacheKey = ''; - final cacheResponseIdKey = '\$id'; - final cacheResponseContainerKey = ''; - - final res = await client.call(withCacheParams( - CallParams( - HttpMethod.delete, - path, - params: params, - headers: headers, - ), - CacheParams( - model: cacheModel, - key: cacheKey, - responseIdKey: cacheResponseIdKey, - responseContainerKey: cacheResponseContainerKey, - ), + final res = await client.call(CallParams( + HttpMethod.delete, + path, + params: params, + headers: headers, )); return res.data; diff --git a/lib/services/teams.dart b/lib/services/teams.dart index dfd73e0b..f2762149 100644 --- a/lib/services/teams.dart +++ b/lib/services/teams.dart @@ -22,24 +22,11 @@ class Teams extends Service { 'content-type': 'application/json', }; - final cacheModel = '/teams'; - final cacheKey = ''; - final cacheResponseIdKey = '\$id'; - final cacheResponseContainerKey = 'teams'; - - final res = await client.call(withCacheParams( - CallParams( - HttpMethod.get, - path, - params: params, - headers: headers, - ), - CacheParams( - model: cacheModel, - key: cacheKey, - responseIdKey: cacheResponseIdKey, - responseContainerKey: cacheResponseContainerKey, - ), + final res = await client.call(CallParams( + HttpMethod.get, + path, + params: params, + headers: headers, )); return models.TeamList.fromMap(res.data); @@ -67,24 +54,11 @@ class Teams extends Service { 'content-type': 'application/json', }; - final cacheModel = ''; - final cacheKey = ''; - final cacheResponseIdKey = '\$id'; - final cacheResponseContainerKey = ''; - - final res = await client.call(withCacheParams( - CallParams( - HttpMethod.post, - path, - params: params, - headers: headers, - ), - CacheParams( - model: cacheModel, - key: cacheKey, - responseIdKey: cacheResponseIdKey, - responseContainerKey: cacheResponseContainerKey, - ), + final res = await client.call(CallParams( + HttpMethod.post, + path, + params: params, + headers: headers, )); return models.Team.fromMap(res.data); @@ -103,24 +77,11 @@ class Teams extends Service { 'content-type': 'application/json', }; - final cacheModel = '/teams'.replaceAll('{teamId}', teamId); - final cacheKey = teamId; - final cacheResponseIdKey = '\$id'; - final cacheResponseContainerKey = ''; - - final res = await client.call(withCacheParams( - CallParams( - HttpMethod.get, - path, - params: params, - headers: headers, - ), - CacheParams( - model: cacheModel, - key: cacheKey, - responseIdKey: cacheResponseIdKey, - responseContainerKey: cacheResponseContainerKey, - ), + final res = await client.call(CallParams( + HttpMethod.get, + path, + params: params, + headers: headers, )); return models.Team.fromMap(res.data); @@ -143,24 +104,11 @@ class Teams extends Service { 'content-type': 'application/json', }; - final cacheModel = '/teams'.replaceAll('{teamId}', teamId); - final cacheKey = teamId; - final cacheResponseIdKey = '\$id'; - final cacheResponseContainerKey = ''; - - final res = await client.call(withCacheParams( - CallParams( - HttpMethod.put, - path, - params: params, - headers: headers, - ), - CacheParams( - model: cacheModel, - key: cacheKey, - responseIdKey: cacheResponseIdKey, - responseContainerKey: cacheResponseContainerKey, - ), + final res = await client.call(CallParams( + HttpMethod.put, + path, + params: params, + headers: headers, )); return models.Team.fromMap(res.data); @@ -180,24 +128,11 @@ class Teams extends Service { 'content-type': 'application/json', }; - final cacheModel = ''; - final cacheKey = ''; - final cacheResponseIdKey = '\$id'; - final cacheResponseContainerKey = ''; - - final res = await client.call(withCacheParams( - CallParams( - HttpMethod.delete, - path, - params: params, - headers: headers, - ), - CacheParams( - model: cacheModel, - key: cacheKey, - responseIdKey: cacheResponseIdKey, - responseContainerKey: cacheResponseContainerKey, - ), + final res = await client.call(CallParams( + HttpMethod.delete, + path, + params: params, + headers: headers, )); return res.data; @@ -222,25 +157,11 @@ class Teams extends Service { 'content-type': 'application/json', }; - final cacheModel = - '/teams/{teamId}/memberships'.replaceAll('{teamId}', teamId); - final cacheKey = ''; - final cacheResponseIdKey = '\$id'; - final cacheResponseContainerKey = 'memberships'; - - final res = await client.call(withCacheParams( - CallParams( - HttpMethod.get, - path, - params: params, - headers: headers, - ), - CacheParams( - model: cacheModel, - key: cacheKey, - responseIdKey: cacheResponseIdKey, - responseContainerKey: cacheResponseContainerKey, - ), + final res = await client.call(CallParams( + HttpMethod.get, + path, + params: params, + headers: headers, )); return models.MembershipList.fromMap(res.data); @@ -284,24 +205,11 @@ class Teams extends Service { 'content-type': 'application/json', }; - final cacheModel = ''; - final cacheKey = ''; - final cacheResponseIdKey = '\$id'; - final cacheResponseContainerKey = ''; - - final res = await client.call(withCacheParams( - CallParams( - HttpMethod.post, - path, - params: params, - headers: headers, - ), - CacheParams( - model: cacheModel, - key: cacheKey, - responseIdKey: cacheResponseIdKey, - responseContainerKey: cacheResponseContainerKey, - ), + final res = await client.call(CallParams( + HttpMethod.post, + path, + params: params, + headers: headers, )); return models.Membership.fromMap(res.data); @@ -324,26 +232,11 @@ class Teams extends Service { 'content-type': 'application/json', }; - final cacheModel = '/teams/{teamId}/memberships' - .replaceAll('{teamId}', teamId) - .replaceAll('{membershipId}', membershipId); - final cacheKey = membershipId; - final cacheResponseIdKey = '\$id'; - final cacheResponseContainerKey = ''; - - final res = await client.call(withCacheParams( - CallParams( - HttpMethod.get, - path, - params: params, - headers: headers, - ), - CacheParams( - model: cacheModel, - key: cacheKey, - responseIdKey: cacheResponseIdKey, - responseContainerKey: cacheResponseContainerKey, - ), + final res = await client.call(CallParams( + HttpMethod.get, + path, + params: params, + headers: headers, )); return models.Membership.fromMap(res.data); @@ -371,24 +264,11 @@ class Teams extends Service { 'content-type': 'application/json', }; - final cacheModel = ''; - final cacheKey = ''; - final cacheResponseIdKey = '\$id'; - final cacheResponseContainerKey = ''; - - final res = await client.call(withCacheParams( - CallParams( - HttpMethod.patch, - path, - params: params, - headers: headers, - ), - CacheParams( - model: cacheModel, - key: cacheKey, - responseIdKey: cacheResponseIdKey, - responseContainerKey: cacheResponseContainerKey, - ), + final res = await client.call(CallParams( + HttpMethod.patch, + path, + params: params, + headers: headers, )); return models.Membership.fromMap(res.data); @@ -412,24 +292,11 @@ class Teams extends Service { 'content-type': 'application/json', }; - final cacheModel = ''; - final cacheKey = ''; - final cacheResponseIdKey = '\$id'; - final cacheResponseContainerKey = ''; - - final res = await client.call(withCacheParams( - CallParams( - HttpMethod.delete, - path, - params: params, - headers: headers, - ), - CacheParams( - model: cacheModel, - key: cacheKey, - responseIdKey: cacheResponseIdKey, - responseContainerKey: cacheResponseContainerKey, - ), + final res = await client.call(CallParams( + HttpMethod.delete, + path, + params: params, + headers: headers, )); return res.data; @@ -463,24 +330,11 @@ class Teams extends Service { 'content-type': 'application/json', }; - final cacheModel = ''; - final cacheKey = ''; - final cacheResponseIdKey = '\$id'; - final cacheResponseContainerKey = ''; - - final res = await client.call(withCacheParams( - CallParams( - HttpMethod.patch, - path, - params: params, - headers: headers, - ), - CacheParams( - model: cacheModel, - key: cacheKey, - responseIdKey: cacheResponseIdKey, - responseContainerKey: cacheResponseContainerKey, - ), + final res = await client.call(CallParams( + HttpMethod.patch, + path, + params: params, + headers: headers, )); return models.Membership.fromMap(res.data); diff --git a/lib/src/call_handlers/offline_call_handler.dart b/lib/src/call_handlers/offline_call_handler.dart index 3f5c87e9..3951dabe 100644 --- a/lib/src/call_handlers/offline_call_handler.dart +++ b/lib/src/call_handlers/offline_call_handler.dart @@ -8,6 +8,9 @@ import '../client_offline_mixin.dart'; import '../enums.dart'; import '../exception.dart'; import '../offline/caller.dart'; +import '../offline/route.dart'; +import '../offline/route_mapping.dart'; +import '../offline/router.dart'; import '../response.dart'; import 'call_handler.dart'; import 'http_call_handler.dart'; @@ -57,8 +60,22 @@ CallParams withCacheParams(CallParams params, CacheParams cacheParams) { class OfflineCallHandler extends CallHandler with ClientMixin, ClientOfflineMixin { final Caller _call; - - OfflineCallHandler(this._call); + final Router _router = Router(); + + OfflineCallHandler(this._call) { + routes.forEach((route) { + final method = (route['method'] as String).toUpperCase(); + final path = route['path'] as String; + final offline = route['offline'] as Map; + _router.addRoute( + Route(method, path) + .label('offline.model', offline['model']!) + .label('offline.key', offline['key']!) + .label('offline.response-key', offline['response-key']!) + .label('offline.container-key', offline['container-key']!), + ); + }); + } @override Future handleCall(CallParams params) async { @@ -70,6 +87,30 @@ class OfflineCallHandler extends CallHandler // if offline, do offline stuff print('checking offline status...'); + final routeMatch = _router.match( + params.method.name(), + params.path, + ); + + final modelPattern = routeMatch?.getLabel('offline.model') as String; + + final pathValues = routeMatch?.getPathValues(params.path); + + final model = modelPattern.split('/').map((part) { + if (!part.startsWith('{') || !part.endsWith('}')) { + return part; + } + return pathValues![part.substring(1, part.length - 1)]; + }).join('/'); + + final cacheParams = CacheParams( + model: model, + key: routeMatch?.getLabel('offline.key') as String, + responseIdKey: routeMatch?.getLabel('offline.response-key') as String, + responseContainerKey: + routeMatch?.getLabel('offline.container-key') as String, + ); + final uri = Uri.parse(endpoint + params.path); http.BaseRequest request = prepareRequest( @@ -83,8 +124,6 @@ class OfflineCallHandler extends CallHandler await checkOnlineStatus(); } - final cacheParams = getCacheParams(params); - if (cacheParams.model.isNotEmpty && offlinePersistency && !isOnline.value && diff --git a/lib/src/offline/route.dart b/lib/src/offline/route.dart new file mode 100644 index 00000000..ad008a1e --- /dev/null +++ b/lib/src/offline/route.dart @@ -0,0 +1,47 @@ +class Route { + String method = ''; + String path; + final List _aliases = []; + final Map labels = {}; + + final Map _pathParams = {}; + + Route(this.method, this.path) : super(); + + List get aliases => _aliases; + Map get pathParams => _pathParams; + + Route alias(String path) { + if (!_aliases.contains(path)) { + _aliases.add(path); + } + + return this; + } + + void setPathParam(String key, int index) { + _pathParams[key] = index; + } + + Map getPathValues(String path) { + var pathValues = {}; + var parts = path.split('/').where((part) => part.isNotEmpty); + + for (var entry in pathParams.entries) { + if (entry.value < parts.length) { + pathValues[entry.key] = parts.elementAt(entry.value); + } + } + + return pathValues; + } + + Route label(String key, String value) { + labels[key] = value; + return this; + } + + String? getLabel(String key, {String? defaultValue}) { + return labels[key] ?? defaultValue; + } +} diff --git a/lib/src/offline/route_mapping.dart b/lib/src/offline/route_mapping.dart new file mode 100644 index 00000000..1a591ef2 --- /dev/null +++ b/lib/src/offline/route_mapping.dart @@ -0,0 +1,80 @@ +final routes = [ + { + "method": "get", + "path": "\/account", + "offline": { + "model": "\/account", + "key": "current", + "response-key": "\$id", + "container-key": "", + }, + }, + { + "method": "post", + "path": "\/account\/sessions\/email", + "offline": { + "model": "", + "key": "", + "response-key": "\$id", + "container-key": "", + }, + }, + { + "method": "get", + "path": "\/databases\/{databaseId}\/collections\/{collectionId}\/documents", + "offline": { + "model": + "\/databases\/{databaseId}\/collections\/{collectionId}\/documents", + "key": "", + "response-key": "\$id", + "container-key": "documents", + }, + }, + { + "method": "post", + "path": "\/databases\/{databaseId}\/collections\/{collectionId}\/documents", + "offline": { + "model": + "\/databases\/{databaseId}\/collections\/{collectionId}\/documents", + "key": "{documentId}", + "response-key": "\$id", + "container-key": "", + }, + }, + { + "method": "get", + "path": + "\/databases\/{databaseId}\/collections\/{collectionId}\/documents\/{documentId}", + "offline": { + "model": + "\/databases\/{databaseId}\/collections\/{collectionId}\/documents", + "key": "{documentId}", + "response-key": "\$id", + "container-key": "", + }, + }, + { + "method": "patch", + "path": + "\/databases\/{databaseId}\/collections\/{collectionId}\/documents\/{documentId}", + "offline": { + "model": + "\/databases\/{databaseId}\/collections\/{collectionId}\/documents", + "key": "{documentId}", + "response-key": "\$id", + "container-key": "", + }, + }, + { + "method": "delete", + "path": + "\/databases\/{databaseId}\/collections\/{collectionId}\/documents\/{documentId}", + "offline": { + "model": + "\/databases\/{databaseId}\/collections\/{collectionId}\/documents", + "key": "{documentId}", + "response-key": "\$id", + "container-key": "", + }, + } +]; diff --git a/lib/src/offline/router.dart b/lib/src/offline/router.dart new file mode 100644 index 00000000..7720ddff --- /dev/null +++ b/lib/src/offline/router.dart @@ -0,0 +1,126 @@ +import 'dart:collection'; + +import 'route.dart'; + +class Router { + static const String placeholderToken = ':::'; + + Map> _routes = { + 'GET': {}, + 'POST': {}, + 'PUT': {}, + 'PATCH': {}, + 'DELETE': {}, + }; + + List _params = []; + + UnmodifiableMapView> getRoutes() { + return UnmodifiableMapView(_routes); + } + + void addRoute(Route route) { + List result = preparePath(route.path); + String path = result[0]; + Map params = result[1]; + + if (!_routes.containsKey(route.method)) { + throw Exception("Method (${route.method}) not supported."); + } + + if (_routes[route.method]!.containsKey(path)) { + throw Exception("Route for (${route.method}:$path) already registered."); + } + + params.forEach((key, index) { + route.setPathParam(key, index); + }); + + _routes[route.method]![path] = (route); + + for (String alias in route.aliases) { + List aliasResult = preparePath(alias); + String aliasPath = aliasResult[0]; + _routes[route.method]![aliasPath] = route; + } + } + + Route? match(String method, String path) { + if (!_routes.containsKey(method)) { + return null; + } + + List parts = path.split('/').where((p) => p.isNotEmpty).toList(); + int length = parts.length - 1; + List filteredParams = _params.where((i) => i <= length).toList(); + + for (List sample in combinations(filteredParams)) { + sample = sample.where((i) => i <= length).toList(); + String match = parts + .asMap() + .entries + .map( + (entry) => + sample.contains(entry.key) ? placeholderToken : entry.value, + ) + .join('/'); + + if (_routes[method]!.containsKey(match)) { + return _routes[method]![match]!; + } + } + + return null; + } + + Iterable> combinations(List set) { + final result = >[[]]; + + for (final element in set) { + final newCombinations = >[]; + for (final combination in result) { + final ret = [element, ...combination]; + newCombinations.add(ret); + } + result.addAll(newCombinations); + } + + return result; + } + + List preparePath(String path) { + List parts = path.split('/').where((p) => p.isNotEmpty).toList(); + String prepare = ''; + Map params = {}; + + for (int key = 0; key < parts.length; key++) { + String part = parts[key]; + if (key != 0) { + prepare += '/'; + } + + if (part.startsWith('{') && part.endsWith('}')) { + prepare += placeholderToken; + params[part.substring(1, part.length - 1)] = key; + if (!_params.contains(key)) { + _params.add(key); + } + } else { + prepare += part; + } + } + + return [prepare, params]; + } + + void reset() { + _params = []; + _routes = { + 'GET': {}, + 'POST': {}, + 'PUT': {}, + 'PATCH': {}, + 'DELETE': {}, + }; + } +} From 4ecf8173e7e5cb1ca7e013593f64cb40f1ea8f09 Mon Sep 17 00:00:00 2001 From: Steven Nguyen Date: Fri, 16 Jun 2023 16:35:10 -0700 Subject: [PATCH 3/3] Add offline support for relationships --- .../call_handlers/offline_call_handler.dart | 55 +++- lib/src/client_io.dart | 4 + lib/src/client_offline_mixin.dart | 103 ++++++- lib/src/offline/route_mapping.dart | 2 +- lib/src/offline/services/cache_size.dart | 24 +- lib/src/offline/services/model_data.dart | 262 ++++++++++++++++-- lib/src/offline_db_io.dart | 2 +- 7 files changed, 402 insertions(+), 50 deletions(-) diff --git a/lib/src/call_handlers/offline_call_handler.dart b/lib/src/call_handlers/offline_call_handler.dart index 3951dabe..b20482d1 100644 --- a/lib/src/call_handlers/offline_call_handler.dart +++ b/lib/src/call_handlers/offline_call_handler.dart @@ -1,3 +1,4 @@ +import 'dart:convert'; import 'dart:io'; import 'package:http/http.dart' as http; @@ -82,6 +83,10 @@ class OfflineCallHandler extends CallHandler final endpoint = getEndpoint(params); final offlinePersistency = getOfflinePersistency(params); + if (offlinePersistency) { + params.headers['X-SDK-Offline'] = 'true'; + } + while (true) { try { // if offline, do offline stuff @@ -96,19 +101,36 @@ class OfflineCallHandler extends CallHandler final pathValues = routeMatch?.getPathValues(params.path); - final model = modelPattern.split('/').map((part) { - if (!part.startsWith('{') || !part.endsWith('}')) { - return part; + String replacePlaceholder( + String input, Map? pathValues) { + if (!input.startsWith('{') || !input.endsWith('}')) { + return input; } - return pathValues![part.substring(1, part.length - 1)]; + return pathValues![input.substring(1, input.length - 1)]!; + } + + final model = modelPattern.split('/').map((part) { + return replacePlaceholder(part, pathValues); }).join('/'); + final keyPattern = routeMatch?.getLabel('offline.key') ?? ''; + final key = replacePlaceholder( + keyPattern, + pathValues, + ); + + final containerKeyPattern = + routeMatch?.getLabel('offline.container-key') ?? ''; + final containerKey = replacePlaceholder( + containerKeyPattern, + pathValues, + ); + final cacheParams = CacheParams( model: model, - key: routeMatch?.getLabel('offline.key') as String, + key: key, responseIdKey: routeMatch?.getLabel('offline.response-key') as String, - responseContainerKey: - routeMatch?.getLabel('offline.container-key') as String, + responseContainerKey: containerKey, ); final uri = Uri.parse(endpoint + params.path); @@ -145,15 +167,23 @@ class OfflineCallHandler extends CallHandler final response = await next.handleCall(params); - // cache stuff - print('cached stuff...'); if (offlinePersistency) { + // cache stuff + print('cached stuff...'); + + final relationsHeader = response.headers['x-appwrite-relations']; + if (relationsHeader != null) { + final relations = (jsonDecode(relationsHeader) as List) + .cast>(); + await cacheCollections(relations); + } cacheResponse( cacheModel: cacheParams.model, cacheKey: cacheParams.key, cacheResponseIdKey: cacheParams.responseIdKey, - request: request, - response: response, + cacheResponseContainerKey: cacheParams.responseContainerKey, + requestMethod: request.method, + responseData: response.data, ); } @@ -170,7 +200,8 @@ class OfflineCallHandler extends CallHandler rethrow; } isOnline.value = false; - } catch (e) { + } catch (e, s) { + print(s); throw AppwriteException(e.toString()); } } diff --git a/lib/src/client_io.dart b/lib/src/client_io.dart index 7f10b48e..e4f089c5 100644 --- a/lib/src/client_io.dart +++ b/lib/src/client_io.dart @@ -374,6 +374,10 @@ class ClientIO extends ClientBase with ClientMixin { @override Future call(CallParams params) async { + while (!_initialized) { + await Future.delayed(Duration(milliseconds: 100)); + } + params.headers.addAll(this._headers!); final response = await _handler.handleCall( withOfflinePersistency( diff --git a/lib/src/client_offline_mixin.dart b/lib/src/client_offline_mixin.dart index 6af74937..7cf56466 100644 --- a/lib/src/client_offline_mixin.dart +++ b/lib/src/client_offline_mixin.dart @@ -366,45 +366,122 @@ class ClientOfflineMixin { return completer.future; } + void cacheRelated({ + required Map document, + }) { + // iterate over each attribute to see if it's nested data + document.entries.forEach((entry) { + if (entry.value is Map) { + final nestedDocument = entry.value as Map; + final nestedDatabaseId = nestedDocument['\$databaseId'] as String?; + final nestedCollectionId = nestedDocument['\$collectionId'] as String?; + final nestedDocumentId = nestedDocument['\$id'] as String?; + if (nestedDatabaseId == null || + nestedCollectionId == null || + nestedDocumentId == null) return; + final nestedModel = + "/databases/$nestedDatabaseId/collections/$nestedCollectionId/documents"; + cacheResponse( + cacheModel: nestedModel, + cacheKey: nestedDocumentId, + cacheResponseIdKey: "\$id", + cacheResponseContainerKey: '', + requestMethod: 'GET', + responseData: entry.value, + ); + document[entry.key] = { + '\$id': nestedDocumentId, + '\$databaseId': nestedDatabaseId, + '\$collectionId': nestedCollectionId, + }; + } else if (entry.value is List && + entry.value != null && + (entry.value as List).isNotEmpty) { + final values = (entry.value as List); + if (!(values.first is Map)) return; + final nestedDocument = values.first; + final nestedDatabaseId = nestedDocument['\$databaseId'] as String?; + final nestedCollectionId = nestedDocument['\$collectionId'] as String?; + if (nestedDatabaseId == null || nestedCollectionId == null) return; + final nestedModel = + "/databases/$nestedDatabaseId/collections/$nestedCollectionId/documents"; + cacheResponse( + cacheModel: nestedModel, + cacheKey: '', + cacheResponseIdKey: "\$id", + cacheResponseContainerKey: 'documents', + requestMethod: 'GET', + responseData: { + 'documents': entry.value, + }, + ); + document[entry.key] = values.map((value) { + final nestedDocumentId = value['\$id'] as String?; + return { + '\$id': nestedDocumentId, + '\$databaseId': nestedDatabaseId, + '\$collectionId': nestedCollectionId, + }; + }).toList(); + } + }); + } + + Future cacheCollections(List> relations) { + final futures = []; + for (var collection in relations) { + futures.add(_modelData.cacheModel(collection)); + } + return Future.wait(futures); + } + void cacheResponse({ required String cacheModel, required String cacheKey, required String cacheResponseIdKey, - required http.BaseRequest request, - required Response response, + required String cacheResponseContainerKey, + required String requestMethod, + required dynamic responseData, }) { if (cacheModel.isEmpty) return; - switch (request.method) { + switch (requestMethod) { case 'GET': - final clone = cloneMap(response.data); + final clone = cloneMap(responseData); if (cacheKey.isNotEmpty) { + cacheRelated(document: clone); _modelData.upsert(model: cacheModel, data: clone, key: cacheKey); } else { - clone.forEach((key, value) { - if (key == 'total') return; - _modelData.batchUpsert( - model: cacheModel, - dataList: value as List, - idKey: cacheResponseIdKey, - ); + final values = clone[cacheResponseContainerKey] as List; + values.forEach((value) { + cacheRelated(document: value); }); + _modelData.batchUpsert( + model: cacheModel, + dataList: values, + idKey: cacheResponseIdKey, + ); } break; case 'POST': case 'PUT': case 'PATCH': - Map clone = cloneMap(response.data); + Map clone = cloneMap(responseData); if (cacheKey.isEmpty) { cacheKey = clone['\$id'] as String; } if (cacheModel.endsWith('/prefs')) { - clone = response.data['prefs']; + clone = responseData['prefs']; } _modelData.upsert(model: cacheModel, data: clone, key: cacheKey); break; case 'DELETE': if (cacheKey.isNotEmpty) { + // _modelData.get(model: cacheModel, key: cacheKey).then((cachedData) { + // if (cachedData == null) { + // return; + // } + // }); _modelData.delete(model: cacheModel, key: cacheKey); } } diff --git a/lib/src/offline/route_mapping.dart b/lib/src/offline/route_mapping.dart index 1a591ef2..81784f70 100644 --- a/lib/src/offline/route_mapping.dart +++ b/lib/src/offline/route_mapping.dart @@ -36,7 +36,7 @@ final routes = [ "offline": { "model": "\/databases\/{databaseId}\/collections\/{collectionId}\/documents", - "key": "{documentId}", + "key": "", "response-key": "\$id", "container-key": "", }, diff --git a/lib/src/offline/services/cache_size.dart b/lib/src/offline/services/cache_size.dart index cf1b0c87..0934179f 100644 --- a/lib/src/offline/services/cache_size.dart +++ b/lib/src/offline/services/cache_size.dart @@ -19,23 +19,33 @@ class CacheSize { return encoded; } - Future applyChange(int change) async { - if (change == 0) return; + Future applyChange(Transaction txn, int change) async { + if (change == 0) return null; final record = getCacheSizeRecordRef(); - - final currentSize = await record.get(_db) ?? 0; - await record.put(_db, currentSize + change); + final currentSize = await record.get(txn) ?? 0; + return await record.put(txn, currentSize + change); } Future update({ - Map? oldData, + required RecordRef> recordRef, + required Transaction txn, Map? newData, }) async { + final oldData = await recordRef.get(txn); final oldSize = oldData != null ? encode(oldData).length : 0; final newSize = newData != null ? encode(newData).length : 0; final change = newSize - oldSize; - await applyChange(change); + final cacheSize = await applyChange(txn, change); + + if (change != 0) { + print([ + '${recordRef.key}: oldSize: $oldSize', + 'newSize: $newSize', + 'change: $change', + 'cacheSize: $cacheSize', + ].join(', ')); + } } void onChange(void callback(int? currentSize)) { diff --git a/lib/src/offline/services/model_data.dart b/lib/src/offline/services/model_data.dart index fbda4788..a158278e 100644 --- a/lib/src/offline/services/model_data.dart +++ b/lib/src/offline/services/model_data.dart @@ -1,5 +1,6 @@ import 'package:appwrite/src/offline/services/cache_size.dart'; import 'package:sembast/sembast.dart'; +import 'package:sembast/utils/value_utils.dart'; import '../../../appwrite.dart'; import 'accessed_at.dart'; @@ -8,6 +9,9 @@ class ModelData { final Database _db; final AccessedAt _accessedAt; final CacheSize _cacheSize; + final int maxDepth = 3; + final documentModelRegex = RegExp( + r'^/databases/([a-zA-Z0-9\-]*)/collections/([a-zA-Z0-9\-]*)/documents$'); ModelData(this._db) : _accessedAt = AccessedAt(_db), @@ -17,15 +21,128 @@ class ModelData { return stringMapStoreFactory.store(model); } + Future cacheModel(Map collection) { + final store = stringMapStoreFactory.store('collections'); + + return store + .record("${collection['databaseId']}|${collection['collectionId']}") + .put(_db, collection); + } + Future?> get({ required String model, required String key, + }) async { + final immutableRecord = await _getRecord(model: model, key: key); + + if (immutableRecord == null) return null; + + final record = cloneMap(immutableRecord); + await _populateRelated(record, 0); + + return record; + } + + Future?> _getRecord({ + required String model, + required String key, }) async { final store = getModelStore(model); final recordRef = store.record(key); return recordRef.get(_db); } + bool _isNestedDocument(Map record) { + return record.containsKey('\$databaseId') && + record.containsKey('\$collectionId') && + record.containsKey('\$id'); + } + + /// Given a record with $databaseId, $collectionId and $id, populate the rest + /// of the attributes from the cache and then populate the related records. + Future?> _populateRecord( + Map? record, int depth) async { + if (record == null) return record; + + if (!_isNestedDocument(record)) return record; + + final databaseId = record['\$databaseId'] as String; + final collectionId = record['\$collectionId'] as String; + final documentId = record['\$id'] as String; + + final nestedModel = + "/databases/$databaseId/collections/$collectionId/documents"; + + final cached = await _getRecord( + model: nestedModel, + key: documentId, + ); + + if (cached == null) return record; + + record.addAll(cloneMap(cached)); + + await _populateRelated(record, depth + 1); + + return record; + } + + /// Iterate over every attribute of a record and fetch related records from + /// the cache. + Future _populateRelated(Map? record, int depth) { + if (record == null) { + return Future.value(); + } + + // iterate over each attribute and check if it is a relation + final futures = []; + for (final attribute in record.entries) { + if (attribute.value is Map) { + final map = attribute.value as Map; + if (_isNestedDocument(record)) { + if (depth >= maxDepth) { + record[attribute.key] = null; + } else { + final future = _populateRecord(map, depth).then((populated) { + record[attribute.key] = populated; + }); + + futures.add(future); + } + } + } else if (attribute.value is List) { + final List list = attribute.value as List; + final futureList = ?>>[]; + if (list.isEmpty) continue; + + if (depth >= maxDepth && + list.first is Map && + _isNestedDocument(list.first)) { + record[attribute.key] = []; + continue; + } + + for (final map in list) { + if (map is! Map) { + continue; + } + futureList.add(_populateRecord(map, depth)); + } + + if (futureList.isEmpty) { + continue; + } + + final future = Future.wait(futureList).then((populated) { + record[attribute.key] = populated; + }); + + futures.add(future); + } + } + return Future.wait(futures); + } + Future> list({ required String model, required String cacheResponseContainerKey, @@ -50,6 +167,7 @@ class ModelData { final List equalFilters = []; value.forEach((v) { equalFilters.add(Filter.equals(q.params[0], v)); + equalFilters.add(Filter.equals("${q.params[0]}.\$id", v)); }); filters.add(Filter.or(equalFilters)); }); @@ -82,6 +200,30 @@ class ModelData { filters.add(Filter.matches(q.params[0], r'${q.params[1]}+')); break; + case 'isNull': + // TODO: Handle this case. + break; + + case 'isNotNull': + // TODO: Handle this case. + break; + + case 'between': + // TODO: Handle this case. + break; + + case 'startsWith': + // TODO: Handle this case. + break; + + case 'endsWith': + // TODO: Handle this case. + break; + + case 'select': + // TODO: Handle this case. + break; + case 'orderAsc': sortOrders.add(SortOrder(q.params[0] as String)); break; @@ -116,19 +258,27 @@ class ModelData { final records = await store.find(_db, finder: finder); final count = await store.count(_db, filter: filter); + final list = records.map((record) { + // convert to Map + final map = Map(); + record.value.entries.forEach((entry) { + map[entry.key] = entry.value; + }); + return map; + }).toList(); + + final futures = ?>>[]; + for (final record in list) { + futures.add(_populateRecord(record, 0)); + } + final keys = records.map((record) => record.key).toList(); _accessedAt.update(model: store.name, keys: keys); return { 'total': count, - cacheResponseContainerKey: records.map((record) { - final map = Map(); - record.value.entries.forEach((entry) { - map[entry.key] = entry.value; - }); - return map; - }).toList(), + cacheResponseContainerKey: await Future.wait(futures), }; } @@ -137,13 +287,90 @@ class ModelData { required Map data, required String key, }) async { - final store = getModelStore(model); + final match = documentModelRegex.firstMatch(model); + if (match?.groupCount == 2) { + // data is a document + + // match starting at 1 since 0 is the full match + final databaseId = match!.group(1)!; + final collectionId = match.group(2)!; + + final collectionStore = getModelStore('collections'); + final recordRef = collectionStore.record('$databaseId|$collectionId'); + final collection = await recordRef.get(_db); + final attributes = (collection?['attributes'] ?? >{}) + as Map; + for (final attributeEntry in attributes.entries) { + final key = attributeEntry.key; + final attribute = attributeEntry.value as Map; + final relatedCollection = attribute['relatedCollection'] as String; + final relationType = attribute['relationType'] as String; + final side = attribute['side'] as String; + + if (!data.containsKey(key)) continue; + + final nestedModel = + "/databases/$databaseId/collections/$relatedCollection/documents"; + + if (relationType == 'oneToOne' || + (relationType == 'oneToMany' && side == 'child') || + (relationType == 'manyToOne' && side == 'parent')) { + // data[key] is a single document + String documentId = ''; + if (data[key] is String) { + // data[key] is a document ID + documentId = data[key] as String; + } else if (data[key] is Map) { + // data[key] is a nested document + final related = data[key] as Map; + documentId = (related['\$id'] ?? ID.unique()) as String; + await upsert(model: nestedModel, key: documentId, data: related); + } + data[key] = { + '\$databaseId': databaseId, + '\$collectionId': relatedCollection, + '\$id': documentId + }; + } else { + // data[key] is a list of documents + final result = >[]; + final relatedList = data[key] as List; + for (final related in relatedList) { + String documentId = ''; + if (related is String) { + // related is a document ID + documentId = related; + } else if (related is Map) { + // related is a nested document + documentId = (related['\$id'] ?? ID.unique()) as String; + await upsert(model: nestedModel, key: documentId, data: related); + } + result.add({ + '\$databaseId': databaseId, + '\$collectionId': relatedCollection, + '\$id': documentId + }); + } + data[key] = result; + } + } + } - final recordRef = store.record(key); - final record = await recordRef.get(_db); - _cacheSize.update(oldData: record, newData: data); + final result = await _db.transaction((txn) async { + final store = getModelStore(model); + + final recordRef = store.record(key); + final oldData = await recordRef.get(txn); + final oldSize = oldData != null ? _cacheSize.encode(oldData).length : 0; - final result = await recordRef.put(_db, data, merge: true); + final result = await recordRef.put(txn, data, merge: true); + + final newSize = _cacheSize.encode(result).length; + final change = newSize - oldSize; + + await _cacheSize.applyChange(txn, change); + return result; + }); await _accessedAt.update(model: model, keys: [key]); return result; } @@ -167,16 +394,19 @@ class ModelData { Future delete({required String model, required String key}) async { final store = getModelStore(model); RecordSnapshot>? record; + final recordRef = store.record(key); - record = await store.record(key).getSnapshot(_db); + record = await recordRef.getSnapshot(_db); if (record == null) { return; } - _cacheSize.update(oldData: record.value); - - await record.ref.delete(_db); + await _db.transaction((txn) async { + final oldSize = _cacheSize.encode(record!.value).length; + await _cacheSize.applyChange(txn, oldSize * -1); + await record.ref.delete(_db); + }); await _accessedAt.delete(model: model, key: record.key); } diff --git a/lib/src/offline_db_io.dart b/lib/src/offline_db_io.dart index 61b36422..d6cf5481 100644 --- a/lib/src/offline_db_io.dart +++ b/lib/src/offline_db_io.dart @@ -3,7 +3,7 @@ import 'dart:io'; import 'package:sembast/sembast.dart'; import 'package:sembast_sqflite/sembast_sqflite.dart'; import 'package:sqflite/sqflite.dart' as sqflite; -import 'package:sqflite_common_ffi/sqflite_ffi.dart'; +import 'package:sqflite_common_ffi/sqflite_ffi.dart' hide Database; class OfflineDatabase { static final OfflineDatabase instance = OfflineDatabase._internal();