diff --git a/.firebase/hosting.YnVpbGRcd2Vi.cache b/.firebase/hosting.YnVpbGRcd2Vi.cache new file mode 100644 index 0000000..d2dfb6f --- /dev/null +++ b/.firebase/hosting.YnVpbGRcd2Vi.cache @@ -0,0 +1,25 @@ +favicon.png,1677126005777,0cab6e3dd5a9f008afdd133e1e1207cf65f2f2a10eb6712e3c209d8a5f76425a +flutter.js,1693436246460,4ac95ce2f44eacd0bda65849a413c7f16fac0f00d2cbc60b9b07709129ea6c17 +manifest.json,1687560981637,54b1e02c6ebcf14748ac03f13630994502efebfb1c24da3e40e0a60a7a8a930c +assets/dotenv,1692906834241,1486c0c327d0a0f5a9a88a0bd023e62bb5c4cc1882eb52ae9ba77bdf6beb4b8c +assets/assets/json-file-icon.jpg,1685588505972,6006729914d8dd9c1c2e5beee84b59aad49aaf0a80eb71a4ed4bd00989156a0c +assets/fonts/MaterialIcons-Regular.otf,1677126118697,26ccc86b05c476a6b792d6abae012d693ce5e7effabb62ca623c44b7ca264aae +assets/packages/cupertino_icons/assets/CupertinoIcons.ttf,1683591161494,007720e2ea8128f223e5f1a08073b8f40df49b41dac35727107ab73dc4488ae0 +assets/packages/font_awesome_flutter/lib/fonts/fa-brands-400.ttf,1685681348229,f3466c9307c065b2ecc7a22a00ea592334cca5af1a69dac1c7f81e847d56b58e +assets/packages/font_awesome_flutter/lib/fonts/fa-regular-400.ttf,1685681348225,1055bd726faa3947a0cee6b4967de6c77d429cbe192eb9614ba89bf8d61a8ecf +assets/packages/font_awesome_flutter/lib/fonts/fa-solid-900.ttf,1685681348223,51e214b4ce9d14a387dae31b961d54e385ac80f974771089ce6d9cf13d5e01c5 +canvaskit/canvaskit.js,1689715551515,8ff9cbe5dbf69c38eb7c466ad2a03f276996bfafabafa667fb31de3a9ce3161b +canvaskit/canvaskit.wasm,1689715551551,c02c266899510d8fe7228271e0c9219e42f3f81c38d2cf677abb3893f2bcb119 +canvaskit/profiling/canvaskit.js,1689715551554,6420bd60a37f0870f2d750e80e38eca52602e2664288d1a2ce6f99b399e946a8 +canvaskit/profiling/canvaskit.wasm,1689715551601,6b433eb1c13eea60832b8f784715a0305ca764effb0443a8134485495203341e +icons/Icon-192.png,1677126005777,eaf2464bfb1d192fdd192a616f7b858dee456d573c6ec619648a1dcf2bdddfa6 +icons/Icon-512.png,1677126005777,9cf4cd298ae95acc1f25e97d88aa3f6bbfdf40867ea0f8a854c4393f49d56e64 +icons/Icon-maskable-192.png,1677126094067,196ce9142a3442ab37ae90cd46c3389e4660400c859b81cbb0538a51b39752eb +icons/Icon-maskable-512.png,1677126094051,6833b7c449e0dd24d5e164a53cc4557e643893e675b476b05efcbb9a6aa05bf0 +index.html,1694752998763,fafa1c15005a99820ca72d88b8bab37911bd416c16909e12b088c19fbc2aa413 +flutter_service_worker.js,1694753000900,8100359d370610945d8fc606f3c0e3d6ef05517397fccfc044861874a2be7c3d +version.json,1694752998638,240e129b016280a8492bc6774bfadc4b0e1d94677bbedd6a6d5d7853120e9ac1 +assets/AssetManifest.json,1694752998747,657386932a5bdcab2b9bfda7f136538f161d5dac8ee2a853961d01d854565e65 +assets/FontManifest.json,1694752998748,513648b64710d048971e49c5969e91a20ae1a4636f93ba888a67fe9a15cd61bf +assets/NOTICES,1694752998748,8172f78c527dc0310af485ab76d7dae07b68ac24807c50abde2139707e30824f +main.dart.js,1694752996192,f626697a08541e466ee0580fa8c6ff7abb35290dcc7f8886c0da39a0ee89122d diff --git a/.firebaserc b/.firebaserc new file mode 100644 index 0000000..44057c0 --- /dev/null +++ b/.firebaserc @@ -0,0 +1,5 @@ +{ + "projects": { + "default": "appflowy-theme-marketpla-e8f9b" + } +} diff --git a/.gitignore b/.gitignore index 3a83c2f..fe4ec87 100644 --- a/.gitignore +++ b/.gitignore @@ -1,27 +1,53 @@ -# See https://www.dartlang.org/guides/libraries/private-files +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ +migrate_working_dir/ -# Files and directories created by pub -.dart_tool/ -.packages -build/ -# If you're building an application, you may want to check-in your pubspec.lock -pubspec.lock - -# Directory created by dartdoc -# If you don't generate documentation locally you can remove this line. -doc/api/ - -# dotenv environment variables file -.env* - -# Avoid committing generated Javascript files: -*.dart.js -*.info.json # Produced by the --dump-info flag. -*.js # When generated by dart2js. Don't specify *.js if your - # project includes source files written in JavaScript. -*.js_ -*.js.deps -*.js.map +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ .flutter-plugins .flutter-plugins-dependencies +.packages +.pub-cache/ +.pub/ +/build/ + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Android Studio will place build artifacts here +/android/app/debug +/android/app/profile +/android/app/release + +.env +.env.dev +.env.default +.env.local +*.env +dotenv + +node_modules \ No newline at end of file diff --git a/.metadata b/.metadata new file mode 100644 index 0000000..f25b3d4 --- /dev/null +++ b/.metadata @@ -0,0 +1,45 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled. + +version: + revision: 4d9e56e694b656610ab87fcf2efbcd226e0ed8cf + channel: stable + +project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: 4d9e56e694b656610ab87fcf2efbcd226e0ed8cf + base_revision: 4d9e56e694b656610ab87fcf2efbcd226e0ed8cf + - platform: android + create_revision: 4d9e56e694b656610ab87fcf2efbcd226e0ed8cf + base_revision: 4d9e56e694b656610ab87fcf2efbcd226e0ed8cf + - platform: ios + create_revision: 4d9e56e694b656610ab87fcf2efbcd226e0ed8cf + base_revision: 4d9e56e694b656610ab87fcf2efbcd226e0ed8cf + - platform: linux + create_revision: 4d9e56e694b656610ab87fcf2efbcd226e0ed8cf + base_revision: 4d9e56e694b656610ab87fcf2efbcd226e0ed8cf + - platform: macos + create_revision: 4d9e56e694b656610ab87fcf2efbcd226e0ed8cf + base_revision: 4d9e56e694b656610ab87fcf2efbcd226e0ed8cf + - platform: web + create_revision: 4d9e56e694b656610ab87fcf2efbcd226e0ed8cf + base_revision: 4d9e56e694b656610ab87fcf2efbcd226e0ed8cf + - platform: windows + create_revision: 4d9e56e694b656610ab87fcf2efbcd226e0ed8cf + base_revision: 4d9e56e694b656610ab87fcf2efbcd226e0ed8cf + + # User provided section + + # List of Local paths (relative to this file) that should be + # ignored by the migrate tool. + # + # Files that are not part of the templates will be ignored by default. + unmanaged_files: + - 'lib/main.dart' + - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..2c7ddc5 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "deno.enable": true, + "deno.lint": true, + "deno.unstable": true +} \ No newline at end of file diff --git a/README.md b/README.md index 9f0c815..231e59a 100644 --- a/README.md +++ b/README.md @@ -1 +1,16 @@ -# AppFlowy-Theme \ No newline at end of file +# appflowy_theme_marketplace + +A new Flutter project. + +## Getting Started + +This project is a starting point for a Flutter application. + +A few resources to get you started if this is your first Flutter project: + +- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab) +- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook) + +For help getting started with Flutter development, view the +[online documentation](https://docs.flutter.dev/), which offers tutorials, +samples, guidance on mobile development, and a full API reference. diff --git a/analysis_options.yaml b/analysis_options.yaml new file mode 100644 index 0000000..17f4230 --- /dev/null +++ b/analysis_options.yaml @@ -0,0 +1,34 @@ +# This file configures the analyzer, which statically analyzes Dart code to +# check for errors, warnings, and lints. +# +# The issues identified by the analyzer are surfaced in the UI of Dart-enabled +# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be +# invoked from the command line by running `flutter analyze`. + +# The following line activates a set of recommended lints for Flutter apps, +# packages, and plugins designed to encourage good coding practices. +include: package:flutter_lints/flutter.yaml + +analyzer: + errors: + invalid_annotation_target: ignore + +linter: + # The lint rules applied to this project can be customized in the + # section below to disable rules from the `package:flutter_lints/flutter.yaml` + # included above or to enable additional rules. A list of all available lints + # and their documentation is published at + # https://dart-lang.github.io/linter/lints/index.html. + # + # Instead of disabling a lint rule for the entire project in the + # section below, it can also be suppressed for a single line of code + # or a specific dart file by using the `// ignore: name_of_lint` and + # `// ignore_for_file: name_of_lint` syntax on the line or in the file + # producing the lint. + rules: + # curly_braces_in_flow_control_structures: false # Disable curly brace enforcement + prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule + # avoid_print: false # Uncomment to disable the `avoid_print` rule + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/android/.gitignore b/android/.gitignore new file mode 100644 index 0000000..6f56801 --- /dev/null +++ b/android/.gitignore @@ -0,0 +1,13 @@ +gradle-wrapper.jar +/.gradle +/captures/ +/gradlew +/gradlew.bat +/local.properties +GeneratedPluginRegistrant.java + +# Remember to never publicly share your keystore. +# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app +key.properties +**/*.keystore +**/*.jks diff --git a/android/app/build.gradle b/android/app/build.gradle new file mode 100644 index 0000000..36be9bf --- /dev/null +++ b/android/app/build.gradle @@ -0,0 +1,72 @@ +def localProperties = new Properties() +def localPropertiesFile = rootProject.file('local.properties') +if (localPropertiesFile.exists()) { + localPropertiesFile.withReader('UTF-8') { reader -> + localProperties.load(reader) + } +} + +def flutterRoot = localProperties.getProperty('flutter.sdk') +if (flutterRoot == null) { + throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") +} + +def flutterVersionCode = localProperties.getProperty('flutter.versionCode') +if (flutterVersionCode == null) { + flutterVersionCode = '1' +} + +def flutterVersionName = localProperties.getProperty('flutter.versionName') +if (flutterVersionName == null) { + flutterVersionName = '1.0' +} + +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" + +android { + compileSdkVersion flutter.compileSdkVersion + ndkVersion flutter.ndkVersion + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlinOptions { + jvmTarget = '1.8' + } + + sourceSets { + main.java.srcDirs += 'src/main/kotlin' + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId "com.example.appflowy_theme_marketplace" + // You can update the following values to match your application needs. + // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration. + minSdkVersion flutter.minSdkVersion + targetSdkVersion flutter.targetSdkVersion + versionCode flutterVersionCode.toInteger() + versionName flutterVersionName + minSdkVersion 19 + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig signingConfigs.debug + } + } +} + +flutter { + source '../..' +} + +dependencies { + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" +} diff --git a/android/app/google-services.json b/android/app/google-services.json new file mode 100644 index 0000000..60210b2 --- /dev/null +++ b/android/app/google-services.json @@ -0,0 +1,46 @@ +{ + "project_info": { + "project_number": "806787763701", + "project_id": "appflowy-theme-marketpla-e8f9b", + "storage_bucket": "appflowy-theme-marketpla-e8f9b.appspot.com" + }, + "client": [ + { + "client_info": { + "mobilesdk_app_id": "1:806787763701:android:f76512fc89190c049664bc", + "android_client_info": { + "package_name": "com.example.appflowy_theme_marketplace" + } + }, + "oauth_client": [ + { + "client_id": "806787763701-g933og69ppi6563pr3ip49tf13d6ugfn.apps.googleusercontent.com", + "client_type": 3 + } + ], + "api_key": [ + { + "current_key": "AIzaSyBl0hcWFhkmFgv9gP-ogjVgp8LhNvsizyc" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [ + { + "client_id": "806787763701-9ld69u6mt1hg2guudh6h8i7q0205duc6.apps.googleusercontent.com", + "client_type": 3 + }, + { + "client_id": "806787763701-en62uhjnm7lr09j09u49lg9d70hfkl6l.apps.googleusercontent.com", + "client_type": 2, + "ios_info": { + "bundle_id": "com.example.appflowyThemeMarketplace" + } + } + ] + } + } + } + ], + "configuration_version": "1" +} \ No newline at end of file diff --git a/android/app/src/debug/AndroidManifest.xml b/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 0000000..b41d6ef --- /dev/null +++ b/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,8 @@ + + + + diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..c21a8bc --- /dev/null +++ b/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + diff --git a/android/app/src/main/kotlin/com/example/appflowy_theme_marketplace/MainActivity.kt b/android/app/src/main/kotlin/com/example/appflowy_theme_marketplace/MainActivity.kt new file mode 100644 index 0000000..ea20f10 --- /dev/null +++ b/android/app/src/main/kotlin/com/example/appflowy_theme_marketplace/MainActivity.kt @@ -0,0 +1,6 @@ +package com.example.appflowy_theme_marketplace + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity: FlutterActivity() { +} diff --git a/android/app/src/main/res/drawable-v21/launch_background.xml b/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 0000000..f74085f --- /dev/null +++ b/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/android/app/src/main/res/drawable/launch_background.xml b/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 0000000..304732f --- /dev/null +++ b/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..db77bb4 Binary files /dev/null and b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..17987b7 Binary files /dev/null and b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..09d4391 Binary files /dev/null and b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..d5f1c8d Binary files /dev/null and b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..4d6372e Binary files /dev/null and b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/android/app/src/main/res/values-night/styles.xml b/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 0000000..06952be --- /dev/null +++ b/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/android/app/src/main/res/values/styles.xml b/android/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..cb1ef88 --- /dev/null +++ b/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/android/app/src/profile/AndroidManifest.xml b/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 0000000..b41d6ef --- /dev/null +++ b/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,8 @@ + + + + diff --git a/android/build.gradle b/android/build.gradle new file mode 100644 index 0000000..58a8c74 --- /dev/null +++ b/android/build.gradle @@ -0,0 +1,31 @@ +buildscript { + ext.kotlin_version = '1.7.10' + repositories { + google() + mavenCentral() + } + + dependencies { + classpath 'com.android.tools.build:gradle:7.2.0' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} + +allprojects { + repositories { + google() + mavenCentral() + } +} + +rootProject.buildDir = '../build' +subprojects { + project.buildDir = "${rootProject.buildDir}/${project.name}" +} +subprojects { + project.evaluationDependsOn(':app') +} + +task clean(type: Delete) { + delete rootProject.buildDir +} diff --git a/android/gradle.properties b/android/gradle.properties new file mode 100644 index 0000000..94adc3a --- /dev/null +++ b/android/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx1536M +android.useAndroidX=true +android.enableJetifier=true diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..3c472b9 --- /dev/null +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip diff --git a/android/settings.gradle b/android/settings.gradle new file mode 100644 index 0000000..44e62bc --- /dev/null +++ b/android/settings.gradle @@ -0,0 +1,11 @@ +include ':app' + +def localPropertiesFile = new File(rootProject.projectDir, "local.properties") +def properties = new Properties() + +assert localPropertiesFile.exists() +localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } + +def flutterSdkPath = properties.getProperty("flutter.sdk") +assert flutterSdkPath != null, "flutter.sdk not set in local.properties" +apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" diff --git a/assets/json-file-icon.jpg b/assets/json-file-icon.jpg new file mode 100644 index 0000000..da13c62 Binary files /dev/null and b/assets/json-file-icon.jpg differ diff --git a/firebase.json b/firebase.json new file mode 100644 index 0000000..d8f1110 --- /dev/null +++ b/firebase.json @@ -0,0 +1,63 @@ +{ + "hosting": { + "public": "build/web", + "ignore": [ + "firebase.json", + "**/.*", + "**/node_modules/**" + ] + }, + "functions": [ + { + "source": "firebase/functions", + "codebase": "default", + "ignore": [ + "node_modules", + ".git", + "firebase-debug.log", + "firebase-debug.*.log" + ], + "predeploy": [ + "npm --prefix \"$RESOURCE_DIR\" run lint", + "npm --prefix \"$RESOURCE_DIR\" run build" + ] + } + ], + "emulators": { + "auth": { + "port": 9099 + }, + "functions": { + "port": 5001 + }, + "firestore": { + "port": 9399 + }, + "database": { + "port": 9000 + }, + "hosting": { + "port": 5000 + }, + "pubsub": { + "port": 8085 + }, + "storage": { + "port": 9199 + }, + "eventarc": { + "port": 9299 + }, + "ui": { + "enabled": true + }, + "singleProjectMode": true + }, + "firestore": { + "rules": "firestore.rules", + "indexes": "firestore.indexes.json" + }, + "storage": { + "rules": "storage.rules" + } +} diff --git a/firebase/functions/.eslintrc.js b/firebase/functions/.eslintrc.js new file mode 100644 index 0000000..247290e --- /dev/null +++ b/firebase/functions/.eslintrc.js @@ -0,0 +1,48 @@ +module.exports = { + root: true, + env: { + es6: true, + node: true, + }, + extends: [ + "eslint:recommended", + "plugin:import/errors", + "plugin:import/warnings", + "plugin:import/typescript", + "google", + "plugin:@typescript-eslint/recommended", + ], + parser: "@typescript-eslint/parser", + parserOptions: { + project: ["tsconfig.json", "tsconfig.dev.json"], + sourceType: "module", + tsconfigRootDir: __dirname, + }, + ignorePatterns: [ + "/lib/**/*", // Ignore built files. + ], + plugins: [ + "@typescript-eslint", + "import", + ], + rules: { + "import/no-unresolved": 0, + "indent": ["error", 2], + "linebreak-style": 0, + "semi": "off", + "object-curly-spacing": "off", + "curly": "off", + "quotes": 'off', + "max-len": "off", + "jsdoc/require-jsdoc": "off", + "require-jsdoc": 0, + "space-before-blocks": "off", + "keyword-spacing": "off", + "space-after-keywords": "off", + /** turn these on to clean up before shipping */ + // "import/no-commonjs": "off", + "warning": "off", + "no-unused-vars": "off", + "no-unused-functions": "off", + }, +}; diff --git a/firebase/functions/.gitignore b/firebase/functions/.gitignore new file mode 100644 index 0000000..4fb75e7 --- /dev/null +++ b/firebase/functions/.gitignore @@ -0,0 +1,16 @@ +# Compiled JavaScript files +lib/**/*.js +lib/**/*.js.map + +# TypeScript v1 declaration files +typings/ + +# Node.js dependency directory +node_modules/ + +.env +.env.dev +.env.default +.env.local + +service_account.json \ No newline at end of file diff --git a/firebase/functions/package-lock.json b/firebase/functions/package-lock.json new file mode 100644 index 0000000..c5dc58b --- /dev/null +++ b/firebase/functions/package-lock.json @@ -0,0 +1,8044 @@ +{ + "name": "functions", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "functions", + "dependencies": { + "cors": "^2.8.5", + "firebase-admin": "^11.10.1", + "firebase-functions": "^4.4.1", + "stripe": "^12.16.0" + }, + "devDependencies": { + "@typescript-eslint/eslint-plugin": "^5.12.0", + "@typescript-eslint/parser": "^5.12.0", + "bad-words": "^3.0.4", + "eslint": "^8.9.0", + "eslint-config-google": "^0.14.0", + "eslint-plugin-import": "^2.25.4", + "firebase-functions-test": "^3.1.0", + "typescript": "^4.9.0" + }, + "engines": { + "node": "18" + } + }, + "node_modules/@aashutoshrathi/word-wrap": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", + "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", + "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==", + "dev": true, + "peer": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.5.tgz", + "integrity": "sha512-Xmwn266vad+6DAqEB2A6V/CcZVp62BbwVmcOJc2RPuwih1kw02TjQvWVWlcKGbBPd+8/0V5DEkOcizRGYsspYQ==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/highlight": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.22.9", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.22.9.tgz", + "integrity": "sha512-5UamI7xkUcJ3i9qVDS+KFDEK8/7oJ55/sJMB1Ge7IEapr7KfdfV/HErR+koZwOfd+SgtFKOKRhRakdg++DcJpQ==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.22.9", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.22.9.tgz", + "integrity": "sha512-G2EgeufBcYw27U4hhoIwFcgc1XU7TlXJ3mv04oOv1WCuo900U/anZSPzEqNjwdjgffkk2Gs0AN0dW1CKVLcG7w==", + "dev": true, + "peer": true, + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.22.5", + "@babel/generator": "^7.22.9", + "@babel/helper-compilation-targets": "^7.22.9", + "@babel/helper-module-transforms": "^7.22.9", + "@babel/helpers": "^7.22.6", + "@babel/parser": "^7.22.7", + "@babel/template": "^7.22.5", + "@babel/traverse": "^7.22.8", + "@babel/types": "^7.22.5", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.2", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "dev": true, + "peer": true + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "peer": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.22.9", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.22.9.tgz", + "integrity": "sha512-KtLMbmicyuK2Ak/FTCJVbDnkN1SlT8/kceFTiuDiiRUUSMnHMidxSCdG4ndkTOHHpoomWe/4xkvHkEOncwjYIw==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/types": "^7.22.5", + "@jridgewell/gen-mapping": "^0.3.2", + "@jridgewell/trace-mapping": "^0.3.17", + "jsesc": "^2.5.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.22.9", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.9.tgz", + "integrity": "sha512-7qYrNM6HjpnPHJbopxmb8hSPoZ0gsX8IvUS32JGVoy+pU9e5N0nLr1VjJoR6kA4d9dmGLxNYOjeB8sUDal2WMw==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/compat-data": "^7.22.9", + "@babel/helper-validator-option": "^7.22.5", + "browserslist": "^4.21.9", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "peer": true, + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "peer": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "peer": true + }, + "node_modules/@babel/helper-environment-visitor": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.5.tgz", + "integrity": "sha512-XGmhECfVA/5sAt+H+xpSg0mfrHq6FzNr9Oxh7PSEBBRUb/mL7Kz3NICXb194rCqAEdxkhPT1a88teizAFyvk8Q==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-function-name": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.22.5.tgz", + "integrity": "sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/template": "^7.22.5", + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-hoist-variables": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", + "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.5.tgz", + "integrity": "sha512-8Dl6+HD/cKifutF5qGd/8ZJi84QeAKh+CEe1sBzz8UayBBGg1dAIJrdHOcOM5b2MpzWL2yuotJTtGjETq0qjXg==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.22.9", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.22.9.tgz", + "integrity": "sha512-t+WA2Xn5K+rTeGtC8jCsdAH52bjggG5TKRuRrAGNM/mjIbO4GxvlLMFOEz9wXY5I2XQ60PMFsAG2WIcG82dQMQ==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.22.5", + "@babel/helper-module-imports": "^7.22.5", + "@babel/helper-simple-access": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/helper-validator-identifier": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz", + "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-simple-access": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz", + "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-split-export-declaration": { + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", + "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", + "integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz", + "integrity": "sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.22.5.tgz", + "integrity": "sha512-R3oB6xlIVKUnxNUxbmgq7pKjxpru24zlimpE8WK47fACIlM0II/Hm1RS8IaOI7NgCr6LNS+jl5l75m20npAziw==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.22.6.tgz", + "integrity": "sha512-YjDs6y/fVOYFV8hAf1rxd1QvR9wJe1pDBZ2AREKq/SDayfPzgk0PBnVuTCE5X1acEpMMNOVUqoe+OwiZGJ+OaA==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/template": "^7.22.5", + "@babel/traverse": "^7.22.6", + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.5.tgz", + "integrity": "sha512-BSKlD1hgnedS5XRnGOljZawtag7H1yPfQp0tdNJCHoH6AZ+Pcm9VvkrK59/Yy593Ypg0zMxH2BxD1VPYUQ7UIw==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.22.5", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "peer": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "peer": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "peer": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@babel/highlight/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true, + "peer": true + }, + "node_modules/@babel/highlight/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "peer": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@babel/highlight/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "peer": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "peer": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/parser": { + "version": "7.22.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.7.tgz", + "integrity": "sha512-7NF8pOkHP5o2vpmGgNGcfAeCvOYhGLyA3Z4eBQkT1RJlWu47n63bCs93QfJ2hIAFCil7L5P2IWhs1oToVgrL0Q==", + "devOptional": true, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-bigint": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", + "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-import-meta": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", + "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.22.5.tgz", + "integrity": "sha512-gvyP4hZrgrs/wWMaocvxZ44Hw0b3W8Pe+cMxc8V1ULQ07oh8VNbIRaoD1LRZVTvD+0nieDKjfgKg89sD7rrKrg==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-top-level-await": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz", + "integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.22.5.tgz", + "integrity": "sha512-1mS2o03i7t1c6VzH6fdQ3OA8tcEIxwG18zIPRp+UY1Ihv6W+XZzBCVxExF9upussPXJ0xE9XRHwMoNs1ep/nRQ==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.5.tgz", + "integrity": "sha512-X7yV7eiwAxdj9k94NEylvbVHLiVG1nvzCV2EAowhxLTwODV1jl9UzZ48leOC0sH7OnuHrIkllaBgneUykIcZaw==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.22.5", + "@babel/parser": "^7.22.5", + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.22.8", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.22.8.tgz", + "integrity": "sha512-y6LPR+wpM2I3qJrsheCTwhIinzkETbplIgPBbwvqPKc+uljeA5gP+3nP8irdYt1mjQaDnlIcG+dw8OjAco4GXw==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.22.5", + "@babel/generator": "^7.22.7", + "@babel/helper-environment-visitor": "^7.22.5", + "@babel/helper-function-name": "^7.22.5", + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/parser": "^7.22.7", + "@babel/types": "^7.22.5", + "debug": "^4.1.0", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse/node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "peer": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/types": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.5.tgz", + "integrity": "sha512-zo3MIHGOkPOfoRXitsgHLjEXmlDaD/5KU1Uzuc9GNiZPhSqVxVRtxuPaSBZDsYZ9qV88AjtMtWW7ww98loJ9KA==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-string-parser": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.5", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true, + "peer": true + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.6.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.6.1.tgz", + "integrity": "sha512-O7x6dMstWLn2ktjcoiNLDkAGG2EjveHL+Vvc+n0fXumkJYAcSqcVYKtwDU+hDZ0uDUsnUagSYaZrOLAYE8un1A==", + "dev": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.0.tgz", + "integrity": "sha512-Lj7DECXqIVCqnqjjHMPna4vn6GJcMgul/wuS0je9OZ9gsL0zzDpKPVtcG1HaDVc+9y+qgXneTeUMbCqXJNpH1A==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/js": { + "version": "8.44.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.44.0.tgz", + "integrity": "sha512-Ag+9YM4ocKQx9AarydN0KY2j0ErMHNIocPDrVo8zAE44xLTjEtz81OdR68/cydGtk6m6jDb5Za3r2useMzYmSw==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@fastify/busboy": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-1.2.1.tgz", + "integrity": "sha512-7PQA7EH43S0CxcOa9OeAnaeA0oQ+e/DHNPZwSQM9CQHW76jle5+OvLdibRp/Aafs9KXbLhxyjOTkRjWUbQEd3Q==", + "dependencies": { + "text-decoding": "^1.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@firebase/app-types": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.9.0.tgz", + "integrity": "sha512-AeweANOIo0Mb8GiYm3xhTEBVCmPwTYAu9Hcd2qSkLuga/6+j9b1Jskl5bpiSQWy9eJ/j5pavxj6eYogmnuzm+Q==" + }, + "node_modules/@firebase/auth-interop-types": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.2.1.tgz", + "integrity": "sha512-VOaGzKp65MY6P5FI84TfYKBXEPi6LmOCSMMzys6o2BN2LOsqy7pCuZCup7NYnfbk5OkkQKzvIfHOzTm0UDpkyg==" + }, + "node_modules/@firebase/component": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.6.4.tgz", + "integrity": "sha512-rLMyrXuO9jcAUCaQXCMjCMUsWrba5fzHlNK24xz5j2W6A/SRmK8mZJ/hn7V0fViLbxC0lPMtrK1eYzk6Fg03jA==", + "dependencies": { + "@firebase/util": "1.9.3", + "tslib": "^2.1.0" + } + }, + "node_modules/@firebase/database": { + "version": "0.14.4", + "resolved": "https://registry.npmjs.org/@firebase/database/-/database-0.14.4.tgz", + "integrity": "sha512-+Ea/IKGwh42jwdjCyzTmeZeLM3oy1h0mFPsTy6OqCWzcu/KFqRAr5Tt1HRCOBlNOdbh84JPZC47WLU18n2VbxQ==", + "dependencies": { + "@firebase/auth-interop-types": "0.2.1", + "@firebase/component": "0.6.4", + "@firebase/logger": "0.4.0", + "@firebase/util": "1.9.3", + "faye-websocket": "0.11.4", + "tslib": "^2.1.0" + } + }, + "node_modules/@firebase/database-compat": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@firebase/database-compat/-/database-compat-0.3.4.tgz", + "integrity": "sha512-kuAW+l+sLMUKBThnvxvUZ+Q1ZrF/vFJ58iUY9kAcbX48U03nVzIF6Tmkf0p3WVQwMqiXguSgtOPIB6ZCeF+5Gg==", + "dependencies": { + "@firebase/component": "0.6.4", + "@firebase/database": "0.14.4", + "@firebase/database-types": "0.10.4", + "@firebase/logger": "0.4.0", + "@firebase/util": "1.9.3", + "tslib": "^2.1.0" + } + }, + "node_modules/@firebase/database-types": { + "version": "0.10.4", + "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-0.10.4.tgz", + "integrity": "sha512-dPySn0vJ/89ZeBac70T+2tWWPiJXWbmRygYv0smT5TfE3hDrQ09eKMF3Y+vMlTdrMWq7mUdYW5REWPSGH4kAZQ==", + "dependencies": { + "@firebase/app-types": "0.9.0", + "@firebase/util": "1.9.3" + } + }, + "node_modules/@firebase/logger": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.4.0.tgz", + "integrity": "sha512-eRKSeykumZ5+cJPdxxJRgAC3G5NknY2GwEbKfymdnXtnT0Ucm4pspfR6GT4MUQEDuJwRVbVcSx85kgJulMoFFA==", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/@firebase/util": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.9.3.tgz", + "integrity": "sha512-DY02CRhOZwpzO36fHpuVysz6JZrscPiBXD0fXp6qSrL9oNOx5KWICKdR95C0lSITzxp0TZosVyHqzatE8JbcjA==", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/@google-cloud/firestore": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/@google-cloud/firestore/-/firestore-6.7.0.tgz", + "integrity": "sha512-bkH2jb5KkQSUa+NAvpip9HQ+rpYhi77IaqHovWuN07adVmvNXX08gPpvPWEzoXYa/wDjEVI7LiAtCWkJJEYTNg==", + "optional": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "functional-red-black-tree": "^1.0.1", + "google-gax": "^3.5.7", + "protobufjs": "^7.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@google-cloud/paginator": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@google-cloud/paginator/-/paginator-3.0.7.tgz", + "integrity": "sha512-jJNutk0arIQhmpUUQJPJErsojqo834KcyB6X7a1mxuic8i1tKXxde8E69IZxNZawRIlZdIK2QY4WALvlK5MzYQ==", + "optional": true, + "dependencies": { + "arrify": "^2.0.0", + "extend": "^3.0.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@google-cloud/projectify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@google-cloud/projectify/-/projectify-3.0.0.tgz", + "integrity": "sha512-HRkZsNmjScY6Li8/kb70wjGlDDyLkVk3KvoEo9uIoxSjYLJasGiCch9+PqRVDOCGUFvEIqyogl+BeqILL4OJHA==", + "optional": true, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@google-cloud/promisify": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-3.0.1.tgz", + "integrity": "sha512-z1CjRjtQyBOYL+5Qr9DdYIfrdLBe746jRTYfaYU6MeXkqp7UfYs/jX16lFFVzZ7PGEJvqZNqYUEtb1mvDww4pA==", + "optional": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/@google-cloud/storage": { + "version": "6.12.0", + "resolved": "https://registry.npmjs.org/@google-cloud/storage/-/storage-6.12.0.tgz", + "integrity": "sha512-78nNAY7iiZ4O/BouWMWTD/oSF2YtYgYB3GZirn0To6eBOugjXVoK+GXgUXOl+HlqbAOyHxAVXOlsj3snfbQ1dw==", + "optional": true, + "dependencies": { + "@google-cloud/paginator": "^3.0.7", + "@google-cloud/projectify": "^3.0.0", + "@google-cloud/promisify": "^3.0.0", + "abort-controller": "^3.0.0", + "async-retry": "^1.3.3", + "compressible": "^2.0.12", + "duplexify": "^4.0.0", + "ent": "^2.2.0", + "extend": "^3.0.2", + "fast-xml-parser": "^4.2.2", + "gaxios": "^5.0.0", + "google-auth-library": "^8.0.1", + "mime": "^3.0.0", + "mime-types": "^2.0.8", + "p-limit": "^3.0.1", + "retry-request": "^5.0.0", + "teeny-request": "^8.0.0", + "uuid": "^8.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@google-cloud/storage/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "optional": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/@grpc/grpc-js": { + "version": "1.8.19", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.8.19.tgz", + "integrity": "sha512-yCkvhtstJvUL3DEQAF5Uq5KoqQL27MTdfxVYvGsGduoGxiJcRCvJ29t5OmLfLhRuRpjAQ1OlhJD/IPOfX+8jNw==", + "optional": true, + "dependencies": { + "@grpc/proto-loader": "^0.7.0", + "@types/node": ">=12.12.47" + }, + "engines": { + "node": "^8.13.0 || >=10.10.0" + } + }, + "node_modules/@grpc/proto-loader": { + "version": "0.7.8", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.8.tgz", + "integrity": "sha512-GU12e2c8dmdXb7XUlOgYWZ2o2i+z9/VeACkxTA/zzAe2IjclC5PnVL0lpgjhrqfpDYHzM8B1TF6pqWegMYAzlA==", + "optional": true, + "dependencies": { + "@types/long": "^4.0.1", + "lodash.camelcase": "^4.3.0", + "long": "^4.0.0", + "protobufjs": "^7.2.4", + "yargs": "^17.7.2" + }, + "bin": { + "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.10.tgz", + "integrity": "sha512-KVVjQmNUepDVGXNuoRRdmmEjruj0KfiGSbS8LVc12LMsWDQzRXJ0qdhN8L8uUigKpfEHRhlaQFY0ib1tnUbNeQ==", + "dev": true, + "dependencies": { + "@humanwhocodes/object-schema": "^1.2.1", + "debug": "^4.1.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "dev": true + }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "peer": true, + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "peer": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "peer": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "peer": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "peer": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "peer": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "peer": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/console": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.6.1.tgz", + "integrity": "sha512-Aj772AYgwTSr5w8qnyoJ0eDYvN6bMsH3ORH1ivMotrInHLKdUz6BDlaEXHdM6kODaBIkNIyQGzsMvRdOv7VG7Q==", + "dev": true, + "peer": true, + "dependencies": { + "@jest/types": "^29.6.1", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^29.6.1", + "jest-util": "^29.6.1", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/core": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.6.1.tgz", + "integrity": "sha512-CcowHypRSm5oYQ1obz1wfvkjZZ2qoQlrKKvlfPwh5jUXVU12TWr2qMeH8chLMuTFzHh5a1g2yaqlqDICbr+ukQ==", + "dev": true, + "peer": true, + "dependencies": { + "@jest/console": "^29.6.1", + "@jest/reporters": "^29.6.1", + "@jest/test-result": "^29.6.1", + "@jest/transform": "^29.6.1", + "@jest/types": "^29.6.1", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-changed-files": "^29.5.0", + "jest-config": "^29.6.1", + "jest-haste-map": "^29.6.1", + "jest-message-util": "^29.6.1", + "jest-regex-util": "^29.4.3", + "jest-resolve": "^29.6.1", + "jest-resolve-dependencies": "^29.6.1", + "jest-runner": "^29.6.1", + "jest-runtime": "^29.6.1", + "jest-snapshot": "^29.6.1", + "jest-util": "^29.6.1", + "jest-validate": "^29.6.1", + "jest-watcher": "^29.6.1", + "micromatch": "^4.0.4", + "pretty-format": "^29.6.1", + "slash": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/environment": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.6.1.tgz", + "integrity": "sha512-RMMXx4ws+Gbvw3DfLSuo2cfQlK7IwGbpuEWXCqyYDcqYTI+9Ju3a5hDnXaxjNsa6uKh9PQF2v+qg+RLe63tz5A==", + "dev": true, + "peer": true, + "dependencies": { + "@jest/fake-timers": "^29.6.1", + "@jest/types": "^29.6.1", + "@types/node": "*", + "jest-mock": "^29.6.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.6.1.tgz", + "integrity": "sha512-N5xlPrAYaRNyFgVf2s9Uyyvr795jnB6rObuPx4QFvNJz8aAjpZUDfO4bh5G/xuplMID8PrnuF1+SfSyDxhsgYg==", + "dev": true, + "peer": true, + "dependencies": { + "expect": "^29.6.1", + "jest-snapshot": "^29.6.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/expect-utils": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.6.1.tgz", + "integrity": "sha512-o319vIf5pEMx0LmzSxxkYYxo4wrRLKHq9dP1yJU7FoPTB0LfAKSz8SWD6D/6U3v/O52t9cF5t+MeJiRsfk7zMw==", + "dev": true, + "peer": true, + "dependencies": { + "jest-get-type": "^29.4.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/fake-timers": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.6.1.tgz", + "integrity": "sha512-RdgHgbXyosCDMVYmj7lLpUwXA4c69vcNzhrt69dJJdf8azUrpRh3ckFCaTPNjsEeRi27Cig0oKDGxy5j7hOgHg==", + "dev": true, + "peer": true, + "dependencies": { + "@jest/types": "^29.6.1", + "@sinonjs/fake-timers": "^10.0.2", + "@types/node": "*", + "jest-message-util": "^29.6.1", + "jest-mock": "^29.6.1", + "jest-util": "^29.6.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/globals": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.6.1.tgz", + "integrity": "sha512-2VjpaGy78JY9n9370H8zGRCFbYVWwjY6RdDMhoJHa1sYfwe6XM/azGN0SjY8kk7BOZApIejQ1BFPyH7FPG0w3A==", + "dev": true, + "peer": true, + "dependencies": { + "@jest/environment": "^29.6.1", + "@jest/expect": "^29.6.1", + "@jest/types": "^29.6.1", + "jest-mock": "^29.6.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/reporters": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.6.1.tgz", + "integrity": "sha512-9zuaI9QKr9JnoZtFQlw4GREQbxgmNYXU6QuWtmuODvk5nvPUeBYapVR/VYMyi2WSx3jXTLJTJji8rN6+Cm4+FA==", + "dev": true, + "peer": true, + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "^29.6.1", + "@jest/test-result": "^29.6.1", + "@jest/transform": "^29.6.1", + "@jest/types": "^29.6.1", + "@jridgewell/trace-mapping": "^0.3.18", + "@types/node": "*", + "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^5.1.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "^29.6.1", + "jest-util": "^29.6.1", + "jest-worker": "^29.6.1", + "slash": "^3.0.0", + "string-length": "^4.0.1", + "strip-ansi": "^6.0.0", + "v8-to-istanbul": "^9.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/@jest/schemas": { + "version": "29.6.0", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.0.tgz", + "integrity": "sha512-rxLjXyJBTL4LQeJW3aKo0M/+GkCOXsO+8i9Iu7eDb6KwtP65ayoDsitrdPBtujxQ88k4wI2FNYfa6TOGwSn6cQ==", + "dev": true, + "peer": true, + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/source-map": { + "version": "29.6.0", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.0.tgz", + "integrity": "sha512-oA+I2SHHQGxDCZpbrsCQSoMLb3Bz547JnM+jUr9qEbuw0vQlWZfpPS7CO9J7XiwKicEz9OFn/IYoLkkiUD7bzA==", + "dev": true, + "peer": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.18", + "callsites": "^3.0.0", + "graceful-fs": "^4.2.9" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-result": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.6.1.tgz", + "integrity": "sha512-Ynr13ZRcpX6INak0TPUukU8GWRfm/vAytE3JbJNGAvINySWYdfE7dGZMbk36oVuK4CigpbhMn8eg1dixZ7ZJOw==", + "dev": true, + "peer": true, + "dependencies": { + "@jest/console": "^29.6.1", + "@jest/types": "^29.6.1", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/test-sequencer": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.6.1.tgz", + "integrity": "sha512-oBkC36PCDf/wb6dWeQIhaviU0l5u6VCsXa119yqdUosYAt7/FbQU2M2UoziO3igj/HBDEgp57ONQ3fm0v9uyyg==", + "dev": true, + "peer": true, + "dependencies": { + "@jest/test-result": "^29.6.1", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.6.1", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/transform": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.6.1.tgz", + "integrity": "sha512-URnTneIU3ZjRSaf906cvf6Hpox3hIeJXRnz3VDSw5/X93gR8ycdfSIEy19FlVx8NFmpN7fe3Gb1xF+NjXaQLWg==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/types": "^29.6.1", + "@jridgewell/trace-mapping": "^0.3.18", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.6.1", + "jest-regex-util": "^29.4.3", + "jest-util": "^29.6.1", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/types": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.1.tgz", + "integrity": "sha512-tPKQNMPuXgvdOn2/Lg9HNfUvjYVGolt04Hp03f5hAk878uwOLikN+JzeLY0HcVgKgFl9Hs3EIqpu3WX27XNhnw==", + "dev": true, + "peer": true, + "dependencies": { + "@jest/schemas": "^29.6.0", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", + "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "dev": true, + "peer": true, + "dependencies": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", + "dev": true, + "peer": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.18", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz", + "integrity": "sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==", + "dev": true, + "peer": true, + "dependencies": { + "@jridgewell/resolve-uri": "3.1.0", + "@jridgewell/sourcemap-codec": "1.4.14" + } + }, + "node_modules/@jridgewell/trace-mapping/node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", + "dev": true, + "peer": true + }, + "node_modules/@jsdoc/salty": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/@jsdoc/salty/-/salty-0.2.5.tgz", + "integrity": "sha512-TfRP53RqunNe2HBobVBJ0VLhK1HbfvBYeTC1ahnN64PWvyYyGebmMiPkuwvD9fpw2ZbkoPb8Q7mwy0aR8Z9rvw==", + "optional": true, + "dependencies": { + "lodash": "^4.17.21" + }, + "engines": { + "node": ">=v12.0.0" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==" + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==" + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==" + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==" + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "dependencies": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==" + }, + "node_modules/@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==" + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==" + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==" + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==" + }, + "node_modules/@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "dev": true, + "peer": true + }, + "node_modules/@sinonjs/commons": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.0.tgz", + "integrity": "sha512-jXBtWAF4vmdNmZgD5FoKsVLv3rPgDnLgPbU84LIJ3otV44vJlDRokVng5v8NFJdCf/da9legHcKaRuZs4L7faA==", + "dev": true, + "peer": true, + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", + "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", + "dev": true, + "peer": true, + "dependencies": { + "@sinonjs/commons": "^3.0.0" + } + }, + "node_modules/@tootallnate/once": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", + "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", + "optional": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.1", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.1.tgz", + "integrity": "sha512-aACu/U/omhdk15O4Nfb+fHgH/z3QsfQzpnvRZhYhThms83ZnAOZz7zZAWO7mn2yyNQaA4xTO8GLK3uqFU4bYYw==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.6.4", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.4.tgz", + "integrity": "sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.1.tgz", + "integrity": "sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.20.1", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.1.tgz", + "integrity": "sha512-MitHFXnhtgwsGZWtT68URpOvLN4EREih1u3QtQiN4VdAxWKRVvGCSvw/Qth0M0Qq3pJpnGOu5JaM/ydK7OGbqg==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/types": "^7.20.7" + } + }, + "node_modules/@types/body-parser": { + "version": "1.19.2", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", + "integrity": "sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==", + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "node_modules/@types/connect": { + "version": "3.4.35", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", + "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/cors": { + "version": "2.8.13", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.13.tgz", + "integrity": "sha512-RG8AStHlUiV5ysZQKq97copd2UmVYw3/pRMLefISZ3S1hK104Cwm7iLQ3fTKx+lsUH2CE8FlLaYeEA2LSeqYUA==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/express": { + "version": "4.17.3", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.3.tgz", + "integrity": "sha512-I8cGRJj3pyOLs/HndoP+25vOqhqWkAZsWMEmq1qXy/b/M3ppufecUwaK2/TVDVxcV61/iSdhykUjQQ2DLSrTdg==", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "4.17.35", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.35.tgz", + "integrity": "sha512-wALWQwrgiB2AWTT91CB62b6Yt0sNHpznUXeZEcnPU3DRdlDIz74x8Qg1UUYKSVFi+va5vKOLYRBI1bRKiLLKIg==", + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, + "node_modules/@types/glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@types/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-IO+MJPVhoqz+28h1qLAcBEH2+xHMK6MTyHJc7MTnnYb6wsoLR29POVGJ7LycmVXIqyy/4/2ShP5sUwTXuOwb/w==", + "optional": true, + "dependencies": { + "@types/minimatch": "^5.1.2", + "@types/node": "*" + } + }, + "node_modules/@types/graceful-fs": { + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.6.tgz", + "integrity": "sha512-Sig0SNORX9fdW+bQuTEovKj3uHcUL6LQKbCrrqb1X7J6/ReAbhCXRAhc+SMejhLELFj2QcyuxmUooZ4bt5ReSw==", + "dev": true, + "peer": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-/K3ds8TRAfBvi5vfjuz8y6+GiAYBZ0x4tXv1Av6CWBWn0IlADc+ZX9pMq7oU0fNQPnBwIZl3rmeLp6SBApbxSQ==" + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", + "integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==", + "dev": true, + "peer": true + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", + "dev": true, + "peer": true, + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz", + "integrity": "sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==", + "dev": true, + "peer": true, + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/json-schema": { + "version": "7.0.12", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.12.tgz", + "integrity": "sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==", + "dev": true + }, + "node_modules/@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "dev": true + }, + "node_modules/@types/jsonwebtoken": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", + "integrity": "sha512-drE6uz7QBKq1fYqqoFKTDRdFCPHd5TCub75BM+D+cMx7NU9hUz7SESLfC2fSCXVFMO5Yj8sOWHuGqPgjc+fz0Q==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/linkify-it": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-3.0.2.tgz", + "integrity": "sha512-HZQYqbiFVWufzCwexrvh694SOim8z2d+xJl5UNamcvQFejLY/2YUtzXHYi3cHdI7PMlS8ejH2slRAOJQ32aNbA==", + "optional": true + }, + "node_modules/@types/lodash": { + "version": "4.14.195", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.195.tgz", + "integrity": "sha512-Hwx9EUgdwf2GLarOjQp5ZH8ZmblzcbTBC2wtQWNKARBSxM9ezRIAUpeDTgoQRAFB0+8CNWXVA9+MaSOzOF3nPg==", + "dev": true + }, + "node_modules/@types/long": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", + "integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==", + "optional": true + }, + "node_modules/@types/markdown-it": { + "version": "12.2.3", + "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-12.2.3.tgz", + "integrity": "sha512-GKMHFfv3458yYy+v/N8gjufHO6MSZKCOXpZc5GXIWWy8uldwfmPn98vp81gZ5f9SVw8YYBctgfJ22a2d7AOMeQ==", + "optional": true, + "dependencies": { + "@types/linkify-it": "*", + "@types/mdurl": "*" + } + }, + "node_modules/@types/mdurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-1.0.2.tgz", + "integrity": "sha512-eC4U9MlIcu2q0KQmXszyn5Akca/0jrQmwDRgpAMJai7qBWq4amIQhZyNau4VYGtCeALvW1/NtjzJJ567aZxfKA==", + "optional": true + }, + "node_modules/@types/mime": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", + "integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==" + }, + "node_modules/@types/minimatch": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-5.1.2.tgz", + "integrity": "sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==", + "optional": true + }, + "node_modules/@types/node": { + "version": "20.4.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.4.4.tgz", + "integrity": "sha512-CukZhumInROvLq3+b5gLev+vgpsIqC2D0deQr/yS1WnxvmYLlJXZpaQrQiseMY+6xusl79E04UjWoqyr+t1/Ew==" + }, + "node_modules/@types/prettier": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.3.tgz", + "integrity": "sha512-+68kP9yzs4LMp7VNh8gdzMSPZFL44MLGqiHWvttYJe+6qnuVr4Ek9wSBQoveqY/r+LwjCcU29kNVkidwim+kYA==", + "dev": true, + "peer": true + }, + "node_modules/@types/qs": { + "version": "6.9.7", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", + "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==" + }, + "node_modules/@types/range-parser": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", + "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==" + }, + "node_modules/@types/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-F3OznnSLAUxFrCEu/L5PY8+ny8DtcFRjx7fZZ9bycvXRi3KPTRS9HOitGZwvPg0juRhXFWIeKX58cnX5YqLohQ==", + "optional": true, + "dependencies": { + "@types/glob": "*", + "@types/node": "*" + } + }, + "node_modules/@types/semver": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.0.tgz", + "integrity": "sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw==", + "dev": true + }, + "node_modules/@types/send": { + "version": "0.17.1", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.1.tgz", + "integrity": "sha512-Cwo8LE/0rnvX7kIIa3QHCkcuF21c05Ayb0ZfxPiv0W8VRiZiNW/WuRupHKpqqGVGf7SUA44QSOUKaEd9lIrd/Q==", + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.2.tgz", + "integrity": "sha512-J2LqtvFYCzaj8pVYKw8klQXrLLk7TBZmQ4ShlcdkELFKGwGMfevMLneMMRkMgZxotOD9wg497LpC7O8PcvAmfw==", + "dependencies": { + "@types/http-errors": "*", + "@types/mime": "*", + "@types/node": "*" + } + }, + "node_modules/@types/stack-utils": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", + "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", + "dev": true, + "peer": true + }, + "node_modules/@types/yargs": { + "version": "17.0.24", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.24.tgz", + "integrity": "sha512-6i0aC7jV6QzQB8ne1joVZ0eSFIstHsCrobmOtghM11yGlH0j43FKL2UhWdELkyps0zuf7qVTUVCCR+tgSlyLLw==", + "dev": true, + "peer": true, + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.0", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz", + "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==", + "dev": true, + "peer": true + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz", + "integrity": "sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag==", + "dev": true, + "dependencies": { + "@eslint-community/regexpp": "^4.4.0", + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/type-utils": "5.62.0", + "@typescript-eslint/utils": "5.62.0", + "debug": "^4.3.4", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "natural-compare-lite": "^1.4.0", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^5.0.0", + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.62.0.tgz", + "integrity": "sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/typescript-estree": "5.62.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz", + "integrity": "sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.62.0.tgz", + "integrity": "sha512-xsSQreu+VnfbqQpW5vnCJdq1Z3Q0U31qiWmRhr98ONQmcp/yhiPJFPq8MXiJVLiksmOKSjIldZzkebzHuCGzew==", + "dev": true, + "dependencies": { + "@typescript-eslint/typescript-estree": "5.62.0", + "@typescript-eslint/utils": "5.62.0", + "debug": "^4.3.4", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/types": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz", + "integrity": "sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz", + "integrity": "sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.62.0.tgz", + "integrity": "sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@types/json-schema": "^7.0.9", + "@types/semver": "^7.3.12", + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/typescript-estree": "5.62.0", + "eslint-scope": "^5.1.1", + "semver": "^7.3.7" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz", + "integrity": "sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "optional": true, + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", + "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", + "devOptional": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "devOptional": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "optional": true, + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "dev": true, + "peer": true, + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-escapes/node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "dev": true, + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "devOptional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "devOptional": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "peer": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "devOptional": true + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz", + "integrity": "sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "is-array-buffer": "^3.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" + }, + "node_modules/array-includes": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.6.tgz", + "integrity": "sha512-sgTbLvL6cNnw24FnbaDyjmvddQ2ML8arZsgaJhoABMoplz/4QRhtrYS+alr1BUM1Bwp6dhx8vVCBSLG+StwOFw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "get-intrinsic": "^1.1.3", + "is-string": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.1.tgz", + "integrity": "sha512-roTU0KWIOmJ4DRLmwKd19Otg0/mT3qPNt0Qb3GWW8iObuZXxrjB/pzn0R3hqpRSWg4HCwqx+0vwOnWnvlOyeIA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.1.tgz", + "integrity": "sha512-8UGn9O1FDVvMNB0UlLv4voxRMze7+FpHyF5mSMRjWHUMlpoDViniy05870VlxhfgTnLbpuwTzvD76MTtWxB/mQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.1.tgz", + "integrity": "sha512-09x0ZWFEjj4WD8PDbykUwo3t9arLn8NIzmmYEJFpYekOAQjpkGSyrQhNoRTcwwcFRu+ycWF78QZ63oWTqSjBcw==", + "dev": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.0", + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "get-intrinsic": "^1.2.1", + "is-array-buffer": "^3.0.2", + "is-shared-array-buffer": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/arrify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", + "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/async-retry": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/async-retry/-/async-retry-1.3.3.tgz", + "integrity": "sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw==", + "optional": true, + "dependencies": { + "retry": "0.13.1" + } + }, + "node_modules/available-typed-arrays": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", + "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/babel-jest": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.6.1.tgz", + "integrity": "sha512-qu+3bdPEQC6KZSPz+4Fyjbga5OODNcp49j6GKzG1EKbkfyJBxEYGVUmVGpwCSeGouG52R4EgYMLb6p9YeEEQ4A==", + "dev": true, + "peer": true, + "dependencies": { + "@jest/transform": "^29.6.1", + "@types/babel__core": "^7.1.14", + "babel-plugin-istanbul": "^6.1.1", + "babel-preset-jest": "^29.5.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.8.0" + } + }, + "node_modules/babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/babel-plugin-jest-hoist": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.5.0.tgz", + "integrity": "sha512-zSuuuAlTMT4mzLj2nPnUm6fsE6270vdOfnpbJ+RmruU75UhLFvL0N2NgI7xpeS7NaB6hGqmd5pVpGTDYvi4Q3w==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.1.14", + "@types/babel__traverse": "^7.0.6" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/babel-preset-current-node-syntax": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", + "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.8.3", + "@babel/plugin-syntax-import-meta": "^7.8.3", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.8.3", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-top-level-await": "^7.8.3" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/babel-preset-jest": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.5.0.tgz", + "integrity": "sha512-JOMloxOqdiBSxMAzjRaH023/vvcaSaec49zvg+2LmNsktC7ei39LTJGw02J+9uUtTZUq6xbLyJ4dxe9sSmIuAg==", + "dev": true, + "peer": true, + "dependencies": { + "babel-plugin-jest-hoist": "^29.5.0", + "babel-preset-current-node-syntax": "^1.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/bad-words": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/bad-words/-/bad-words-3.0.4.tgz", + "integrity": "sha512-v/Q9uRPH4+yzDVLL4vR1+S9KoFgOEUl5s4axd6NIAq8SV2mradgi4E8lma/Y0cw1ltVdvyegCQQKffCPRCp8fg==", + "dev": true, + "dependencies": { + "badwords-list": "^1.0.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/badwords-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/badwords-list/-/badwords-list-1.0.0.tgz", + "integrity": "sha512-oWhaSG67e+HQj3OGHQt2ucP+vAPm1wTbdp2aDHeuh4xlGXBdWwzZ//pfu6swf5gZ8iX0b7JgmSo8BhgybbqszA==", + "dev": true + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "devOptional": true + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "optional": true + }, + "node_modules/bignumber.js": { + "version": "9.1.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.1.tgz", + "integrity": "sha512-pHm4LsMJ6lzgNGVfZHjMoO8sdoRhOzOH4MLmY65Jg70bpxCKu5iOHNJyfF6OyvYw7t8Fpf35RuzUyqnQsj8Vig==", + "optional": true, + "engines": { + "node": "*" + } + }, + "node_modules/bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", + "optional": true + }, + "node_modules/body-parser": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", + "integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.11.0", + "raw-body": "2.5.1", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/body-parser/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/body-parser/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "devOptional": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.21.9", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.9.tgz", + "integrity": "sha512-M0MFoZzbUrRU4KNfCrDLnvyE7gub+peetoTid3TBIqtunaDJyXlwhakT+/VkvSXcfIzFfK/nkCs4nmyTmxdNSg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "peer": true, + "dependencies": { + "caniuse-lite": "^1.0.30001503", + "electron-to-chromium": "^1.4.431", + "node-releases": "^2.0.12", + "update-browserslist-db": "^1.0.11" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dev": true, + "peer": true, + "dependencies": { + "node-int64": "^0.4.0" + } + }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true, + "peer": true + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dependencies": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001517", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001517.tgz", + "integrity": "sha512-Vdhm5S11DaFVLlyiKu4hiUTkpZu+y1KA/rZZqVQfOD5YdDT/eQKlkt7NaE0WGOFgX32diqt9MiP9CAiFeRklaA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "peer": true + }, + "node_modules/catharsis": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/catharsis/-/catharsis-0.9.0.tgz", + "integrity": "sha512-prMTQVpcns/tzFgFVkVp6ak6RykZyWb3gu8ckUpd6YkTlacOd3DXGJjIpD4Q6zJirizvaiAjSSHlOsA+6sNh2A==", + "optional": true, + "dependencies": { + "lodash": "^4.17.15" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "devOptional": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/char-regex": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", + "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", + "dev": true, + "peer": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/ci-info": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.8.0.tgz", + "integrity": "sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/cjs-module-lexer": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.3.tgz", + "integrity": "sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ==", + "dev": true, + "peer": true + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "devOptional": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", + "dev": true, + "peer": true, + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, + "node_modules/collect-v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", + "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==", + "dev": true, + "peer": true + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "devOptional": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "devOptional": true + }, + "node_modules/compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "optional": true, + "dependencies": { + "mime-db": ">= 1.43.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "devOptional": true + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "peer": true + }, + "node_modules/cookie": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", + "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" + }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/dedent": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", + "integrity": "sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==", + "dev": true, + "peer": true + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "devOptional": true + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "dev": true, + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/define-properties": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.0.tgz", + "integrity": "sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==", + "dev": true, + "dependencies": { + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/detect-newline": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", + "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", + "dev": true, + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/diff-sequences": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.4.3.tgz", + "integrity": "sha512-ofrBgwpPhCD85kMKtE9RYFFq6OC1A89oW2vvgWZNCwxrUpRUILopY7lsYyMDSjc8g6U6aiO0Qubg6r4Wgt5ZnA==", + "dev": true, + "peer": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/duplexify": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.2.tgz", + "integrity": "sha512-fz3OjcNCHmRP12MJoZMPglx8m4rrFP8rovnk4vT8Fs+aonZoCwGg10dSsQsfP/E62eZcPTMSMP6686fu9Qlqtw==", + "optional": true, + "dependencies": { + "end-of-stream": "^1.4.1", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1", + "stream-shift": "^1.0.0" + } + }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" + }, + "node_modules/electron-to-chromium": { + "version": "1.4.469", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.469.tgz", + "integrity": "sha512-HRN9XQjElxJBrdDky5iiUUr3eDwXGTg6Cp4IV8MuNc8VqMkYSneSnIe6poFKx9PsNzkudCgaWCBVxwDqirwQWQ==", + "dev": true, + "peer": true + }, + "node_modules/emittery": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", + "integrity": "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==", + "dev": true, + "peer": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sindresorhus/emittery?sponsor=1" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "devOptional": true + }, + "node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "optional": true, + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/ent": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz", + "integrity": "sha512-GHrMyVZQWvTIdDtpiEXdHZnFQKzeO09apj8Cbl4pKWy4i0Oprcq17usfDt5aO63swf0JOeMWjWQE/LzgSRuWpA==", + "optional": true + }, + "node_modules/entities": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz", + "integrity": "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==", + "optional": true, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "peer": true, + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-abstract": { + "version": "1.22.1", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.22.1.tgz", + "integrity": "sha512-ioRRcXMO6OFyRpyzV3kE1IIBd4WG5/kltnzdxSCqoP8CMGs/Li+M1uF5o7lOkZVFjDs+NLesthnF66Pg/0q0Lw==", + "dev": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.0", + "arraybuffer.prototype.slice": "^1.0.1", + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "es-set-tostringtag": "^2.0.1", + "es-to-primitive": "^1.2.1", + "function.prototype.name": "^1.1.5", + "get-intrinsic": "^1.2.1", + "get-symbol-description": "^1.0.0", + "globalthis": "^1.0.3", + "gopd": "^1.0.1", + "has": "^1.0.3", + "has-property-descriptors": "^1.0.0", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.5", + "is-array-buffer": "^3.0.2", + "is-callable": "^1.2.7", + "is-negative-zero": "^2.0.2", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.2", + "is-string": "^1.0.7", + "is-typed-array": "^1.1.10", + "is-weakref": "^1.0.2", + "object-inspect": "^1.12.3", + "object-keys": "^1.1.1", + "object.assign": "^4.1.4", + "regexp.prototype.flags": "^1.5.0", + "safe-array-concat": "^1.0.0", + "safe-regex-test": "^1.0.0", + "string.prototype.trim": "^1.2.7", + "string.prototype.trimend": "^1.0.6", + "string.prototype.trimstart": "^1.0.6", + "typed-array-buffer": "^1.0.0", + "typed-array-byte-length": "^1.0.0", + "typed-array-byte-offset": "^1.0.0", + "typed-array-length": "^1.0.4", + "unbox-primitive": "^1.0.2", + "which-typed-array": "^1.1.10" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz", + "integrity": "sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.3", + "has": "^1.0.3", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-shim-unscopables": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz", + "integrity": "sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==", + "dev": true, + "dependencies": { + "has": "^1.0.3" + } + }, + "node_modules/es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "dependencies": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "devOptional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/escodegen": { + "version": "1.14.3", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.3.tgz", + "integrity": "sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw==", + "optional": true, + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^4.2.0", + "esutils": "^2.0.2", + "optionator": "^0.8.1" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=4.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, + "node_modules/escodegen/node_modules/levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", + "optional": true, + "dependencies": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/escodegen/node_modules/optionator": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", + "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "optional": true, + "dependencies": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.6", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "word-wrap": "~1.2.3" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/escodegen/node_modules/prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==", + "optional": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/escodegen/node_modules/type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", + "optional": true, + "dependencies": { + "prelude-ls": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/eslint": { + "version": "8.45.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.45.0.tgz", + "integrity": "sha512-pd8KSxiQpdYRfYa9Wufvdoct3ZPQQuVuU5O6scNgMuOMYuxvH0IGaYK0wUFjo4UYYQQCUndlXiMbnxopwvvTiw==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.4.0", + "@eslint/eslintrc": "^2.1.0", + "@eslint/js": "8.44.0", + "@humanwhocodes/config-array": "^0.11.10", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.0", + "eslint-visitor-keys": "^3.4.1", + "espree": "^9.6.0", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-config-google": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/eslint-config-google/-/eslint-config-google-0.14.0.tgz", + "integrity": "sha512-WsbX4WbjuMvTdeVL6+J3rK1RGhCTqjsFjX7UMSMgZiyxxaNLkoJENbrGExzERFeoTpGw3F3FypTiWAP9ZXzkEw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + }, + "peerDependencies": { + "eslint": ">=5.16.0" + } + }, + "node_modules/eslint-import-resolver-node": { + "version": "0.3.7", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.7.tgz", + "integrity": "sha512-gozW2blMLJCeFpBwugLTGyvVjNoeo1knonXAcatC6bjPBZitotxdWf7Gimr25N4c0AAOo4eOUfaG82IJPDpqCA==", + "dev": true, + "dependencies": { + "debug": "^3.2.7", + "is-core-module": "^2.11.0", + "resolve": "^1.22.1" + } + }, + "node_modules/eslint-import-resolver-node/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-module-utils": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.8.0.tgz", + "integrity": "sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw==", + "dev": true, + "dependencies": { + "debug": "^3.2.7" + }, + "engines": { + "node": ">=4" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/eslint-module-utils/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import": { + "version": "2.27.5", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.27.5.tgz", + "integrity": "sha512-LmEt3GVofgiGuiE+ORpnvP+kAm3h6MLZJ4Q5HCyHADofsb4VzXFsRiWj3c0OFiV+3DWFh0qg3v9gcPlfc3zRow==", + "dev": true, + "dependencies": { + "array-includes": "^3.1.6", + "array.prototype.flat": "^1.3.1", + "array.prototype.flatmap": "^1.3.1", + "debug": "^3.2.7", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.7", + "eslint-module-utils": "^2.7.4", + "has": "^1.0.3", + "is-core-module": "^2.11.0", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.values": "^1.1.6", + "resolve": "^1.22.1", + "semver": "^6.3.0", + "tsconfig-paths": "^3.14.1" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8" + } + }, + "node_modules/eslint-plugin-import/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import/node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-import/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz", + "integrity": "sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==", + "devOptional": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/eslint-scope": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.1.tgz", + "integrity": "sha512-CvefSOsDdaYYvxChovdrPo/ZGt8d5lrJWleAc1diXRKhHGiTYEI26cvo8Kle/wGnsizoCJjK73FMg1/IkIwiNA==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "devOptional": true, + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "devOptional": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esquery": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esquery/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "devOptional": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "devOptional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "optional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "peer": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", + "dev": true, + "peer": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/expect": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.6.1.tgz", + "integrity": "sha512-XEdDLonERCU1n9uR56/Stx9OqojaLAQtZf9PrCHH9Hl8YXiEIka3H4NXJ3NOIBmQJTg7+j7buh34PMHfJujc8g==", + "dev": true, + "peer": true, + "dependencies": { + "@jest/expect-utils": "^29.6.1", + "@types/node": "*", + "jest-get-type": "^29.4.3", + "jest-matcher-utils": "^29.6.1", + "jest-message-util": "^29.6.1", + "jest-util": "^29.6.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/express": { + "version": "4.18.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", + "integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.1", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.5.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.2.0", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.7", + "qs": "6.11.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.18.0", + "serve-static": "1.15.0", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/express/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/express/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "optional": true + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "devOptional": true + }, + "node_modules/fast-glob": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz", + "integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "devOptional": true + }, + "node_modules/fast-text-encoding": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/fast-text-encoding/-/fast-text-encoding-1.0.6.tgz", + "integrity": "sha512-VhXlQgj9ioXCqGstD37E/HBeqEGV/qOD/kmbVG8h5xKBYvM1L3lR1Zn4555cQ8GkYbJa8aJSipLPndE1k6zK2w==", + "optional": true + }, + "node_modules/fast-xml-parser": { + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.2.6.tgz", + "integrity": "sha512-Xo1qV++h/Y3Ng8dphjahnYe+rGHaaNdsYOBWL9Y9GCPKpNKilJtilvWkLcI9f9X2DoKTLsZsGYAls5+JL5jfLA==", + "funding": [ + { + "type": "paypal", + "url": "https://paypal.me/naturalintelligence" + }, + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "optional": true, + "dependencies": { + "strnum": "^1.0.5" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, + "node_modules/fastq": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", + "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/faye-websocket": { + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", + "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", + "dependencies": { + "websocket-driver": ">=0.5.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/fb-watchman": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", + "integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==", + "dev": true, + "peer": true, + "dependencies": { + "bser": "2.1.1" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/firebase-admin": { + "version": "11.10.1", + "resolved": "https://registry.npmjs.org/firebase-admin/-/firebase-admin-11.10.1.tgz", + "integrity": "sha512-atv1E6GbuvcvWaD3eHwrjeP5dAVs+EaHEJhu9CThMzPY6In8QYDiUR6tq5SwGl4SdA/GcAU0nhwWc/FSJsAzfQ==", + "dependencies": { + "@fastify/busboy": "^1.2.1", + "@firebase/database-compat": "^0.3.4", + "@firebase/database-types": "^0.10.4", + "@types/node": ">=12.12.47", + "jsonwebtoken": "^9.0.0", + "jwks-rsa": "^3.0.1", + "node-forge": "^1.3.1", + "uuid": "^9.0.0" + }, + "engines": { + "node": ">=14" + }, + "optionalDependencies": { + "@google-cloud/firestore": "^6.6.0", + "@google-cloud/storage": "^6.9.5" + } + }, + "node_modules/firebase-functions": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/firebase-functions/-/firebase-functions-4.4.1.tgz", + "integrity": "sha512-3no53Lg12ToNlPSgLZtAFLQAz6si7ilHvzO8NC3/2EybyUwegpj5YhHwNiCw839lmAWp3znjATJDTvADFiZMrg==", + "dependencies": { + "@types/cors": "^2.8.5", + "@types/express": "4.17.3", + "cors": "^2.8.5", + "express": "^4.17.1", + "node-fetch": "^2.6.7", + "protobufjs": "^7.2.2" + }, + "bin": { + "firebase-functions": "lib/bin/firebase-functions.js" + }, + "engines": { + "node": ">=14.10.0" + }, + "peerDependencies": { + "firebase-admin": "^10.0.0 || ^11.0.0" + } + }, + "node_modules/firebase-functions-test": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/firebase-functions-test/-/firebase-functions-test-3.1.0.tgz", + "integrity": "sha512-yfm9ToguShxmRXb7TINN88zE2bM9gsBbs7vMWVKJAxGcl/n1f/U0sT5k2yho676QIcSqXVSjCONU8W4cUEL+Sw==", + "dev": true, + "dependencies": { + "@types/lodash": "^4.14.104", + "lodash": "^4.17.5", + "ts-deepmerge": "^2.0.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "firebase-admin": "^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0", + "firebase-functions": ">=4.3.0", + "jest": ">=28.0.0" + } + }, + "node_modules/flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "dev": true, + "dependencies": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", + "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", + "dev": true + }, + "node_modules/for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dev": true, + "dependencies": { + "is-callable": "^1.1.3" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "devOptional": true + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "node_modules/function.prototype.name": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz", + "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.0", + "functions-have-names": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==", + "optional": true + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gaxios": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-5.1.3.tgz", + "integrity": "sha512-95hVgBRgEIRQQQHIbnxBXeHbW4TqFk4ZDJW7wmVtvYar72FdhRIo1UGOLS2eRAKCPEdPBWu+M7+A33D9CdX9rA==", + "optional": true, + "dependencies": { + "extend": "^3.0.2", + "https-proxy-agent": "^5.0.0", + "is-stream": "^2.0.0", + "node-fetch": "^2.6.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/gcp-metadata": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-5.3.0.tgz", + "integrity": "sha512-FNTkdNEnBdlqF2oatizolQqNANMrcqJt6AAYt99B3y1aLLC8Hc5IOBb+ZnnzllodEEf6xMBp6wRcBbc16fa65w==", + "optional": true, + "dependencies": { + "gaxios": "^5.0.0", + "json-bigint": "^1.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "devOptional": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", + "integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==", + "dependencies": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "dev": true, + "peer": true, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-symbol-description": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", + "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "devOptional": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "13.20.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", + "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globalthis": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", + "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", + "dev": true, + "dependencies": { + "define-properties": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/google-auth-library": { + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-8.9.0.tgz", + "integrity": "sha512-f7aQCJODJFmYWN6PeNKzgvy9LI2tYmXnzpNDHEjG5sDNPgGb2FXQyTBnXeSH+PAtpKESFD+LmHw3Ox3mN7e1Fg==", + "optional": true, + "dependencies": { + "arrify": "^2.0.0", + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "fast-text-encoding": "^1.0.0", + "gaxios": "^5.0.0", + "gcp-metadata": "^5.3.0", + "gtoken": "^6.1.0", + "jws": "^4.0.0", + "lru-cache": "^6.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/google-gax": { + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-3.6.1.tgz", + "integrity": "sha512-g/lcUjGcB6DSw2HxgEmCDOrI/CByOwqRvsuUvNalHUK2iPPPlmAIpbMbl62u0YufGMr8zgE3JL7th6dCb1Ry+w==", + "optional": true, + "dependencies": { + "@grpc/grpc-js": "~1.8.0", + "@grpc/proto-loader": "^0.7.0", + "@types/long": "^4.0.0", + "@types/rimraf": "^3.0.2", + "abort-controller": "^3.0.0", + "duplexify": "^4.0.0", + "fast-text-encoding": "^1.0.3", + "google-auth-library": "^8.0.2", + "is-stream-ended": "^0.1.4", + "node-fetch": "^2.6.1", + "object-hash": "^3.0.0", + "proto3-json-serializer": "^1.0.0", + "protobufjs": "7.2.4", + "protobufjs-cli": "1.1.1", + "retry-request": "^5.0.0" + }, + "bin": { + "compileProtos": "build/tools/compileProtos.js", + "minifyProtoJson": "build/tools/minify.js" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/google-p12-pem": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-4.0.1.tgz", + "integrity": "sha512-WPkN4yGtz05WZ5EhtlxNDWPhC4JIic6G8ePitwUWy4l+XPVYec+a0j0Ts47PDtW59y3RwAhUd9/h9ZZ63px6RQ==", + "optional": true, + "dependencies": { + "node-forge": "^1.3.1" + }, + "bin": { + "gp12-pem": "build/src/bin/gp12-pem.js" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "devOptional": true + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true + }, + "node_modules/gtoken": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-6.1.2.tgz", + "integrity": "sha512-4ccGpzz7YAr7lxrT2neugmXQ3hP9ho2gcaityLVkiUecAiwiy60Ii8gRbZeOsXV19fYaRjgBSshs8kXw+NKCPQ==", + "optional": true, + "dependencies": { + "gaxios": "^5.0.1", + "google-p12-pem": "^4.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-bigints": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "devOptional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", + "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", + "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", + "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, + "peer": true + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-parser-js": { + "version": "0.5.8", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.8.tgz", + "integrity": "sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==" + }, + "node_modules/http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "optional": true, + "dependencies": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "optional": true, + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "peer": true, + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ignore": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", + "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-local": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", + "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", + "dev": true, + "peer": true, + "dependencies": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + }, + "bin": { + "import-local-fixture": "fixtures/cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "devOptional": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/internal-slot": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz", + "integrity": "sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.0", + "has": "^1.0.3", + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz", + "integrity": "sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.0", + "is-typed-array": "^1.1.10" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true, + "peer": true + }, + "node_modules/is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dev": true, + "dependencies": { + "has-bigints": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.1.tgz", + "integrity": "sha512-Q4ZuBAe2FUsKtyQJoQHlvP8OvBERxO3jEmy1I7hcRXcJBGGHFh/aJBswbXuS9sgrDH2QUO8ilkwNPHvHMd8clg==", + "dev": true, + "dependencies": { + "has": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "devOptional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-negative-zero": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", + "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-number-object": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", + "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "devOptional": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-stream-ended": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-stream-ended/-/is-stream-ended-0.1.4.tgz", + "integrity": "sha512-xj0XPvmr7bQFTvirqnFr50o0hQIh6ZItDqloxt5aJrR4NQsYeSsyFQERYGCAzfindAcnKjINnwEEgLx4IqVzQw==", + "optional": true + }, + "node_modules/is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.12.tgz", + "integrity": "sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg==", + "dev": true, + "dependencies": { + "which-typed-array": "^1.1.11" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz", + "integrity": "sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==", + "dev": true, + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", + "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/core": "^7.12.3", + "@babel/parser": "^7.14.7", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-instrument/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "peer": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", + "dev": true, + "peer": true, + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^3.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", + "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "dev": true, + "peer": true, + "dependencies": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.5.tgz", + "integrity": "sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w==", + "dev": true, + "peer": true, + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest/-/jest-29.6.1.tgz", + "integrity": "sha512-Nirw5B4nn69rVUZtemCQhwxOBhm0nsp3hmtF4rzCeWD7BkjAXRIji7xWQfnTNbz9g0aVsBX6aZK3n+23LM6uDw==", + "dev": true, + "peer": true, + "dependencies": { + "@jest/core": "^29.6.1", + "@jest/types": "^29.6.1", + "import-local": "^3.0.2", + "jest-cli": "^29.6.1" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-changed-files": { + "version": "29.5.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.5.0.tgz", + "integrity": "sha512-IFG34IUMUaNBIxjQXF/iu7g6EcdMrGRRxaUSw92I/2g2YC6vCdTltl4nHvt7Ci5nSJwXIkCu8Ka1DKF+X7Z1Ag==", + "dev": true, + "peer": true, + "dependencies": { + "execa": "^5.0.0", + "p-limit": "^3.1.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-circus": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.6.1.tgz", + "integrity": "sha512-tPbYLEiBU4MYAL2XoZme/bgfUeotpDBd81lgHLCbDZZFaGmECk0b+/xejPFtmiBP87GgP/y4jplcRpbH+fgCzQ==", + "dev": true, + "peer": true, + "dependencies": { + "@jest/environment": "^29.6.1", + "@jest/expect": "^29.6.1", + "@jest/test-result": "^29.6.1", + "@jest/types": "^29.6.1", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "dedent": "^0.7.0", + "is-generator-fn": "^2.0.0", + "jest-each": "^29.6.1", + "jest-matcher-utils": "^29.6.1", + "jest-message-util": "^29.6.1", + "jest-runtime": "^29.6.1", + "jest-snapshot": "^29.6.1", + "jest-util": "^29.6.1", + "p-limit": "^3.1.0", + "pretty-format": "^29.6.1", + "pure-rand": "^6.0.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-cli": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.6.1.tgz", + "integrity": "sha512-607dSgTA4ODIN6go9w6xY3EYkyPFGicx51a69H7yfvt7lN53xNswEVLovq+E77VsTRi5fWprLH0yl4DJgE8Ing==", + "dev": true, + "peer": true, + "dependencies": { + "@jest/core": "^29.6.1", + "@jest/test-result": "^29.6.1", + "@jest/types": "^29.6.1", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "import-local": "^3.0.2", + "jest-config": "^29.6.1", + "jest-util": "^29.6.1", + "jest-validate": "^29.6.1", + "prompts": "^2.0.1", + "yargs": "^17.3.1" + }, + "bin": { + "jest": "bin/jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-config": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.6.1.tgz", + "integrity": "sha512-XdjYV2fy2xYixUiV2Wc54t3Z4oxYPAELUzWnV6+mcbq0rh742X2p52pii5A3oeRzYjLnQxCsZmp0qpI6klE2cQ==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/test-sequencer": "^29.6.1", + "@jest/types": "^29.6.1", + "babel-jest": "^29.6.1", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-circus": "^29.6.1", + "jest-environment-node": "^29.6.1", + "jest-get-type": "^29.4.3", + "jest-regex-util": "^29.4.3", + "jest-resolve": "^29.6.1", + "jest-runner": "^29.6.1", + "jest-util": "^29.6.1", + "jest-validate": "^29.6.1", + "micromatch": "^4.0.4", + "parse-json": "^5.2.0", + "pretty-format": "^29.6.1", + "slash": "^3.0.0", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "peerDependencies": { + "@types/node": "*", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/jest-diff": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.6.1.tgz", + "integrity": "sha512-FsNCvinvl8oVxpNLttNQX7FAq7vR+gMDGj90tiP7siWw1UdakWUGqrylpsYrpvj908IYckm5Y0Q7azNAozU1Kg==", + "dev": true, + "peer": true, + "dependencies": { + "chalk": "^4.0.0", + "diff-sequences": "^29.4.3", + "jest-get-type": "^29.4.3", + "pretty-format": "^29.6.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-docblock": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.4.3.tgz", + "integrity": "sha512-fzdTftThczeSD9nZ3fzA/4KkHtnmllawWrXO69vtI+L9WjEIuXWs4AmyME7lN5hU7dB0sHhuPfcKofRsUb/2Fg==", + "dev": true, + "peer": true, + "dependencies": { + "detect-newline": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-each": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.6.1.tgz", + "integrity": "sha512-n5eoj5eiTHpKQCAVcNTT7DRqeUmJ01hsAL0Q1SMiBHcBcvTKDELixQOGMCpqhbIuTcfC4kMfSnpmDqRgRJcLNQ==", + "dev": true, + "peer": true, + "dependencies": { + "@jest/types": "^29.6.1", + "chalk": "^4.0.0", + "jest-get-type": "^29.4.3", + "jest-util": "^29.6.1", + "pretty-format": "^29.6.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-environment-node": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.6.1.tgz", + "integrity": "sha512-ZNIfAiE+foBog24W+2caIldl4Irh8Lx1PUhg/GZ0odM1d/h2qORAsejiFc7zb+SEmYPn1yDZzEDSU5PmDkmVLQ==", + "dev": true, + "peer": true, + "dependencies": { + "@jest/environment": "^29.6.1", + "@jest/fake-timers": "^29.6.1", + "@jest/types": "^29.6.1", + "@types/node": "*", + "jest-mock": "^29.6.1", + "jest-util": "^29.6.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-get-type": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.4.3.tgz", + "integrity": "sha512-J5Xez4nRRMjk8emnTpWrlkyb9pfRQQanDrvWHhsR1+VUfbwxi30eVcZFlcdGInRibU4G5LwHXpI7IRHU0CY+gg==", + "dev": true, + "peer": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-haste-map": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.6.1.tgz", + "integrity": "sha512-0m7f9PZXxOCk1gRACiVgX85knUKPKLPg4oRCjLoqIm9brTHXaorMA0JpmtmVkQiT8nmXyIVoZd/nnH1cfC33ig==", + "dev": true, + "peer": true, + "dependencies": { + "@jest/types": "^29.6.1", + "@types/graceful-fs": "^4.1.3", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "graceful-fs": "^4.2.9", + "jest-regex-util": "^29.4.3", + "jest-util": "^29.6.1", + "jest-worker": "^29.6.1", + "micromatch": "^4.0.4", + "walker": "^1.0.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" + } + }, + "node_modules/jest-leak-detector": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.6.1.tgz", + "integrity": "sha512-OrxMNyZirpOEwkF3UHnIkAiZbtkBWiye+hhBweCHkVbCgyEy71Mwbb5zgeTNYWJBi1qgDVfPC1IwO9dVEeTLwQ==", + "dev": true, + "peer": true, + "dependencies": { + "jest-get-type": "^29.4.3", + "pretty-format": "^29.6.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-matcher-utils": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.6.1.tgz", + "integrity": "sha512-SLaztw9d2mfQQKHmJXKM0HCbl2PPVld/t9Xa6P9sgiExijviSp7TnZZpw2Fpt+OI3nwUO/slJbOfzfUMKKC5QA==", + "dev": true, + "peer": true, + "dependencies": { + "chalk": "^4.0.0", + "jest-diff": "^29.6.1", + "jest-get-type": "^29.4.3", + "pretty-format": "^29.6.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-message-util": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.6.1.tgz", + "integrity": "sha512-KoAW2zAmNSd3Gk88uJ56qXUWbFk787QKmjjJVOjtGFmmGSZgDBrlIL4AfQw1xyMYPNVD7dNInfIbur9B2rd/wQ==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.6.1", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.6.1", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-mock": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.6.1.tgz", + "integrity": "sha512-brovyV9HBkjXAEdRooaTQK42n8usKoSRR3gihzUpYeV/vwqgSoNfrksO7UfSACnPmxasO/8TmHM3w9Hp3G1dgw==", + "dev": true, + "peer": true, + "dependencies": { + "@jest/types": "^29.6.1", + "@types/node": "*", + "jest-util": "^29.6.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-pnp-resolver": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz", + "integrity": "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "jest-resolve": "*" + }, + "peerDependenciesMeta": { + "jest-resolve": { + "optional": true + } + } + }, + "node_modules/jest-regex-util": { + "version": "29.4.3", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.4.3.tgz", + "integrity": "sha512-O4FglZaMmWXbGHSQInfXewIsd1LMn9p3ZXB/6r4FOkyhX2/iP/soMG98jGvk/A3HAN78+5VWcBGO0BJAPRh4kg==", + "dev": true, + "peer": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.6.1.tgz", + "integrity": "sha512-AeRkyS8g37UyJiP9w3mmI/VXU/q8l/IH52vj/cDAyScDcemRbSBhfX/NMYIGilQgSVwsjxrCHf3XJu4f+lxCMg==", + "dev": true, + "peer": true, + "dependencies": { + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.6.1", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^29.6.1", + "jest-validate": "^29.6.1", + "resolve": "^1.20.0", + "resolve.exports": "^2.0.0", + "slash": "^3.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-resolve-dependencies": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.6.1.tgz", + "integrity": "sha512-BbFvxLXtcldaFOhNMXmHRWx1nXQO5LoXiKSGQcA1LxxirYceZT6ch8KTE1bK3X31TNG/JbkI7OkS/ABexVahiw==", + "dev": true, + "peer": true, + "dependencies": { + "jest-regex-util": "^29.4.3", + "jest-snapshot": "^29.6.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runner": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.6.1.tgz", + "integrity": "sha512-tw0wb2Q9yhjAQ2w8rHRDxteryyIck7gIzQE4Reu3JuOBpGp96xWgF0nY8MDdejzrLCZKDcp8JlZrBN/EtkQvPQ==", + "dev": true, + "peer": true, + "dependencies": { + "@jest/console": "^29.6.1", + "@jest/environment": "^29.6.1", + "@jest/test-result": "^29.6.1", + "@jest/transform": "^29.6.1", + "@jest/types": "^29.6.1", + "@types/node": "*", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "graceful-fs": "^4.2.9", + "jest-docblock": "^29.4.3", + "jest-environment-node": "^29.6.1", + "jest-haste-map": "^29.6.1", + "jest-leak-detector": "^29.6.1", + "jest-message-util": "^29.6.1", + "jest-resolve": "^29.6.1", + "jest-runtime": "^29.6.1", + "jest-util": "^29.6.1", + "jest-watcher": "^29.6.1", + "jest-worker": "^29.6.1", + "p-limit": "^3.1.0", + "source-map-support": "0.5.13" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runtime": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.6.1.tgz", + "integrity": "sha512-D6/AYOA+Lhs5e5il8+5pSLemjtJezUr+8zx+Sn8xlmOux3XOqx4d8l/2udBea8CRPqqrzhsKUsN/gBDE/IcaPQ==", + "dev": true, + "peer": true, + "dependencies": { + "@jest/environment": "^29.6.1", + "@jest/fake-timers": "^29.6.1", + "@jest/globals": "^29.6.1", + "@jest/source-map": "^29.6.0", + "@jest/test-result": "^29.6.1", + "@jest/transform": "^29.6.1", + "@jest/types": "^29.6.1", + "@types/node": "*", + "chalk": "^4.0.0", + "cjs-module-lexer": "^1.0.0", + "collect-v8-coverage": "^1.0.0", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.6.1", + "jest-message-util": "^29.6.1", + "jest-mock": "^29.6.1", + "jest-regex-util": "^29.4.3", + "jest-resolve": "^29.6.1", + "jest-snapshot": "^29.6.1", + "jest-util": "^29.6.1", + "slash": "^3.0.0", + "strip-bom": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-snapshot": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.6.1.tgz", + "integrity": "sha512-G4UQE1QQ6OaCgfY+A0uR1W2AY0tGXUPQpoUClhWHq1Xdnx1H6JOrC2nH5lqnOEqaDgbHFgIwZ7bNq24HpB180A==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/core": "^7.11.6", + "@babel/generator": "^7.7.2", + "@babel/plugin-syntax-jsx": "^7.7.2", + "@babel/plugin-syntax-typescript": "^7.7.2", + "@babel/types": "^7.3.3", + "@jest/expect-utils": "^29.6.1", + "@jest/transform": "^29.6.1", + "@jest/types": "^29.6.1", + "@types/prettier": "^2.1.5", + "babel-preset-current-node-syntax": "^1.0.0", + "chalk": "^4.0.0", + "expect": "^29.6.1", + "graceful-fs": "^4.2.9", + "jest-diff": "^29.6.1", + "jest-get-type": "^29.4.3", + "jest-matcher-utils": "^29.6.1", + "jest-message-util": "^29.6.1", + "jest-util": "^29.6.1", + "natural-compare": "^1.4.0", + "pretty-format": "^29.6.1", + "semver": "^7.5.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-util": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.6.1.tgz", + "integrity": "sha512-NRFCcjc+/uO3ijUVyNOQJluf8PtGCe/W6cix36+M3cTFgiYqFOOW5MgN4JOOcvbUhcKTYVd1CvHz/LWi8d16Mg==", + "dev": true, + "peer": true, + "dependencies": { + "@jest/types": "^29.6.1", + "@types/node": "*", + "chalk": "^4.0.0", + "ci-info": "^3.2.0", + "graceful-fs": "^4.2.9", + "picomatch": "^2.2.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.6.1.tgz", + "integrity": "sha512-r3Ds69/0KCN4vx4sYAbGL1EVpZ7MSS0vLmd3gV78O+NAx3PDQQukRU5hNHPXlyqCgFY8XUk7EuTMLugh0KzahA==", + "dev": true, + "peer": true, + "dependencies": { + "@jest/types": "^29.6.1", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^29.4.3", + "leven": "^3.1.0", + "pretty-format": "^29.6.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-validate/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-watcher": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.6.1.tgz", + "integrity": "sha512-d4wpjWTS7HEZPaaj8m36QiaP856JthRZkrgcIY/7ISoUWPIillrXM23WPboZVLbiwZBt4/qn2Jke84Sla6JhFA==", + "dev": true, + "peer": true, + "dependencies": { + "@jest/test-result": "^29.6.1", + "@jest/types": "^29.6.1", + "@types/node": "*", + "ansi-escapes": "^4.2.1", + "chalk": "^4.0.0", + "emittery": "^0.13.1", + "jest-util": "^29.6.1", + "string-length": "^4.0.1" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.6.1.tgz", + "integrity": "sha512-U+Wrbca7S8ZAxAe9L6nb6g8kPdia5hj32Puu5iOqBCMTMWFHXuK6dOV2IFrpedbTV8fjMFLdWNttQTBL6u2MRA==", + "dev": true, + "peer": true, + "dependencies": { + "@types/node": "*", + "jest-util": "^29.6.1", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "peer": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/jose": { + "version": "4.14.4", + "resolved": "https://registry.npmjs.org/jose/-/jose-4.14.4.tgz", + "integrity": "sha512-j8GhLiKmUAh+dsFXlX1aJCbt5KMibuKb+d7j1JaOJG6s2UjX1PQlW+OKB/sD4a/5ZYF4RcmYmLSndOoU3Lt/3g==", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "peer": true + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/js2xmlparser": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/js2xmlparser/-/js2xmlparser-4.0.2.tgz", + "integrity": "sha512-6n4D8gLlLf1n5mNLQPRfViYzu9RATblzPEtm1SthMX1Pjao0r9YI9nw7ZIfRxQMERS87mcswrg+r/OYrPRX6jA==", + "optional": true, + "dependencies": { + "xmlcreate": "^2.0.4" + } + }, + "node_modules/jsdoc": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-4.0.2.tgz", + "integrity": "sha512-e8cIg2z62InH7azBBi3EsSEqrKx+nUtAS5bBcYTSpZFA+vhNPyhv8PTFZ0WsjOPDj04/dOLlm08EDcQJDqaGQg==", + "optional": true, + "dependencies": { + "@babel/parser": "^7.20.15", + "@jsdoc/salty": "^0.2.1", + "@types/markdown-it": "^12.2.3", + "bluebird": "^3.7.2", + "catharsis": "^0.9.0", + "escape-string-regexp": "^2.0.0", + "js2xmlparser": "^4.0.2", + "klaw": "^3.0.0", + "markdown-it": "^12.3.2", + "markdown-it-anchor": "^8.4.1", + "marked": "^4.0.10", + "mkdirp": "^1.0.4", + "requizzle": "^0.2.3", + "strip-json-comments": "^3.1.0", + "underscore": "~1.13.2" + }, + "bin": { + "jsdoc": "jsdoc.js" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/jsdoc/node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true, + "peer": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/json-bigint": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", + "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", + "optional": true, + "dependencies": { + "bignumber.js": "^9.0.0" + } + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true, + "peer": true + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "peer": true, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonwebtoken": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.1.tgz", + "integrity": "sha512-K8wx7eJ5TPvEjuiVSkv167EVboBDv9PZdDoF7BgeQnBLVvZWW9clr2PsQHVJDTKaEIH5JBIwHujGcHp7GgI2eg==", + "dependencies": { + "jws": "^3.2.2", + "lodash": "^4.17.21", + "ms": "^2.1.1", + "semver": "^7.3.8" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jsonwebtoken/node_modules/jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jsonwebtoken/node_modules/jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "dependencies": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jwa": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.0.tgz", + "integrity": "sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==", + "optional": true, + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jwks-rsa": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/jwks-rsa/-/jwks-rsa-3.0.1.tgz", + "integrity": "sha512-UUOZ0CVReK1QVU3rbi9bC7N5/le8ziUj0A2ef1Q0M7OPD2KvjEYizptqIxGIo6fSLYDkqBrazILS18tYuRc8gw==", + "dependencies": { + "@types/express": "^4.17.14", + "@types/jsonwebtoken": "^9.0.0", + "debug": "^4.3.4", + "jose": "^4.10.4", + "limiter": "^1.1.5", + "lru-memoizer": "^2.1.4" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/jwks-rsa/node_modules/@types/express": { + "version": "4.17.17", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.17.tgz", + "integrity": "sha512-Q4FmmuLGBG58btUnfS1c1r/NQdlp3DMfGDGig8WhfpA2YRUtEkxAjkZb0yvplJGYdF1fsQ81iMDcH24sSCNC/Q==", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "node_modules/jws": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz", + "integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==", + "optional": true, + "dependencies": { + "jwa": "^2.0.0", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/klaw": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/klaw/-/klaw-3.0.0.tgz", + "integrity": "sha512-0Fo5oir+O9jnXu5EefYbVK+mHMBeEVEy2cmctR1O1NECcCkPRreJKrS6Qt/j3KC2C148Dfo9i3pCmCMsdqGr0g==", + "optional": true, + "dependencies": { + "graceful-fs": "^4.1.9" + } + }, + "node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/limiter": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/limiter/-/limiter-1.1.5.tgz", + "integrity": "sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA==" + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "peer": true + }, + "node_modules/linkify-it": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-3.0.3.tgz", + "integrity": "sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ==", + "optional": true, + "dependencies": { + "uc.micro": "^1.0.1" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "node_modules/lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", + "optional": true + }, + "node_modules/lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==" + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "node_modules/long": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==", + "optional": true + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/lru-memoizer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/lru-memoizer/-/lru-memoizer-2.2.0.tgz", + "integrity": "sha512-QfOZ6jNkxCcM/BkIPnFsqDhtrazLRsghi9mBwFAzol5GCvj4EkFT899Za3+QwikCg5sRX8JstioBDwOxEyzaNw==", + "dependencies": { + "lodash.clonedeep": "^4.5.0", + "lru-cache": "~4.0.0" + } + }, + "node_modules/lru-memoizer/node_modules/lru-cache": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.0.2.tgz", + "integrity": "sha512-uQw9OqphAGiZhkuPlpFGmdTU2tEuhxTourM/19qGJrxBPHAr/f8BT1a0i/lOclESnGatdJG/UCkP9kZB/Lh1iw==", + "dependencies": { + "pseudomap": "^1.0.1", + "yallist": "^2.0.0" + } + }, + "node_modules/lru-memoizer/node_modules/yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==" + }, + "node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "peer": true, + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "peer": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/makeerror": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", + "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", + "dev": true, + "peer": true, + "dependencies": { + "tmpl": "1.0.5" + } + }, + "node_modules/markdown-it": { + "version": "12.3.2", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-12.3.2.tgz", + "integrity": "sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg==", + "optional": true, + "dependencies": { + "argparse": "^2.0.1", + "entities": "~2.1.0", + "linkify-it": "^3.0.1", + "mdurl": "^1.0.1", + "uc.micro": "^1.0.5" + }, + "bin": { + "markdown-it": "bin/markdown-it.js" + } + }, + "node_modules/markdown-it-anchor": { + "version": "8.6.7", + "resolved": "https://registry.npmjs.org/markdown-it-anchor/-/markdown-it-anchor-8.6.7.tgz", + "integrity": "sha512-FlCHFwNnutLgVTflOYHPW2pPcl2AACqVzExlkGQNsi4CJgqOHN7YTgDd4LuhgN1BFO3TS0vLAruV1Td6dwWPJA==", + "optional": true, + "peerDependencies": { + "@types/markdown-it": "*", + "markdown-it": "*" + } + }, + "node_modules/marked": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz", + "integrity": "sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==", + "optional": true, + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 12" + } + }, + "node_modules/mdurl": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", + "integrity": "sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==", + "optional": true + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true, + "peer": true + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", + "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", + "optional": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "devOptional": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "devOptional": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "optional": true, + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "node_modules/natural-compare-lite": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz", + "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==", + "dev": true + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/node-fetch": { + "version": "2.6.12", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.12.tgz", + "integrity": "sha512-C/fGU2E8ToujUivIO0H+tpQ6HWo4eEmchoPIoXtxCrVghxdKq+QOHqEZW7tuP3KlV3bC8FRMO5nMCC7Zm1VP6g==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/node-forge": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", + "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", + "engines": { + "node": ">= 6.13.0" + } + }, + "node_modules/node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", + "dev": true, + "peer": true + }, + "node_modules/node-releases": { + "version": "2.0.13", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz", + "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==", + "dev": true, + "peer": true + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "peer": true, + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "optional": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/object-inspect": { + "version": "1.12.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", + "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", + "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.values": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.6.tgz", + "integrity": "sha512-FVVTkD1vENCsAcwNs9k6jea2uHC/X0+JcjG8YA60FN5CMaJmG95wT9jek/xX9nornqGRrBkKtzuAu2wuHpKqvw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "devOptional": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "peer": true, + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/optionator": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", + "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", + "dev": true, + "dependencies": { + "@aashutoshrathi/word-wrap": "^1.2.3", + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "devOptional": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "devOptional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true, + "peer": true + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pirates": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", + "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", + "dev": true, + "peer": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "peer": true, + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "peer": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "peer": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "peer": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "peer": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/pretty-format": { + "version": "29.6.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.6.1.tgz", + "integrity": "sha512-7jRj+yXO0W7e4/tSJKoR7HRIHLPPjtNaUGG2xxKQnGvPNRkgWcQ0AZX6P4KBRJN4FcTBWb3sa7DVUJmocYuoog==", + "dev": true, + "peer": true, + "dependencies": { + "@jest/schemas": "^29.6.0", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "dev": true, + "peer": true, + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/proto3-json-serializer": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/proto3-json-serializer/-/proto3-json-serializer-1.1.1.tgz", + "integrity": "sha512-AwAuY4g9nxx0u52DnSMkqqgyLHaW/XaPLtaAo3y/ZCfeaQB/g4YDH4kb8Wc/mWzWvu0YjOznVnfn373MVZZrgw==", + "optional": true, + "dependencies": { + "protobufjs": "^7.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/protobufjs": { + "version": "7.2.4", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.2.4.tgz", + "integrity": "sha512-AT+RJgD2sH8phPmCf7OUZR8xGdcJRga4+1cOaXJ64hvcSkVhNcRHOwIxUatPH15+nj59WAGTDv3LSGZPEQbJaQ==", + "hasInstallScript": true, + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/node": ">=13.7.0", + "long": "^5.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/protobufjs-cli": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/protobufjs-cli/-/protobufjs-cli-1.1.1.tgz", + "integrity": "sha512-VPWMgIcRNyQwWUv8OLPyGQ/0lQY/QTQAVN5fh+XzfDwsVw1FZ2L3DM/bcBf8WPiRz2tNpaov9lPZfNcmNo6LXA==", + "optional": true, + "dependencies": { + "chalk": "^4.0.0", + "escodegen": "^1.13.0", + "espree": "^9.0.0", + "estraverse": "^5.1.0", + "glob": "^8.0.0", + "jsdoc": "^4.0.0", + "minimist": "^1.2.0", + "semver": "^7.1.2", + "tmp": "^0.2.1", + "uglify-js": "^3.7.7" + }, + "bin": { + "pbjs": "bin/pbjs", + "pbts": "bin/pbts" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "protobufjs": "^7.0.0" + } + }, + "node_modules/protobufjs-cli/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "optional": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/protobufjs-cli/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "optional": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/protobufjs-cli/node_modules/glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "optional": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/protobufjs-cli/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "optional": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/protobufjs/node_modules/long": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/long/-/long-5.2.3.tgz", + "integrity": "sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==" + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==" + }, + "node_modules/punycode": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", + "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/pure-rand": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.0.2.tgz", + "integrity": "sha512-6Yg0ekpKICSjPswYOuC5sku/TSWaRYlA0qsXqJgM/d/4pLPHPuTxK7Nbf7jFKzAeedUhR8C7K9Uv63FBsSo8xQ==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "peer": true + }, + "node_modules/qs": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", + "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", + "dev": true, + "peer": true + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "optional": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.0.tgz", + "integrity": "sha512-0SutC3pNudRKgquxGoRGIz946MZVHqbNfPjBdxeOhBrdgDKlRoXmYLQN9xRbrR09ZXWeGAdPuif7egofn6v5LA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "functions-have-names": "^1.2.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "devOptional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/requizzle": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/requizzle/-/requizzle-0.2.4.tgz", + "integrity": "sha512-JRrFk1D4OQ4SqovXOgdav+K8EAhSB/LJZqCz8tbX0KObcdeM15Ss59ozWMBWmmINMagCwmqn4ZNryUGpBsl6Jw==", + "optional": true, + "dependencies": { + "lodash": "^4.17.21" + } + }, + "node_modules/resolve": { + "version": "1.22.2", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz", + "integrity": "sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==", + "dev": true, + "dependencies": { + "is-core-module": "^2.11.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "peer": true, + "dependencies": { + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-cwd/node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/resolve.exports": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.2.tgz", + "integrity": "sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==", + "dev": true, + "peer": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/retry": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.13.1.tgz", + "integrity": "sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==", + "optional": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/retry-request": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-5.0.2.tgz", + "integrity": "sha512-wfI3pk7EE80lCIXprqh7ym48IHYdwmAAzESdbU8Q9l7pnRCk9LEhpbOTNKjz6FARLm/Bl5m+4F0ABxOkYUujSQ==", + "optional": true, + "dependencies": { + "debug": "^4.1.1", + "extend": "^3.0.2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "devOptional": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-array-concat": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.0.0.tgz", + "integrity": "sha512-9dVEFruWIsnie89yym+xWTAYASdpw3CJV7Li/6zBewGf9z2i1j31rP6jnY0pHEO4QZh6N0K11bFjWmdR8UGdPQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.0", + "has-symbols": "^1.0.3", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/safe-regex-test": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", + "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "is-regex": "^1.1.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/send/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/send/node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/serve-static": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "dependencies": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.18.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dependencies": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "peer": true + }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true, + "peer": true + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "devOptional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz", + "integrity": "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==", + "dev": true, + "peer": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true, + "peer": true + }, + "node_modules/stack-utils": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz", + "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==", + "dev": true, + "peer": true, + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/stack-utils/node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/stream-events": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/stream-events/-/stream-events-1.0.5.tgz", + "integrity": "sha512-E1GUzBSgvct8Jsb3v2X15pjzN1tYebtbLaMg+eBOUOAxgbLoSbT2NS91ckc5lJD1KfLjId+jXJRgo0qnV5Nerg==", + "optional": true, + "dependencies": { + "stubs": "^3.0.0" + } + }, + "node_modules/stream-shift": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz", + "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==", + "optional": true + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "optional": true, + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-length": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", + "integrity": "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==", + "dev": true, + "peer": true, + "dependencies": { + "char-regex": "^1.0.2", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "devOptional": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.7.tgz", + "integrity": "sha512-p6TmeT1T3411M8Cgg9wBTMRtY2q9+PNy9EV1i2lIXUN/btt763oIfxwN3RR8VU6wHX8j/1CFy0L+YuThm6bgOg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz", + "integrity": "sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz", + "integrity": "sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "devOptional": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true, + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "devOptional": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/stripe": { + "version": "12.16.0", + "resolved": "https://registry.npmjs.org/stripe/-/stripe-12.16.0.tgz", + "integrity": "sha512-iwVkyZsS9KsWmEVK5qnbPeX/m94+Z3fXIRU7Q4iNBS2Zuj1spGIjLKMN3tejUs/ZZ2o7dCYFJvVdC2aZMYo8GA==", + "dependencies": { + "@types/node": ">=8.1.0", + "qs": "^6.11.0" + }, + "engines": { + "node": ">=12.*" + } + }, + "node_modules/strnum": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.0.5.tgz", + "integrity": "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==", + "optional": true + }, + "node_modules/stubs": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/stubs/-/stubs-3.0.0.tgz", + "integrity": "sha512-PdHt7hHUJKxvTCgbKX9C1V/ftOcjJQgz8BZwNfV5c4B6dcGqlpelTbJ999jBGZ2jYiPAwcX5dP6oBwVlBlUbxw==", + "optional": true + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "devOptional": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/teeny-request": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-8.0.3.tgz", + "integrity": "sha512-jJZpA5He2y52yUhA7pyAGZlgQpcB+xLjcN0eUFxr9c8hP/H7uOXbBNVo/O0C/xVfJLJs680jvkFgVJEEvk9+ww==", + "optional": true, + "dependencies": { + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.0", + "node-fetch": "^2.6.1", + "stream-events": "^1.0.5", + "uuid": "^9.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/test-exclude": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", + "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "dev": true, + "peer": true, + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/text-decoding": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/text-decoding/-/text-decoding-1.0.0.tgz", + "integrity": "sha512-/0TJD42KDnVwKmDK6jj3xP7E2MG7SHAOG4tyTgyUCRPdHwvkquYNLEQltmdMa3owq3TkddCVcTsoctJI8VQNKA==" + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, + "node_modules/tmp": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", + "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", + "optional": true, + "dependencies": { + "rimraf": "^3.0.0" + }, + "engines": { + "node": ">=8.17.0" + } + }, + "node_modules/tmpl": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", + "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", + "dev": true, + "peer": true + }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "dev": true, + "peer": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "node_modules/ts-deepmerge": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/ts-deepmerge/-/ts-deepmerge-2.0.7.tgz", + "integrity": "sha512-3phiGcxPSSR47RBubQxPoZ+pqXsEsozLo4G4AlSrsMKTFg9TA3l+3he5BqpUi9wiuDbaHWXH/amlzQ49uEdXtg==", + "dev": true + }, + "node_modules/tsconfig-paths": { + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.2.tgz", + "integrity": "sha512-o/9iXgCYc5L/JxCHPe3Hvh8Q/2xm5Z+p18PESBU6Ff33695QnCHBEjcytY2q19ua7Mbl/DavtBOLq+oG0RCL+g==", + "dev": true, + "dependencies": { + "@types/json5": "^0.0.29", + "json5": "^1.0.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + }, + "node_modules/tsconfig-paths/node_modules/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/tsconfig-paths/node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/tslib": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.1.tgz", + "integrity": "sha512-t0hLfiEKfMUoqhG+U1oid7Pva4bbDPHYfJNiB7BiIjRkj1pyC++4N3huJfqY6aRH6VTB0rvtzQwjM4K6qpfOig==" + }, + "node_modules/tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dev": true, + "dependencies": { + "tslib": "^1.8.1" + }, + "engines": { + "node": ">= 6" + }, + "peerDependencies": { + "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" + } + }, + "node_modules/tsutils/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "peer": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typed-array-buffer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.0.tgz", + "integrity": "sha512-Y8KTSIglk9OZEr8zywiIHG/kmQ7KWyjseXs1CbSo8vC42w7hg2HgYTxSWwP0+is7bWDc1H+Fo026CpHFwm8tkw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.1", + "is-typed-array": "^1.1.10" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.0.tgz", + "integrity": "sha512-Or/+kvLxNpeQ9DtSydonMxCx+9ZXOswtwJn17SNLvhptaXYDJvkFFP5zbfU/uLmvnBJlI4yrnXRxpdWH/M5tNA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "has-proto": "^1.0.1", + "is-typed-array": "^1.1.10" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.0.tgz", + "integrity": "sha512-RD97prjEt9EL8YgAgpOkf3O4IF9lhJFr9g0htQkm0rchFp/Vx7LW5Q8fSXXub7BXAODyUQohRMyOc3faCPd0hg==", + "dev": true, + "dependencies": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "has-proto": "^1.0.1", + "is-typed-array": "^1.1.10" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz", + "integrity": "sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "is-typed-array": "^1.1.9" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typescript": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/uc.micro": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", + "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==", + "optional": true + }, + "node_modules/uglify-js": { + "version": "3.17.4", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz", + "integrity": "sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==", + "optional": true, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/unbox-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", + "which-boxed-primitive": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/underscore": { + "version": "1.13.6", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.6.tgz", + "integrity": "sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A==", + "optional": true + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz", + "integrity": "sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "peer": true, + "dependencies": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "optional": true + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", + "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/v8-to-istanbul": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.1.0.tgz", + "integrity": "sha512-6z3GW9x8G1gd+JIIgQQQxXuiJtCXeAjp6RaPEPLv62mH3iPHPxV6W3robxtCzNErRo6ZwTmzWhsbNvjyEBKzKA==", + "dev": true, + "peer": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^1.6.0" + }, + "engines": { + "node": ">=10.12.0" + } + }, + "node_modules/v8-to-istanbul/node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "dev": true, + "peer": true + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/walker": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "dev": true, + "peer": true, + "dependencies": { + "makeerror": "1.0.12" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "node_modules/websocket-driver": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", + "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", + "dependencies": { + "http-parser-js": ">=0.5.1", + "safe-buffer": ">=5.1.0", + "websocket-extensions": ">=0.1.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/websocket-extensions": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", + "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dev": true, + "dependencies": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.11.tgz", + "integrity": "sha512-qe9UWWpkeG5yzZ0tNYxDmd7vo58HDBc39mZ0xWWpolAGADdFOzkfamWLDxkOWcvHQKVmdTyQdLD4NOfjLWTKew==", + "dev": true, + "dependencies": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "devOptional": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "devOptional": true + }, + "node_modules/write-file-atomic": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", + "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "dev": true, + "peer": true, + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.7" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/xmlcreate": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/xmlcreate/-/xmlcreate-2.0.4.tgz", + "integrity": "sha512-nquOebG4sngPmGPICTS5EnxqhKbCmz5Ox5hsszI2T6U5qdrJizBc+0ilYSEjTSzU0yZcmvppztXe/5Al5fUwdg==", + "optional": true + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "devOptional": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "devOptional": true, + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "devOptional": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "devOptional": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/firebase/functions/package.json b/firebase/functions/package.json new file mode 100644 index 0000000..5407f11 --- /dev/null +++ b/firebase/functions/package.json @@ -0,0 +1,34 @@ +{ + "name": "functions", + "scripts": { + "lint": "eslint --ext .js,.ts .", + "build": "tsc", + "build:watch": "tsc --watch", + "serve": "npm run build && firebase emulators:start --only functions", + "shell": "npm run build && firebase functions:shell", + "start": "npm run shell", + "deploy": "firebase deploy --only functions", + "logs": "firebase functions:log" + }, + "engines": { + "node": "18" + }, + "main": "lib/index.js", + "dependencies": { + "cors": "^2.8.5", + "firebase-admin": "^11.10.1", + "firebase-functions": "^4.4.1", + "stripe": "^12.16.0" + }, + "devDependencies": { + "@typescript-eslint/eslint-plugin": "^5.12.0", + "@typescript-eslint/parser": "^5.12.0", + "bad-words": "^3.0.4", + "eslint": "^8.9.0", + "eslint-config-google": "^0.14.0", + "eslint-plugin-import": "^2.25.4", + "firebase-functions-test": "^3.1.0", + "typescript": "^4.9.0" + }, + "private": true +} diff --git a/firebase/functions/src/configs/firebase.ts b/firebase/functions/src/configs/firebase.ts new file mode 100644 index 0000000..3b07891 --- /dev/null +++ b/firebase/functions/src/configs/firebase.ts @@ -0,0 +1,13 @@ +import admin from 'firebase-admin'; +// const serviceAccount = require("../../service_account.json"); +// admin.initializeApp({ +// credential: admin.credential.cert(serviceAccount), +// }) +admin.initializeApp(); + +const db = admin.firestore(); +db.settings({ ignoreUndefinedProperties: true }) + +const storage = admin.storage().bucket(); + +export {admin, db, storage}; diff --git a/firebase/functions/src/firestore/files.ts b/firebase/functions/src/firestore/files.ts new file mode 100644 index 0000000..e69de29 diff --git a/firebase/functions/src/firestore/orders.ts b/firebase/functions/src/firestore/orders.ts new file mode 100644 index 0000000..e69de29 diff --git a/firebase/functions/src/firestore/ratings.ts b/firebase/functions/src/firestore/ratings.ts new file mode 100644 index 0000000..e69de29 diff --git a/firebase/functions/src/firestore/users.ts b/firebase/functions/src/firestore/users.ts new file mode 100644 index 0000000..9cd409b --- /dev/null +++ b/firebase/functions/src/firestore/users.ts @@ -0,0 +1,94 @@ +/* eslint-disable */ +import * as functions from 'firebase-functions' +import { db } from '../configs/firebase' +import { onRequest } from 'firebase-functions/v2/https' +import { DocumentSnapshot } from 'firebase-admin/firestore' +import { UploaderData } from '../models/uploader' +import { userFacingMessage, validateUser } from '../utils/utils' +import { createAccount } from '../payment/stripe' + +const updateUsername = onRequest(async (req, res) => { + try { + const authToken : string = req.headers.authorization || '' + const validateSuccess = await validateUser(authToken.split(' ')[1], req.body.uid, req.body.email) + if(validateSuccess){ + await db.collection('Users').doc(req.body.uid).update({ name: req.body.name }) + res.status(200).json({message: 'user data updated'}) + } + else + res.status(401).json({ error: 'User not authorized' }) + } catch (err) { + res.status(500).json({ error: err }) + } +}) + +async function getUserDataByMail(email: string) { + const userData = await db.collection('Users').where('email', '==', email).get() + return userData +} + +const getUserData = onRequest(async (req, res) => { + try { + const authToken : string = req.headers.authorization || '' + const validateSuccess = await validateUser(authToken.split(' ')[1], req.body.uid, req.body.email) + if(validateSuccess){ + const userData : DocumentSnapshot = await db.collection('Users').doc(req.body.uid).get() + res.status(200).json(userData) + } + else + res.status(401).json({ error: 'Unauthorized. Please login to access this resource.' }) + } catch (err) { + res.status(500).json({ error: err }) + } +}) + +const updatePurchasedItems = onRequest(async (req, res) => { + try { + const authToken : string = req.headers.authorization || '' + const validateSuccess = await validateUser(authToken.split(' ')[1], req.body.uid, req.body.email) + if(validateSuccess){ + await db.collection('Users').doc(req.body.uid).update({ purchasedItems: req.body.purchasedItems }) + res.status(200).json({message: 'user data updated'}) + } + else + throw new Error('user not authorized') + } catch (e) { + res.status(401).json({ error: 'Unauthorized. Please login to access this resource.' }) + } +}) + +const onUserRegistered = functions.auth.user().onCreate(async (user) => { + if(user == null) + throw Error('There is no user') + try{ + if(user.email == null) + throw new Error('user\' email is missing') + const stripeInfo = await createAccount(user.email!, 'standard') + const userData : UploaderData = { + uid: user.uid, + name: user.displayName || 'Anonymous User', + email: user.email, + stripeId: stripeInfo.id, + purchasedItems: [], + } + await db.collection('Users').doc(user.uid).create(userData) + } catch(err) { + userFacingMessage(err) + } +}) + +// async function deleteUser(userId:string) { + +// } + +// async function fallBackRegistration() { + +// } + +export { + updateUsername, + onUserRegistered, + updatePurchasedItems, + getUserData, + getUserDataByMail, +} diff --git a/firebase/functions/src/index.ts b/firebase/functions/src/index.ts new file mode 100644 index 0000000..21bd347 --- /dev/null +++ b/firebase/functions/src/index.ts @@ -0,0 +1,21 @@ +import { updateUsername, onUserRegistered } from './firestore/users' +// import { onFileUploadComplete } from './storage/storage' +import { testHelpers } from './utils/utils' +import { createAccountLink, createCheckoutSession, stripeWebhook } from './payment/stripe' +import cors from 'cors'; + +cors({ + origin: true, + methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], + allowedHeaders: ['Content-Type', 'Authorization'], +}); + +export { + updateUsername, + onUserRegistered, + // onFileUploadComplete, + testHelpers, + createAccountLink, + createCheckoutSession, + stripeWebhook, +} diff --git a/firebase/functions/src/models/plugin_zip_file.ts b/firebase/functions/src/models/plugin_zip_file.ts new file mode 100644 index 0000000..4a8c4d4 --- /dev/null +++ b/firebase/functions/src/models/plugin_zip_file.ts @@ -0,0 +1,16 @@ +/* eslint-disable */ + +import { Timestamp } from 'firebase-admin/firestore' + +type PluginZipFile = { + downloadCount: number + name: string + rating: number + ratingCount: number + uploadDate: Timestamp + downloadURL: string + price: number + uploader: {} +} + +export { PluginZipFile } diff --git a/firebase/functions/src/models/uploader.ts b/firebase/functions/src/models/uploader.ts new file mode 100644 index 0000000..7840246 --- /dev/null +++ b/firebase/functions/src/models/uploader.ts @@ -0,0 +1,9 @@ +type UploaderData = { + uid: string, + name?: string, + email?: string + stripeId: string + purchasedItems: string[] +} + +export { UploaderData } diff --git a/firebase/functions/src/payment/stripe.ts b/firebase/functions/src/payment/stripe.ts new file mode 100644 index 0000000..ec322b4 --- /dev/null +++ b/firebase/functions/src/payment/stripe.ts @@ -0,0 +1,113 @@ +/* eslint-disable */ +import { onRequest } from 'firebase-functions/v2/https' +import { getUserDataByMail } from '../firestore/users'; +import { db } from '../configs/firebase'; +const { Stripe } = require('stripe'); +const stripe = new Stripe(process.env.STRIPE_SECRET); + +async function createAccount(email:string, accountType:string) { + const accountsList = await stripe.accounts.list() + const duplicatesList : Array = accountsList.data.filter((account:any) => account.email === email); + if (duplicatesList.length > 0) + console.log('has duplicate') + // throw new Error('Account with the same email already exists.'); + const customer = await stripe.accounts.create({ email: email, type: accountType }); + return customer; +} + +const createAccountLink = onRequest(async (req, res) => { + try { + const accountLink = await stripe.accountLinks.create({ + account: req.body.stripeId, + refresh_url: process.env.REDIRECT_URL, + return_url: process.env.REDIRECT_URL, + type: 'account_onboarding', + }); + res.set('Access-Control-Allow-Origin', '*'); + res.set('Access-Control-Allow-Methods', 'GET'); + res.set('Access-Control-Allow-Headers', 'Content-Type'); + res.set('Access-Control-Max-Age', '3600'); + res.json(accountLink); + } catch (err:any) { + res.json(err); + } +}) + +//TODO: check if error should be handled and whether it causes XMLhttprequest error +const createCheckoutSession = onRequest(async (req, res) => { + const sellerDataQuery : any = await getUserDataByMail(req.body.uploaderEmail) + // if(sellerDataQuery.docs.length === 0) + // throw new Error('no seller found with the given email') + // else if(sellerDataQuery.docs.length > 0) + // throw new Error('found duplicate user with the given email') + const cutPercentage = 10 //NOTE: the server decides how much the cut should be + const sellerData = sellerDataQuery.docs[0].data() + const cutAmount = cutPercentage/100 * parseInt(req.body.price, 10); + const timeNow = new Date() + const newCheckoutSession = await stripe.checkout.sessions.create({ + payment_method_types: ['card'], + mode: 'payment', + invoice_creation: { + enabled: true, + }, + line_items: [ + { + quantity: 1, + price_data: { + currency: 'usd', + unit_amount: req.body.price * 100, + product_data: { + name: req.body.name, + description: req.body.description ?? 'No description', + } + } + } + ], + payment_intent_data: { + application_fee_amount: cutAmount * 100, + transfer_data: { + destination: sellerData.stripeId + }, + }, + metadata: { + 'productName': req.body.name, + 'productId': req.body.productId, + 'customerUid': req.body.customerUid ?? 'Guest checkout', + 'purchaseDate': timeNow.toLocaleDateString(), + }, + allow_promotion_codes: true, + 'success_url': process.env.REDIRECT_URL, + 'cancel_url': process.env.REDIRECT_URL, + }) + res.set('Access-Control-Allow-Origin', '*'); + res.set('Access-Control-Allow-Methods', 'GET'); + res.set('Access-Control-Allow-Headers', 'Content-Type'); + res.set('Access-Control-Max-Age', '3600'); + res.json(newCheckoutSession) +}) + +const stripeWebhook = onRequest(async (req, res) => { + try { + const event = stripe.webhooks.constructEvent( + req.rawBody, + req.headers['stripe-signature'], + process.env.WEBHOOK_SECRET + ) + const metadata = event.data.object.metadata; + const result = await db.collection(`Users/${metadata.customerUid}/Orders`).doc(metadata.productId).set(event.data.object) + res.set('Access-Control-Allow-Origin', '*'); + res.set('Access-Control-Allow-Methods', 'GET'); + res.set('Access-Control-Allow-Headers', 'Content-Type'); + res.set('Access-Control-Max-Age', '3600'); + res.status(200).send(result); + } catch (err:any) { + res.status(400).send(`Webhook Error: ${err.message}`) + } +}) + +export { + createAccount, + createAccountLink, + createCheckoutSession, + stripeWebhook, +} \ No newline at end of file diff --git a/firebase/functions/src/storage/storage.ts b/firebase/functions/src/storage/storage.ts new file mode 100644 index 0000000..648e7d9 --- /dev/null +++ b/firebase/functions/src/storage/storage.ts @@ -0,0 +1,109 @@ +import { onRequest } from 'firebase-functions/v2/https' +import { db } from '../configs/firebase' +import { PluginZipFile } from '../models/plugin_zip_file' +import { getDecodedToken } from '../utils/utils' +import { storage } from '../configs/firebase' +import { Timestamp } from 'firebase-admin/firestore' +import functions = require('firebase-functions'); +import { ObjectMetadata } from 'firebase-functions/v1/storage' + +// import { Bucket } from '@google-cloud/storage' +// import {Filter} from 'bad-words' + +function validateUploadFile(bytes : Uint8Array) : boolean { + return false; +} + +// const getPersistentDownloadUrl = (bucket:Bucket, pathToFile:string, downloadToken:string) => { +// console.log(bucket) +// console.log(pathToFile) +// console.log(downloadToken) +// return `https://firebasestorage.googleapis.com/v0/b/${bucket}/o/${encodeURIComponent( +// pathToFile +// )}?alt=media&token=${downloadToken}`; +// }; + +const uploadFile = onRequest(async (req, res) => { + const authToken : string = req.headers.authorization || '' + const bytesFile: Buffer = Buffer.from(req.body.bytesFile) + const fileName : string = req.body.fileName + const uploaderName : string = req.body.uploaderName + const price : number = req.body.price + try { + const decodedToken = await getDecodedToken(authToken.split(' ')[1]); + if(validateUploadFile(bytesFile)) + res.status(401).json({ error: 'Validation failed! Cannot upload file with profanity' }) + const folder = price === 0 ? 'public' : 'private' + const path = `${folder}/${decodedToken.uid}/${fileName}` + const file = storage.file(path) + const metadata = { + contentType: 'application/zip', + customMetadata: { + uploaderName: uploaderName ?? 'Anonymous user', + uploaderEmail: decodedToken.email ?? 'Unknown', + }, + } + + try{ + const zipFile : PluginZipFile = { + downloadCount: 0, + name: fileName || 'undefined', + rating: 0, + ratingCount: 0, + uploadDate: Timestamp.now(), + downloadURL: '', + price: price || -1, + uploader: { + name: uploaderName, + email: decodedToken.email, + }, + } + await file.save(bytesFile, {metadata}); + // const [signedUrl] = await file.getSignedUrl({action: 'read', expires: '01-01-2030' }); + // console.log(`signed Url: ${signedUrl}`) + // const currentTimestamp = admin.firestore.Timestamp.now(); + // const expirationTime = new admin.firestore.Timestamp(currentTimestamp.seconds + 60, 0); // expire after 1 minute + // const downloadUrl : string = getPersistentDownloadUrl(storage, path, signedUrl) + // console.log(`persistentUrl: ${downloadUrl}`) + await db.collection(`Files/`).doc().create(zipFile); + } catch(err:any) { + console.log(err) + res.status(401).json({error: err.code}) + } + res.status(200).json({message: 'upload success'}) + } catch(err:any){ + if (err.code === 'auth/invalid-id-token') + res.status(401).json({ error: 'Invalid token' }) + else if (err.code === 'auth/argument-error') + res.status(401).json({ error: 'User not authorized' }) + else + res.status(401).json({ error: err.code }) + } +}) + +const onFileUploadComplete = functions.storage.object().onFinalize(async (fileObj: ObjectMetadata) => { + try { + const fileName:string = fileObj.name && fileObj.name.split('/')[2] || 'untitled' + const zipFile: PluginZipFile = { + downloadCount: 0, + name: fileName, + rating: 0, + ratingCount: 0, + uploadDate: Timestamp.now(), + downloadURL: '', + price: parseFloat(fileObj.metadata?.price || '0'), + uploader: { + name: fileObj.metadata?.uploaderName || 'Anonymouse User', + email: fileObj.metadata?.uploaderEmail || 'Unknown uploader', + }, + } + // TODO: move to /firestore, client calls this route instead of directly on the firebase + // check for duplicate, then decide whether to add or to update + // const list = await db.collection(`Files/`).doc().where('name', '==', fileObj.name).get() + await db.collection(`Files/`).doc().create(zipFile) + } catch (err: any) { + throw new Error(`something wrong updating the database error: ${err}`) + } +}) + +export { uploadFile, onFileUploadComplete } diff --git a/firebase/functions/src/utils/utils.ts b/firebase/functions/src/utils/utils.ts new file mode 100644 index 0000000..fe661a2 --- /dev/null +++ b/firebase/functions/src/utils/utils.ts @@ -0,0 +1,28 @@ +import { onRequest } from 'firebase-functions/v2/https'; +import { admin } from '../configs/firebase' +import { getUserDataByMail } from '../firestore/users'; + +async function validateUser(token: string, requestUid: string, requestEmail: string) { + const decodedToken = await getDecodedToken(token); + return decodedToken.uid == requestUid && decodedToken.email == requestEmail +} + +async function getDecodedToken(token: string) { + const decodedToken = await admin.auth().verifyIdToken(token) + return decodedToken; +} + +function userFacingMessage(err:any) { + console.log(`dev environment: ${process.env.ENVIRONMENT}`) + if(process.env.ENVIRONMENT === 'development') + console.log(err) + return err.type ? err.message : 'An error occurred, developers have been alerted' +} + +const testHelpers = onRequest(async (req, res) => { + // await createAccount(req.body.email, 'standard') + await getUserDataByMail(req.body.uploaderEmail) + res.json(); +}) + +export { validateUser, getDecodedToken, userFacingMessage, testHelpers } diff --git a/firebase/functions/tsconfig.dev.json b/firebase/functions/tsconfig.dev.json new file mode 100644 index 0000000..7560eed --- /dev/null +++ b/firebase/functions/tsconfig.dev.json @@ -0,0 +1,5 @@ +{ + "include": [ + ".eslintrc.js" + ] +} diff --git a/firebase/functions/tsconfig.json b/firebase/functions/tsconfig.json new file mode 100644 index 0000000..62c7755 --- /dev/null +++ b/firebase/functions/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "module": "commonjs", + "noImplicitReturns": true, + "noUnusedLocals": true, + "outDir": "lib", + "sourceMap": true, + "strict": true, + "target": "es2017", + "resolveJsonModule": true, // json imports + "esModuleInterop": true, // import common modules as ES6 Modules + "allowSyntheticDefaultImports": true, // support typesystem compatibility + }, + "compileOnSave": true, + "include": [ + "src" + ] +} diff --git a/firestore.indexes.json b/firestore.indexes.json new file mode 100644 index 0000000..415027e --- /dev/null +++ b/firestore.indexes.json @@ -0,0 +1,4 @@ +{ + "indexes": [], + "fieldOverrides": [] +} diff --git a/firestore.rules b/firestore.rules new file mode 100644 index 0000000..f10dcc7 --- /dev/null +++ b/firestore.rules @@ -0,0 +1,11 @@ +rules_version = '2'; +service cloud.firestore { + match /databases/{database}/documents { + match /{document=**} { + allow read: if true; + allow create: if request.auth != null; + allow update, delete: if request.auth != null; + } + } +} +// match User document then only allow update if the auth.uid is the same as the doc's uid \ No newline at end of file diff --git a/ios/.gitignore b/ios/.gitignore new file mode 100644 index 0000000..7a7f987 --- /dev/null +++ b/ios/.gitignore @@ -0,0 +1,34 @@ +**/dgph +*.mode1v3 +*.mode2v3 +*.moved-aside +*.pbxuser +*.perspectivev3 +**/*sync/ +.sconsign.dblite +.tags* +**/.vagrant/ +**/DerivedData/ +Icon? +**/Pods/ +**/.symlinks/ +profile +xcuserdata +**/.generated/ +Flutter/App.framework +Flutter/Flutter.framework +Flutter/Flutter.podspec +Flutter/Generated.xcconfig +Flutter/ephemeral/ +Flutter/app.flx +Flutter/app.zip +Flutter/flutter_assets/ +Flutter/flutter_export_environment.sh +ServiceDefinitions.json +Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. +!default.mode1v3 +!default.mode2v3 +!default.pbxuser +!default.perspectivev3 diff --git a/ios/Flutter/AppFrameworkInfo.plist b/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 0000000..9625e10 --- /dev/null +++ b/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + MinimumOSVersion + 11.0 + + diff --git a/ios/Flutter/Debug.xcconfig b/ios/Flutter/Debug.xcconfig new file mode 100644 index 0000000..592ceee --- /dev/null +++ b/ios/Flutter/Debug.xcconfig @@ -0,0 +1 @@ +#include "Generated.xcconfig" diff --git a/ios/Flutter/Release.xcconfig b/ios/Flutter/Release.xcconfig new file mode 100644 index 0000000..592ceee --- /dev/null +++ b/ios/Flutter/Release.xcconfig @@ -0,0 +1 @@ +#include "Generated.xcconfig" diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000..e36ca3a --- /dev/null +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,483 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXBuildFile section */ + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; +/* End PBXBuildFile section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 9705A1C41CF9048500538489 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; + 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; + 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 9740EEB11CF90186004384FC /* Flutter */ = { + isa = PBXGroup; + children = ( + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 9740EEB31CF90195004384FC /* Generated.xcconfig */, + ); + name = Flutter; + sourceTree = ""; + }; + 97C146E51CF9000F007C117D = { + isa = PBXGroup; + children = ( + 9740EEB11CF90186004384FC /* Flutter */, + 97C146F01CF9000F007C117D /* Runner */, + 97C146EF1CF9000F007C117D /* Products */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + ); + name = Products; + sourceTree = ""; + }; + 97C146F01CF9000F007C117D /* Runner */ = { + isa = PBXGroup; + children = ( + 97C146FA1CF9000F007C117D /* Main.storyboard */, + 97C146FD1CF9000F007C117D /* Assets.xcassets */, + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, + 97C147021CF9000F007C117D /* Info.plist */, + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, + ); + path = Runner; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Runner; + productName = Runner; + productReference = 97C146EE1CF9000F007C117D /* Runner.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 97C146E61CF9000F007C117D /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 1300; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 97C146ED1CF9000F007C117D = { + CreatedOnToolsVersion = 7.3.1; + LastSwiftMigration = 1100; + }; + }; + }; + buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 97C146E51CF9000F007C117D; + productRefGroup = 97C146EF1CF9000F007C117D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 97C146ED1CF9000F007C117D /* Runner */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 97C146EC1CF9000F007C117D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; + }; + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 97C146EA1CF9000F007C117D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + 97C146FA1CF9000F007C117D /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C146FB1CF9000F007C117D /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C147001CF9000F007C117D /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 249021D3217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Profile; + }; + 249021D4217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.appflowyThemeMarketplace; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Profile; + }; + 97C147031CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 97C147041CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 97C147061CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.appflowyThemeMarketplace; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 97C147071CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.appflowyThemeMarketplace; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147031CF9000F007C117D /* Debug */, + 97C147041CF9000F007C117D /* Release */, + 249021D3217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147061CF9000F007C117D /* Debug */, + 97C147071CF9000F007C117D /* Release */, + 249021D4217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 97C146E61CF9000F007C117D /* Project object */; +} diff --git a/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000..c87d15a --- /dev/null +++ b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ios/Runner.xcworkspace/contents.xcworkspacedata b/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..1d526a1 --- /dev/null +++ b/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..f9b0d7c --- /dev/null +++ b/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift new file mode 100644 index 0000000..70693e4 --- /dev/null +++ b/ios/Runner/AppDelegate.swift @@ -0,0 +1,13 @@ +import UIKit +import Flutter + +@UIApplicationMain +@objc class AppDelegate: FlutterAppDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + GeneratedPluginRegistrant.register(with: self) + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } +} diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..d36b1fa --- /dev/null +++ b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,122 @@ +{ + "images" : [ + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@3x.png", + "scale" : "3x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@3x.png", + "scale" : "3x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@3x.png", + "scale" : "3x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@2x.png", + "scale" : "2x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@3x.png", + "scale" : "3x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@1x.png", + "scale" : "1x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@1x.png", + "scale" : "1x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@1x.png", + "scale" : "1x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@2x.png", + "scale" : "2x" + }, + { + "size" : "83.5x83.5", + "idiom" : "ipad", + "filename" : "Icon-App-83.5x83.5@2x.png", + "scale" : "2x" + }, + { + "size" : "1024x1024", + "idiom" : "ios-marketing", + "filename" : "Icon-App-1024x1024@1x.png", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png new file mode 100644 index 0000000..dc9ada4 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png new file mode 100644 index 0000000..7353c41 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png new file mode 100644 index 0000000..797d452 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 0000000..6ed2d93 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png new file mode 100644 index 0000000..4cd7b00 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 0000000..fe73094 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png new file mode 100644 index 0000000..321773c Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png new file mode 100644 index 0000000..797d452 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 0000000..502f463 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png new file mode 100644 index 0000000..0ec3034 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 0000000..0ec3034 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 0000000..e9f5fea Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png new file mode 100644 index 0000000..84ac32a Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png new file mode 100644 index 0000000..8953cba Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png new file mode 100644 index 0000000..0467bf1 Binary files /dev/null and b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json new file mode 100644 index 0000000..0bedcf2 --- /dev/null +++ b/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "LaunchImage.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png differ diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png differ diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 0000000..9da19ea Binary files /dev/null and b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png differ diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 0000000..89c2725 --- /dev/null +++ b/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md @@ -0,0 +1,5 @@ +# Launch Screen Assets + +You can customize the launch screen with your own desired assets by replacing the image files in this directory. + +You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. \ No newline at end of file diff --git a/ios/Runner/Base.lproj/LaunchScreen.storyboard b/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000..f2e259c --- /dev/null +++ b/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ios/Runner/Base.lproj/Main.storyboard b/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 0000000..f3c2851 --- /dev/null +++ b/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ios/Runner/GoogleService-Info.plist b/ios/Runner/GoogleService-Info.plist new file mode 100644 index 0000000..e4a9db6 --- /dev/null +++ b/ios/Runner/GoogleService-Info.plist @@ -0,0 +1,34 @@ + + + + + CLIENT_ID + 806787763701-en62uhjnm7lr09j09u49lg9d70hfkl6l.apps.googleusercontent.com + REVERSED_CLIENT_ID + com.googleusercontent.apps.806787763701-en62uhjnm7lr09j09u49lg9d70hfkl6l + API_KEY + AIzaSyCI77BNBYn4xhi6jlz0oNimd5IPz6mgye4 + GCM_SENDER_ID + 806787763701 + PLIST_VERSION + 1 + BUNDLE_ID + com.example.appflowyThemeMarketplace + PROJECT_ID + appflowy-theme-marketpla-e8f9b + STORAGE_BUCKET + appflowy-theme-marketpla-e8f9b.appspot.com + IS_ADS_ENABLED + + IS_ANALYTICS_ENABLED + + IS_APPINVITE_ENABLED + + IS_GCM_ENABLED + + IS_SIGNIN_ENABLED + + GOOGLE_APP_ID + 1:806787763701:ios:6519fad4c098674b9664bc + + \ No newline at end of file diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist new file mode 100644 index 0000000..5d8043b --- /dev/null +++ b/ios/Runner/Info.plist @@ -0,0 +1,51 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Appflowy Theme Marketplace + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + appflowy_theme_marketplace + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UIViewControllerBasedStatusBarAppearance + + CADisableMinimumFrameDurationOnPhone + + UIApplicationSupportsIndirectInputEvents + + + diff --git a/ios/Runner/Runner-Bridging-Header.h b/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 0000000..308a2a5 --- /dev/null +++ b/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" diff --git a/ios/firebase_app_id_file.json b/ios/firebase_app_id_file.json new file mode 100644 index 0000000..1604dbb --- /dev/null +++ b/ios/firebase_app_id_file.json @@ -0,0 +1,7 @@ +{ + "file_generated_by": "FlutterFire CLI", + "purpose": "FirebaseAppID & ProjectID for this Firebase app in this directory", + "GOOGLE_APP_ID": "1:806787763701:ios:6519fad4c098674b9664bc", + "FIREBASE_PROJECT_ID": "appflowy-theme-marketpla-e8f9b", + "GCM_SENDER_ID": "806787763701" +} \ No newline at end of file diff --git a/lib/firebase_options.dart b/lib/firebase_options.dart new file mode 100644 index 0000000..ae188e3 --- /dev/null +++ b/lib/firebase_options.dart @@ -0,0 +1,83 @@ +// File generated by FlutterFire CLI. +// ignore_for_file: lines_longer_than_80_chars, avoid_classes_with_only_static_members +import 'package:firebase_core/firebase_core.dart' show FirebaseOptions; +import 'package:flutter/foundation.dart' + show defaultTargetPlatform, kIsWeb, TargetPlatform; + +/// Default [FirebaseOptions] for use with your Firebase apps. +/// +/// Example: +/// ```dart +/// import 'firebase_options.dart'; +/// // ... +/// await Firebase.initializeApp( +/// options: DefaultFirebaseOptions.currentPlatform, +/// ); +/// ``` +class DefaultFirebaseOptions { + static FirebaseOptions get currentPlatform { + if (kIsWeb) { + return web; + } + switch (defaultTargetPlatform) { + case TargetPlatform.android: + return android; + case TargetPlatform.iOS: + return ios; + case TargetPlatform.macOS: + return macos; + case TargetPlatform.windows: + throw UnsupportedError( + 'DefaultFirebaseOptions have not been configured for windows - ' + 'you can reconfigure this by running the FlutterFire CLI again.', + ); + case TargetPlatform.linux: + throw UnsupportedError( + 'DefaultFirebaseOptions have not been configured for linux - ' + 'you can reconfigure this by running the FlutterFire CLI again.', + ); + default: + throw UnsupportedError( + 'DefaultFirebaseOptions are not supported for this platform.', + ); + } + } + + static const FirebaseOptions web = FirebaseOptions( + apiKey: 'AIzaSyDABhPDC4X6Sq20RJL9kur1b8FWwN0NZSw', + appId: '1:806787763701:web:97d1bdd16abb30df9664bc', + messagingSenderId: '806787763701', + projectId: 'appflowy-theme-marketpla-e8f9b', + authDomain: 'appflowy-theme-marketpla-e8f9b.firebaseapp.com', + storageBucket: 'appflowy-theme-marketpla-e8f9b.appspot.com', + measurementId: 'G-NXP4DZ43R5', + ); + + static const FirebaseOptions android = FirebaseOptions( + apiKey: 'AIzaSyBl0hcWFhkmFgv9gP-ogjVgp8LhNvsizyc', + appId: '1:806787763701:android:f76512fc89190c049664bc', + messagingSenderId: '806787763701', + projectId: 'appflowy-theme-marketpla-e8f9b', + storageBucket: 'appflowy-theme-marketpla-e8f9b.appspot.com', + ); + + static const FirebaseOptions ios = FirebaseOptions( + apiKey: 'AIzaSyCI77BNBYn4xhi6jlz0oNimd5IPz6mgye4', + appId: '1:806787763701:ios:6519fad4c098674b9664bc', + messagingSenderId: '806787763701', + projectId: 'appflowy-theme-marketpla-e8f9b', + storageBucket: 'appflowy-theme-marketpla-e8f9b.appspot.com', + iosClientId: '806787763701-en62uhjnm7lr09j09u49lg9d70hfkl6l.apps.googleusercontent.com', + iosBundleId: 'com.example.appflowyThemeMarketplace', + ); + + static const FirebaseOptions macos = FirebaseOptions( + apiKey: 'AIzaSyCI77BNBYn4xhi6jlz0oNimd5IPz6mgye4', + appId: '1:806787763701:ios:6519fad4c098674b9664bc', + messagingSenderId: '806787763701', + projectId: 'appflowy-theme-marketpla-e8f9b', + storageBucket: 'appflowy-theme-marketpla-e8f9b.appspot.com', + iosClientId: '806787763701-en62uhjnm7lr09j09u49lg9d70hfkl6l.apps.googleusercontent.com', + iosBundleId: 'com.example.appflowyThemeMarketplace', + ); +} diff --git a/lib/main.dart b/lib/main.dart new file mode 100644 index 0000000..ac58ba4 --- /dev/null +++ b/lib/main.dart @@ -0,0 +1,112 @@ +import 'package:appflowy_theme_marketplace/src/payment/application/payment_bloc/payment_bloc.dart'; +import 'package:appflowy_theme_marketplace/src/payment/data/repositories/stripe_payment_repository.dart'; +import 'package:appflowy_theme_marketplace/src/plugins/domain/repositories/plugin_repository.dart'; +import 'package:appflowy_theme_marketplace/src/plugins/domain/repositories/ratings_repository.dart'; +import 'package:appflowy_theme_marketplace/src/user/application/bloc/storage_bloc/storage_bloc.dart'; +import 'package:appflowy_theme_marketplace/src/user/application/bloc/user_bloc/user_bloc.dart'; +import 'package:appflowy_theme_marketplace/src/user/data/supabase_repository/supabase_storage_repository.dart'; +import 'package:appflowy_theme_marketplace/src/user/domain/repositories/storage_repository.dart'; +import 'package:appflowy_theme_marketplace/src/user/presentation/orders_page/orders_page.dart'; +import 'package:appflowy_theme_marketplace/src/user/presentation/user_dashboard_page/user_dashboard_page.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:get_it/get_it.dart'; +import 'package:supabase_flutter/supabase_flutter.dart'; +import 'src/authentication/application/auth_bloc/auth_bloc.dart'; +import 'src/authentication/domain/repositories/authentication_repository.dart'; +import 'src/authentication/presentation/signin_page/signin.dart'; +import 'src/payment/domain/repositories/payment_repository.dart'; +import 'src/plugins/application/plugin/plugin_bloc.dart'; +import 'src/plugins/presentation/dashboard_page/dashboard_page.dart'; +import 'src/serverless_api/supabase_api.dart'; +import 'src/user/application/bloc/orders_bloc/orders_bloc.dart'; +import 'src/plugins/data/supabase_repository/supabase_plugin_repository.dart'; +import 'src/authentication/data/repositories/supabase_authentication_repository.dart'; +import 'src/plugins/data/supabase_repository/supabase_rating_repository.dart'; +import 'src/user/application/bloc/ratings_bloc/ratings_bloc.dart'; +import 'src/user/data/supabase_repository/supabase_orders_repository.dart'; +import 'src/user/data/supabase_repository/supabase_user_repository.dart'; +import 'src/user/domain/repositories/orders_repository.dart'; +import 'src/user/domain/repositories/user_repository.dart'; +import 'src/user/presentation/ratings_page/ratings_page.dart'; +import 'src/user/presentation/register_page/register.dart'; +import 'package:flutter_dotenv/flutter_dotenv.dart'; + +import 'src/widgets/ui_utils.dart'; + +final getIt = GetIt.instance; + +Future main() async { + WidgetsFlutterBinding.ensureInitialized(); + await dotenv.load(fileName: 'dotenv'); + await Supabase.initialize( + url: SupabaseApi.supabaseUrl, + anonKey: dotenv.env['ANON_KEY'] as String, + ); + getIt.registerSingleton(SupabasePluginRepository()); + getIt.registerSingleton(SupabaseRatingsRepository()); + getIt.registerSingleton(SupabaseUserRepository()); + getIt.registerSingleton(SupabaseOrdersRepository()); + getIt.registerSingleton(SupabaseAuthenticationRepository()); + getIt.registerSingleton(StripePaymentRepository()); + getIt.registerSingleton(SupabaseStorageRepository()); + + runApp( + MultiBlocProvider( + providers: [ + BlocProvider( + create: (BuildContext context) => AuthBloc( + authenticationRepository: getIt.get(), + ), + ), + BlocProvider( + create: (BuildContext context) => PluginBloc( + pluginRepository: getIt.get(), + ratingsRepository: getIt.get(), + ), + ), + BlocProvider( + create: (BuildContext context) => UserBloc( + userRepository: getIt.get(), + ), + ), + BlocProvider( + create: (BuildContext context) => RatingsBloc( + ratingsRepository: getIt.get(), + ), + ), + BlocProvider( + create: (BuildContext context) => OrdersBloc( + ordersRepository: getIt.get(), + ), + ), + BlocProvider( + create: (BuildContext context) => PaymentBloc( + paymentRepository: getIt.get()), + ), + BlocProvider( + create: (BuildContext context) => StorageBloc( + storageRepository: getIt.get()), + ), + ], + child: MaterialApp( + title: 'Files Storage Manager', + theme: ThemeData.dark().copyWith( + appBarTheme: const AppBarTheme( + backgroundColor: UiUtils.transparent, + elevation: 0, + ), + ), + initialRoute: '/', + routes: { + '/': (context) => DashboardPage(pluginRepository: getIt.get()), + '/signin': (context) => const SignInPage(), + '/register': (context) => const RegisterPage(), + '/ratings': (context) => RatingsPage(ratingsRepository: getIt.get()), + '/orders': (context) => OrdersPage(ordersRepository: getIt.get()), + '/user/plugins': (context) => UserDashboardPage(userRepository: getIt.get()), + }, + ), + ), + ); +} \ No newline at end of file diff --git a/lib/src/authentication/application/auth_bloc/auth_bloc.dart b/lib/src/authentication/application/auth_bloc/auth_bloc.dart new file mode 100644 index 0000000..75c381a --- /dev/null +++ b/lib/src/authentication/application/auth_bloc/auth_bloc.dart @@ -0,0 +1,123 @@ +import 'package:appflowy_theme_marketplace/src/widgets/ui_utils.dart'; +import 'package:equatable/equatable.dart'; +import 'package:appflowy_theme_marketplace/src/authentication/domain/repositories/authentication_repository.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_dotenv/flutter_dotenv.dart'; +import '../../domain/models/user.dart'; +part 'auth_event.dart'; +part 'auth_state.dart'; + +class AuthBloc extends Bloc { + final AuthenticationRepository authenticationRepository; + + AuthBloc({ + required this.authenticationRepository, + }) : super(const UnAuthenticated()) { + authenticationRepository.authStateChanges().listen((User? user) { + if (user != null) { + emit(AuthenticateSuccess(userData: user)); + } else { + emit(const UnAuthenticated()); + } + }); + on( + ( + RegisterUserRequested event, + Emitter emit, + ) async { + emit(const Loading()); + await UiUtils.delayLoading(); + try { + final key = dotenv.env['ANON_KEY'] as String; + final User? registeredUser = await authenticationRepository.register( + emailAddress: event.email, + password: event.password, + name: event.name, + key: key, + ); + if (registeredUser == null) + throw Exception('user is null'); + User? user = User(uid: registeredUser.uid, email: registeredUser.email); + emit(RegistrationSuccess(userData: user)); + } on Exception catch (e) { + emit(RegistrationFailed(message: e.toString())); + } + }, + ); + on( + (SignInRequested event, Emitter emit) async { + emit(const Loading()); + await UiUtils.delayLoading(); + try { + final User? signedInUser = await authenticationRepository.signIn(emailAddress: event.email, password: event.password); + late User? user; + if (signedInUser == null) { + throw Exception('user is null'); + } + user = User(uid: signedInUser.uid, email: signedInUser.email); + emit(AuthenticateSuccess(userData: user)); + } on Exception catch (e) { + emit(AuthenticateFailed(message: e.toString())); + } + }, + ); + on( + (GoogleSignInRequested event, Emitter emit) async { + emit(const Loading()); + await UiUtils.delayLoading(); + try { + await authenticationRepository.signInWithGoogle(); + } on Exception catch (e) { + emit(AuthenticateFailed(message: e.toString())); + } + }, + ); + on( + (SignOutRequested event, Emitter emit) async { + emit(const Loading()); + await UiUtils.delayLoading(); + try { + await authenticationRepository.signOut(); + emit(const UnAuthenticated()); + } on Exception catch (e) { + emit(AuthenticateFailed(message: e.toString())); + } + }, + ); + on( + (ResetAuthStateRequested event, Emitter emit) async { + emit(const Loading()); + await UiUtils.delayLoading(); + try { + emit(const UnAuthenticated()); + } on Exception catch (e) { + emit(AuthenticateFailed(message: e.toString())); + } + }, + ); + on( + (VerifyEmailRequested event, Emitter emit) async { + emit(const SendingEmail()); + await UiUtils.delayLoading(); + try { + await authenticationRepository.sendEmailVerification(); + emit(const EmailSent()); + } on Exception catch (e) { + emit(AuthenticateFailed(message: e.toString())); + } + }, + ); + on( + (RecoverEmailRequested event, Emitter emit) async { + emit(const SendingEmail()); + await UiUtils.delayLoading(); + try { + await authenticationRepository.sendRecoveryEmail(event.email); + emit(const EmailSent()); + } on Exception catch (e) { + emit(AuthenticateFailed(message: e.toString())); + } + }, + ); + } +} diff --git a/lib/src/authentication/application/auth_bloc/auth_event.dart b/lib/src/authentication/application/auth_bloc/auth_event.dart new file mode 100644 index 0000000..47099e8 --- /dev/null +++ b/lib/src/authentication/application/auth_bloc/auth_event.dart @@ -0,0 +1,43 @@ +part of 'auth_bloc.dart'; + +abstract class AuthEvent extends Equatable{ + @override + List get props => []; +} + +class RegisterUserRequested extends AuthEvent { + RegisterUserRequested(this.email, this.password, [this.name]); + + final String? name; + final String email; + final String password; +} + +class SignInRequested extends AuthEvent { + SignInRequested(this.email, this.password); + + final String email; + final String password; +} + +class GoogleSignInRequested extends AuthEvent { + GoogleSignInRequested(); +} + +class SignOutRequested extends AuthEvent { + SignOutRequested(); +} + +class ResetAuthStateRequested extends AuthEvent { + ResetAuthStateRequested(); +} + +class VerifyEmailRequested extends AuthEvent { + VerifyEmailRequested(); +} + +class RecoverEmailRequested extends AuthEvent { + RecoverEmailRequested({required this.email}); + + final String email; +} \ No newline at end of file diff --git a/lib/src/authentication/application/auth_bloc/auth_state.dart b/lib/src/authentication/application/auth_bloc/auth_state.dart new file mode 100644 index 0000000..bc0fea2 --- /dev/null +++ b/lib/src/authentication/application/auth_bloc/auth_state.dart @@ -0,0 +1,74 @@ +part of 'auth_bloc.dart'; + +abstract class AuthState extends Equatable { + const AuthState(); + + @override + List get props => []; +} + +class Loading extends AuthState { + const Loading(); + + @override + List get props => []; +} + +class AuthenticateSuccess extends AuthState { + const AuthenticateSuccess({required userData}) : user = userData; + + final User? user; + + @override + List get props => [user]; +} + +class RegistrationSuccess extends AuthState { + const RegistrationSuccess({required userData}) : user = userData; + + final User? user; + + @override + List get props => []; +} + +class RegistrationFailed extends AuthState { + const RegistrationFailed({required this.message}); + + final String message; + + @override + List get props => []; + +} + +class AuthenticateFailed extends AuthState { + const AuthenticateFailed({required this.message}); + + final String message; + + @override + List get props => [message]; + +} + +class UnAuthenticated extends AuthState{ + const UnAuthenticated(); + + @override + List get props => []; +} + +class SendingEmail extends AuthState{ + const SendingEmail(); + + @override + List get props => []; +} + +class EmailSent extends AuthState{ + const EmailSent(); + + @override + List get props => []; +} diff --git a/lib/src/authentication/application/factories/supabase_signup_data.dart b/lib/src/authentication/application/factories/supabase_signup_data.dart new file mode 100644 index 0000000..a2512b4 --- /dev/null +++ b/lib/src/authentication/application/factories/supabase_signup_data.dart @@ -0,0 +1,21 @@ +import 'package:appflowy_theme_marketplace/src/widgets/ui_utils.dart'; + +class SupabaseAuthSignUpData { + SupabaseAuthSignUpData({ + required this.email, + this.username, + }); + final String email; + final String? username; + + factory SupabaseAuthSignUpData.fromSignInData({ + required String? username, + required String email, + }) { + return SupabaseAuthSignUpData (username: username, email: email); + } + + Map toJson() => { + 'username': username ?? UiUtils.defaultUsername(email), + }; +} \ No newline at end of file diff --git a/lib/src/authentication/data/repositories/firebase_authentication_repository.dart b/lib/src/authentication/data/repositories/firebase_authentication_repository.dart new file mode 100644 index 0000000..79eab5d --- /dev/null +++ b/lib/src/authentication/data/repositories/firebase_authentication_repository.dart @@ -0,0 +1,101 @@ +import 'dart:async'; + +import 'package:appflowy_theme_marketplace/src/authentication/domain/repositories/authentication_repository.dart'; +import 'package:firebase_auth/firebase_auth.dart' as firebase; +import '../../domain/models/user.dart'; + +class FirebaseAuthenticationRepository implements AuthenticationRepository { + firebase.User? currentUser; + @override + Future register({required String emailAddress, required String password, String? name, required String key}) async { + try { + final firebase.UserCredential credential = await firebase.FirebaseAuth.instance.createUserWithEmailAndPassword( + email: emailAddress, + password: password, + ); + final firebase.User? firebaseUser = credential.user; + late User uploader; + if (firebaseUser != null) { + await firebaseUser.updateDisplayName(name); + uploader = User(uid: firebaseUser.uid, name: name, email: firebaseUser.email); + currentUser = firebaseUser; + } else { + throw Exception('There is no user data'); + } + return uploader; + } on firebase.FirebaseAuthException catch (e) { + if (e.code == 'weak-password') { + throw Exception('The password provided is too weak.'); + } else if (e.code == 'email-already-in-use') { + throw Exception('Exist an account with the given email.'); + } + } on Exception catch (_) { + rethrow; + } + return null; + } + + @override + Future signIn({required String emailAddress, required String password}) async { + try { + final credential = + await firebase.FirebaseAuth.instance + .signInWithEmailAndPassword(email: emailAddress, password: password); + final firebaseUser = credential.user; + final User user = User(uid: firebaseUser!.uid, email: firebaseUser.email); + return user; + } on firebase.FirebaseAuthException catch (e) { + if (e.code == 'user-not-found' || e.code == 'wrong-password') { + throw Exception('In correct email or password'); + } + rethrow; + } + } + + @override + Future signInWithGoogle() async { + firebase.GoogleAuthProvider googleProvider = firebase.GoogleAuthProvider(); + googleProvider.setCustomParameters({'login_hint': 'user@example.com'}); + try { + await firebase.FirebaseAuth.instance.signInWithRedirect(googleProvider); + } on Exception catch (_) { + rethrow; + } + } + + @override + Future signOut() async { + try { + await firebase.FirebaseAuth.instance.signOut(); + } on Exception catch (_) { + rethrow; + } + } + + @override + Stream authStateChanges() { + return firebase.FirebaseAuth.instance.authStateChanges().map( + (firebaseUser) { + if (firebaseUser == null) return null; + User user = User( + email: firebaseUser.email, + uid: firebaseUser.uid, + name: firebaseUser.displayName, + ); + return user; + }, + ); + } + + @override + Future sendEmailVerification() async { + (currentUser != null) + ? await currentUser!.sendEmailVerification() + : throw Exception('user is null'); + } + + @override + Future sendRecoveryEmail(String email) { + throw UnimplementedError(); + } +} diff --git a/lib/src/authentication/data/repositories/supabase_authentication_repository.dart b/lib/src/authentication/data/repositories/supabase_authentication_repository.dart new file mode 100644 index 0000000..2bb568e --- /dev/null +++ b/lib/src/authentication/data/repositories/supabase_authentication_repository.dart @@ -0,0 +1,129 @@ +import 'dart:async'; +import 'dart:convert'; +import 'package:appflowy_theme_marketplace/src/widgets/ui_utils.dart'; +import 'package:http/http.dart' as http; +import 'package:appflowy_theme_marketplace/src/authentication/domain/repositories/authentication_repository.dart'; +import 'package:supabase_flutter/supabase_flutter.dart' as supabase; + +import '../../../serverless_api/supabase_api.dart'; +import '../../application/factories/supabase_signup_data.dart'; +import '../../domain/models/user.dart'; + +class SupabaseAuthenticationRepository implements AuthenticationRepository { + SupabaseAuthenticationRepository({supabase.GoTrueClient? auth, http.Client? client}) + : auth = auth ?? supabase.Supabase.instance.client.auth, + client = client ?? http.Client(); + + final supabase.GoTrueClient auth; + final http.Client client; + supabase.User? currentUser; + + @override + Future register({required String emailAddress, required String password, String? name, required String key}) async { + try { + final supabase.AuthResponse res = await auth.signUp( + email: emailAddress, + password: password, + data: SupabaseAuthSignUpData.fromSignInData(username: name, email: emailAddress).toJson() + ); + final supabase.Session? session = res.session; + final supabase.User? supabaseUser = res.user; + late User uploader; + if (supabaseUser != null) { + uploader = User(uid: supabaseUser.id, name: name, email: supabaseUser.email); + currentUser = supabaseUser; + } else { + throw Exception('There is no user data'); + } + + const url = SupabaseApi.createStripeAccount; + final body = { + 'email': uploader.email + }; + + await client.post( + Uri.parse(url), + headers: { + 'Content-type': 'application/json', + 'Accept': 'application/json', + 'Authorization': 'Bearer $key' + }, + body: jsonEncode(body), + ); + + return uploader; + } on Exception catch(_) { + rethrow; + } + } + + @override + Future signIn({required String emailAddress, required String password}) async { + try { + final supabase.AuthResponse res = await auth.signInWithPassword( + email: emailAddress, + password: password, + ); + final supabase.Session? session = res.session; + final supabase.User? supabaseUser = res.user; + if(supabaseUser == null) { + throw Exception('User is null'); + } + final User user = User(uid: supabaseUser.id, email: supabaseUser.email); + return user; + } on Exception catch (_){ + rethrow; + } + } + + @override + Future signOut() async { + try { + await auth.signOut(); + } on Exception catch (_) { + rethrow; + } + } + + @override + Future signInWithGoogle() async{ + try { + await auth.signInWithOAuth(supabase.Provider.google); + } on Exception catch(_){ + rethrow; + } + } + + @override + Stream authStateChanges() { + return auth.onAuthStateChange.map( + (supabase.AuthState state) { + final supabase.Session? session = state.session; + if (session == null) + return null; + final String? email = session.user.email; + if (email == null) + throw Exception('user email is null'); + User user = User( + email: session.user.email, + uid: session.user.id, + name: UiUtils.defaultUsername(email), + ); + return user; + }, + ); + } + + @override + Future sendEmailVerification() async { + if (currentUser == null) + throw Exception('User is null'); + await auth.resend(type: supabase.OtpType.signup, email: currentUser!.email); + } + + @override + Future sendRecoveryEmail(String email) async { + await auth.resetPasswordForEmail(email, redirectTo: '${SupabaseApi.redirect_url}/password/reset'); + await Future.delayed(const Duration(milliseconds: 300), () {}); + } +} diff --git a/lib/src/authentication/domain/models/user.dart b/lib/src/authentication/domain/models/user.dart new file mode 100644 index 0000000..efbc8b6 --- /dev/null +++ b/lib/src/authentication/domain/models/user.dart @@ -0,0 +1,22 @@ +class User { + final String uid; + final String? name; + final String? email; + + User({ + required this.uid, + this.name, + this.email, + }); + + User.fromJson(Map object) + : uid = object['uid'] ?? '', + name = object['name'] ?? '', + email = object['email'] ?? ''; + + Map toJson() => { + 'uid': uid, + 'name': name ?? '', + 'email': email ?? '', + }; +} diff --git a/lib/src/authentication/domain/repositories/authentication_repository.dart b/lib/src/authentication/domain/repositories/authentication_repository.dart new file mode 100644 index 0000000..c4b8bf3 --- /dev/null +++ b/lib/src/authentication/domain/repositories/authentication_repository.dart @@ -0,0 +1,13 @@ +import 'dart:async'; + +import '../models/user.dart'; + +abstract class AuthenticationRepository { + Future register({required String emailAddress, required String password, String? name, required String key}); + Future signIn({required String emailAddress, required String password}); + Future signInWithGoogle(); + Future signOut(); + Stream authStateChanges(); + Future sendEmailVerification(); + Future sendRecoveryEmail(String email); +} diff --git a/lib/src/authentication/presentation/signin_page/signin.dart b/lib/src/authentication/presentation/signin_page/signin.dart new file mode 100644 index 0000000..382330f --- /dev/null +++ b/lib/src/authentication/presentation/signin_page/signin.dart @@ -0,0 +1,13 @@ +import 'package:appflowy_theme_marketplace/src/authentication/presentation/signin_page/signin_body.dart'; +import 'package:flutter/material.dart'; + +class SignInPage extends StatelessWidget { + const SignInPage({super.key}); + + @override + Widget build(BuildContext context) { + return const Scaffold( + body: SigninBody(), + ); + } +} diff --git a/lib/src/authentication/presentation/signin_page/signin_body.dart b/lib/src/authentication/presentation/signin_page/signin_body.dart new file mode 100644 index 0000000..65e1339 --- /dev/null +++ b/lib/src/authentication/presentation/signin_page/signin_body.dart @@ -0,0 +1,185 @@ +import './widgets/password_recovery_form.dart'; +import 'package:appflowy_theme_marketplace/src/widgets/popup_dialog.dart'; +import 'package:appflowy_theme_marketplace/src/widgets/ui_utils.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:font_awesome_flutter/font_awesome_flutter.dart'; +import '../../../authentication/application/auth_bloc/auth_bloc.dart'; + +class SigninBody extends StatefulWidget { + const SigninBody({super.key}); + + @override + State createState() => _SigninBodyState(); +} + +class _SigninBodyState extends State { + late String _emailAddress; + late String _password; + bool hidePassword = true; + + @override + void initState() { + super.initState(); + _emailAddress = ''; + _password = ''; + } + + @override + Widget build(BuildContext context) { + return SingleChildScrollView( + child: Center( + child: SizedBox( + width: 300, + child: Column( + children: [ + const SizedBox(height: 100), + const Text( + 'Sign In', + style: TextStyle( + fontSize: 32, + ), + ), + const SizedBox(height: 20), + TextField( + textInputAction: TextInputAction.next, + style: const TextStyle(fontSize: 14), + decoration: const InputDecoration( + labelText: 'Email', + labelStyle: TextStyle(fontSize: 14), + ), + onChanged: (value) => setState(() { + _emailAddress = value; + }), + ), + const SizedBox(height: 20), + TextField( + textInputAction: TextInputAction.done, + obscureText: hidePassword, + style: const TextStyle(fontSize: 14), + decoration: InputDecoration( + labelText: 'Password', + labelStyle: const TextStyle(fontSize: 14), + suffixIcon: GestureDetector( + onTap: () => { + setState(() { + hidePassword = !hidePassword; + }) + }, + child: hidePassword ? const Icon(Icons.visibility_off) : const Icon(Icons.visibility), + ), + ), + onChanged: (value) => setState(() { + _password = value; + }), + onSubmitted: (_) => BlocProvider.of(context).add(SignInRequested(_emailAddress, _password)), + ), + const SizedBox(height: 10), + BlocConsumer( + listener: (_, state) { + if (state is AuthenticateSuccess) + Navigator.of(context).pushNamedAndRemoveUntil('/', (Route route) => false); + }, + builder: (context, state) { + if (state is Loading) return const CircularProgressIndicator(); + return Column( + children: [ + if (state is AuthenticateFailed) + Text( + state.message, + style: const TextStyle( + color: Colors.red, + ), + ), + const SizedBox(height: 20), + SizedBox( + width: double.infinity, + child: FilledButton( + onPressed: () => context.read().add(SignInRequested(_emailAddress, _password)), + child: const Text('Sign in')), + ), + ], + ); + }, + ), + const SizedBox(height: 10), + Row( + children: [ + GestureDetector( + onTap: () => Navigator.pushReplacementNamed(context, '/register'), + child: const Text( + 'Register', + style: TextStyle( + decoration: TextDecoration.underline, + color: UiUtils.blue, + fontSize: 14, + ), + ), + ), + const Spacer(), + GestureDetector( + onTap: () { + showDialog( + context: context, + builder: (BuildContext context) { + return PopupDialog( + content: const PasswordRecoveryForm(), + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(), + child: const Text('Close'), + ), + + ], + title: const Text('Account Recovery'), + ); + }, + ); + }, + child: const Text( + 'Forgot password?', + style: TextStyle( + decoration: TextDecoration.underline, + color: UiUtils.blue, + fontSize: 14, + ), + ), + ), + ], + ), + const SizedBox(height: 20), + Row( + children: [ + Expanded( + child: Divider( + height: 50, + color: Colors.grey[400], + ), + ), + const Padding( + padding: EdgeInsets.all(8), + child: Text( + 'Or continue with', + ), + ), + Expanded( + child: Divider( + height: 50, + color: Colors.grey[400], + ), + ), + ], + ), + const SizedBox(height: 20), + IconButton( + icon: const FaIcon(FontAwesomeIcons.google), + onPressed: () => context.read().add(GoogleSignInRequested()), + ), + const SizedBox(height: 20), + ], + ), + ), + ), + ); + } +} diff --git a/lib/src/authentication/presentation/signin_page/widgets/password_recovery_form.dart b/lib/src/authentication/presentation/signin_page/widgets/password_recovery_form.dart new file mode 100644 index 0000000..484c78d --- /dev/null +++ b/lib/src/authentication/presentation/signin_page/widgets/password_recovery_form.dart @@ -0,0 +1,56 @@ +import 'package:appflowy_theme_marketplace/src/widgets/ui_utils.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import '../../../application/auth_bloc/auth_bloc.dart'; + +class PasswordRecoveryForm extends StatefulWidget { + const PasswordRecoveryForm({super.key}); + + @override + State createState() => PasswordRecoveryFormState(); +} + +class PasswordRecoveryFormState extends State { + String emailAddress = ''; + + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + if (state is SendingEmail) + return const Center(child: CircularProgressIndicator()); + return SizedBox( + width: 300, + child: Column( + children: [ + TextField( + textInputAction: TextInputAction.next, + style: const TextStyle(fontSize: 14), + decoration: const InputDecoration( + labelText: 'Email', + labelStyle: TextStyle(fontSize: 14), + ), + onChanged: (value) => setState(() { + emailAddress = value; + }), + ), + const SizedBox(height: UiUtils.sizeL), + SizedBox( + width: double.infinity, + child: ElevatedButton( + onPressed: () => context.read().add( + RecoverEmailRequested( + email: emailAddress, + ), + ), + child: const Text('Send recovery Email'), + ), + ), + ], + ), + ); + }, + ); + } +} diff --git a/lib/src/payment/application/payment_bloc/payment_bloc.dart b/lib/src/payment/application/payment_bloc/payment_bloc.dart new file mode 100644 index 0000000..a18a7f2 --- /dev/null +++ b/lib/src/payment/application/payment_bloc/payment_bloc.dart @@ -0,0 +1,52 @@ +import 'dart:html' as html; +import 'package:appflowy_theme_marketplace/src/payment/domain/models/plugin.dart'; +import 'package:appflowy_theme_marketplace/src/payment/domain/repositories/payment_repository.dart'; +import 'package:equatable/equatable.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import '../../../widgets/ui_utils.dart'; +import '../../domain/models/user.dart'; + +part 'payment_event.dart'; +part 'payment_state.dart'; + +class PaymentBloc extends Bloc { + final PaymentRepository paymentRepository; + + PaymentBloc({ + required this.paymentRepository + }) : super(const CreateCheckoutSession()) { + on( + ( + CheckOutSessionRequested event, + Emitter emit, + ) async { + emit(const CreatingCheckoutSession()); + await UiUtils.delayLoading(); + try { + final res = await paymentRepository.createInvoice(event.product, event.customer); + html.window.location.assign(res['url']); + emit(const CheckoutSessionCreated()); + } on Exception catch (e) { + emit(CheckoutFailed(message: e.toString())); + } + }, + ); + on( + ( + CreateOnboardingLinkRequested event, + Emitter emit, + ) async { + emit(const CreatingOnboardingSession()); + await UiUtils.delayLoading(); + try { + final res = await paymentRepository.createAccountLink(event.accountId); + html.window.location.assign(res['url']); + emit(const OnboardingSessionCreated()); + } on Exception catch (e) { + emit(CheckoutFailed(message: e.toString())); + } + }, + ); + } +} \ No newline at end of file diff --git a/lib/src/payment/application/payment_bloc/payment_event.dart b/lib/src/payment/application/payment_bloc/payment_event.dart new file mode 100644 index 0000000..fda25ce --- /dev/null +++ b/lib/src/payment/application/payment_bloc/payment_event.dart @@ -0,0 +1,20 @@ +part of 'payment_bloc.dart'; + +abstract class PaymentEvent extends Equatable{ + @override + List get props => []; +} + +class CheckOutSessionRequested extends PaymentEvent { + CheckOutSessionRequested(this.product, this.customer); + + final Plugin product; + final User customer; +} + +class CreateOnboardingLinkRequested extends PaymentEvent { + CreateOnboardingLinkRequested(this.accountId); + + final String accountId; + +} \ No newline at end of file diff --git a/lib/src/payment/application/payment_bloc/payment_state.dart b/lib/src/payment/application/payment_bloc/payment_state.dart new file mode 100644 index 0000000..7e9b5cc --- /dev/null +++ b/lib/src/payment/application/payment_bloc/payment_state.dart @@ -0,0 +1,51 @@ +part of 'payment_bloc.dart'; + +class PaymentState extends Equatable { + const PaymentState(); + + @override + List get props => []; +} + +class CreateCheckoutSession extends PaymentState { + const CreateCheckoutSession(); + + @override + List get props => []; +} + +class CreatingCheckoutSession extends PaymentState { + const CreatingCheckoutSession(); + + @override + List get props => []; +} + +class CheckoutSessionCreated extends PaymentState { + const CheckoutSessionCreated(); + + @override + List get props => []; +} + +class CreatingOnboardingSession extends PaymentState { + const CreatingOnboardingSession(); + + @override + List get props => []; +} +class OnboardingSessionCreated extends PaymentState { + const OnboardingSessionCreated(); + + @override + List get props => []; +} + +class CheckoutFailed extends PaymentState { + const CheckoutFailed({required this.message}); + + final String message; + + @override + List get props => [message]; +} \ No newline at end of file diff --git a/lib/src/payment/data/repositories/stripe_payment_repository.dart b/lib/src/payment/data/repositories/stripe_payment_repository.dart new file mode 100644 index 0000000..00c5830 --- /dev/null +++ b/lib/src/payment/data/repositories/stripe_payment_repository.dart @@ -0,0 +1,65 @@ +import 'package:appflowy_theme_marketplace/src/payment/domain/models/plugin.dart'; +import 'package:appflowy_theme_marketplace/src/payment/domain/repositories/payment_repository.dart'; +import 'package:appflowy_theme_marketplace/src/serverless_api/supabase_api.dart'; +import 'package:flutter_dotenv/flutter_dotenv.dart'; +import 'package:http/http.dart' as http; +import 'dart:convert'; +import '../../domain/models/user.dart'; + +class StripePaymentRepository implements PaymentRepository { + final key = dotenv.env['ANON_KEY'] as String; + + @override + Future> createAccountLink(String stripeId) async { + const url = SupabaseApi.createStripeAccountLink; + final body = { + 'stripeId': stripeId, + }; + final response = await http.post( + Uri.parse(url), + headers: { + 'Content-type': 'application/json', + 'Accept': 'application/json', + 'Authorization': 'Bearer $key', + }, + body: jsonEncode(body), + ); + if (response.statusCode == 200) { + final responseData = jsonDecode(response.body) as Map; + return responseData; + } + throw Exception({ + 'exitCode': response.statusCode, + }); + } + + @override + Future> createInvoice(Plugin product, User customer) async { + const url = SupabaseApi.stripeCheckoutSession; + final body = { + 'name': product.name, + 'productId': product.id, + 'price': product.price.toString(), + 'uploaderEmail': product.seller.email, + 'uploaderName': product.seller.name, + 'description': product.description, + 'customerUid': customer.uid, + }; + + final response = await http.post( + Uri.parse(url), + headers: { + 'Content-type': 'application/json', + 'Accept': 'application/json', + 'Authorization': 'Bearer $key', + }, + body: jsonEncode(body), + ); + + if (response.statusCode == 200) { + final responseData = jsonDecode(response.body) as Map; + return responseData; + } + throw Exception({'exitCode': response.statusCode, 'message': response.body}); + } +} diff --git a/lib/src/payment/domain/models/account.dart b/lib/src/payment/domain/models/account.dart new file mode 100644 index 0000000..e69de29 diff --git a/lib/src/payment/domain/models/plugin.dart b/lib/src/payment/domain/models/plugin.dart new file mode 100644 index 0000000..2901697 --- /dev/null +++ b/lib/src/payment/domain/models/plugin.dart @@ -0,0 +1,25 @@ +import 'user.dart'; + +class Plugin { + final String name; + final double price; + final String description; + final String id; + final User seller; + + const Plugin({ + required this.name, + required this.price, + required this.description, + required this.id, + required this.seller, + }); + + Map toJson() => { + 'name': name, + 'price': price, + 'description': description, + 'id': id, + 'seller': seller.toJson(), + }; +} diff --git a/lib/src/payment/domain/models/user.dart b/lib/src/payment/domain/models/user.dart new file mode 100644 index 0000000..ab5f0f0 --- /dev/null +++ b/lib/src/payment/domain/models/user.dart @@ -0,0 +1,36 @@ +import 'package:appflowy_theme_marketplace/src/plugins/domain/models/user.dart' as plugin; + +class User { + final String uid; + final String? name; + final String email; + final List purchasedItems; + + User({ + required this.uid, + this.name, + required this.email, + List? purchasedItems, + }) : purchasedItems = purchasedItems ?? []; + + factory User.fromPlugin(plugin.User user) { + return User( + uid: user.uid, + email: user.email ?? 'Unknown', + name: user.name ?? 'Unknown', + ); + } + + User.fromJson(Map object) + : uid = object['uid'] ?? '', + name = object['name'] ?? '', + email = object['email'] ?? '', + purchasedItems = object['purchasedItems'] ?? []; + + Map toJson() => { + 'uid': uid, + 'name': name ?? '', + 'email': email, + 'purchasedItems': purchasedItems, + }; +} diff --git a/lib/src/payment/domain/repositories/payment_repository.dart b/lib/src/payment/domain/repositories/payment_repository.dart new file mode 100644 index 0000000..56c90c1 --- /dev/null +++ b/lib/src/payment/domain/repositories/payment_repository.dart @@ -0,0 +1,9 @@ +import 'package:appflowy_theme_marketplace/src/payment/domain/models/plugin.dart'; + +import '../models/user.dart'; + + +abstract class PaymentRepository { + Future> createAccountLink(String accountId); + Future> createInvoice(Plugin product, User customer); +} diff --git a/lib/src/plugins/application/factories/order_factory.dart b/lib/src/plugins/application/factories/order_factory.dart new file mode 100644 index 0000000..ce4626d --- /dev/null +++ b/lib/src/plugins/application/factories/order_factory.dart @@ -0,0 +1,9 @@ +import 'package:appflowy_theme_marketplace/src/user/domain/models/order.dart' as user; + +import '../../domain/models/order.dart'; + +class OrderFactory { + static user.Order fromUser(Order order) { + return user.Order.fromJson(order.toJson()); + } +} \ No newline at end of file diff --git a/lib/src/plugins/application/factories/plugin_factory.dart b/lib/src/plugins/application/factories/plugin_factory.dart new file mode 100644 index 0000000..19c2151 --- /dev/null +++ b/lib/src/plugins/application/factories/plugin_factory.dart @@ -0,0 +1,21 @@ +import 'package:appflowy_theme_marketplace/src/payment/domain/models/plugin.dart' as payment; +import 'package:appflowy_theme_marketplace/src/plugins/domain/models/plugin.dart' as plugins; +import '../../domain/models/plugin.dart'; +import './user_factory.dart'; + +class PluginFactory { + static payment.Plugin toPayment(Plugin plugin) { + final seller = UserFactory.toPayment(plugin.uploader); + return payment.Plugin( + name: plugin.name, + price: plugin.price, + description: 'No description', //TODO: might need to include description later + id: plugin.pluginId, + seller: seller, + ); + } + + static plugins.Plugin toPlugins(Plugin plugin) { + return plugins.Plugin.fromJson(plugin.toJson()); + } +} \ No newline at end of file diff --git a/lib/src/plugins/application/factories/user_factory.dart b/lib/src/plugins/application/factories/user_factory.dart new file mode 100644 index 0000000..b0753fd --- /dev/null +++ b/lib/src/plugins/application/factories/user_factory.dart @@ -0,0 +1,30 @@ +import '../../domain/models/user.dart'; +import 'package:appflowy_theme_marketplace/src/authentication/domain/models/user.dart' as auth; +import 'package:appflowy_theme_marketplace/src/payment/domain/models/user.dart' as payment; + +class UserFactory { + static User fromAuth(auth.User user) { + return User( + uid: user.uid, + email: user.email ?? 'Unknown', + name: user.name ?? 'Unknown', + ); + } + + static User fromPayment(payment.User user) { + return User( + uid: user.uid, + email: user.email, + name: user.name ?? '', + ); + } + + static payment.User toPayment(User user) { + return payment.User( + uid: user.uid, + name: user.name ?? 'Unknown', + email: user.email ?? 'Unknown', + purchasedItems: [], + ); + } +} diff --git a/lib/src/plugins/application/plugin/plugin_bloc.dart b/lib/src/plugins/application/plugin/plugin_bloc.dart new file mode 100644 index 0000000..68746ca --- /dev/null +++ b/lib/src/plugins/application/plugin/plugin_bloc.dart @@ -0,0 +1,89 @@ +import 'package:appflowy_theme_marketplace/src/plugins/domain/models/plugin.dart'; +import 'package:appflowy_theme_marketplace/src/plugins/domain/models/rating.dart'; +import 'package:appflowy_theme_marketplace/src/plugins/domain/repositories/plugin_repository.dart'; +import 'package:appflowy_theme_marketplace/src/plugins/domain/repositories/ratings_repository.dart'; +import 'package:bloc/bloc.dart'; +import 'package:equatable/equatable.dart'; +import 'package:flutter/material.dart'; + +import '../../domain/models/picked_file.dart'; +import '../../domain/models/user.dart'; + +part 'plugin_event.dart'; +part 'plugin_state.dart'; + +class PluginBloc extends Bloc { + final PluginRepository pluginRepository; + final RatingsRepository ratingsRepository; + PluginBloc({ + required this.pluginRepository, + required this.ratingsRepository, + }) : super(PluginDefault()) { + on((UploadDataRequested event, Emitter emit) async { + emit(PluginLoading()); + try { + final picked = event.plugin; + final plugin = Plugin.upload( + pickedFile: picked, + name: picked.name, + uploader: event.user, + price: event.price, + ); + await pluginRepository.add(plugin); + emit(PluginUpdated()); + } on Exception catch (e) { + debugPrint(e.toString()); + emit(PluginFailed(message: e.toString())); + } + }); + on((UpdatePluginRequested event, Emitter emit) async { + emit(PluginLoading()); + try { + final picked = event.plugin; + final plugin = Plugin.upload( + pickedFile: picked, + name: picked.name, + uploader: event.user, + price: event.price, + ); + await pluginRepository.add(plugin); + emit(PluginUpdated()); + } on Exception catch (e) { + debugPrint(e.toString()); + emit(PluginFailed(message: e.toString())); + } + }); + on((AddRatingDataRequested event, Emitter emit) async { + emit(PluginLoading()); + try { + await ratingsRepository.add(event.rating); + emit(PluginUpdated()); + } on Exception catch (e) { + debugPrint(e.toString()); + emit(PluginFailed(message: e.toString())); + } + }); + on((IncrementDownloadCountRequested event, Emitter emit) async { + try { + await pluginRepository.updateDonloadCount(event.plugin); + } on Exception catch (e) { + debugPrint(e.toString()); + } + }); + on( + (ResetStateRequested event, Emitter emit) async { + emit(PluginLoading()); + try { + emit(PluginDefault()); + } on Exception catch (e) { + emit(PluginFailed(message: e.toString())); + } + }, + ); + on((PluginReloadRequested event, Emitter emit) async { + emit(PluginReloading()); + await Future.delayed(const Duration(milliseconds: 100), () {}); + emit(PluginDefault()); + }); + } +} diff --git a/lib/src/plugins/application/plugin/plugin_event.dart b/lib/src/plugins/application/plugin/plugin_event.dart new file mode 100644 index 0000000..91cae42 --- /dev/null +++ b/lib/src/plugins/application/plugin/plugin_event.dart @@ -0,0 +1,67 @@ +part of 'plugin_bloc.dart'; + +abstract class PluginEvent extends Equatable { + const PluginEvent(); + + @override + List get props => []; +} + +class UploadDataRequested extends PluginEvent { + const UploadDataRequested(this.user, this.plugin, this.price); + + final User user; + final PickedFile plugin; + final double price; + + @override + List get props => []; +} + +class AddRatingDataRequested extends PluginEvent { + const AddRatingDataRequested(this.rating); + + final Rating rating; + + @override + List get props => []; +} + +class UpdatePluginRequested extends PluginEvent { + const UpdatePluginRequested({required this.user, required this.plugin, required this.price}); + + final User user; + final PickedFile plugin; + final double price; + + @override + List get props => []; +} + +class DeletePluginRequested extends PluginEvent { + const DeletePluginRequested(this.plugin); + + final Plugin plugin; + + @override + List get props => []; +} + +class IncrementDownloadCountRequested extends PluginEvent { + const IncrementDownloadCountRequested(this.plugin); + + final Plugin plugin; + + @override + List get props => []; +} + +class ResetStateRequested extends PluginEvent { + @override + List get props => []; +} + +class PluginReloadRequested extends PluginEvent { + @override + List get props => []; +} diff --git a/lib/src/plugins/application/plugin/plugin_state.dart b/lib/src/plugins/application/plugin/plugin_state.dart new file mode 100644 index 0000000..7c1816e --- /dev/null +++ b/lib/src/plugins/application/plugin/plugin_state.dart @@ -0,0 +1,47 @@ +part of 'plugin_bloc.dart'; + +abstract class PluginState extends Equatable { + const PluginState(); + + @override + List get props => []; +} + +class PluginDefault extends PluginState { + @override + List get props => []; +} + +class PluginLoading extends PluginState { + @override + List get props => []; +} + +class PluginUpdated extends PluginState { + @override + List get props => []; +} + +class PluginReloading extends PluginState { + @override + List get props => []; +} + +class PluginUpdating extends PluginState { + @override + List get props => []; +} + +class PluginDeleting extends PluginState { + @override + List get props => []; +} + +class PluginFailed extends PluginState { + PluginFailed({required message}) : message = message.replaceAll('Exception: ', ''); + + final String message; + + @override + List get props => []; +} diff --git a/lib/src/plugins/application/search/plugin_search_bloc.dart b/lib/src/plugins/application/search/plugin_search_bloc.dart new file mode 100644 index 0000000..4e68dbc --- /dev/null +++ b/lib/src/plugins/application/search/plugin_search_bloc.dart @@ -0,0 +1,30 @@ +import 'package:appflowy_theme_marketplace/src/plugins/domain/models/plugin.dart'; +import 'package:appflowy_theme_marketplace/src/plugins/domain/repositories/plugin_repository.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:equatable/equatable.dart'; + +part 'plugin_search_event.dart'; +part 'plugin_search_state.dart'; + +class PluginSearchBloc extends Bloc { + final PluginRepository pluginRepository; + PluginSearchBloc({ + required this.pluginRepository, + }) : super(EmptySearch()) { + on( + (PluginSearchRequested event, Emitter emit) async { + emit(SearchLoading()); + try { + if (event.searchTerm == '') { + emit(EmptySearch()); + } else { + final filesList = await pluginRepository.list(event.searchTerm); + emit(SearchSuccess(filesList: filesList)); + } + } on Exception catch (_) { + emit(SearchFailed()); + } + }, + ); + } +} diff --git a/lib/src/plugins/application/search/plugin_search_event.dart b/lib/src/plugins/application/search/plugin_search_event.dart new file mode 100644 index 0000000..6437b64 --- /dev/null +++ b/lib/src/plugins/application/search/plugin_search_event.dart @@ -0,0 +1,12 @@ +part of 'plugin_search_bloc.dart'; + +abstract class PluginSearchEvent extends Equatable { + @override + List get props => []; +} + +class PluginSearchRequested extends PluginSearchEvent { + PluginSearchRequested(this.searchTerm); + + final String searchTerm; +} diff --git a/lib/src/plugins/application/search/plugin_search_state.dart b/lib/src/plugins/application/search/plugin_search_state.dart new file mode 100644 index 0000000..bd52d0e --- /dev/null +++ b/lib/src/plugins/application/search/plugin_search_state.dart @@ -0,0 +1,35 @@ +part of 'plugin_search_bloc.dart'; + +abstract class PluginSearchState extends Equatable { + PluginSearchState({List? filesList}) : filesList = filesList ?? []; + + final List filesList; +} + +class EmptySearch extends PluginSearchState { + EmptySearch({super.filesList}); + + @override + List get props => []; +} + +class SearchLoading extends PluginSearchState { + SearchLoading({super.filesList}); + + @override + List get props => []; +} + +class SearchSuccess extends PluginSearchState { + SearchSuccess({required List filesList}) : super(filesList: filesList); + + @override + List get props => []; +} + +class SearchFailed extends PluginSearchState { + SearchFailed({super.filesList}); + + @override + List get props => []; +} diff --git a/lib/src/plugins/data/firebase_repository/firebase_plugin_repository.dart b/lib/src/plugins/data/firebase_repository/firebase_plugin_repository.dart new file mode 100644 index 0000000..df832ad --- /dev/null +++ b/lib/src/plugins/data/firebase_repository/firebase_plugin_repository.dart @@ -0,0 +1,97 @@ +import 'dart:collection'; +import 'dart:typed_data'; +import 'package:appflowy_theme_marketplace/src/plugins/data/firebase_repository/helpers/firestore_utils.dart'; +import 'package:appflowy_theme_marketplace/src/plugins/data/firebase_repository/helpers/storage_utils.dart'; +import 'package:archive/archive.dart'; +import 'package:appflowy_theme_marketplace/src/plugins/domain/models/plugin.dart'; +import 'package:appflowy_theme_marketplace/src/plugins/domain/repositories/plugin_repository.dart'; +import 'package:cloud_firestore/cloud_firestore.dart'; + +import '../../domain/models/user.dart'; + +class FirebasePluginRepository implements PluginRepository { + static final FirebaseFirestore _firestore = FirebaseFirestore.instance; + static final CollectionReference _filesCollectionRef = _firestore.collection('Files'); + + List toPlugin(List> objectEventsList) { + final pluginsList = objectEventsList.map((object) { + final fileMap = Map.from(object.data() as LinkedHashMap); + User uploader = User( + uid: fileMap['uploader']['uid'], + email: fileMap['uploader']['email'] ?? 'Unknown', + name: fileMap['uploader']['name'] ?? 'Unknown', + ); + final DateTime time = fileMap['uploaded_on'].toDate(); + final Plugin zipFile = Plugin( + pluginId: fileMap['plugin_id'], + downloadCount: fileMap['download_count'], + downloadURL: fileMap['downloadURL'], + name: fileMap['name'], + rating: fileMap['rating'], + ratingCount: fileMap['ratingCount'], + uploadDate: time, + pickedFile: null, + price: fileMap['price'], + uploader: uploader, + ); + return zipFile; + }).toList(); + return pluginsList; + } + + @override + Future add(Plugin plugin) async { + if (plugin.pickedFile == null) + throw Exception('no file is picked'); + await StorageUtils.uploadFile(plugin.pickedFile!.bytes, plugin.name, plugin.uploader, plugin.price); + } + + @override + Future delete(Plugin plugin) => throw UnimplementedError(); + + @override + Future get(String id) => throw UnimplementedError(); + + @override + Future> list([String? searchTerm = '']) async { + final list = await FireStoreUtils.listFiles(searchTerm); + return toPlugin(list); + } + + @override + Future update(Plugin plugin) => throw UnimplementedError(); + + @override + Future> byDate([bool ascending = true]) async { + final list = await FireStoreUtils.listFilesByDate(); + return toPlugin(list); + } + + @override + Future> byDownloadCount([bool ascending = true]) async { + final list = await FireStoreUtils.listFilesByDownloadCount(); + return toPlugin(list); + } + + @override + Future> byName([bool ascending = true]) async { + final list = await FireStoreUtils.listFilesByName(); + return toPlugin(list); + } + + @override + Future> byRatings([bool ascending = true]) async { + final list = await FireStoreUtils.listFilesByRatings(); + return toPlugin(list); + } + + @override + Future upload(Plugin plugin) { + throw UnimplementedError(); + } + + @override + Future updateDonloadCount(Plugin plugin) { + throw UnimplementedError(); + } +} diff --git a/lib/src/plugins/data/firebase_repository/firebase_rating_repository.dart b/lib/src/plugins/data/firebase_repository/firebase_rating_repository.dart new file mode 100644 index 0000000..98fde20 --- /dev/null +++ b/lib/src/plugins/data/firebase_repository/firebase_rating_repository.dart @@ -0,0 +1,31 @@ +import 'package:appflowy_theme_marketplace/src/plugins/domain/models/rating.dart'; +import 'package:appflowy_theme_marketplace/src/plugins/domain/repositories/ratings_repository.dart'; + +import 'helpers/firestore_utils.dart'; + +class FirebaseRatingsRepository implements RatingsRepository { + @override + Future> getAll(String uid) { + throw UnimplementedError(); + } + + @override + Future add(Rating rating) async { + await FireStoreUtils.addRating(rating); + } + + @override + Future average(String ratingDocId) async { + throw UnimplementedError(); + } + + @override + Future count(String ratingDocId) async { + throw UnimplementedError(); + } + + @override + Future update(Rating rating) async { + throw UnimplementedError(); + } +} \ No newline at end of file diff --git a/lib/src/plugins/data/firebase_repository/helpers/firestore_utils.dart b/lib/src/plugins/data/firebase_repository/helpers/firestore_utils.dart new file mode 100644 index 0000000..736cf4f --- /dev/null +++ b/lib/src/plugins/data/firebase_repository/helpers/firestore_utils.dart @@ -0,0 +1,161 @@ +import 'dart:collection'; + +import 'package:appflowy_theme_marketplace/src/plugins/domain/models/plugin.dart'; +import 'package:appflowy_theme_marketplace/src/plugins/domain/models/rating.dart'; +import 'package:cloud_firestore/cloud_firestore.dart' as firebase; + +class FireStoreUtils { + static final firebase.FirebaseFirestore _firestore = firebase.FirebaseFirestore.instance; + static final firebase.CollectionReference _filesCollectionRef = _firestore.collection('Files'); + + static Future uploadFileData(Plugin plugin) async { + final querySnapShot = await _filesCollectionRef.where('name', isEqualTo: plugin.name).get(); + if (querySnapShot.docs.isNotEmpty) { + throw Exception('Duplicate file name in document'); + } + await _filesCollectionRef.doc(plugin.pluginId).set(plugin.toJson()); + } + + static Future addRating(Rating rating) async { + final docId = rating.pluginId; + final firebase.DocumentReference ratingsDocumentRef = _filesCollectionRef.doc(docId); + final firebase.CollectionReference ratingsCollectionRef = ratingsDocumentRef.collection('Ratings'); + final firebase.QuerySnapshot querySnapshot = await ratingsCollectionRef + .where('reviewer.email', isEqualTo: rating.reviewer.email) + .get(); + if (querySnapshot.docs.isEmpty){ + await ratingsCollectionRef.doc().set(rating.toJson()); + await _updateRating(docId); + } + else if (querySnapshot.docs.length > 1) { + throw Exception('Duplicate ratings for the same user in this file'); + } else{ + await ratingsCollectionRef.doc(querySnapshot.docs[0].id).set(rating.toJson()); + await _updateRating(docId); + } + } + + static Future _updateRating(String docId) async { + final firebase.DocumentReference ratingsDocumentRef = _filesCollectionRef.doc(docId); + final firebase.CollectionReference ratingsCollectionRef = ratingsDocumentRef.collection('Ratings'); + final int countRating = await numRatings(docId); + final double totalStars = await totalStarsRating(docId); + final double avgRating = totalStars/countRating; + await ratingsDocumentRef.update({'ratingCount': countRating, 'rating': double.parse((avgRating).toStringAsFixed(2))}); + } + + static Future incrementDownloadCount(String docId) async { + final firebase.DocumentReference ratingsDocumentRef = _filesCollectionRef.doc(docId); + final firebase.DocumentSnapshot fileData = await ratingsDocumentRef.get(); + final fileMap = Map.from(fileData.data() as LinkedHashMap); + Plugin zipFile = Plugin.fromJson(fileMap); + await ratingsDocumentRef.update({'download_count': zipFile.downloadCount + 1}); + } + + static Future numRatings(String docId) async { + final firebase.DocumentReference ratingsDocumentRef = _filesCollectionRef.doc(docId); + final firebase.CollectionReference ratingsCollectionRef = ratingsDocumentRef.collection('Ratings'); + int countRatings = 0; + + try { + await ratingsCollectionRef.get().then((event) { + countRatings = event.docs.length; + }); + return countRatings; + } on Exception catch(_) { + rethrow; + } + } + + static Future totalStarsRating(String docId) async { + final firebase.DocumentReference ratingsDocumentRef = _filesCollectionRef.doc(docId); + final firebase.CollectionReference ratingsCollectionRef = ratingsDocumentRef.collection('Ratings'); + int countRatings = 0; + double totalStars = 0; + try { + final firebase.QuerySnapshot snapshot = await ratingsCollectionRef.get(); + countRatings = snapshot.docs.length; + for(final rating in snapshot.docs){ + double? rate = rating['rating']; + if (rate == null) { + continue; + } + totalStars += rate; + } + if (countRatings == 0) { + return 1; + } + return totalStars; + } on Exception catch(_) { + rethrow; + } + } + + //TODO: paginate the query instead of having all the result out + static Future>> listFiles([String? searchTerm = '']) async { + List> objectEventsList = []; + try{ + firebase.QuerySnapshot snapshot = await _filesCollectionRef.orderBy('name').get(); + objectEventsList = snapshot.docs; + if (searchTerm != null || searchTerm == ''){ + objectEventsList = objectEventsList.where((item) => item['name'].toLowerCase().contains(searchTerm)).toList(); + } + return objectEventsList; + } on Exception catch(_) { + return []; + } + } + + static Future>> listFilesByName([bool descending = true]) async { + List> objectEventsList = []; + try{ + await _filesCollectionRef.orderBy('name', descending: descending).get().then((event) { + objectEventsList = event.docs; + }); + return objectEventsList; + } on Exception catch(_) { + return []; + } + } + + static Future>> listFilesByDownloadCount([bool descending = true]) async { + List> objectEventsList = []; + try{ + await _filesCollectionRef.orderBy('download_count', descending: descending).get().then((event) { + objectEventsList = event.docs; + }); + return objectEventsList; + } on Exception catch(_) { + return []; + } + } + + static Future>> listFilesByRatings([bool descending = true]) async { + List> objectEventsList = []; + try{ + await _filesCollectionRef.orderBy('rating', descending: descending).get().then((event) { + objectEventsList = event.docs; + }); + return objectEventsList; + } on Exception catch(_) { + return []; + } + } + + static Future>> listFilesByDate([bool descending = true]) async { + List> objectEventsList = []; + try{ + await _filesCollectionRef.orderBy('uploaded_on', descending: descending).get().then((event) { + objectEventsList = event.docs; + }); + return objectEventsList; + } on Exception catch(_) { + return []; + } + } + + static Stream fileCollectionStream() { + return _filesCollectionRef.snapshots(); + } + +} \ No newline at end of file diff --git a/lib/src/plugins/data/firebase_repository/helpers/storage_utils.dart b/lib/src/plugins/data/firebase_repository/helpers/storage_utils.dart new file mode 100644 index 0000000..d1bac86 --- /dev/null +++ b/lib/src/plugins/data/firebase_repository/helpers/storage_utils.dart @@ -0,0 +1,122 @@ +import 'dart:typed_data'; +import 'package:appflowy_theme_marketplace/src/plugins/domain/models/picked_file.dart'; +import 'package:appflowy_theme_marketplace/src/plugins/domain/models/plugin.dart'; +import 'package:appflowy_theme_marketplace/src/plugins/domain/models/user.dart'; +import 'package:archive/archive.dart'; +import 'package:firebase_storage/firebase_storage.dart'; +import 'package:flutter/foundation.dart'; +import 'package:profanity_filter/profanity_filter.dart'; + +import '../../../../widgets/ui_utils.dart'; +import 'firestore_utils.dart'; + + +class StorageUtils { + String? _nextPageToken; + StorageUtils(this._nextPageToken); + + static bool validateUploadFile(Uint8List bytes) { + Archive archive = ZipDecoder().decodeBytes(bytes); + + // guarantee there must be two files light and dark no more or less + if (archive.length != 2) { + return false; + } + for (ArchiveFile file in archive) { + if (file.isFile){ + final list = file.name.split('/'); + if (!list[0].endsWith(UiUtils.plugins)) { + return false; + } + if (!(list[1].endsWith(UiUtils.lightJsonTheme) || list[1].endsWith(UiUtils.darkJsonTheme))) { + return false; + } + } + } + return true; + } + + static Future uploadFile(Uint8List? byteFile, String? fileName, User? user, double price) async { + if (byteFile != null && fileName != null && user != null){ + final filter = ProfanityFilter(); + if (filter.hasProfanity(fileName.toLowerCase().substring(0, fileName.lastIndexOf('.')))) { + throw Exception('Cannot upload file that has profanity'); + } + + if (fileName.toLowerCase().substring(fileName.lastIndexOf('.')) != '.zip') { + throw Exception('$fileName is not a .zip file'); + } + + if (!validateUploadFile(byteFile)) { + throw Exception('Zip does not meet the required format'); + } + + String path = price == 0 ? 'public/${user.uid}' : 'private/${user.uid}'; + Reference storageReference = FirebaseStorage.instance.ref(path).child(fileName); + + SettableMetadata metadata = SettableMetadata( + customMetadata: { + 'name': user.name ?? '', + 'uid': user.uid, + 'email': user.email ?? '', + } + ); + UploadTask uploadTask = storageReference.putData(byteFile, metadata); + PickedFile pickedFile = PickedFile(byteFile, fileName); + await uploadTask.whenComplete(() async { + final String downloadURL = price == 0 ? await storageReference.getDownloadURL() : ''; + final Plugin plugin = Plugin.upload( + pickedFile: pickedFile, + name: fileName, + uploader: user, + downloadURL: downloadURL, + price: price, + ); + await FireStoreUtils.uploadFileData(plugin).catchError((err) => throw Exception(err)); + }); + } else if (byteFile == null) { + throw Exception('byteFile cannot be null'); + } else if (fileName == null) { + throw Exception('fileName cannot be null'); + } else if (user == null) { + throw Exception('user cannot be null'); + } else { + throw UnimplementedError('error from storage utils'); + } + } + static Future> listAllFiles(String? searchTerm) async { + final storage = FirebaseStorage.instance; + + try{ + if (searchTerm == null || searchTerm.isEmpty){ + final ListResult listResult = await storage.ref().listAll(); + return listResult.items; + } + else{ + final ListResult listResult = await storage.ref().listAll(); + final List filteredResult = listResult.items.where((item) => item.name.toLowerCase().contains(searchTerm)).toList(); + return filteredResult; + } + } on Exception catch(e) { + Exception(e); + return []; + } + } + + Future> listByPage() async { + final storage = FirebaseStorage.instance; + + final listResult = await storage.ref().list( + ListOptions( + maxResults: 10, + pageToken: _nextPageToken, + ), + ); + setPageToken(listResult.nextPageToken); + return listResult.items; + } + + void setPageToken(String? pageToken){ + _nextPageToken = pageToken; + } +} \ No newline at end of file diff --git a/lib/src/plugins/data/supabase_repository/helpers/supabase_db_utils.dart b/lib/src/plugins/data/supabase_repository/helpers/supabase_db_utils.dart new file mode 100644 index 0000000..e69de29 diff --git a/lib/src/plugins/data/supabase_repository/supabase_plugin_repository.dart b/lib/src/plugins/data/supabase_repository/supabase_plugin_repository.dart new file mode 100644 index 0000000..1f4652a --- /dev/null +++ b/lib/src/plugins/data/supabase_repository/supabase_plugin_repository.dart @@ -0,0 +1,176 @@ +import 'dart:typed_data'; +import 'package:appflowy_theme_marketplace/src/plugins/domain/models/plugin.dart'; +import 'package:appflowy_theme_marketplace/src/plugins/domain/repositories/plugin_repository.dart'; +import 'package:archive/archive.dart'; +import 'package:profanity_filter/profanity_filter.dart'; +import 'package:supabase_flutter/supabase_flutter.dart' as supabase; + +import '../../../widgets/ui_utils.dart'; + +class SupabasePluginRepository implements PluginRepository { + SupabasePluginRepository({supabase.SupabaseClient? sp, supabase.SupabaseStorageClient? storage}) + : sp = sp ?? supabase.Supabase.instance.client, + storage = storage ?? supabase.Supabase.instance.client.storage; + + final supabase.SupabaseClient sp; + final supabase.SupabaseStorageClient storage; + + static List toPlugin(List objectEventsList) { + final pluginsList = objectEventsList.map((fileMap) { + final Plugin zipFile = Plugin.fromJson(fileMap); + return zipFile; + }).toList(); + return pluginsList; + } + + static bool validateUploadFile(Uint8List bytes) { + late Archive archive; + try { + archive = ZipDecoder().decodeBytes(bytes); + } on Exception catch(_) { + return false; + } + + // guarantee there must be two files light and dark no more or less + if(archive.length != 2) { + return false; + } + for (ArchiveFile file in archive) { + if(file.isFile){ + final list = file.name.split('/'); + if(!list[0].endsWith('.plugin')) { + return false; + } + if (!(list[1].endsWith(UiUtils.lightJsonTheme) || list[1].endsWith(UiUtils.darkJsonTheme))) { + return false; + } + } + } + return true; + } + + @override + Future add(Plugin plugin) async { + if (plugin.pickedFile == null) { + throw Exception('no file is picked'); + } + + if(plugin.pickedFile == null){ + throw Exception('picked file is undefined'); + } + else { + final filter = ProfanityFilter(); + if(filter.hasProfanity(plugin.name.toLowerCase().substring(0, plugin.name.lastIndexOf('.')))){ + throw Exception('Cannot upload file that has profanity'); + } + if(plugin.pickedFile == null){ + throw Exception('Picked File is undefined'); + } + if(!validateUploadFile(plugin.pickedFile!.bytes)){ + throw Exception('Does not meet the required format'); + } + + final String path = '${plugin.uploader.uid}/${plugin.name}'; + final bucket = plugin.price == 0 ? 'free_plugins' : 'paid_plugins'; + final String res = await storage.from(bucket).uploadBinary( + path, + plugin.pickedFile!.bytes, + fileOptions: + const supabase.FileOptions( + contentType: 'application/zip', + ), + ); + if(res.isEmpty){ + throw Exception('Failed to upload file'); + } + final String downloadUrl = plugin.price == 0 ? storage.from(bucket).getPublicUrl(path) : ''; + if(res.isNotEmpty) { + final duplicate = await sp.from('files').select('*').eq('name', plugin.name).eq('price', plugin.price); + if(duplicate.isNotEmpty){ + await storage.from(bucket).remove([path]); + throw Exception('Exist plugin with the same data'); + } + try { + await sp.from('files').insert(Plugin.upload( + name: plugin.name, + uploader: plugin.uploader, + price: plugin.price, + downloadURL: downloadUrl, + ).toJson()); + } on Exception catch(_) { + await storage.from(bucket).remove([path]); + rethrow; + } + } + else { + throw Exception('Error uploading plugin'); + } + } + + } + + @override + Future> byDate([bool ascending = false]) async { + final data = await sp.from('files').select('*').order('created_at', ascending: ascending); + final List plugins = toPlugin(data); + return plugins; + } + + @override + Future> byDownloadCount([bool ascending = false]) async { + final data = await sp.from('files').select('*').order('download_count', ascending: ascending); + final List plugins = toPlugin(data); + return plugins; + } + + @override + Future> byName([bool ascending = false]) async { + final data = await sp.from('files').select('*').order('name', ascending: ascending); + final List result = toPlugin(data); + return result; + } + + @override + Future> byRatings([bool ascending = false]) async { + final data = await sp.from('files').select('*').order('rating', ascending: ascending); + final List plugins = toPlugin(data); + return plugins; + } + + @override + Future delete(Plugin plugin) { + throw UnimplementedError(); + } + + @override + Future get(String id) { + throw UnimplementedError(); + } + + @override + Future> list([String? searchTerm = '']) async { + late final List result; + final data = await sp.from('files').select('*'); + result = toPlugin(data); + if(searchTerm == null || searchTerm == '') { + return result; + } else { + return result.where((item) => item.name.toLowerCase().contains(searchTerm)).toList(); + } + } + + @override + Future update(Plugin plugin) async { + await sp.from('files').update(plugin.toJson()); + } + + @override + Future updateDonloadCount(Plugin plugin) async { + await sp.rpc( + 'increment_plugin_download_count', + params: { + 'product_id': plugin.pluginId, + }, + ); + } +} diff --git a/lib/src/plugins/data/supabase_repository/supabase_rating_repository.dart b/lib/src/plugins/data/supabase_repository/supabase_rating_repository.dart new file mode 100644 index 0000000..c134542 --- /dev/null +++ b/lib/src/plugins/data/supabase_repository/supabase_rating_repository.dart @@ -0,0 +1,50 @@ +import 'package:appflowy_theme_marketplace/src/plugins/domain/models/rating.dart'; +import 'package:appflowy_theme_marketplace/src/plugins/domain/repositories/ratings_repository.dart'; +import 'package:supabase_flutter/supabase_flutter.dart' as supabase; + +class SupabaseRatingsRepository implements RatingsRepository { + SupabaseRatingsRepository({supabase.SupabaseClient? sp}) : sp = sp ?? supabase.Supabase.instance.client; + + final supabase.SupabaseClient sp; + + @override + Future> getAll(String uid) async { + List ratings = []; + final List data = await sp.from('ratings').select('*').eq('reviewer_id', uid); + ratings = data.map((rating) { + return Rating.fromJson(rating); + }).toList(); + return ratings; + } + + @override + Future add(Rating rating) async { + await sp.from('ratings').upsert(rating.toJsonInsert()); + } + + @override + Future average(String ratingDocId) async { + final value = await sp.from('ratings').select( + 'avg(rating)', + ); + final double average = value; + return average; + } + + @override + Future count(String ratingDocId) async { + final count = await sp.from('ratings').select( + '*', + const supabase.FetchOptions( + count: supabase.CountOption.exact, + ), + ); + final int average = count; + return average; + } + + @override + Future update(Rating rating) async { + throw UnimplementedError(); + } +} \ No newline at end of file diff --git a/lib/src/plugins/domain/models/order.dart b/lib/src/plugins/domain/models/order.dart new file mode 100644 index 0000000..8fcca24 --- /dev/null +++ b/lib/src/plugins/domain/models/order.dart @@ -0,0 +1,29 @@ +class Order { + final String customerUid; + final String productId; + final String productName; + final DateTime purchaseDate; + final String? downloadUrl; + Order({ + required this.customerUid, + required this.productId, + required this.productName, + required this.purchaseDate, + this.downloadUrl + }); + + Order.fromJson(Map object) + : customerUid = object['customerUid'], + productId = object['productId'], + productName = object['productName'], + purchaseDate = object['purchaseDate'], + downloadUrl = object['downloadUrl'] ?? ''; + + Map toJson() => { + 'customerUid': customerUid, + 'productId': productId, + 'productName': productName, + 'purchaseDate': purchaseDate, + 'downloadUrl': downloadUrl ?? '', + }; +} diff --git a/lib/src/plugins/domain/models/picked_file.dart b/lib/src/plugins/domain/models/picked_file.dart new file mode 100644 index 0000000..4517408 --- /dev/null +++ b/lib/src/plugins/domain/models/picked_file.dart @@ -0,0 +1,8 @@ +import 'dart:typed_data'; + +class PickedFile { + final Uint8List bytes; + final String name; + + const PickedFile(this.bytes, this.name); +} \ No newline at end of file diff --git a/lib/src/plugins/domain/models/plugin.dart b/lib/src/plugins/domain/models/plugin.dart new file mode 100644 index 0000000..c1769c5 --- /dev/null +++ b/lib/src/plugins/domain/models/plugin.dart @@ -0,0 +1,80 @@ +import 'package:appflowy_theme_marketplace/src/plugins/domain/models/picked_file.dart'; +import 'package:uuid/uuid.dart'; + +import 'user.dart'; + +class Plugin { + final String pluginId; + final int downloadCount; + final String downloadURL; + final String name; + final double rating; + final int ratingCount; + final DateTime? uploadDate; + final PickedFile? pickedFile; + final double price; + final User uploader; + + Plugin({ + required this.pluginId, + required this.downloadCount, + String? downloadURL, + required this.name, + required this.rating, + required this.ratingCount, + this.uploadDate, + this.pickedFile, + required this.price, + required this.uploader, + }) : downloadURL = downloadURL ?? ''; + + factory Plugin.upload({ + PickedFile? pickedFile, + required String name, + required User uploader, + DateTime? uploadDate, + String? downloadURL, + required double price, + }) => + Plugin( + pluginId: const Uuid().v4(), + downloadCount: 0, + downloadURL: downloadURL ?? '', + name: name, + rating: 0, + ratingCount: 0, + pickedFile: pickedFile, + price: price, + uploader: uploader, + ); + + Plugin.fromJson(Map object) + : pluginId = object['plugin_id'], + downloadCount = object['download_count'], + downloadURL = object['download_url'], + name = object['name'], + rating = object['rating'], + ratingCount = object['rating_count'], + uploadDate = DateTime.parse(object['created_at']), + pickedFile = null, // pickedFile is always null when receive data from a json request + price = object['price'], + uploader = User( + uid: object['uploader_id'], + name: object['uploader_name'], + email: object['uploader_email'], + ); + + Map toJson() => { + 'plugin_id': pluginId, + 'download_count': downloadCount, + 'download_url': downloadURL, + 'name': name, + 'rating': rating, + 'rating_count': ratingCount, + if (uploadDate != null) 'created_at': uploadDate.toString(), + 'price': price, + 'uploader_id': uploader.uid, + 'uploader_email': uploader.email, + 'uploader_name': uploader.name, + }; +} diff --git a/lib/src/plugins/domain/models/rating.dart b/lib/src/plugins/domain/models/rating.dart new file mode 100644 index 0000000..2f53efd --- /dev/null +++ b/lib/src/plugins/domain/models/rating.dart @@ -0,0 +1,51 @@ +import 'package:appflowy_theme_marketplace/src/plugins/domain/models/user.dart'; +import 'package:appflowy_theme_marketplace/src/widgets/ui_utils.dart'; + +class Rating { + final DateTime date; + final double rating; + final String? review; + final User reviewer; + final String pluginId; + final String? pluginName; + + Rating({ + required this.date, + required this.rating, + this.review, + required this.reviewer, + required this.pluginId, + this.pluginName, + }); + + Rating.fromJson(Map object) + : date = DateTime.parse(object['created_at']), + rating = object['rating'], + review = object['review'], + pluginId = object['plugin_id'], + pluginName = object['plugin_name'], + reviewer = User( + email: object['reviewer_email'], + name: UiUtils.defaultUsername(object['reviewer_email']), + uid: object['reviewer_id'], + ); + + Map toJson() => { + 'created_at': date, + 'rating': rating, + 'review': review ?? '', + 'plugin_id': pluginId, + 'plugin_name': pluginName, + 'reviewer_id': reviewer.uid, + 'reviewer_email': reviewer.email, + }; + + Map toJsonInsert() => { + 'review': review, + 'rating': rating, + 'reviewer_id': reviewer.uid, + 'plugin_id': pluginId, + 'reviewer_email': reviewer.email, + 'plugin_name': pluginName, + }; +} \ No newline at end of file diff --git a/lib/src/plugins/domain/models/user.dart b/lib/src/plugins/domain/models/user.dart new file mode 100644 index 0000000..c13332c --- /dev/null +++ b/lib/src/plugins/domain/models/user.dart @@ -0,0 +1,23 @@ +class User { + final String uid; + final String? name; + final String? email; + + + User({ + required this.uid, + this.name, + this.email, + }); + + User.fromJson(Map object) + : uid = object['uid'] ?? '', + name = object['name'] ?? '', + email = object['email'] ?? ''; + + Map toJson() => { + 'uid': uid, + 'name': name ?? '', + 'email': email ?? '', + }; +} diff --git a/lib/src/plugins/domain/repositories/plugin_repository.dart b/lib/src/plugins/domain/repositories/plugin_repository.dart new file mode 100644 index 0000000..797e750 --- /dev/null +++ b/lib/src/plugins/domain/repositories/plugin_repository.dart @@ -0,0 +1,14 @@ +import '../models/plugin.dart'; + +abstract class PluginRepository { + Future> list([String? searchTerm = '']); + Future> byName([bool descending = true]); + Future> byDownloadCount([bool descending = true]); + Future> byRatings([bool descending = true]); + Future> byDate([bool descending = true]); + Future get(String id); + Future add(Plugin plugin); + Future updateDonloadCount(Plugin plugin); + Future update(Plugin plugin); + Future delete(Plugin plugin); +} diff --git a/lib/src/plugins/domain/repositories/ratings_repository.dart b/lib/src/plugins/domain/repositories/ratings_repository.dart new file mode 100644 index 0000000..4d9bb87 --- /dev/null +++ b/lib/src/plugins/domain/repositories/ratings_repository.dart @@ -0,0 +1,9 @@ +import 'package:appflowy_theme_marketplace/src/plugins/domain/models/rating.dart'; + +abstract class RatingsRepository { + Future> getAll(String uid); + Future add(Rating rating); + Future update(Rating rating); + Future count(String ratingDocId); + Future average(String ratingDocId); +} diff --git a/lib/src/plugins/presentation/dashboard_page/dashboard_body.dart b/lib/src/plugins/presentation/dashboard_page/dashboard_body.dart new file mode 100644 index 0000000..0c27185 --- /dev/null +++ b/lib/src/plugins/presentation/dashboard_page/dashboard_body.dart @@ -0,0 +1,169 @@ +import 'package:appflowy_theme_marketplace/main.dart'; +import 'package:appflowy_theme_marketplace/src/plugins/presentation/widgets/item_card/item_listile.dart'; +import 'package:appflowy_theme_marketplace/src/widgets/ui_utils.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import '../../../widgets/icon_button.dart'; +import '../../domain/models/plugin.dart'; +import '../widgets/files_listing.dart'; +import '../widgets/item_card/item_card.dart'; +import '../widgets/list_category_text.dart'; +import '../../domain/models/user.dart'; +import '../../domain/repositories/plugin_repository.dart'; +import '../../application/search/plugin_search_bloc.dart'; +import '../../application/plugin/plugin_bloc.dart'; + +enum ViewMode { + grid, + list, +} + +class DashboardBody extends StatefulWidget { + const DashboardBody({super.key, this.user}); + + final User? user; + + @override + State createState() => _DashboardBodyState(); +} + +class _DashboardBodyState extends State { + late ViewMode _viewMode; + int? _selectedIndex; + + @override + void initState() { + super.initState(); + _viewMode = ViewMode.list; + } + + void gridViewMode() { + setState(() { + _viewMode = ViewMode.grid; + }); + } + void listViewMode() { + setState(() { + _viewMode = ViewMode.list; + }); + } + + void setSelectedIndex(index) { + setState(() { + _selectedIndex = index; + }); + } + + Widget renderList(List filesList) { + if (_viewMode == ViewMode.grid) { + List files = List.generate( + filesList.length, + (index) { + return ItemCard( + index: index, + file: filesList[index], + ); + } + ); + return SizedBox( + child: Wrap( + children: files, + ), + ); + } + List files = List.generate( + filesList.length, + (index) { + return ItemListile( + index: index, + file: filesList[index], + ); + } + ); + return Column( + children: files, + ); + } + + Column categoryListing = Column( + children: [ + ContentSpacer.verticalSpacer_32, + const ListCategoryText('Recently Added'), + FilesListing(fetchFunction: getIt.get().byDate), + ContentSpacer.verticalSpacer_32, + const ListCategoryText('Most Rated'), + FilesListing(fetchFunction: getIt.get().byRatings), + ContentSpacer.verticalSpacer_32, + const ListCategoryText('Most Downloaded'), + FilesListing(fetchFunction: getIt.get().byDownloadCount), + ContentSpacer.verticalSpacer_32, + const ListCategoryText('Featured'), + FilesListing(fetchFunction: getIt.get().list), + ], + ); + + @override + Widget build(BuildContext context) { + Row searchModeRow = Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + DefaulIconButton( + onPressed: gridViewMode, + icon: Icon( + Icons.list_sharp, + color: _viewMode == ViewMode.grid ? UiUtils.blue : null, + ), + ), + DefaulIconButton( + onPressed: listViewMode, + icon: Icon( + Icons.grid_view_sharp, + color: _viewMode == ViewMode.list ? UiUtils.blue : null + ), + ), + ], + ); + + return SingleChildScrollView( + child: BlocConsumer( + listener: (BuildContext context, PluginState state) { + if (state is PluginUpdated) { + context.read().add(PluginReloadRequested()); + } + }, + builder: (BuildContext context, PluginState state) { + if (state is PluginReloading) { + return const Center(child: CircularProgressIndicator()); + } else { + return Container( + padding: const EdgeInsets.symmetric(horizontal: UiUtils.sizeXXL, vertical: 0), + child: BlocBuilder( + builder: (BuildContext context, PluginSearchState state) { + if (state is EmptySearch) { + return categoryListing; + } else if (state is SearchLoading) { + return const Padding( + padding: EdgeInsets.all(16.0), + child: Center( + child: CircularProgressIndicator(), + ), + ); + } else if (state is SearchSuccess) { + Widget list = renderList(state.filesList); + return Column( + children: [ + searchModeRow, + list, + ], + ); + } + return const SizedBox(); + }, + ), + ); + } + }, + ), + ); + } +} diff --git a/lib/src/plugins/presentation/dashboard_page/dashboard_page.dart b/lib/src/plugins/presentation/dashboard_page/dashboard_page.dart new file mode 100644 index 0000000..5fd3a3b --- /dev/null +++ b/lib/src/plugins/presentation/dashboard_page/dashboard_page.dart @@ -0,0 +1,32 @@ +import 'package:appflowy_theme_marketplace/src/widgets/page_layout.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import '../../../authentication/application/auth_bloc/auth_bloc.dart'; +import '../../application/search/plugin_search_bloc.dart'; +import '../../domain/repositories/plugin_repository.dart'; +import '../widgets/upload_btn/upload_btn.dart'; +import 'dashboard_body.dart'; + +class DashboardPage extends StatelessWidget { + const DashboardPage({super.key, required this.pluginRepository}); + + final PluginRepository pluginRepository; + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (BuildContext context) => PluginSearchBloc(pluginRepository: pluginRepository), + child: PageLayout( + body: const DashboardBody(), + floatingActionButton: BlocBuilder( + builder: (BuildContext context, AuthState state) { + if (state is AuthenticateSuccess) + return const UploadButton(); + else + return const SizedBox(); + }, + ), + ), + ); + } +} diff --git a/lib/src/plugins/presentation/widgets/dropdown_sort_options.dart b/lib/src/plugins/presentation/widgets/dropdown_sort_options.dart new file mode 100644 index 0000000..9135ca7 --- /dev/null +++ b/lib/src/plugins/presentation/widgets/dropdown_sort_options.dart @@ -0,0 +1,58 @@ +import 'package:flutter/material.dart'; + +class DropDownSortOption extends StatefulWidget { + const DropDownSortOption({super.key, required this.sort}); + + final Function(bool, String) sort; + + @override + State createState() => _DropDownSortOptionState(); +} + +class _DropDownSortOptionState extends State { + bool _ascending = false; + String? _selectedOption; + + List> options = const [ + DropdownMenuItem(value: 'none', child: Text('Sort by', style: TextStyle(color: Colors.white),),), + DropdownMenuItem(value: 'rating', child: Text('Rating', style: TextStyle(color: Colors.white),),), + DropdownMenuItem(value: 'name', child: Text('Name', style: TextStyle(color: Colors.white),),), + DropdownMenuItem(value: 'latest', child: Text('Latest', style: TextStyle(color: Colors.white),),), + DropdownMenuItem(value: 'download count', child: Text('Download count', style: TextStyle(color: Colors.white),),), + ]; + + @override + void initState() { + super.initState(); + _selectedOption = options[0].value; + } + + @override + Widget build(BuildContext context) { + return DropdownButton( + value: _selectedOption, + icon: const Icon(Icons.arrow_downward), + elevation: 16, + style: const TextStyle(color:Colors.black54), + iconEnabledColor: Colors.green, + underline: Container( + height: 1, + color: Colors.green[800], + ), + onChanged: (String? value) { + setState(() { + if (_selectedOption == value){ + _ascending = !_ascending; + } + else{ + _ascending = false; + _selectedOption = value!; //reset state if a different sort category is used + } + widget.sort(_ascending, _selectedOption ?? 'none'); + }); + }, + items: options, + + ); + } +} \ No newline at end of file diff --git a/lib/src/plugins/presentation/widgets/files_listing.dart b/lib/src/plugins/presentation/widgets/files_listing.dart new file mode 100644 index 0000000..36d5788 --- /dev/null +++ b/lib/src/plugins/presentation/widgets/files_listing.dart @@ -0,0 +1,145 @@ +import 'package:appflowy_theme_marketplace/src/plugins/domain/models/plugin.dart'; +import 'package:appflowy_theme_marketplace/src/widgets/ui_utils.dart'; +import 'package:flutter/material.dart'; +import 'package:skeletons/skeletons.dart'; +import './item_card/item_card.dart'; + +class FilesListing extends StatefulWidget { + const FilesListing({super.key, required this.fetchFunction}); + + final Function fetchFunction; + + @override + State createState() => _FilesListingState(); +} + +class _FilesListingState extends State { + int currentPage = 0; + + final PageController _pageController = PageController( + initialPage: 0, + keepPage: true, + ); + + Future> fetchFiles() async { + debugPrint('fetching files...'); + final List list = await widget.fetchFunction(); + return list; + } + + @override + void initState() { + super.initState(); + } + + @override + void dispose() { + super.dispose(); + } + + List subWidgetsList(List list, int curIndex, int cardsPerPage) { + int start = curIndex * cardsPerPage; + int end = start + cardsPerPage; + if (end > list.length) { + end = start + (list.length % cardsPerPage); + } + return list.sublist(start, end); + } + + void nextPage() { + setState(() { + currentPage++; + }); + _pageController.animateToPage( + currentPage, + duration: const Duration(milliseconds: 300), + curve: Curves.easeInOut, + ); + } + + void prevPage() { + setState(() { + currentPage--; + }); + _pageController.animateToPage( + currentPage, + duration: const Duration(milliseconds: 300), + curve: Curves.easeInOut, + ); + } + + Row showControlButtons(int cardsPerPage, int numCards) { + return Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + IconButton( + onPressed: (currentPage > 0) ? prevPage : null, + icon: const Icon(Icons.chevron_left_sharp), + splashColor: UiUtils.transparent, + splashRadius: UiUtils.sizeL, + ), + const SizedBox(width: UiUtils.sizeL), + IconButton( + onPressed: (currentPage < (numCards / cardsPerPage).floor()) ? nextPage : null, + icon: const Icon(Icons.chevron_right_sharp), + splashColor: UiUtils.transparent, + splashRadius: UiUtils.sizeL, + ), + ], + ); + } + + @override + Widget build(BuildContext context) { + final double screenSize = (MediaQuery.of(context).size.width - 64); + final double cardSize = UiUtils.calculateCardSize(screenSize); + final cardsPerPage = ScreenSize.from(screenSize).numCards; + return FutureBuilder( + future: fetchFiles(), + builder: (BuildContext context, AsyncSnapshot snapshot) { + if(snapshot.hasData){ + final List filesList = snapshot.data; + final List widgetsList = filesList.asMap().entries.map((entry) { + final int index = entry.key; + final Plugin plugin = entry.value; + return ItemCard(index: index, file: plugin); + }).toList(); + return Column( + children: [ + showControlButtons(cardsPerPage, filesList.length), + SizedBox( + height: cardSize, + child: PageView.builder( + controller: _pageController, + onPageChanged: (int page) { + setState(() { + currentPage = page; + }); + }, + itemCount: (widgetsList.length / cardsPerPage).ceil(), + itemBuilder: (BuildContext context, int index) { + return Row( + children: subWidgetsList(widgetsList, index, cardsPerPage), + ); + }, + ), + ), + ], + ); + } + return Skeleton( + isLoading: !snapshot.hasData, + skeleton: SkeletonAvatar( + style: SkeletonAvatarStyle( + width: double.infinity, + height: cardSize, + padding: const EdgeInsets.all(8), + borderRadius: BorderRadius.circular(8), + ), + ), + child: const SizedBox(), + ); + }, + ); + } +} diff --git a/lib/src/plugins/presentation/widgets/item_card/download_dialog.dart b/lib/src/plugins/presentation/widgets/item_card/download_dialog.dart new file mode 100644 index 0000000..7f94fe4 --- /dev/null +++ b/lib/src/plugins/presentation/widgets/item_card/download_dialog.dart @@ -0,0 +1,163 @@ +import 'package:appflowy_theme_marketplace/main.dart'; +import 'package:appflowy_theme_marketplace/src/plugins/application/factories/plugin_factory.dart'; +import 'package:appflowy_theme_marketplace/src/plugins/application/factories/user_factory.dart'; +import 'package:appflowy_theme_marketplace/src/plugins/domain/models/plugin.dart'; +import 'package:appflowy_theme_marketplace/src/user/application/bloc/orders_bloc/orders_bloc.dart'; +import 'package:appflowy_theme_marketplace/src/user/domain/repositories/orders_repository.dart'; +import 'package:appflowy_theme_marketplace/src/widgets/ui_utils.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:url_launcher/url_launcher.dart'; +import '../../../../payment/application/payment_bloc/payment_bloc.dart'; +import '../../../application/plugin/plugin_bloc.dart'; +import '../../../domain/models/order.dart'; +import '../../../domain/models/user.dart'; + +class DownloadDialog extends StatefulWidget { + const DownloadDialog({super.key, required this.plugin, required this.user}); + + final Plugin plugin; + final User user; + + @override + State createState() => _DownloadDialogState(); +} + +class _DownloadDialogState extends State { + List ordersList = []; + bool _isLoading = true; + + Future getUserOrders() async { + _isLoading = true; + await UiUtils.delayLoading(500); + final List list = await getIt.get().getAll(widget.user.uid); + setState(() { + ordersList = list.map((order) { + return Order.fromJson(order.toJson()); + }).toList(); + _isLoading = false; + }); + } + + bool canDownload(List orders) { + bool isFree = widget.plugin.price == 0; + bool isUploader = widget.plugin.uploader.uid == widget.user.uid; + bool isPurchased = orders.any((order) => order.productId == widget.plugin.pluginId); + return isFree || isUploader || isPurchased; + } + + @override + void initState() { + super.initState(); + getUserOrders(); + } + + Future downloadPlugin() async { + final product = PluginFactory.toPayment(widget.plugin); + final String downloadUrl = widget.plugin.downloadURL; + if (downloadUrl.isNotEmpty) { + context.read().add(IncrementDownloadCountRequested(widget.plugin)); + await launchUrl(Uri.parse(downloadUrl)); + } + else { + context.read().add(GetDownloadUrlRequested(widget.user.uid, product.id)); + } + } + + Future purchasePlugin(User user, ) async { + final product = PluginFactory.toPayment(widget.plugin); + context.read().add(CheckOutSessionRequested(product, UserFactory.toPayment(user))); + } + + @override + Widget build(BuildContext context) { + bool dowloadable = canDownload(ordersList); + final String buttonText = dowloadable ? 'Download' : 'Purchase'; + + final List actions = [ + TextButton( + style: TextButton.styleFrom( + textStyle: Theme.of(context).textTheme.labelLarge, + ), + child: const Text('Cancel'), + onPressed: () => Navigator.of(context).pop(), + ), + TextButton( + style: TextButton.styleFrom( + textStyle: Theme.of(context).textTheme.labelLarge, + ), + child: const Text('Complete'), + onPressed: () => Navigator.of(context).pop(), + ), + ]; + + if (_isLoading){ + return AlertDialog( + title: Text(buttonText), + content: const TextButton( + onPressed: null, + child: CircularProgressIndicator(), + ), + actions: actions + ); + } + + if (dowloadable){ + return BlocConsumer ( + listener: (BuildContext context, OrdersState state) async { + if (state is OrdersUrlCreated){ + context.read().add(IncrementDownloadCountRequested(widget.plugin)); + await launchUrl(Uri.parse(state.url)); + } + }, + builder: (BuildContext context, OrdersState state) { + if (state is OrdersLoading) { + return AlertDialog( + title: Text(buttonText), + content: const TextButton( + onPressed: null, + child: CircularProgressIndicator(), + ), + ); + } + return AlertDialog( + title: Text(buttonText), + content: ElevatedButton( + onPressed: () async { + await downloadPlugin(); + }, + child: Text(buttonText), + ), + actions: actions + ); + } + ); + } + + else { + return BlocBuilder( + builder: (BuildContext context, PaymentState state) { + if (state is CreatingCheckoutSession) { + return AlertDialog( + title: Text(buttonText), + content: const TextButton( + onPressed: null, + child: CircularProgressIndicator(), + ), + ); + } + return AlertDialog( + title: Text(buttonText), + content: ElevatedButton( + onPressed: () async { + await purchasePlugin(widget.user); + }, + child: Text(buttonText), + ), + actions: actions + ); + } + ); + } + } +} \ No newline at end of file diff --git a/lib/src/plugins/presentation/widgets/item_card/item_card.dart b/lib/src/plugins/presentation/widgets/item_card/item_card.dart new file mode 100644 index 0000000..de04a37 --- /dev/null +++ b/lib/src/plugins/presentation/widgets/item_card/item_card.dart @@ -0,0 +1,69 @@ +import 'package:appflowy_theme_marketplace/src/plugins/presentation/widgets/item_card/item_card_body.dart'; +import 'package:appflowy_theme_marketplace/src/plugins/presentation/widgets/item_card/item_card_detail.dart'; +import 'package:appflowy_theme_marketplace/src/widgets/ui_utils.dart'; +import 'package:flutter/material.dart'; +import '../../../domain/models/plugin.dart'; + +class ItemCard extends StatefulWidget { + const ItemCard({super.key, required this.index, required this.file}); + + final int index; + final Plugin file; + + @override + State createState() => _ItemCardState(); +} + +class _ItemCardState extends State { + bool isHovered = false; + + @override + Widget build(BuildContext context) { + final double screenSize = (MediaQuery.of(context).size.width - UiUtils.sizeXXL * 2); + void showCardDetails() { + showDialog( + context: context, + builder: (BuildContext context) { + return ItemCardDetails(fileContent: widget.file); + }, + ); + } + return MouseRegion( + onEnter: (_) { + setState(() { + isHovered = true; + }); + }, + onExit: (_) { + setState(() { + isHovered = false; + }); + }, + child: GestureDetector( + onTap: showCardDetails, + child: Container( + padding: const EdgeInsets.all(UiUtils.sizeS), + width: UiUtils.calculateCardSize(screenSize), + height: UiUtils.calculateCardSize(screenSize), + child: AnimatedContainer( + padding: const EdgeInsets.fromLTRB(0, 0, 0, UiUtils.sizeS), + duration: const Duration(milliseconds: 200), + decoration: BoxDecoration( + color: Colors.grey[800], + borderRadius: BorderRadius.circular(UiUtils.sizeS), + boxShadow: [ + BoxShadow( + color: isHovered ? Colors.grey[700]! : Colors.black54, + offset: const Offset(UiUtils.sizeXS / 2, UiUtils.sizeXS / 2), + blurRadius: UiUtils.sizeXS / 2, + spreadRadius: UiUtils.sizeXS / 2, + ), + ], + ), + child: ItemCardBody(file: widget.file), + ) + ) + ) + ); + } +} \ No newline at end of file diff --git a/lib/src/plugins/presentation/widgets/item_card/item_card_body.dart b/lib/src/plugins/presentation/widgets/item_card/item_card_body.dart new file mode 100644 index 0000000..ac15980 --- /dev/null +++ b/lib/src/plugins/presentation/widgets/item_card/item_card_body.dart @@ -0,0 +1,165 @@ +import 'package:appflowy_theme_marketplace/src/authentication/application/auth_bloc/auth_bloc.dart'; +import 'package:appflowy_theme_marketplace/src/plugins/application/factories/user_factory.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_rating_bar/flutter_rating_bar.dart'; +import '../../../../widgets/error_dialog.dart'; +import '../../../../widgets/ui_utils.dart'; +import '../../../domain/models/plugin.dart'; +import '../../../domain/models/user.dart'; +import '../rating_form.dart'; +import 'download_dialog.dart'; +import 'price_tag.dart'; + +class ItemCardBody extends StatelessWidget { + const ItemCardBody({ + super.key, + required this.file, + }); + final Plugin file; + + void _showDownloadDialog(BuildContext context, User user) { + showDialog( + context: context, + builder: (BuildContext context) { + return DownloadDialog(plugin: file, user: user); + }, + ); + } + + void _showRequireLoginDialog(BuildContext context) { + showDialog( + context: context, + builder: (BuildContext context) { + return const ErrorDialog(message: 'Please log in to make purchase'); + }, + ); + } + + @override + Widget build(BuildContext context) { + final User uploader = file.uploader; + + final Widget uploaderDetails = Row( + children: [ + const CircleAvatar( + backgroundColor: Colors.black, + radius: UiUtils.sizeS, + child: Icon(Icons.person, size: UiUtils.sizeL), + ), + const SizedBox(width: UiUtils.sizeS), + Text( + (uploader.name == '' || uploader.name == null) ? 'Unknown' : uploader.name!, + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ], + ); + + final Widget ratingDetails = Row( + children: [ + RatingBar.builder( + initialRating: file.rating, + minRating: 1, + direction: Axis.horizontal, + ignoreGestures: true, + allowHalfRating: true, + itemCount: 5, + itemSize: UiUtils.sizeM, + itemPadding: const EdgeInsets.symmetric( + horizontal: 0.0, + vertical: 0.0, + ), + itemBuilder: (context, _) => const Icon( + Icons.star, + color: Colors.amber, + ), + onRatingUpdate: (r) {}, + ), + Text( + '(${file.ratingCount})', + style: const TextStyle( + decoration: TextDecoration.underline, + color: UiUtils.blue, + fontSize: UiUtils.sizeL, + ), + ), + ], + ); + + final downloadButton = ElevatedButton( + style: ElevatedButton.styleFrom( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(UiUtils.sizeL), + ), + ), + child: const Icon( + Icons.download, + color: Colors.white, + size: UiUtils.sizeXL, + ), + onPressed: () { + AuthState userStatus = context.read().state; + if (userStatus is AuthenticateSuccess){ + final user = userStatus.user; + if (user == null) + throw Exception('Could not find user'); + final pluginUser = UserFactory.fromAuth(user); + _showDownloadDialog(context, pluginUser); + } + else { + _showRequireLoginDialog(context); + } + } + ); + + final Widget featuresBar = Row( + children: [ + GestureDetector( + onTap: () { + showDialog( + context: context, + builder: (BuildContext context) { + return RatingForm(file, file.rating); + }, + ); + }, + child: ratingDetails, + ), + const SizedBox(width: UiUtils.sizeS), + const Spacer(), + SizedBox( + height: UiUtils.sizeXL, + child: downloadButton, + ), + ], + ); + + return Stack( + children: [ + PriceTag(price: file.price), + Padding( + padding: const EdgeInsets.all(UiUtils.sizeS), + child: Column( + children: [ + const Expanded( + child: Icon( + Icons.folder, + size: UiUtils.sizeXXL * 2, + ), + ), + SizedBox( + width: double.infinity, + child: Text(file.name), + ), + const SizedBox(height: UiUtils.sizeS), + uploaderDetails, + const SizedBox(height: UiUtils.sizeS), + featuresBar, + ], + ), + ), + ], + ); + } +} \ No newline at end of file diff --git a/lib/src/plugins/presentation/widgets/item_card/item_card_detail.dart b/lib/src/plugins/presentation/widgets/item_card/item_card_detail.dart new file mode 100644 index 0000000..7798303 --- /dev/null +++ b/lib/src/plugins/presentation/widgets/item_card/item_card_detail.dart @@ -0,0 +1,84 @@ +import 'package:appflowy_theme_marketplace/src/widgets/popup_dialog.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_rating_bar/flutter_rating_bar.dart'; + +import '../../../../widgets/ui_utils.dart'; +import '../../../domain/models/plugin.dart'; + +class ItemCardDetails extends StatefulWidget { + const ItemCardDetails({super.key, required this.fileContent}); + final Plugin fileContent; + + @override + State createState() => _ItemCardDetailsState(); +} + +class _ItemCardDetailsState extends State { + @override + Widget build(BuildContext context) { + List fileContent = widget.fileContent.toJson().entries.map((entry) { + if (entry.key == 'rating') { + return ListTile( + title: Text(entry.key), + subtitle: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const SizedBox(height: UiUtils.sizeM), + RatingBar.builder( + initialRating: widget.fileContent.rating, + direction: Axis.horizontal, + ignoreGestures: true, + allowHalfRating: true, + itemCount: 5, + itemSize: UiUtils.sizeXL, + itemPadding: const EdgeInsets.symmetric( + horizontal: 0.0, + vertical: 0.0, + ), + itemBuilder: (context, _) => const Icon( + Icons.star, + color: Colors.amber, + ), + onRatingUpdate: (_) {}, + ), + ], + ), + ); + } else { + return ListTile( + title: Text(entry.key), + subtitle: Text(entry.value.toString()), + ); + } + }).toList(); + + return PopupDialog( + title: Row( + children: [ + const Text('File details'), + const Spacer(), + IconButton( + splashColor: UiUtils.transparent, + splashRadius: UiUtils.sizeL, + icon: const Icon(Icons.close), + onPressed: () => Navigator.of(context).pop(), + ), + ], + ), + content: Container( + width: MediaQuery.of(context).size.width * (1 / 3), + height: MediaQuery.of(context).size.height * (2 / 3), + color: Colors.grey[800], + child: ListView.builder( + scrollDirection: Axis.vertical, + controller: null, + itemCount: fileContent.length, + itemBuilder: (BuildContext context, int index) { + return fileContent[index]; + }, + ), + ), + actions: const [], + ); + } +} diff --git a/lib/src/plugins/presentation/widgets/item_card/item_listile.dart b/lib/src/plugins/presentation/widgets/item_card/item_listile.dart new file mode 100644 index 0000000..75459bc --- /dev/null +++ b/lib/src/plugins/presentation/widgets/item_card/item_listile.dart @@ -0,0 +1,71 @@ +import 'package:appflowy_theme_marketplace/src/plugins/presentation/widgets/item_card/item_card_detail.dart'; +import 'package:appflowy_theme_marketplace/src/widgets/star_rating_bar.dart'; +import 'package:appflowy_theme_marketplace/src/widgets/ui_utils.dart'; +import 'package:flutter/material.dart'; +import '../../../domain/models/plugin.dart'; + +class ItemListile extends StatefulWidget { + const ItemListile({ + super.key, + required this.file, + required this.index, + }); + + final Plugin file; + final int index; + + @override + State createState() => _ItemListileState(); +} + +class _ItemListileState extends State { + + @override + Widget build(BuildContext context) { + void showCardDetails(Plugin plugin) { + showDialog( + context: context, + builder: (BuildContext context) { + return ItemCardDetails(fileContent: plugin); + }, + ); + } + return ListTile( + enabled: true, + dense: false, + horizontalTitleGap: 0.0, + selectedColor: UiUtils.blue, + selectedTileColor: Colors.grey[800], + splashColor: UiUtils.transparent, + contentPadding: const EdgeInsets.symmetric( + horizontal: UiUtils.sizeL, + ), + leading: const SizedBox( + height: double.infinity, + child: Icon(Icons.file_copy), + ), + title: Text(widget.file.name), + subtitle: Text(widget.file.uploader.email ?? widget.file.uploader.uid), + trailing: SizedBox( + width: 200, + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + StarRatingBar(rating: widget.file.rating), + Text( + '(${widget.file.ratingCount})', + style: const TextStyle( + decoration: TextDecoration.underline, + color: UiUtils.blue, + fontSize: UiUtils.sizeL, + ), + ), + ], + ), + ), + onTap: () { + showCardDetails(widget.file); + }, + ); + } +} \ No newline at end of file diff --git a/lib/src/plugins/presentation/widgets/item_card/price_tag.dart b/lib/src/plugins/presentation/widgets/item_card/price_tag.dart new file mode 100644 index 0000000..1fdc40c --- /dev/null +++ b/lib/src/plugins/presentation/widgets/item_card/price_tag.dart @@ -0,0 +1,30 @@ +import 'package:flutter/material.dart'; +import '../../../../widgets/ui_utils.dart'; + +class PriceTag extends StatelessWidget { + const PriceTag({super.key, required this.price}); + final double price; + @override + Widget build(BuildContext context) { + return Positioned( + top: UiUtils.sizeS, + right: 0, + child: Container( + padding: const EdgeInsets.symmetric(horizontal: UiUtils.sizeM, vertical: UiUtils.sizeXS), + decoration: const BoxDecoration( + color: UiUtils.blue, + borderRadius: BorderRadius.only( + topLeft: Radius.circular(UiUtils.sizeL), + bottomLeft: Radius.circular(UiUtils.sizeL), + topRight: Radius.zero, + bottomRight: Radius.zero, + ), + ), + child: Text( + price == 0 ? 'Free' : '$price\$', + style: const TextStyle(color: Colors.white, fontSize: UiUtils.sizeL), + ), + ), + ); + } +} \ No newline at end of file diff --git a/lib/src/plugins/presentation/widgets/list_category_text.dart b/lib/src/plugins/presentation/widgets/list_category_text.dart new file mode 100644 index 0000000..07b99ef --- /dev/null +++ b/lib/src/plugins/presentation/widgets/list_category_text.dart @@ -0,0 +1,22 @@ + +import 'package:flutter/material.dart'; + +class ListCategoryText extends StatelessWidget { + const ListCategoryText(this.text, {super.key}); + final String text; + + @override + Widget build(BuildContext context) { + return Align( + alignment: Alignment.topLeft, + child: Text( + text, + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.w300, + color: Colors.grey, + ), + ), + ); + } +} diff --git a/lib/src/plugins/presentation/widgets/rating_form.dart b/lib/src/plugins/presentation/widgets/rating_form.dart new file mode 100644 index 0000000..b669fca --- /dev/null +++ b/lib/src/plugins/presentation/widgets/rating_form.dart @@ -0,0 +1,180 @@ +import 'package:appflowy_theme_marketplace/src/authentication/application/auth_bloc/auth_bloc.dart'; +import 'package:appflowy_theme_marketplace/src/plugins/application/factories/user_factory.dart'; +import 'package:appflowy_theme_marketplace/src/plugins/application/plugin/plugin_bloc.dart'; +import 'package:appflowy_theme_marketplace/src/plugins/domain/models/user.dart'; +import 'package:appflowy_theme_marketplace/src/widgets/ui_utils.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_rating_bar/flutter_rating_bar.dart'; + +import '../../../widgets/error_dialog.dart'; +import '../../../widgets/popup_dialog.dart'; +import '../../../widgets/snackbar_status.dart'; +import '../../domain/models/plugin.dart'; +import '../../domain/models/rating.dart'; + +class RatingForm extends StatefulWidget { + const RatingForm(this.plugin, this.rating, {super.key}); + final Plugin plugin; + final double rating; + + @override + State createState() => _RatingFormState(); +} + +class _RatingFormState extends State { + late double rating; + late String review; + + @override + void initState() { + super.initState(); + rating = widget.rating; + review = ''; + } + + @override + Widget build(BuildContext context) { + final userStatus = context.read().state; + final dialogWidth = MediaQuery.of(context).size.width / 2; + final dialogHeight = MediaQuery.of(context).size.height / 3; + const lineHeight = UiUtils.sizeXL; + final maxLines = (dialogHeight / lineHeight).floor(); + + if (userStatus is AuthenticateSuccess) { + if (userStatus.user == null) + throw Exception('user is undefined'); + final User reviewer = UserFactory.fromAuth(userStatus.user!); + return PopupDialog( + title: const Text('Rating'), + content: SizedBox( + width: dialogWidth, + height: dialogHeight, + child: BlocConsumer( + builder: (BuildContext context, PluginState state) { + if (state is PluginLoading) { + return const Center( + child: CircularProgressIndicator(), + ); + } else { + return Column( + children: [ + Row( + children: [ + const Text( + 'Review', + textAlign: TextAlign.start, + ), + RatingBar.builder( + initialRating: rating, + minRating: 1, + direction: Axis.horizontal, + allowHalfRating: true, + itemCount: 5, + itemSize: UiUtils.sizeL, + itemPadding: const EdgeInsets.symmetric( + horizontal: 0.0, + vertical: 0.0, + ), + itemBuilder: (context, _) => const Icon( + Icons.star, + color: Colors.amber, + ), + onRatingUpdate: (r) async { + setState(() { + rating = r; + }); + }, + ), + ], + ), + TextField( + maxLines: maxLines, + keyboardType: TextInputType.multiline, + style: const TextStyle(fontSize: UiUtils.sizeL), + decoration: const InputDecoration( + contentPadding: EdgeInsets.symmetric(vertical: UiUtils.sizeL, horizontal: UiUtils.sizeL), + enabledBorder: OutlineInputBorder( + borderSide: BorderSide( + color: Colors.grey, + width: 1.0, + ), + borderRadius: BorderRadius.only( + topLeft: Radius.circular(5.0), + bottomLeft: Radius.circular(5.0), + topRight: Radius.zero, + bottomRight: Radius.zero, + ), + ), + focusedBorder: OutlineInputBorder( + borderSide: BorderSide( + color: UiUtils.blue, + width: 1.0, + ), + ), + filled: true, + fillColor: Colors.black12), + onChanged: (value) { + review = value; + }, + ) + ], + ); + } + }, + listener: (BuildContext context, PluginState state) { + if (state is PluginUpdated) { + Navigator.pop(context); + final errorSnackbar = SnackbarSuccess(message: 'Upload Succeed'); + ScaffoldMessenger.of(context).showSnackBar(errorSnackbar); + } else if (state is PluginFailed) { + context.read().add(ResetStateRequested()); + Navigator.pop(context); + final errorSnackbar = SnackbarError(message: state.message); + ScaffoldMessenger.of(context).showSnackBar(errorSnackbar); + } + }, + ), + ), + actions: [ + TextButton( + style: TextButton.styleFrom( + textStyle: Theme.of(context).textTheme.labelLarge, + ), + child: const Text('Cancel'), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + TextButton( + style: TextButton.styleFrom( + textStyle: Theme.of(context).textTheme.labelLarge, + ), + child: const Text('Submit'), + onPressed: () async { + try { + final Rating rate = Rating( + date: DateTime.now(), + rating: rating, + review: review, + reviewer: reviewer, + pluginId: widget.plugin.pluginId, + pluginName: widget.plugin.name, + ); + context.read().add(AddRatingDataRequested(rate)); + } on Exception catch (e) { + return showDialog( + context: context, + builder: (BuildContext context) { + return ErrorDialog(message: e.toString()); + }, + ); + } + }, + ), + ], + ); + } + return const ErrorDialog(message: 'You must log in to add rating.'); + } +} diff --git a/lib/src/plugins/presentation/widgets/search_input.dart b/lib/src/plugins/presentation/widgets/search_input.dart new file mode 100644 index 0000000..92d6b27 --- /dev/null +++ b/lib/src/plugins/presentation/widgets/search_input.dart @@ -0,0 +1,97 @@ +import 'dart:async'; +import 'package:appflowy_theme_marketplace/src/plugins/application/search/plugin_search_bloc.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import '../../../widgets/ui_utils.dart'; + + +class SearchInput extends StatefulWidget { + const SearchInput({super.key}); + + @override + State createState() => _SearchInputState(); +} + +class _SearchInputState extends State { + String? _searchTerm; + Timer? _debounceTimer; + + void _onSearchTermChanged(String value) { + if (_debounceTimer != null && _debounceTimer!.isActive) { + _debounceTimer!.cancel(); + } + + _debounceTimer = Timer(const Duration(milliseconds: 300), () { + context.read().add(PluginSearchRequested(value)); + }); + } + + @override + Widget build(BuildContext context) { + return SizedBox( + width: MediaQuery.of(context).size.width * 1/2, + child: Row( + children: [ + Expanded( + child: TextField( + style: const TextStyle(fontSize: 14), + decoration: const InputDecoration( + hintText: 'Search for plugin', + contentPadding: EdgeInsets.symmetric(vertical: 0.0, horizontal: 16.0), + enabledBorder: OutlineInputBorder( + borderSide: BorderSide( + color: UiUtils.transparent, + width: 1.0, + ), + borderRadius: BorderRadius.only( + topLeft: Radius.circular(5.0), + bottomLeft: Radius.circular(5.0), + topRight: Radius.zero, + bottomRight: Radius.zero, + ), + ), + focusedBorder: OutlineInputBorder( + borderSide: BorderSide( + color: Colors.blue, + width: 1.0, + ), + borderRadius: BorderRadius.only( + topLeft: Radius.circular(5.0), + bottomLeft: Radius.circular(5.0), + topRight: Radius.zero, + bottomRight: Radius.zero, + ), + ), + filled: true, + fillColor: Colors.black12 + ), + onChanged: (value) { + _onSearchTermChanged(value); + }, + ), + ), + ElevatedButton( + style: ButtonStyle( + padding: MaterialStateProperty.all( + const EdgeInsets.symmetric(horizontal: 0, vertical: 20), + ), + shape: MaterialStateProperty.all( + const RoundedRectangleBorder( + borderRadius: BorderRadius.only( + topLeft: Radius.zero, + bottomLeft: Radius.zero, + topRight: Radius.circular(5.0), + bottomRight: Radius.circular(5.0), + ), + ), + ), + ), + onPressed: () {}, + child: const Icon(Icons.search), + ), + ], + ), + ); + } +} diff --git a/lib/src/plugins/presentation/widgets/upload_btn/upload_btn.dart b/lib/src/plugins/presentation/widgets/upload_btn/upload_btn.dart new file mode 100644 index 0000000..e1a1377 --- /dev/null +++ b/lib/src/plugins/presentation/widgets/upload_btn/upload_btn.dart @@ -0,0 +1,27 @@ +import 'package:flutter/material.dart'; +import '../../../../widgets/ui_utils.dart'; +import './upload_btn_modal.dart'; + +class UploadButton extends StatefulWidget { + const UploadButton({super.key}); + + @override + State createState() => _UploadButtonState(); +} + +class _UploadButtonState extends State { + @override + void dispose() { + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return FloatingActionButton( + splashColor: UiUtils.transparent, + onPressed: () => + showDialog(context: context, builder: (BuildContext context) => const UploadButtonModal()), + child: const Icon(Icons.upload), + ); + } +} diff --git a/lib/src/plugins/presentation/widgets/upload_btn/upload_btn_modal.dart b/lib/src/plugins/presentation/widgets/upload_btn/upload_btn_modal.dart new file mode 100644 index 0000000..55c4540 --- /dev/null +++ b/lib/src/plugins/presentation/widgets/upload_btn/upload_btn_modal.dart @@ -0,0 +1,175 @@ +import 'package:appflowy_theme_marketplace/src/plugins/application/plugin/plugin_bloc.dart'; +import 'package:appflowy_theme_marketplace/src/widgets/ui_utils.dart'; +import 'package:appflowy_theme_marketplace/src/widgets/error_dialog.dart'; +import 'package:file_picker/file_picker.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import '../../../../authentication/application/auth_bloc/auth_bloc.dart'; +import '../../../../widgets/snackbar_status.dart'; +import '../../../application/factories/user_factory.dart'; +import '../../../domain/models/picked_file.dart'; +import '../../../domain/models/user.dart'; + +class UploadButtonModal extends StatefulWidget { + const UploadButtonModal({super.key}); + + @override + State createState() => _UploadButtonModalState(); +} + +class _UploadButtonModalState extends State { + PickedFile? plugin; + double? price; + + @override + void dispose() { + super.dispose(); + } + + Future _pickFile() async { + final result = await FilePicker.platform.pickFiles(); + final bytes = result?.files.first.bytes; + if (bytes == null) throw Exception('No file selected'); + return PickedFile(bytes, result!.files.first.name); + } + + @override + Widget build(BuildContext context) { + final TextField priceInput = TextField( + keyboardType: TextInputType.number, + textInputAction: TextInputAction.next, + style: const TextStyle(fontSize: UiUtils.sizeM), + decoration: const InputDecoration( + labelText: 'price', + labelStyle: TextStyle(fontSize: UiUtils.sizeM), + suffixIcon: Icon(Icons.attach_money, size: UiUtils.sizeXL), + ), + onChanged: (value) => setState(() { + if (value == '') + price = null; + else { + try { + price = double.parse(value); + } on Exception catch (_) { + showDialog( + context: context, + builder: (BuildContext context) { + return const ErrorDialog(message: 'Invalid price value'); + }); + price = null; + } + } + }), + ); + + final OutlinedButton uploadButtonArea = OutlinedButton( + onPressed: () async { + try { + final pickedFile = await _pickFile(); + setState(() { + plugin = pickedFile; + }); + } on Exception catch(e) { + debugPrint(e.toString()); + } + }, + style: ButtonStyle( + foregroundColor: MaterialStateProperty.all(Colors.white), + ), + child: SizedBox( + width: double.infinity, + height: 200, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Icon( + Icons.folder, + size: UiUtils.sizeXL * 2, + ), + const SizedBox(height: UiUtils.sizeL), + Text(plugin != null ? plugin!.name : 'Select a zip file to upload'), + ], + ), + ), + ); + + final Row uploadedFileInfo = Row( + children: [ + Expanded( + child: TextField( + enabled: false, // Disable input + controller: TextEditingController(text: plugin == null ? '' : plugin?.name), + decoration: const InputDecoration( + border: UnderlineInputBorder(), + ), + ), + ), + const SizedBox(width: UiUtils.sizeL), + SizedBox( + width: 100, + child: priceInput, + ), + ], + ); + + final AuthState userState = context.read().state; + late final User? user; + if (userState is AuthenticateSuccess){ + if (userState.user == null) + throw Exception('user is undefined'); + user = UserFactory.fromAuth(userState.user!); + } + return AlertDialog( + title: const Text('Upload file'), + content: SizedBox( + width: 400, + height: 300, + child: BlocConsumer( + builder: (BuildContext context, PluginState state) { + if (state is PluginLoading) { + return const Center( + child: CircularProgressIndicator(), + ); + } + else { + return Column( + children: [ + uploadButtonArea, + uploadedFileInfo, + ], + ); + } + }, + listener: (BuildContext context, PluginState state) { + if (state is PluginUpdated) { + Navigator.pop(context); + final errorSnackbar = SnackbarSuccess(message: 'Upload Succeed'); + ScaffoldMessenger.of(context).showSnackBar(errorSnackbar); + } else if (state is PluginFailed) { + context.read().add(ResetStateRequested()); + Navigator.pop(context); + final errorSnackbar = SnackbarError(message: state.message); + ScaffoldMessenger.of(context).showSnackBar(errorSnackbar); + } + }, + ), + ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: const Text('Cancel'), + ), + TextButton( + onPressed: (plugin != null && user != null && price != null) + ? () { + if (user == null) return; + context.read().add(UploadDataRequested(user, plugin!, price!)); + } + : null, + child: const Text('Upload'), + ), + ], + ); + } +} diff --git a/lib/src/plugins/presentation/widgets/user_dropdown/popup_text.dart b/lib/src/plugins/presentation/widgets/user_dropdown/popup_text.dart new file mode 100644 index 0000000..3f2fc04 --- /dev/null +++ b/lib/src/plugins/presentation/widgets/user_dropdown/popup_text.dart @@ -0,0 +1,20 @@ + +import 'package:appflowy_theme_marketplace/src/widgets/ui_utils.dart'; +import 'package:flutter/material.dart'; + +class PopupText extends StatelessWidget { + const PopupText({super.key, required this.text}); + + final String text; + + @override + Widget build(BuildContext context) { + return Text( + text, + style: const TextStyle( + color: Colors.white, + fontSize: UiUtils.sizeM*1.1, + ), + ); + } +} \ No newline at end of file diff --git a/lib/src/plugins/presentation/widgets/user_dropdown/user_dropdown.dart b/lib/src/plugins/presentation/widgets/user_dropdown/user_dropdown.dart new file mode 100644 index 0000000..9f522d3 --- /dev/null +++ b/lib/src/plugins/presentation/widgets/user_dropdown/user_dropdown.dart @@ -0,0 +1,132 @@ +import 'package:appflowy_theme_marketplace/src/authentication/application/auth_bloc/auth_bloc.dart'; +import 'package:appflowy_theme_marketplace/src/plugins/presentation/widgets/user_dropdown/popup_text.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import '../../../../widgets/ui_utils.dart'; +import '../../../domain/models/user.dart'; + +class UserDropDown extends StatefulWidget { + const UserDropDown({super.key, required this.user}); + + final User user; + + @override + State createState() => _UserDropDownState(); +} + +class _UserDropDownState extends State { + String? username; + + @override + void initState() { + super.initState(); + } + + String truncateText(String value, int maxLength) { + if (value.length > maxLength) { + value = '${value.substring(0, maxLength - 3)}...'; + } + return value; + } + + @override + Widget build(BuildContext context) { + final String userEmail = widget.user.email ?? UiUtils.defaultEmail; + final username = widget.user.name ?? UiUtils.defaultUsername(userEmail); + final maxLength = widget.user.uid.length; + final userInfo = SelectionArea( + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 0, vertical: UiUtils.sizeM), + width: double.infinity, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + truncateText(userEmail, maxLength), + style: const TextStyle( + color: Colors.white, + fontSize: UiUtils.sizeM, + ), + ), + const SizedBox(height: UiUtils.sizeXS), + Text( + truncateText(username, maxLength), + style: const TextStyle( + color: Colors.white, + fontSize: UiUtils.sizeM, + ), + ), + const SizedBox(height: UiUtils.sizeXS), + Text( + widget.user.uid, + style: const TextStyle( + color: Colors.white, + fontSize: UiUtils.sizeM, + ), + ), + Divider( + height: UiUtils.sizeXXL, + color: Colors.grey[400], + ), + ], + ), + ), + ); + + final PopupMenuItem userHeader = PopupMenuItem( + value: 'user', + enabled: false, + child: userInfo, + ); + + List> options = [ + userHeader, + const PopupMenuItem( + value: '/user/plugins', + child: PopupText(text: 'Manage Plugin'), + ), + const PopupMenuItem( + value: '/ratings', + child: PopupText(text: 'Ratings'), + ), + const PopupMenuItem( + value: '/orders', + child: PopupText(text: 'Orders'), + ), + const PopupMenuItem( + value: 'signout', + child: PopupText(text: 'Signout'), + ), + ]; + return PopupMenuButton( + itemBuilder: (context) => options, + tooltip: 'Show User Menu', + splashRadius: UiUtils.sizeXL, + padding: const EdgeInsets.all(0), + onSelected: (value) { + final curRoute = ModalRoute.of(context)?.settings.name; + if (value != curRoute) { + switch (value) { + case '/ratings': + Navigator.pushNamed(context, value); + break; + case '/orders': + Navigator.pushNamed(context, value); + break; + case '/user/plugins': + Navigator.pushNamed(context, value); + break; + case 'signout': + BlocProvider.of(context).add(SignOutRequested()); + Navigator.pushNamed(context, '/'); + break; + } + } + }, + icon: Icon( + Icons.account_circle_sharp, + color: Colors.grey[300], + ), + ); + } +} diff --git a/lib/src/serverless_api/supabase_api.dart b/lib/src/serverless_api/supabase_api.dart new file mode 100644 index 0000000..63bde1c --- /dev/null +++ b/lib/src/serverless_api/supabase_api.dart @@ -0,0 +1,9 @@ +class SupabaseApi { + static const redirect_url = 'https://appflowy-theme-marketpla-e8f9b.firebaseapp.com/#/'; + static const supabaseUrl = 'https://zrcomcrmmuwrclfwvknj.supabase.co'; + static const functionUrl = '$supabaseUrl/functions/v1'; + static const createStripeAccount = '$functionUrl/create-stripe-account'; + static const createStripeAccountLink = '$functionUrl/create-stripe-account-link'; + static const stripeCheckoutSession = '$functionUrl/stripe-checkout-session'; + static const getStripeAccountInfo = '$functionUrl/get-account-info'; +} \ No newline at end of file diff --git a/lib/src/user/application/bloc/orders_bloc/orders_bloc.dart b/lib/src/user/application/bloc/orders_bloc/orders_bloc.dart new file mode 100644 index 0000000..4b52735 --- /dev/null +++ b/lib/src/user/application/bloc/orders_bloc/orders_bloc.dart @@ -0,0 +1,51 @@ +import 'package:appflowy_theme_marketplace/src/widgets/ui_utils.dart'; +import 'package:bloc/bloc.dart'; +import 'package:equatable/equatable.dart'; +import 'package:flutter/material.dart'; + +import '../../../domain/models/order.dart'; +import '../../../domain/repositories/orders_repository.dart'; + +part 'orders_event.dart'; +part 'orders_state.dart'; + +class OrdersBloc extends Bloc { + final OrdersRepository ordersRepository; + OrdersBloc({ + required this.ordersRepository, + }) : super(OrdersInitial()) { + on((event, emit) async { + emit(OrdersLoading()); + await UiUtils.delayLoading(); + try { + List orders = await ordersRepository.getAll(event.uid); + emit(OrdersLoaded(orders)); + } on Exception catch (e) { + debugPrint('failed $e'); + emit(OrdersLoadFailed(message: e.toString())); + } + }); + on((event, emit) async { + emit(OrdersLoading()); + await UiUtils.delayLoading(); + try { + List orders = await ordersRepository.searchOrder(event.uid, event.productId); + emit(OrdersLoaded(orders)); + } on Exception catch (e) { + debugPrint('failed $e'); + emit(OrdersLoadFailed(message: e.toString())); + } + }); + on((event, emit) async { + emit(OrdersLoading()); + await UiUtils.delayLoading(); + try { + final url = await ordersRepository.getDownloadUrl(event.uid, event.productId); + emit(OrdersUrlCreated(url)); + } on Exception catch (e) { + debugPrint('operation failed, message: $e'); + emit(OrdersLoadFailed(message: e.toString())); + } + }); + } +} diff --git a/lib/src/user/application/bloc/orders_bloc/orders_event.dart b/lib/src/user/application/bloc/orders_bloc/orders_event.dart new file mode 100644 index 0000000..2de5fc3 --- /dev/null +++ b/lib/src/user/application/bloc/orders_bloc/orders_event.dart @@ -0,0 +1,25 @@ +part of 'orders_bloc.dart'; + +abstract class OrdersEvent { + const OrdersEvent(); +} + +class GetAllOrdersRequested extends OrdersEvent { + const GetAllOrdersRequested(this.uid); + + final String uid; +} + +class FindOrderRequested extends OrdersEvent { + const FindOrderRequested(this.uid, this.productId); + + final String uid; + final String productId; +} + +class GetDownloadUrlRequested extends OrdersEvent { + const GetDownloadUrlRequested(this.uid, this.productId); + + final String uid; + final String productId; +} \ No newline at end of file diff --git a/lib/src/user/application/bloc/orders_bloc/orders_state.dart b/lib/src/user/application/bloc/orders_bloc/orders_state.dart new file mode 100644 index 0000000..d08e9eb --- /dev/null +++ b/lib/src/user/application/bloc/orders_bloc/orders_state.dart @@ -0,0 +1,45 @@ +part of 'orders_bloc.dart'; + +abstract class OrdersState extends Equatable { + const OrdersState(); + + @override + List get props => []; +} + +class OrdersInitial extends OrdersState { + @override + List get props => []; +} + +class OrdersLoading extends OrdersState { + @override + List get props => []; +} + +class OrdersLoaded extends OrdersState { + const OrdersLoaded(this.orders); + + final List orders; + + @override + List get props => []; +} + +class OrdersUrlCreated extends OrdersState { + const OrdersUrlCreated(this.url); + + final String url; + + @override + List get props => []; +} + +class OrdersLoadFailed extends OrdersState { + const OrdersLoadFailed({required this.message}); + + final String message; + @override + List get props => []; +} + diff --git a/lib/src/user/application/bloc/ratings_bloc/ratings_bloc.dart b/lib/src/user/application/bloc/ratings_bloc/ratings_bloc.dart new file mode 100644 index 0000000..c884251 --- /dev/null +++ b/lib/src/user/application/bloc/ratings_bloc/ratings_bloc.dart @@ -0,0 +1,33 @@ +import 'package:appflowy_theme_marketplace/src/plugins/domain/repositories/ratings_repository.dart'; +import 'package:bloc/bloc.dart'; +import 'package:flutter/material.dart'; + +import '../../../../widgets/ui_utils.dart'; +import '../../../domain/models/rating.dart'; +import '../../factories/rating_factory.dart'; + +part 'ratings_event.dart'; +part 'ratings_state.dart'; + +class RatingsBloc extends Bloc { + final RatingsRepository ratingsRepository; + RatingsBloc({ + required this.ratingsRepository, + }) : super(RatingsInitial()) { + on((event, emit) async { + emit(RatingsLoading()); + await UiUtils.delayLoading(); + try { + List list = await ratingsRepository.getAll(event.uid); + List ratings = []; + ratings = list.map((rating) { + return RatingFactory.fromPlugin(rating); + }).toList(); + emit(RatingsLoaded(ratings)); + } on Exception catch (e) { + debugPrint('failed $e'); + emit(RatingsLoadFailed(message: e.toString())); + } + }); + } +} diff --git a/lib/src/user/application/bloc/ratings_bloc/ratings_event.dart b/lib/src/user/application/bloc/ratings_bloc/ratings_event.dart new file mode 100644 index 0000000..26e1d12 --- /dev/null +++ b/lib/src/user/application/bloc/ratings_bloc/ratings_event.dart @@ -0,0 +1,18 @@ +part of 'ratings_bloc.dart'; + +abstract class RatingsEvent { + const RatingsEvent(); +} + +class GetAllRatingsRequested extends RatingsEvent { + const GetAllRatingsRequested(this.uid); + + final String uid; +} + +class FindRatingRequested extends RatingsEvent { + const FindRatingRequested(this.uid, this.productId); + + final String uid; + final String productId; +} \ No newline at end of file diff --git a/lib/src/user/application/bloc/ratings_bloc/ratings_state.dart b/lib/src/user/application/bloc/ratings_bloc/ratings_state.dart new file mode 100644 index 0000000..564f89a --- /dev/null +++ b/lib/src/user/application/bloc/ratings_bloc/ratings_state.dart @@ -0,0 +1,29 @@ +part of 'ratings_bloc.dart'; + +abstract class RatingsState{ + const RatingsState(); +} + +class RatingsInitial extends RatingsState { + +} + +class RatingsLoading extends RatingsState { + +} + +class RatingsLoaded extends RatingsState { + const RatingsLoaded(this.ratings); + + final List ratings; + + +} + +class RatingsLoadFailed extends RatingsState { + const RatingsLoadFailed({required this.message}); + + final String message; + +} + diff --git a/lib/src/user/application/bloc/storage_bloc/storage_bloc.dart b/lib/src/user/application/bloc/storage_bloc/storage_bloc.dart new file mode 100644 index 0000000..0efcfd0 --- /dev/null +++ b/lib/src/user/application/bloc/storage_bloc/storage_bloc.dart @@ -0,0 +1,65 @@ + +import 'package:appflowy_theme_marketplace/src/user/domain/models/plugin_file_object.dart'; +import 'package:appflowy_theme_marketplace/src/user/domain/repositories/storage_repository.dart'; +import 'package:appflowy_theme_marketplace/src/widgets/ui_utils.dart'; +import 'package:bloc/bloc.dart'; +import 'package:flutter/material.dart'; + +import '../../../domain/models/picked_file.dart'; +import '../../../domain/models/plugin.dart'; +import '../../../domain/models/user.dart'; + +part 'storage_event.dart'; +part 'storage_state.dart'; + +class StorageBloc extends Bloc { + final FileStorageRepository storageRepository; + StorageBloc({ + required this.storageRepository, + }) : super(StorageDefault()) { + on((UploadFileRequested event, Emitter emit) async { + emit(StorageUploading()); + try { + final picked = event.plugin; + final plugin = Plugin.upload( + pickedFile: picked, + name: picked.name, + uploader: event.user, + price: event.price, + ); + await storageRepository.add(plugin); + emit(StorageUploadSuccess()); + } on Exception catch (e) { + debugPrint(e.toString()); + emit(StorageFailed(message: e.toString())); + } + }); + on((GetUploadedFilesRequested event, Emitter emit) async { + emit(StorageSearching()); + try { + List files = await storageRepository.get(event.searchTerm, event.uid, event.bucket); + emit(StorageSearchSuccess(files: files)); + } on Exception catch (e) { + debugPrint(e.toString()); + emit(StorageFailed(message: e.toString())); + } + }); + on((DeleteFileRequested event, Emitter emit) async { + emit(StorageDeletingFile()); + await UiUtils.delayLoading(); + try { + List files = await storageRepository.delete(event.fileName, event.bucket, event.user.uid); + emit(StorageDeleteSuccess(files: files)); + } on Exception catch (e) { + debugPrint(e.toString()); + emit(StorageFailed(message: e.toString())); + } + }); + on((StorageReloadRequested event, Emitter emit) async { + emit(StorageUploading()); + await Future.delayed(const Duration(milliseconds: 100), () {}); + emit(StorageDefault()); + }); + } + +} \ No newline at end of file diff --git a/lib/src/user/application/bloc/storage_bloc/storage_event.dart b/lib/src/user/application/bloc/storage_bloc/storage_event.dart new file mode 100644 index 0000000..8e7b6a4 --- /dev/null +++ b/lib/src/user/application/bloc/storage_bloc/storage_event.dart @@ -0,0 +1,35 @@ +part of 'storage_bloc.dart'; + +abstract class StorageEvent { + const StorageEvent(); + +} + +class UploadFileRequested extends StorageEvent { + const UploadFileRequested({required this.user, required this.plugin, required this.price}); + + final User user; + final PickedFile plugin; + final double price; + +} + +class DeleteFileRequested extends StorageEvent { + const DeleteFileRequested({required this.fileName, required this.user, required this.bucket}); + + final User user; + final String fileName; + final String bucket; +} + +class StorageReloadRequested extends StorageEvent { + StorageReloadRequested(); +} + +class GetUploadedFilesRequested extends StorageEvent { + GetUploadedFilesRequested({this.searchTerm, required this.uid, required this.bucket}); + + final String uid; + final String bucket; + final String? searchTerm; +} diff --git a/lib/src/user/application/bloc/storage_bloc/storage_state.dart b/lib/src/user/application/bloc/storage_bloc/storage_state.dart new file mode 100644 index 0000000..289d19b --- /dev/null +++ b/lib/src/user/application/bloc/storage_bloc/storage_state.dart @@ -0,0 +1,45 @@ +part of 'storage_bloc.dart'; + +abstract class StorageState { + StorageState({List? files}) : files = files ?? []; + final List files; +} + +class StorageDefault extends StorageState { + StorageDefault({super.files}); +} + + +class StorageUploading extends StorageState { + StorageUploading({super.files}); + +} + +class StorageUploadSuccess extends StorageState { + StorageUploadSuccess({super.files}); + +} + + +class StorageSearching extends StorageState { + StorageSearching({super.files}); + +} + +class StorageSearchSuccess extends StorageState { + StorageSearchSuccess({required List files}) : super(files: files); +} + +class StorageDeletingFile extends StorageState { + StorageDeletingFile({super.files}); +} + +class StorageDeleteSuccess extends StorageState { + StorageDeleteSuccess({required List files}) : super(files: files); +} + +class StorageFailed extends StorageState { + StorageFailed({required this.message}); + + final String message; +} diff --git a/lib/src/user/application/bloc/user_bloc/user_bloc.dart b/lib/src/user/application/bloc/user_bloc/user_bloc.dart new file mode 100644 index 0000000..0793961 --- /dev/null +++ b/lib/src/user/application/bloc/user_bloc/user_bloc.dart @@ -0,0 +1,39 @@ +import 'package:appflowy_theme_marketplace/src/user/domain/repositories/user_repository.dart'; +import 'package:bloc/bloc.dart'; +import 'package:equatable/equatable.dart'; +import '../../../domain/models/user.dart'; + +part 'user_event.dart'; +part 'user_state.dart'; + +class UserBloc extends Bloc { + final UserRepository userRepository; + UserBloc({ + required this.userRepository, + }) : super(UserInitial()) { + on((event, emit) async { + emit(UserLoading()); + try { + User user = await userRepository.get(event.uid); + bool onboardCompleted = user.onboardCompleted; + String? stripeId = user.stripeId; + if (stripeId == null || stripeId.isEmpty) { + stripeId = await userRepository.add(user.email); + } + + if (stripeId == null || stripeId.isEmpty) { + throw Exception('Could not get stripe id from user'); + } + + if (!onboardCompleted) { + await userRepository.update(stripeId); + } + + user = await userRepository.get(event.uid); + emit(UserLoaded(user)); + } on Exception catch (e) { + emit(UserLoadFailed(message: e.toString())); + } + }); + } +} diff --git a/lib/src/user/application/bloc/user_bloc/user_event.dart b/lib/src/user/application/bloc/user_bloc/user_event.dart new file mode 100644 index 0000000..a354a86 --- /dev/null +++ b/lib/src/user/application/bloc/user_bloc/user_event.dart @@ -0,0 +1,17 @@ +part of 'user_bloc.dart'; + +abstract class UserEvent extends Equatable { + const UserEvent(); + + @override + List get props => []; +} + +class GetAndUpdateUserDataRequested extends UserEvent { + const GetAndUpdateUserDataRequested(this.uid); + + final String uid; + + @override + List get props => []; +} \ No newline at end of file diff --git a/lib/src/user/application/bloc/user_bloc/user_state.dart b/lib/src/user/application/bloc/user_bloc/user_state.dart new file mode 100644 index 0000000..b592392 --- /dev/null +++ b/lib/src/user/application/bloc/user_bloc/user_state.dart @@ -0,0 +1,36 @@ +part of 'user_bloc.dart'; + +abstract class UserState extends Equatable { + const UserState(); + + @override + List get props => []; +} + +class UserInitial extends UserState { + @override + List get props => []; +} + +class UserLoading extends UserState { + @override + List get props => []; +} + +class UserLoaded extends UserState { + const UserLoaded(this.user); + + final User user; + + @override + List get props => []; +} + +class UserLoadFailed extends UserState { + const UserLoadFailed({required this.message}); + + final String message; + @override + List get props => []; +} + diff --git a/lib/src/user/application/factories/plugin_file_object_factory.dart b/lib/src/user/application/factories/plugin_file_object_factory.dart new file mode 100644 index 0000000..639c251 --- /dev/null +++ b/lib/src/user/application/factories/plugin_file_object_factory.dart @@ -0,0 +1,17 @@ +import 'package:appflowy_theme_marketplace/src/user/domain/models/plugin_file_object.dart'; +import 'package:supabase_flutter/supabase_flutter.dart'; + +class PluginFileObjectFactory { + static PluginFileObject fromFileObject(FileObject obj) { + return PluginFileObject( + name: obj.name, + bucketId: obj.bucketId, + owner: obj.owner, + id: obj.id, + updatedAt: obj.updatedAt, + createdAt: obj.createdAt, + lastAccessedAt: obj.lastAccessedAt, + metadata: obj.metadata, + ); + } +} \ No newline at end of file diff --git a/lib/src/user/application/factories/rating_factory.dart b/lib/src/user/application/factories/rating_factory.dart new file mode 100644 index 0000000..dec08c0 --- /dev/null +++ b/lib/src/user/application/factories/rating_factory.dart @@ -0,0 +1,14 @@ +import '../../../plugins/domain/models/rating.dart' as plugin; +import '../../domain/models/rating.dart'; + +class RatingFactory { + static Rating fromPlugin(plugin.Rating rating) { + return Rating( + date: rating.date, + rating: rating.rating, + reviewer: rating.reviewer, + pluginId: rating.pluginId, + pluginName: rating.pluginName, + ); + } +} \ No newline at end of file diff --git a/lib/src/user/application/factories/user_factory.dart b/lib/src/user/application/factories/user_factory.dart new file mode 100644 index 0000000..17e76d8 --- /dev/null +++ b/lib/src/user/application/factories/user_factory.dart @@ -0,0 +1,31 @@ +import '../../domain/models/user.dart'; +import 'package:appflowy_theme_marketplace/src/authentication/domain/models/user.dart' as auth; +import 'package:appflowy_theme_marketplace/src/plugins/domain/models/user.dart' as plugin; + +class UserFactory { + static User fromPlugin(plugin.User user) { + return User( + uid: user.uid, + email: user.email ?? 'Unknown', + name: user.name ?? 'Unknown', + ); + } + + static plugin.User authToPlugin(auth.User user) { + return plugin.User( + uid: user.uid, + email: user.email, + name: user.name, + ); + } + + static User fromAuth(auth.User user) { + return User( + uid: user.uid, + email: user.email ?? 'undefined', + name: user.name ?? 'undefined', + ); + } + + +} diff --git a/lib/src/user/data/firebase_repository/firebase_orders_repository.dart b/lib/src/user/data/firebase_repository/firebase_orders_repository.dart new file mode 100644 index 0000000..9b94a14 --- /dev/null +++ b/lib/src/user/data/firebase_repository/firebase_orders_repository.dart @@ -0,0 +1,23 @@ +import 'package:appflowy_theme_marketplace/src/user/data/firebase_repository/helpers/orders_helpers.dart'; +import 'package:appflowy_theme_marketplace/src/user/domain/models/order.dart'; + +import '../../domain/repositories/orders_repository.dart'; + +class FirebaseOrdersRepository implements OrdersRepository { + @override + Future> getAll(String id) async { + final orders = await OrdersHelper.getAll(id); + return orders; + } + + @override + Future> searchOrder(String uid, String searchTerm) async { + final orders = await OrdersHelper.searchOrder(uid, searchTerm); + return orders; + } + + @override + Future getDownloadUrl(String customerId, String productId) { + throw UnimplementedError(); + } +} \ No newline at end of file diff --git a/lib/src/user/data/firebase_repository/firebase_user_repository.dart b/lib/src/user/data/firebase_repository/firebase_user_repository.dart new file mode 100644 index 0000000..8748d1e --- /dev/null +++ b/lib/src/user/data/firebase_repository/firebase_user_repository.dart @@ -0,0 +1,22 @@ +import 'package:appflowy_theme_marketplace/src/user/data/firebase_repository/helpers/user_helpers.dart'; +import 'package:appflowy_theme_marketplace/src/user/domain/models/user.dart'; + +import '../../domain/repositories/user_repository.dart'; + +class FirebaseUserRepository implements UserRepository { + @override + Future get(String id) async { + User user = await UserHelper.getUserData(id); + return user; + } + + @override + Future add(String email) { + throw UnimplementedError(); + } + + @override + Future update(String stripeId) { + throw UnimplementedError(); + } +} \ No newline at end of file diff --git a/lib/src/user/data/firebase_repository/helpers/orders_helpers.dart b/lib/src/user/data/firebase_repository/helpers/orders_helpers.dart new file mode 100644 index 0000000..74ce626 --- /dev/null +++ b/lib/src/user/data/firebase_repository/helpers/orders_helpers.dart @@ -0,0 +1,63 @@ +import 'dart:collection'; + +import 'package:appflowy_theme_marketplace/src/user/domain/models/order.dart'; +import 'package:appflowy_theme_marketplace/src/user/presentation/orders_page/orders_page.dart'; +import 'package:cloud_firestore/cloud_firestore.dart' as firebase; +import 'package:intl/intl.dart'; + +import '../../../domain/models/user.dart'; + +class OrdersHelper { + static final firebase.FirebaseFirestore _firestore = firebase.FirebaseFirestore.instance; + static final firebase.CollectionReference _usersCollectionRef = _firestore.collection('Users'); + + static Future> getAll(String uid) async { + final firebase.DocumentReference ordersDocumentRef = _usersCollectionRef.doc(uid); + final firebase.CollectionReference ordersCollectionRef = ordersDocumentRef.collection('Orders'); + + try { + final firebase.QuerySnapshot querySnapshot = await ordersCollectionRef.get(); + List orders = querySnapshot.docs.map((orderDocument) { + DateFormat inputFormat = DateFormat('M/d/yyyy'); + DateTime purchasedDate = inputFormat.parse(orderDocument['metadata']['purchaseDate']); + final orderData = Order( + customerUid: orderDocument['metadata']['customerUid'], + productId: orderDocument['metadata']['productId'], + productName: orderDocument['metadata']['productName'], + purchaseDate: purchasedDate, + ); + return orderData; + }).toList(); + return orders; + } on Exception catch(_) { + rethrow; + } + } + + static Future> searchOrder(String uid, [String? searchTerm = '']) async { + final firebase.DocumentReference ordersDocumentRef = _usersCollectionRef.doc(uid); + final firebase.CollectionReference ordersCollectionRef = ordersDocumentRef.collection('Orders'); + List> objectEventsList = []; + try{ + final firebase.QuerySnapshot querySnapshot = await ordersCollectionRef.get(); + objectEventsList = querySnapshot.docs; + if (searchTerm != null || searchTerm != ''){ + objectEventsList = objectEventsList.where((item) => item['metadata']['productName'].toLowerCase().contains(searchTerm)).toList(); + } + List orders = objectEventsList.map((orderDocument) { + DateFormat inputFormat = DateFormat('M/d/yyyy'); + DateTime purchasedDate = inputFormat.parse(orderDocument['metadata']['purchaseDate']); + final orderData = Order( + customerUid: orderDocument['metadata']['customerUid'], + productId: orderDocument['metadata']['productId'], + productName: orderDocument['metadata']['productName'], + purchaseDate: purchasedDate, + ); + return orderData; + }).toList(); + return orders; + } on Exception catch(e) { + return []; + } + } +} diff --git a/lib/src/user/data/firebase_repository/helpers/user_helpers.dart b/lib/src/user/data/firebase_repository/helpers/user_helpers.dart new file mode 100644 index 0000000..f780adb --- /dev/null +++ b/lib/src/user/data/firebase_repository/helpers/user_helpers.dart @@ -0,0 +1,67 @@ +import 'dart:collection'; + +import 'package:cloud_firestore/cloud_firestore.dart' as firebase; + +import '../../../domain/models/user.dart'; + +class UserHelper { + static final firebase.FirebaseFirestore _firestore = firebase.FirebaseFirestore.instance; + static final firebase.CollectionReference _filesCollectionRef = _firestore.collection('Files'); + static final firebase.CollectionReference _usersCollectionRef = _firestore.collection('Users'); + + static Future addNewUser(User user) async { + final querySnapShot = await _usersCollectionRef.where('email', isEqualTo: user.email).get(); + if (querySnapShot.docs.isNotEmpty) + throw Exception('Exist user with the same email address'); + await _usersCollectionRef.doc(user.uid).set(user.toJson()); + } + + static Future updateUser(User user) async { + try { + firebase.DocumentSnapshot userDocument = await _getUserDocument(user.uid); + String userDocId = userDocument.id; + await _usersCollectionRef.doc(userDocId).update({ + // 'purchasedItems': user.purchasedItems, + // 'stripeId': user.stripeId + }, + ); + } on Exception catch(_) { + rethrow; + } + } + + static Future _getUserDocument(String uid) async { + try { + final firebase.QuerySnapshot querySnapshot = await _usersCollectionRef + .where('uid', isEqualTo: uid) + .get(); + return querySnapshot.docs[0]; + } on Exception catch(_) { + rethrow; + } + } + + static Future getUserData(String uid) async { + try { + firebase.DocumentSnapshot userDocument = await _getUserDocument(uid); + final uploaderMap = Map.from(userDocument.data() as LinkedHashMap); + final User uploader = User.fromJson(uploaderMap); + return uploader; + } on Exception catch(_) { + rethrow; + } + } + + static Future getUserDataByMail(String email) async { + try { + final firebase.QuerySnapshot querySnapshot = await _usersCollectionRef + .where('email', isEqualTo: email) + .get(); + final uploaderMap = Map.from(querySnapshot.docs[0].data() as LinkedHashMap); + final User uploader = User.fromJson(uploaderMap); + return uploader; + } on Exception catch(_) { + rethrow; + } + } +} diff --git a/lib/src/user/data/supabase_repository/supabase_orders_repository.dart b/lib/src/user/data/supabase_repository/supabase_orders_repository.dart new file mode 100644 index 0000000..3380547 --- /dev/null +++ b/lib/src/user/data/supabase_repository/supabase_orders_repository.dart @@ -0,0 +1,61 @@ +import '../../domain/models/order.dart'; +import '../../domain/repositories/orders_repository.dart'; +import 'package:supabase_flutter/supabase_flutter.dart' as supabase; + +class SupabaseOrdersRepository implements OrdersRepository { + SupabaseOrdersRepository({supabase.SupabaseClient? sp}) : sp = sp ?? supabase.Supabase.instance.client; + + final supabase.SupabaseClient sp; + + @override + Future> getAll(String uid) async{ + List orders = []; + final List> data = await sp.from('orders').select('*'); + + orders = data.map((order) { + return Order( + customerUid: order['customer_id'], + productId: order['plugin_id'], + productName: order['product_name'], + purchaseDate: DateTime.parse(order['created_at']), + ); + }).toList(); + return orders; + } + + @override + Future> searchOrder(String uid, String searchTerm) async { + List orders = []; + final List> data = await sp.from('orders').select('plugin_id, created_at, product_name, customer_id'); + + orders = data.map((order) { + return Order( + customerUid: order['customer_id'], + productId: order['plugin_id'], + productName: order['product_name'], + purchaseDate: DateTime.parse(order['created_at']), + ); + }).toList(); + return orders; + } + + @override + Future getDownloadUrl(String customerId, String productId) async { + final supabase.FunctionResponse response = await sp.functions.invoke( + 'download-plugin', + body: { + 'customer_id': customerId, + 'plugin_id': productId + }, + headers: { + 'Content-type': 'application/json', + 'Accept': 'application/json', + 'Authorization': 'Bearer ${sp.auth.currentSession?.accessToken}', + }, + ); + if (response.status == 400) { + throw Exception(response.data['message']); + } + return(response.data['signedUrl']); + } +} \ No newline at end of file diff --git a/lib/src/user/data/supabase_repository/supabase_storage_repository.dart b/lib/src/user/data/supabase_repository/supabase_storage_repository.dart new file mode 100644 index 0000000..305ca6f --- /dev/null +++ b/lib/src/user/data/supabase_repository/supabase_storage_repository.dart @@ -0,0 +1,165 @@ +import 'dart:typed_data'; +import 'package:appflowy_theme_marketplace/src/user/application/factories/plugin_file_object_factory.dart'; +import 'package:archive/archive.dart'; +import 'package:supabase_flutter/supabase_flutter.dart' as supabase; +import 'package:profanity_filter/profanity_filter.dart'; +import 'package:supabase_flutter/supabase_flutter.dart'; +import '../../../widgets/ui_utils.dart'; +import '../../domain/models/plugin.dart'; +import '../../domain/models/plugin_file_object.dart'; +import '../../domain/repositories/storage_repository.dart'; + +class SupabaseStorageRepository implements FileStorageRepository { + SupabaseStorageRepository({supabase.SupabaseClient? sp, supabase.SupabaseStorageClient? storage}) + : sp = sp ?? supabase.Supabase.instance.client, + storage = storage ?? supabase.Supabase.instance.client.storage; + + final supabase.SupabaseClient sp; + final supabase.SupabaseStorageClient storage; + + static bool validateUploadFile(Uint8List bytes) { + late Archive archive; + try { + archive = ZipDecoder().decodeBytes(bytes); + } on Exception catch(_) { + return false; + } + + // guarantee there must be two files light and dark no more or less + if (archive.length != 2) { + return false; + } + for (ArchiveFile file in archive) { + if (file.isFile){ + final list = file.name.split('/'); + if (!list[0].endsWith(UiUtils.plugins)) { + return false; + } + if(!(list[1].endsWith(UiUtils.lightJsonTheme) || list[1].endsWith(UiUtils.darkJsonTheme))) { + return false; + } + } + } + return true; + } + + @override + Future> get(String? searchTerm, String uid, String bucket) async { + List fileObjList = []; + const SearchOptions searchOptions = SearchOptions( + sortBy: SortBy( + column: 'updated_at', + order: 'desc', + ), + ); + try { + final List files = await storage + .from(bucket) + .list( + path: uid, + searchOptions: searchOptions + ); + fileObjList = files + .map((file) => PluginFileObjectFactory.fromFileObject(file)) + .toList(); + if (searchTerm != null && searchTerm.isNotEmpty) { + fileObjList = fileObjList + .where((file) => file.name.toLowerCase().contains(searchTerm)) + .toList(); + } + } on Exception catch (e) { + rethrow; + } + + return fileObjList; + } + + @override + Future add(Plugin plugin) async { + if(plugin.pickedFile == null){ + throw Exception('picked file is undefined'); + } + else { + final filter = ProfanityFilter(); + if(filter.hasProfanity(plugin.name.toLowerCase().substring(0, plugin.name.lastIndexOf('.')))){ + throw Exception('Cannot upload file that has profanity'); + } + if(plugin.pickedFile == null){ + throw Exception('Picked File is undefined'); + } + if(!validateUploadFile(plugin.pickedFile!.bytes)){ + throw Exception('Does not meet the required format'); + } + + final String path = '${plugin.uploader.uid}/${plugin.name}'; + final bucket = plugin.price == 0 ? 'free_plugins' : 'paid_plugins'; + final String res = await storage.from(bucket).uploadBinary( + path, + plugin.pickedFile!.bytes, + fileOptions: + const supabase.FileOptions( + contentType: 'application/zip', + ), + ); + if(res.isEmpty){ + throw Exception('Failed to upload file'); + } + final String downloadUrl = plugin.price == 0 ? storage.from(bucket).getPublicUrl(path) : ''; + if(res.isNotEmpty) { + final duplicate = await sp.from('files').select('*').eq('name', plugin.name).eq('price', plugin.price); + if(duplicate.isNotEmpty){ + await storage.from(bucket).remove([path]); + throw Exception('Exist plugin with the same data'); + } + try { + await sp.from('files').insert(Plugin.upload( + name: plugin.name, + uploader: plugin.uploader, + price: plugin.price, + downloadURL: downloadUrl, + ).toJson()); + } on Exception catch(_) { + await storage.from(bucket).remove([path]); + rethrow; + } + } + else { + throw Exception('Error uploading plugin'); + } + } + } + + @override + Future update(Plugin plugin) async { + + } + + @override + Future> delete(String fileName, String bucket, String uid) async { + try { + String path = '$uid/$fileName'; + final List filesObjects = await storage + .from(bucket) + .remove([path]); + final List files = filesObjects + .map((file) => PluginFileObjectFactory.fromFileObject(file)) + .toList(); + if(filesObjects.isNotEmpty) { + for(PluginFileObject deletedFile in files){ + String deletedFileName = deletedFile.name.split('/')[1]; + if(bucket == 'free_plugins') { + await sp.from('files').delete().eq('name', deletedFileName).eq('price', 0); + } else { + await sp.from('files').delete().eq('name', deletedFileName).gt('price', 0); + } + } + } + return files; + } on Exception catch(e) { + if(e is StorageException){ + rethrow; + } + throw Exception('Something went wrong while deleteting file'); + } + } +} diff --git a/lib/src/user/data/supabase_repository/supabase_user_repository.dart b/lib/src/user/data/supabase_repository/supabase_user_repository.dart new file mode 100644 index 0000000..d231a64 --- /dev/null +++ b/lib/src/user/data/supabase_repository/supabase_user_repository.dart @@ -0,0 +1,80 @@ + +import 'dart:convert'; + +import 'package:appflowy_theme_marketplace/src/widgets/ui_utils.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter_dotenv/flutter_dotenv.dart'; +import 'package:supabase_flutter/supabase_flutter.dart' as supabase; +import 'package:http/http.dart' as http; +import '../../../serverless_api/supabase_api.dart'; +import '../../domain/repositories/user_repository.dart'; +import '../../domain/models/user.dart'; + +class SupabaseUserRepository implements UserRepository { + SupabaseUserRepository({supabase.SupabaseClient? sp}) : sp = sp ?? supabase.Supabase.instance.client; + + final supabase.SupabaseClient sp; + final key = dotenv.env['ANON_KEY'] as String; + + @override + Future get(String id) async { + // user can only get their own data anyway so no need to filter by uid + dynamic data = await sp.from('users').select('*'); + User user = User( + uid: data[0]['uid'], + email: data[0]['email'], + name: data[0]['name'] ?? UiUtils.defaultUsername(data[0]['email']), + stripeId: data[0]['stripe_id'], + onboardCompleted: data[0]['onboarded'], + ); + return user; + } + + @override + Future add(String email) async { + http.Response? response; + try { + const url = SupabaseApi.createStripeAccount; + final body = { + 'email': email, + }; + response = await http.post( + Uri.parse(url), + headers: { + 'Content-type': 'application/json', + 'Accept': 'application/json', + 'Authorization': 'Bearer $key' + }, + body: jsonEncode(body), + ); + + } on Exception catch(e) { + debugPrint(e.toString()); + } + if (response == null) + throw Exception('response is undefined'); + final result = json.decode(response.body); + return result['id']; + } + + @override + Future update(String stripeId) async { + final String url = '${SupabaseApi.getStripeAccountInfo}/$stripeId'; + final stripeDataResponse = await http.get( + Uri.parse(url), + headers: { + 'Content-type': 'application/json', + 'Accept': 'application/json', + 'apikey': key, + 'Authorization': 'Bearer ${sp.auth.currentSession?.accessToken}', + }, + ); + final stripeDataJson = json.decode(stripeDataResponse.body); + final onboardCompleted = stripeDataJson['charges_enabled']; + final response = await sp.from('users') + .update({ 'onboarded': onboardCompleted }) + .eq('stripe_id', stripeId) + .select(); + return User.fromJson(response[0]); + } +} \ No newline at end of file diff --git a/lib/src/user/domain/models/order.dart b/lib/src/user/domain/models/order.dart new file mode 100644 index 0000000..5cee0a7 --- /dev/null +++ b/lib/src/user/domain/models/order.dart @@ -0,0 +1,29 @@ +class Order { + final String customerUid; + final String productId; + final String productName; + final DateTime purchaseDate; + final String? downloadUrl; + Order({ + required this.customerUid, + required this.productId, + required this.productName, + required this.purchaseDate, + this.downloadUrl + }); + + Order.fromJson(Map object) + : customerUid = object['customerUid'], + productId = object['productId'], + productName = object['productName'], + purchaseDate = object['purchaseDate'], + downloadUrl = object['downloadUrl']; + + Map toJson() => { + 'customerUid': customerUid, + 'productId': productId, + 'productName': productName, + 'purchaseDate': purchaseDate, + 'downloadUrl': downloadUrl ?? '', + }; +} diff --git a/lib/src/user/domain/models/picked_file.dart b/lib/src/user/domain/models/picked_file.dart new file mode 100644 index 0000000..4517408 --- /dev/null +++ b/lib/src/user/domain/models/picked_file.dart @@ -0,0 +1,8 @@ +import 'dart:typed_data'; + +class PickedFile { + final Uint8List bytes; + final String name; + + const PickedFile(this.bytes, this.name); +} \ No newline at end of file diff --git a/lib/src/user/domain/models/plugin.dart b/lib/src/user/domain/models/plugin.dart new file mode 100644 index 0000000..3569ecb --- /dev/null +++ b/lib/src/user/domain/models/plugin.dart @@ -0,0 +1,80 @@ +import 'package:uuid/uuid.dart'; + +import 'picked_file.dart'; +import 'user.dart'; + +class Plugin { + final String pluginId; + final int downloadCount; + final String downloadURL; + final String name; + final double rating; + final int ratingCount; + final DateTime? uploadDate; + final PickedFile? pickedFile; + final double price; + final User uploader; + + Plugin({ + required this.pluginId, + required this.downloadCount, + String? downloadURL, + required this.name, + required this.rating, + required this.ratingCount, + this.uploadDate, + this.pickedFile, + required this.price, + required this.uploader, + }) : downloadURL = downloadURL ?? ''; + + factory Plugin.upload({ + PickedFile? pickedFile, + required String name, + required User uploader, + DateTime? uploadDate, + String? downloadURL, + required double price, + }) => + Plugin( + pluginId: const Uuid().v4(), + downloadCount: 0, + downloadURL: downloadURL ?? '', + name: name, + rating: 0, + ratingCount: 0, + pickedFile: pickedFile, + price: price, + uploader: uploader, + ); + + Plugin.fromJson(Map object) + : pluginId = object['plugin_id'], + downloadCount = object['download_count'], + downloadURL = object['download_url'], + name = object['name'], + rating = object['rating'], + ratingCount = object['rating_count'], + uploadDate = DateTime.parse(object['created_at']), + pickedFile = null, // pickedFile is always null when receive data from a json request + price = object['price'], + uploader = User( + uid: object['uploader_id'], + name: object['uploader_name'], + email: object['uploader_email'], + ); + + Map toJson() => { + 'plugin_id': pluginId, + 'download_count': downloadCount, + 'download_url': downloadURL, + 'name': name, + 'rating': rating, + 'rating_count': ratingCount, + if (uploadDate != null) 'created_at': uploadDate.toString(), + 'price': price, + 'uploader_id': uploader.uid, + 'uploader_email': uploader.email, + 'uploader_name': uploader.name, + }; +} diff --git a/lib/src/user/domain/models/plugin_file_object.dart b/lib/src/user/domain/models/plugin_file_object.dart new file mode 100644 index 0000000..135cdef --- /dev/null +++ b/lib/src/user/domain/models/plugin_file_object.dart @@ -0,0 +1,31 @@ +class PluginFileObject { + final String name; + final String? bucketId; + final String? owner; + final String? id; + final String? updatedAt; + final String? createdAt; + final String? lastAccessedAt; + final Map? metadata; + + const PluginFileObject({ + required this.name, + required this.bucketId, + required this.owner, + required this.id, + required this.updatedAt, + required this.createdAt, + required this.lastAccessedAt, + required this.metadata, + }); + + PluginFileObject.fromJson(dynamic json) + : id = (json as Map)['id'] as String?, + name = json['name'] as String, + bucketId = json['bucket_id'] as String?, + owner = json['owner'] as String?, + updatedAt = json['updated_at'] as String?, + createdAt = json['created_at'] as String?, + lastAccessedAt = json['last_accessed_at'] as String?, + metadata = json['metadata'] as Map?; +} \ No newline at end of file diff --git a/lib/src/user/domain/models/rating.dart b/lib/src/user/domain/models/rating.dart new file mode 100644 index 0000000..2f53efd --- /dev/null +++ b/lib/src/user/domain/models/rating.dart @@ -0,0 +1,51 @@ +import 'package:appflowy_theme_marketplace/src/plugins/domain/models/user.dart'; +import 'package:appflowy_theme_marketplace/src/widgets/ui_utils.dart'; + +class Rating { + final DateTime date; + final double rating; + final String? review; + final User reviewer; + final String pluginId; + final String? pluginName; + + Rating({ + required this.date, + required this.rating, + this.review, + required this.reviewer, + required this.pluginId, + this.pluginName, + }); + + Rating.fromJson(Map object) + : date = DateTime.parse(object['created_at']), + rating = object['rating'], + review = object['review'], + pluginId = object['plugin_id'], + pluginName = object['plugin_name'], + reviewer = User( + email: object['reviewer_email'], + name: UiUtils.defaultUsername(object['reviewer_email']), + uid: object['reviewer_id'], + ); + + Map toJson() => { + 'created_at': date, + 'rating': rating, + 'review': review ?? '', + 'plugin_id': pluginId, + 'plugin_name': pluginName, + 'reviewer_id': reviewer.uid, + 'reviewer_email': reviewer.email, + }; + + Map toJsonInsert() => { + 'review': review, + 'rating': rating, + 'reviewer_id': reviewer.uid, + 'plugin_id': pluginId, + 'reviewer_email': reviewer.email, + 'plugin_name': pluginName, + }; +} \ No newline at end of file diff --git a/lib/src/user/domain/models/storage_file.dart b/lib/src/user/domain/models/storage_file.dart new file mode 100644 index 0000000..a663fd0 --- /dev/null +++ b/lib/src/user/domain/models/storage_file.dart @@ -0,0 +1,18 @@ +class StorageFile { + final String name; + final String path; + + StorageFile({ + required this.name, + required this.path, + }); + + StorageFile.fromJson(Map object) + : name = object['name'], + path = object['path'] ?? ''; + + Map toJson() => { + 'name': name, + 'path': path, + }; +} diff --git a/lib/src/user/domain/models/user.dart b/lib/src/user/domain/models/user.dart new file mode 100644 index 0000000..98de576 --- /dev/null +++ b/lib/src/user/domain/models/user.dart @@ -0,0 +1,32 @@ +class User { + final String uid; + final String name; + final String email; + final String? stripeId; + final bool onboardCompleted; + + User({ + required this.uid, + required this.name, + required this.email, + this.stripeId, + List? purchasedItems, + bool? onboardCompleted, + }) : onboardCompleted = onboardCompleted ?? false; + + + User.fromJson(Map object) + : uid = object['uid'], + name = object['name'], + email = object['email'], + stripeId = object['stripeId'], + onboardCompleted = object['onboardCompleted'] ?? false; + + Map toJson() => { + 'uid': uid, + 'name': name, + 'email': email, + 'stripeId': stripeId ?? '', + 'onboardCompleted': onboardCompleted, + }; +} diff --git a/lib/src/user/domain/repositories/orders_repository.dart b/lib/src/user/domain/repositories/orders_repository.dart new file mode 100644 index 0000000..8b09147 --- /dev/null +++ b/lib/src/user/domain/repositories/orders_repository.dart @@ -0,0 +1,7 @@ +import 'package:appflowy_theme_marketplace/src/user/domain/models/order.dart'; + +abstract class OrdersRepository { + Future> getAll(String id); + Future> searchOrder(String uid, String searchTerm); + Future getDownloadUrl(String customerId, String productId); +} \ No newline at end of file diff --git a/lib/src/user/domain/repositories/storage_repository.dart b/lib/src/user/domain/repositories/storage_repository.dart new file mode 100644 index 0000000..140f215 --- /dev/null +++ b/lib/src/user/domain/repositories/storage_repository.dart @@ -0,0 +1,13 @@ + +import 'dart:typed_data' as type; + +import '../models/plugin.dart'; +import '../models/plugin_file_object.dart'; +import '../models/user.dart'; + +abstract class FileStorageRepository { + Future> get(String? searchTerm,String uid, String bucket); + Future add(Plugin plugin); + Future update(Plugin plugin); + Future> delete(String fileName, String bucket, String uid); +} \ No newline at end of file diff --git a/lib/src/user/domain/repositories/user_repository.dart b/lib/src/user/domain/repositories/user_repository.dart new file mode 100644 index 0000000..7f7fb00 --- /dev/null +++ b/lib/src/user/domain/repositories/user_repository.dart @@ -0,0 +1,7 @@ +import 'package:appflowy_theme_marketplace/src/user/domain/models/user.dart'; + +abstract class UserRepository { + Future get(String id); + Future add(String email); + Future update(String stripeId); +} diff --git a/lib/src/user/presentation/orders_page/orders_body.dart b/lib/src/user/presentation/orders_page/orders_body.dart new file mode 100644 index 0000000..ae7a799 --- /dev/null +++ b/lib/src/user/presentation/orders_page/orders_body.dart @@ -0,0 +1,99 @@ +import 'package:appflowy_theme_marketplace/src/user/application/bloc/orders_bloc/orders_bloc.dart'; +import 'package:appflowy_theme_marketplace/src/widgets/ui_utils.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import '../../../authentication/application/auth_bloc/auth_bloc.dart'; +import '../../domain/models/order.dart'; +import '../../domain/repositories/orders_repository.dart'; +import 'orders_item.dart'; + +class OrdersBody extends StatefulWidget { + const OrdersBody({super.key, required this.userStatus, required this.ordersRepository}); + + final AuthenticateSuccess userStatus; + final OrdersRepository ordersRepository; + + @override + State createState() => _OrdersBodyState(); +} + +class _OrdersBodyState extends State { + int? _selectedIndex; + + @override + void initState() { + super.initState(); + context.read().add(FindOrderRequested(widget.userStatus.user!.uid, '')); + } + + void showOrderDetail(Order order, BuildContext context) { + showDialog( + context: context, + builder: (BuildContext context) { + return OrderDetail(order: order); + }, + ); + } + + @override + Widget build(BuildContext context) { + return Center( + child: BlocBuilder( + builder: (BuildContext context, OrdersState state) { + if (state is OrdersLoading) { + return const CircularProgressIndicator(); + } + if (state is OrdersLoaded) { + return Column( + children: [ + const Text( + 'Orders', + style: TextStyle( + fontSize: UiUtils.sizeXL, + ), + ), + SizedBox( + width: MediaQuery.of(context).size.width * (1 / 3), + height: MediaQuery.of(context).size.height * (2 / 3), + child: ListView.builder( + scrollDirection: Axis.vertical, + controller: null, + itemCount: state.orders.length, + itemBuilder: (BuildContext context, int index) { + final Order order = state.orders[index]; + return ListTile( + enabled: true, + selected: index == _selectedIndex, + dense: false, + horizontalTitleGap: 0.0, + selectedColor: UiUtils.blue, + selectedTileColor: Colors.grey[800], + splashColor: UiUtils.transparent, + contentPadding: const EdgeInsets.symmetric( + horizontal: UiUtils.sizeL), + leading: const SizedBox( + height: double.infinity, + child: Icon(Icons.file_copy), + ), + title: Text(order.productName), + subtitle: Text(order.productId), + onTap: () { + setState(() { + _selectedIndex = index; + }); + showOrderDetail(order, context); + }, + ); + }, + ), + ), + ], + ); + } + return Text(state is OrdersLoadFailed ? state.message : state.toString()); + }, + ), + ); + } +} \ No newline at end of file diff --git a/lib/src/user/presentation/orders_page/orders_item.dart b/lib/src/user/presentation/orders_page/orders_item.dart new file mode 100644 index 0000000..aa13aea --- /dev/null +++ b/lib/src/user/presentation/orders_page/orders_item.dart @@ -0,0 +1,53 @@ +import 'package:appflowy_theme_marketplace/src/user/domain/models/order.dart'; +import 'package:flutter/material.dart'; + +import '../../../widgets/popup_dialog.dart'; +import '../../../widgets/ui_utils.dart'; + +class OrderDetail extends StatefulWidget { + const OrderDetail({super.key, required this.order}); + final Order order; + + @override + State createState() => _OrderDetailState(); +} + +class _OrderDetailState extends State { + @override + void initState() { + super.initState(); + } + + @override + Widget build(BuildContext context) { + return PopupDialog( + title: Row( + children: [ + const Text('Order details'), + const Spacer(), + IconButton( + splashColor: UiUtils.transparent, + splashRadius: UiUtils.sizeL, + icon: const Icon(Icons.close), + onPressed: () => Navigator.of(context).pop(), + ), + ], + ), + content: SizedBox( + width: MediaQuery.of(context).size.width * (1 / 3), + height: MediaQuery.of(context).size.height * (2 / 3), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('plugin name: ${widget.order.productName}'), + Text('plugin id: ${widget.order.productId}'), + Text('customer id: ${widget.order.customerUid}'), + Text('purchased date: ${widget.order.purchaseDate.toString()}'), + ], + ), + ), + actions: const [], + ); + } +} \ No newline at end of file diff --git a/lib/src/user/presentation/orders_page/orders_page.dart b/lib/src/user/presentation/orders_page/orders_page.dart new file mode 100644 index 0000000..da99bc9 --- /dev/null +++ b/lib/src/user/presentation/orders_page/orders_page.dart @@ -0,0 +1,30 @@ +import 'package:appflowy_theme_marketplace/src/user/domain/repositories/orders_repository.dart'; +import 'package:appflowy_theme_marketplace/src/widgets/page_layout.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import '../../../authentication/application/auth_bloc/auth_bloc.dart'; +import 'orders_body.dart'; + +class OrdersPage extends StatelessWidget { + const OrdersPage({super.key, required this.ordersRepository}); + + final OrdersRepository ordersRepository; + + @override + Widget build(BuildContext context) { + return PageLayout( + body: BlocBuilder( + builder: (BuildContext context, AuthState state) { + if (state is! AuthenticateSuccess) { + return const SizedBox(); + } + return OrdersBody( + userStatus: state, + ordersRepository: ordersRepository, + ); + }, + ), + ); + } +} diff --git a/lib/src/user/presentation/ratings_page/rating_item.dart b/lib/src/user/presentation/ratings_page/rating_item.dart new file mode 100644 index 0000000..af2e8ac --- /dev/null +++ b/lib/src/user/presentation/ratings_page/rating_item.dart @@ -0,0 +1,55 @@ +import 'package:flutter/material.dart'; + +import '../../../widgets/popup_dialog.dart'; +import '../../../widgets/ui_utils.dart'; +import '../../domain/models/rating.dart'; + +class RatingDetail extends StatefulWidget { + const RatingDetail({super.key, required this.rating}); + final Rating rating; + + @override + State createState() => _RatingDetailState(); +} + +class _RatingDetailState extends State { + @override + void initState() { + super.initState(); + } + + @override + Widget build(BuildContext context) { + return PopupDialog( + title: Row( + children: [ + const Text('Order details'), + const Spacer(), + IconButton( + splashColor: UiUtils.transparent, + splashRadius: UiUtils.sizeL, + icon: const Icon(Icons.close), + onPressed: () => Navigator.of(context).pop(), + ), + ], + ), + content: SizedBox( + width: MediaQuery.of(context).size.width * (1 / 3), + height: MediaQuery.of(context).size.height * (2 / 3), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('review date: ${widget.rating.date}'), + Text('rating: ${widget.rating.rating}'), + Text('review: ${widget.rating.review}'), + Text('reviewer_email: ${widget.rating.reviewer.email}'), + Text('reviewer_id: ${widget.rating.reviewer.uid}'), + Text('plugin_id: ${widget.rating.pluginId}'), + ], + ), + ), + actions: const [], + ); + } +} \ No newline at end of file diff --git a/lib/src/user/presentation/ratings_page/ratings_body.dart b/lib/src/user/presentation/ratings_page/ratings_body.dart new file mode 100644 index 0000000..3d39ab9 --- /dev/null +++ b/lib/src/user/presentation/ratings_page/ratings_body.dart @@ -0,0 +1,122 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_rating_bar/flutter_rating_bar.dart'; + +import '../../../authentication/application/auth_bloc/auth_bloc.dart'; +import '../../../plugins/domain/repositories/ratings_repository.dart'; +import '../../../widgets/ui_utils.dart'; +import '../../application/bloc/ratings_bloc/ratings_bloc.dart'; +import '../../domain/models/rating.dart'; +import 'rating_item.dart'; + +class RatingsBody extends StatefulWidget { + const RatingsBody({super.key, required this.userStatus, required this.ratingsRepository}); + + final AuthenticateSuccess userStatus; + final RatingsRepository ratingsRepository; + + @override + State createState() => _RatingsBody(); +} + +class _RatingsBody extends State { + int? _selectedIndex; + + @override + void initState() { + super.initState(); + final user = widget.userStatus.user; + if (user == null) { + throw Exception('User is not defined'); + } + context.read().add(GetAllRatingsRequested(user.uid)); + } + + void showRatingDetailDialog(Rating rating, BuildContext context) { + showDialog( + context: context, + builder: (BuildContext context) { + return RatingDetail(rating: rating); + }, + ); + } + + @override + Widget build(BuildContext context) { + return Center( + child: BlocBuilder( + builder: (BuildContext context, RatingsState state) { + if (state is RatingsLoading) { + return const CircularProgressIndicator(); + + } else if (state is RatingsLoaded) { + return Column( + children: [ + const Text( + 'Ratings', + style: TextStyle( + fontSize: UiUtils.sizeXL, + ), + ), + SizedBox( + width: MediaQuery.of(context).size.width * (1 / 3), + height: MediaQuery.of(context).size.height * (2 / 3), + child: ListView.builder( + scrollDirection: Axis.vertical, + controller: null, + itemCount: state.ratings.length, + itemBuilder: (BuildContext context, int index) { + final Rating rating = state.ratings[index]; + return ListTile( + enabled: true, + selected: index == _selectedIndex, + dense: false, + horizontalTitleGap: 0.0, + selectedColor: UiUtils.blue, + selectedTileColor: Colors.grey[800], + splashColor: UiUtils.transparent, + contentPadding: const EdgeInsets.symmetric(horizontal: UiUtils.sizeL), + title: Text(rating.pluginName ?? rating.pluginId), + subtitle: Text(UiUtils.formatDate(rating.date)), + trailing: SizedBox( + child: RatingBar.builder( + initialRating: rating.rating, + minRating: 1, + direction: Axis.horizontal, + ignoreGestures: true, + allowHalfRating: true, + itemCount: 5, + itemSize: UiUtils.sizeM, + itemPadding: const EdgeInsets.symmetric( + horizontal: 0.0, + vertical: 0.0, + ), + itemBuilder: (context, _) => const Icon( + Icons.star, + color: Colors.amber, + ), + onRatingUpdate: (r) {}, + ), + ), + onTap: () { + setState(() { + _selectedIndex = index; + }); + showRatingDetailDialog(rating, context); + }, + ); + }, + ), + ), + ], + ); + } + else { + return Text(state is RatingsLoadFailed ? state.message : state.toString()); + } + }, + ), + + ); + } +} \ No newline at end of file diff --git a/lib/src/user/presentation/ratings_page/ratings_page.dart b/lib/src/user/presentation/ratings_page/ratings_page.dart new file mode 100644 index 0000000..fcc6831 --- /dev/null +++ b/lib/src/user/presentation/ratings_page/ratings_page.dart @@ -0,0 +1,44 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:skeletons/skeletons.dart'; +import '../../../authentication/application/auth_bloc/auth_bloc.dart'; +import '../../../plugins/domain/repositories/ratings_repository.dart'; +import '../../../widgets/page_layout.dart'; +import '../../../widgets/ui_utils.dart'; +import 'ratings_body.dart'; + +class RatingsPage extends StatelessWidget { + const RatingsPage({super.key, required this.ratingsRepository}); + + final RatingsRepository ratingsRepository; + + @override + Widget build(BuildContext context) { + double screenSize = (MediaQuery.of(context).size.width - 64); + final double cardSize = UiUtils.calculateCardSize(screenSize); + return PageLayout( + body: BlocBuilder( + builder: (BuildContext context, AuthState state) { + if (state is AuthenticateSuccess) { + return RatingsBody( + userStatus: state, + ratingsRepository: ratingsRepository, + ); + } else { + return PageLayout( + body: SkeletonAvatar( + style: SkeletonAvatarStyle( + width: double.infinity, + height: cardSize, + padding: const EdgeInsets.all(8), + borderRadius: BorderRadius.circular(8), + ), + ), + ); + } + }, + ), + ); + + } +} diff --git a/lib/src/user/presentation/register_page/register.dart b/lib/src/user/presentation/register_page/register.dart new file mode 100644 index 0000000..85a639f --- /dev/null +++ b/lib/src/user/presentation/register_page/register.dart @@ -0,0 +1,13 @@ +import 'package:flutter/material.dart'; +import 'register_body.dart'; + +class RegisterPage extends StatelessWidget { + const RegisterPage({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + body: RegisterBody(), + ); + } +} diff --git a/lib/src/user/presentation/register_page/register_body.dart b/lib/src/user/presentation/register_page/register_body.dart new file mode 100644 index 0000000..8e85e1a --- /dev/null +++ b/lib/src/user/presentation/register_page/register_body.dart @@ -0,0 +1,170 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import '../../../authentication/application/auth_bloc/auth_bloc.dart'; +import '../../../widgets/ui_utils.dart'; + +class RegisterBody extends StatelessWidget { + RegisterBody({super.key}); + + final _formKey = GlobalKey(); + final TextEditingController _nameController = TextEditingController(); + final TextEditingController _emailController = TextEditingController(); + final TextEditingController _passwordController = TextEditingController(); + final TextEditingController _verifyPasswordController = TextEditingController(); + + InputDecoration decoration({required String labelText}) { + return InputDecoration(labelText: labelText); + } + + Widget buildForm(BuildContext context) { + return Form( + key: _formKey, + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + children: [ + const SizedBox(height: 100), + const Text( + 'Register', + style: TextStyle( + fontSize: HeaderSize.h6, + ), + ), + TextFormField( + controller: _nameController, + decoration: decoration(labelText: 'name'), + validator: (value) { + if (value == null || value.isEmpty) return 'Please enter your name'; + return null; + }, + ), + const SizedBox(height: 16), + TextFormField( + controller: _emailController, + decoration: decoration(labelText: 'Email'), + validator: (value) { + if (value == null || value.isEmpty) { + return 'Please enter your email'; + } else if (!(value.contains('@') && value.contains('.'))) { + return 'Invalid email format'; + } + return null; + }, + ), + const SizedBox(height: 16), + TextFormField( + controller: _passwordController, + decoration: decoration(labelText: 'Enter Password'), + obscureText: true, + validator: (value) { + if (value == null || value.isEmpty) { + return 'Please enter a password'; + } else if (value.length < 6) { + return 'Password must be at least 6 characters'; + } + return null; + }, + ), + const SizedBox(height: 16), + TextFormField( + controller: _verifyPasswordController, + decoration: decoration(labelText: 'Renter your Password'), + obscureText: true, + validator: (value) { + if (value == null || value.isEmpty) { + return 'Please re-enter a password'; + } else if (value != _passwordController.text) { + return 'The password does not match'; + } + return null; + }, + onFieldSubmitted: (_) => _submitForm(context), + ), + const SizedBox(height: 16), + ], + ), + ), + ); + } + + void _submitForm(BuildContext context) { + if (_formKey.currentState!.validate()) { + String name = _nameController.text; + String email = _emailController.text; + String password = _passwordController.text; + context.read().add(RegisterUserRequested(email, password, name)); + } + } + + @override + Widget build(BuildContext context) { + return SingleChildScrollView( + child: Center( + child: SizedBox( + width: 300, + child: BlocBuilder( + builder: (context, state) { + if (state is RegistrationSuccess || state is EmailSent || state is SendingEmail) { + return Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + const SizedBox(height: 100), + const Icon(Icons.check, size: IconSize.size_32), + const SizedBox(height: UiUtils.sizeM), + const Text( + 'Thank you for registering, please check your email to verify the account', + style: TextStyle(fontSize: HeaderSize.h3), + ), + const SizedBox(height: UiUtils.sizeM), + (state is SendingEmail) + ? const Center(child: CircularProgressIndicator()) + : ElevatedButton( + onPressed: () => BlocProvider.of(context).add(VerifyEmailRequested()), + child:const Text('Resend the email verification'), + ), + const SizedBox(height: UiUtils.sizeM), + ElevatedButton( + onPressed: () { + BlocProvider.of(context).add(ResetAuthStateRequested()); + Navigator.of(context).pop(); + }, + child: const Text('Go to dashboard'), + ), + const SizedBox(height: UiUtils.sizeM), + ], + ); + } else { + return Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + buildForm(context), + if (state is RegistrationFailed) + Text( + textAlign: TextAlign.center, + state.message, + style: const TextStyle( + color: UiUtils.error, + ), + ), + const SizedBox(height: UiUtils.sizeL), + if (state is Loading) + const Center( + child: CircularProgressIndicator(), + ), + const SizedBox(height: UiUtils.sizeL), + Padding( + padding: const EdgeInsets.symmetric(horizontal: UiUtils.sizeM), + child: ElevatedButton( + onPressed: () => _submitForm(context), + child: Text(state is RegistrationFailed ? 'Try again' : 'Register'), + ), + ), + ], + ); + } + }, + )), + ), + ); + } +} diff --git a/lib/src/user/presentation/user_dashboard_page/user_dashboard_body.dart b/lib/src/user/presentation/user_dashboard_page/user_dashboard_body.dart new file mode 100644 index 0000000..4f43964 --- /dev/null +++ b/lib/src/user/presentation/user_dashboard_page/user_dashboard_body.dart @@ -0,0 +1,156 @@ +import 'package:appflowy_theme_marketplace/src/user/presentation/user_dashboard_page/widgets/file_detail.dart'; +import 'package:appflowy_theme_marketplace/src/user/presentation/user_dashboard_page/widgets/left_nav_bar.dart'; +import 'package:appflowy_theme_marketplace/src/widgets/ui_utils.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:skeletons/skeletons.dart'; + +import '../../application/bloc/storage_bloc/storage_bloc.dart'; +import '../../domain/models/user.dart'; +import '../../domain/repositories/user_repository.dart'; + +enum FileType { + free, + paid, +} + +enum ViewMode { + ratings, + orders, + storageFiles, + releasedFiles, +} + +class UserDashboardBody extends StatefulWidget { + const UserDashboardBody({ + super.key, + required this.user, + required this.userRepository, + required this.openDrawer, + required this.setDrawerContent, + }); + final User user; + final UserRepository userRepository; + final VoidCallback openDrawer; + final Function setDrawerContent; + + @override + State createState() => _UserBodyState(); +} + +class _UserBodyState extends State { + int? _selectedIndex; + FileType listType = FileType.paid; + ViewMode viewmode = ViewMode.storageFiles; + bool isLoading = true; + + @override + void initState() { + context.read().add(GetUploadedFilesRequested(uid: widget.user.uid, bucket: 'paid_plugins')); + super.initState(); + } + + void getFilesList(FileType type) { + setState(() { + if (type == FileType.free) { + context.read().add( + GetUploadedFilesRequested( + uid: widget.user.uid, + bucket: 'free_plugins', + ), + ); + } else { + context.read().add( + GetUploadedFilesRequested( + uid: widget.user.uid, + bucket: 'paid_plugins', + ), + ); + } + listType = type; + _selectedIndex = null; + }); + } + + @override + Widget build(BuildContext context) { + final Widget filesList = BlocConsumer( + listener: (BuildContext context, StorageState state) { + if (state is StorageUploadSuccess || state is StorageFailed || state is StorageDeleteSuccess) { + getFilesList(listType); + } + }, + builder: (BuildContext context, StorageState state) { + return Expanded( + child: Skeleton( + skeleton: SkeletonListView(), + isLoading: state is StorageSearching, + child: ListView.builder( + itemCount: state.files.length, + itemBuilder: (BuildContext context, int index) { + final DateTime date = DateTime.parse(state.files[index].createdAt!); + return ListTile( + selected: index == _selectedIndex ? true : false, + splashColor: UiUtils.transparent, + selectedColor: UiUtils.blue, + leading: const Icon(Icons.folder_zip), + title: Text(state.files[index].name), + minLeadingWidth : UiUtils.sizeS, + subtitle: Text(UiUtils.formatDate(date)), + onTap: () { + widget.openDrawer(); + widget.setDrawerContent( + FileDetail( + fileContent: state.files[index], + bucket: listType == FileType.free ? 'free_plugins' : 'paid_plugins', + ), + ); + setState(() => _selectedIndex = index); + }, + ); + }, + ), + ), + ); + }, + ); + + final Widget filesListingOptions = Row( + children: [ + ContentSpacer.horizontalSpacer_16, + const Text('My Storage Files', style: FontText.font_18), + const Spacer(), + ElevatedButton( + onPressed: listType == FileType.free ? () => getFilesList(FileType.paid) : null, + child: const Text('Paid'), + ), + ContentSpacer.horizontalSpacer_16, + ElevatedButton( + onPressed: listType == FileType.paid ? () => getFilesList(FileType.free) : null, + child: const Text('Free'), + ), + ContentSpacer.horizontalSpacer_16, + ], + ); + + return SizedBox( + height: MediaQuery.of(context).size.height, + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + LeftNavigationBar(uid: widget.user.uid), + Expanded( + child: Column( + children: [ + filesListingOptions, + ContentSpacer.verticalSpacer_16, + filesList, + ], + ), + ), + ], + ), + ); + } +} diff --git a/lib/src/user/presentation/user_dashboard_page/user_dashboard_page.dart b/lib/src/user/presentation/user_dashboard_page/user_dashboard_page.dart new file mode 100644 index 0000000..b592898 --- /dev/null +++ b/lib/src/user/presentation/user_dashboard_page/user_dashboard_page.dart @@ -0,0 +1,56 @@ +import 'package:appflowy_theme_marketplace/src/user/application/factories/user_factory.dart'; +import 'package:appflowy_theme_marketplace/src/widgets/page_layout.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import '../../../authentication/application/auth_bloc/auth_bloc.dart'; +import '../../domain/repositories/user_repository.dart'; +import 'user_dashboard_body.dart'; + +class UserDashboardPage extends StatefulWidget { + const UserDashboardPage({super.key, required this.userRepository}); + + final UserRepository userRepository; + + @override + State createState() => _UserDashboardPageState(); +} + +class _UserDashboardPageState extends State { + final GlobalKey _scaffoldKey = GlobalKey(); + Widget _drawerContent = const SizedBox(); + + void openDrawer() { + _scaffoldKey.currentState?.openEndDrawer(); + } + + void setDrawerContent(Widget content) { + setState(() { + _drawerContent = content; + }); + } + + @override + Widget build(BuildContext context) { + return PageLayout( + scaffoldKey: _scaffoldKey, + drawerContent: _drawerContent, + body: BlocBuilder( + builder: (BuildContext context, AuthState state) { + if (state is! AuthenticateSuccess) { + return const SizedBox(); + } + final user = state.user; + if (user == null) { + throw Exception('user is not defined'); + } + return UserDashboardBody( + openDrawer: openDrawer, + user: UserFactory.fromAuth(user), + userRepository: widget.userRepository, + setDrawerContent: setDrawerContent, + ); + }, + ), + ); + } +} diff --git a/lib/src/user/presentation/user_dashboard_page/widgets/delete_file_form.dart b/lib/src/user/presentation/user_dashboard_page/widgets/delete_file_form.dart new file mode 100644 index 0000000..c4c60d5 --- /dev/null +++ b/lib/src/user/presentation/user_dashboard_page/widgets/delete_file_form.dart @@ -0,0 +1,78 @@ + +import 'package:appflowy_theme_marketplace/src/user/domain/models/plugin_file_object.dart'; +import 'package:appflowy_theme_marketplace/src/widgets/popup_dialog.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import '../../../../widgets/snackbar_status.dart'; +import '../../../../widgets/ui_utils.dart'; +import '../../../application/bloc/storage_bloc/storage_bloc.dart'; +import '../../../application/bloc/user_bloc/user_bloc.dart'; + +class DeleteFileForm extends StatelessWidget { + const DeleteFileForm({super.key, required this.fileContent, required this.bucket}); + + final PluginFileObject fileContent; + final String bucket; + + @override + Widget build(BuildContext context) { + final UserState userState = context.read().state; + + return PopupDialog( + content: BlocConsumer( + listener: (BuildContext context, StorageState state) { + if (state is StorageDeleteSuccess) { + Navigator.pop(context); + final errorSnackbar = SnackbarSuccess(message: 'Delete Succeed'); + ScaffoldMessenger.of(context).showSnackBar(errorSnackbar); + } else if (state is StorageFailed) { + Navigator.pop(context); + final errorSnackbar = SnackbarError(message: state.message); + ScaffoldMessenger.of(context).showSnackBar(errorSnackbar); + } + }, + builder: (BuildContext context, StorageState state) { + if (state is StorageDeletingFile) { + return const Center( + child: CircularProgressIndicator(), + ); + } else { + return SizedBox( + child: Text( + 'Delete ${fileContent.name}', + ), + ); + } + }, + ), + actions: [ + ElevatedButton( + onPressed: () => Navigator.of(context).pop(), + child: const Text('No'), + ), + ElevatedButton( + onPressed: () { + if (userState is UserLoaded) { + context.read().add( + DeleteFileRequested( + fileName: fileContent.name, + user: userState.user, + bucket: bucket, + ), + ); + } + }, + style: ButtonStyle( + backgroundColor: + MaterialStateProperty.all(UiUtils.error), + foregroundColor: + MaterialStateProperty.all(UiUtils.white), + ), + child: const Text('Yes'), + ), + ], + title: const Text('Are you Sure?'), + ); + } +} \ No newline at end of file diff --git a/lib/src/user/presentation/user_dashboard_page/widgets/file_detail.dart b/lib/src/user/presentation/user_dashboard_page/widgets/file_detail.dart new file mode 100644 index 0000000..fba0b69 --- /dev/null +++ b/lib/src/user/presentation/user_dashboard_page/widgets/file_detail.dart @@ -0,0 +1,102 @@ + +import 'package:appflowy_theme_marketplace/src/user/presentation/user_dashboard_page/widgets/upload_form.dart'; +import 'package:appflowy_theme_marketplace/src/widgets/ui_utils.dart'; +import 'package:flutter/material.dart'; +import '../../../domain/models/plugin_file_object.dart'; +import 'delete_file_form.dart'; + +class FileDetail extends StatefulWidget { + const FileDetail({super.key, required this.fileContent, required this.bucket}); + final PluginFileObject fileContent; + final String bucket; + + @override + State createState() => _FileDetailState(); +} + +class _FileDetailState extends State { + void showDeleteDialog() { + showDialog( + context: context, + builder: (context) { + return DeleteFileForm( + fileContent: widget.fileContent, + bucket: widget.bucket, + ); + }, + ); + } + + void showUploadDialog() { + showDialog( + context: context, + builder: (context) { + return UploadForm( + file: widget.fileContent, + ); + }, + ); + } + + @override + Widget build(BuildContext context) { + DateTime date = DateTime.parse(widget.fileContent.updatedAt!); + String formattedDate = UiUtils.formatDate(date); + + return Container( + padding: const EdgeInsets.all(ContentPadding.padding_12), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + const Spacer(), + IconButton( + splashColor: UiUtils.transparent, + splashRadius: IconSize.size_16, + onPressed: () => Navigator.of(context).pop(), + icon: const Icon(Icons.close), + ), + ], + ), + Text( + widget.fileContent.name, + style: FontText.font_18, + ), + ContentSpacer.verticalSpacer_24, + Text(style: FontText.font_14, 'bucket: ${widget.bucket}'), + ContentSpacer.verticalSpacer_16, + Text(style: FontText.font_14, 'bucketId: ${widget.fileContent.bucketId}'), + ContentSpacer.verticalSpacer_16, + Text(style: FontText.font_14, 'owner: ${widget.fileContent.owner}'), + ContentSpacer.verticalSpacer_16, + Text(style: FontText.font_14, 'id: ${widget.fileContent.id}'), + ContentSpacer.verticalSpacer_16, + Text(style: FontText.font_14, 'updatedAt: $formattedDate'), + ContentSpacer.verticalSpacer_16, + Text(style: FontText.font_14, 'createdAt: ${widget.fileContent.createdAt}'), + ContentSpacer.verticalSpacer_16, + Text(style: FontText.font_14, 'lastAccessedAt: ${widget.fileContent.lastAccessedAt}'), + const Spacer(), + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + OutlinedButton( + onPressed: () => showDeleteDialog(), + style: ButtonStyle( + foregroundColor: MaterialStateProperty.all(UiUtils.error), + ), + child: const Text('Delete'), + ), + ContentSpacer.horizontalSpacer_16, + ElevatedButton( + onPressed: () => Navigator.of(context).pop(), + child: const Text('close'), + ), + ], + ) + ], + ), + ); + } +} \ No newline at end of file diff --git a/lib/src/user/presentation/user_dashboard_page/widgets/left_nav_bar.dart b/lib/src/user/presentation/user_dashboard_page/widgets/left_nav_bar.dart new file mode 100644 index 0000000..a6c0fed --- /dev/null +++ b/lib/src/user/presentation/user_dashboard_page/widgets/left_nav_bar.dart @@ -0,0 +1,125 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:skeletons/skeletons.dart'; + +import '../../../../widgets/ui_utils.dart'; +import '../../../application/bloc/user_bloc/user_bloc.dart'; +import '../../../domain/models/user.dart'; +import 'onboarding_info.dart'; +import 'upload_btn.dart'; + +class LeftNavigationBar extends StatefulWidget { + const LeftNavigationBar({super.key, required this.uid}); + + final String uid; + + @override + State createState() => _LeftNavigationBarState(); +} + +class _LeftNavigationBarState extends State { + int? _selectedIndex; + + @override + void initState() { + context.read().add(GetAndUpdateUserDataRequested(widget.uid)); + super.initState(); + } + + Widget userInfo(User user) { + return Container( + color: UiUtils.transparent, + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (!user.onboardCompleted) + OnboardInfo(stripeId: user.stripeId!), + const SizedBox(height: UiUtils.sizeM), + ], + ), + ); + } + + void listTiletap(int index) { + setState(() { + _selectedIndex = index; + }); + switch (index) { + case 1: + Navigator.pushNamed(context, '/orders'); + break; + case 2: + Navigator.pushNamed(context, '/ratings'); + break; + default: + break; + } + } + + @override + Widget build(BuildContext context) { + final List itemsList = [ + const UploadButton(), + const Text( + 'My Orders', + style: FontText.font_14, + ), + const Text( + 'My Reviews', + style: FontText.font_14, + ), + ]; + + return Container( + width: UiUtils.leftNavBarWidth, + decoration: const BoxDecoration( + border: Border( + right: BorderSide( + color: UiUtils.gray, + width: UiUtils.borderWidth_1, + ), + ), + ), + child: BlocBuilder( + builder: (BuildContext context, UserState state) { + return Skeleton( + isLoading: state is! UserLoaded, + skeleton: SkeletonAvatar( + style: SkeletonAvatarStyle( + width: UiUtils.leftNavBarWidth, + height: double.infinity, + padding: const EdgeInsets.all(8), + borderRadius: BorderRadius.circular(8), + ), + ), + child: Column( + children: [ + Expanded( + child: ListView.builder( + itemCount: itemsList.length, + itemBuilder: (BuildContext context, int index) { + if (index == 0) { + return itemsList[index]; + } + return ListTile( + selected: index == _selectedIndex ? true : false, + splashColor: UiUtils.transparent, + selectedColor: UiUtils.blue, + title: itemsList[index], + onTap: () => listTiletap(index), + ); + }, + ), + ), + const Spacer(), + if (state is UserLoaded) + userInfo(state.user), + ], + ), + ); + } + ), + ); + } +} diff --git a/lib/src/user/presentation/user_dashboard_page/widgets/onboarding_info.dart b/lib/src/user/presentation/user_dashboard_page/widgets/onboarding_info.dart new file mode 100644 index 0000000..8934727 --- /dev/null +++ b/lib/src/user/presentation/user_dashboard_page/widgets/onboarding_info.dart @@ -0,0 +1,39 @@ +import 'package:appflowy_theme_marketplace/src/widgets/ui_utils.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import '../../../../payment/application/payment_bloc/payment_bloc.dart'; + +class OnboardInfo extends StatelessWidget { + const OnboardInfo({super.key, required this.stripeId}); + final String stripeId; + + @override + Widget build(BuildContext context) { + return Column( + children: [ + const Text('You need to link your Stripe account', style: FontText.font_12), + const Text('To receive payment on our platform', style: FontText.font_12), + const Text('Please, finish the onboarding process here', style: FontText.font_12), + ContentSpacer.verticalSpacer_16, + BlocBuilder( + builder: (BuildContext context, PaymentState state) { + if (state is CreatingOnboardingSession) { + return const TextButton( + onPressed: null, + child: CircularProgressIndicator(), + ); + } + return ElevatedButton( + onPressed: () { + BlocProvider.of(context).add(CreateOnboardingLinkRequested(stripeId)); + }, + child: const Text('Onboard', style: FontText.font_12), + ); + }, + ), + + ], + ); + } +} \ No newline at end of file diff --git a/lib/src/user/presentation/user_dashboard_page/widgets/upload_btn.dart b/lib/src/user/presentation/user_dashboard_page/widgets/upload_btn.dart new file mode 100644 index 0000000..4e63b1c --- /dev/null +++ b/lib/src/user/presentation/user_dashboard_page/widgets/upload_btn.dart @@ -0,0 +1,31 @@ +import 'package:flutter/material.dart'; +import '../../../../widgets/ui_utils.dart'; +import './upload_btn_modal.dart'; + +class UploadButton extends StatelessWidget { + const UploadButton({super.key}); + + @override + Widget build(BuildContext context) { + return ElevatedButton( + style: ButtonStyle( + backgroundColor: MaterialStateProperty.all( + UiUtils.gray, + ), + ), + onPressed: () => showDialog( + context: context, + builder: (BuildContext context) => const UploadButtonModal(), + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: const [ + Icon(Icons.add, size: UiUtils.sizeL), + SizedBox(width: UiUtils.sizeS), + Text('Upload file'), + ], + ), + ); + } +} diff --git a/lib/src/user/presentation/user_dashboard_page/widgets/upload_btn_modal.dart b/lib/src/user/presentation/user_dashboard_page/widgets/upload_btn_modal.dart new file mode 100644 index 0000000..9faa8bf --- /dev/null +++ b/lib/src/user/presentation/user_dashboard_page/widgets/upload_btn_modal.dart @@ -0,0 +1,177 @@ +import 'package:appflowy_theme_marketplace/src/widgets/ui_utils.dart'; +import 'package:appflowy_theme_marketplace/src/widgets/error_dialog.dart'; +import 'package:file_picker/file_picker.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import '../../../../authentication/application/auth_bloc/auth_bloc.dart'; +import '../../../../widgets/snackbar_status.dart'; +import '../../../application/bloc/storage_bloc/storage_bloc.dart'; +import '../../../application/factories/user_factory.dart'; +import '../../../domain/models/picked_file.dart'; +import '../../../domain/models/user.dart'; + +class UploadButtonModal extends StatefulWidget { + const UploadButtonModal({super.key}); + + @override + State createState() => _UploadButtonModalState(); +} + +class _UploadButtonModalState extends State { + PickedFile? plugin; + double? price; + + @override + void dispose() { + super.dispose(); + } + + Future _pickFile() async { + final result = await FilePicker.platform.pickFiles(); + final bytes = result?.files.first.bytes; + if (bytes == null) throw Exception('No file selected'); + return PickedFile(bytes, result!.files.first.name); + } + + @override + Widget build(BuildContext context) { + final TextField priceInput = TextField( + keyboardType: TextInputType.number, + textInputAction: TextInputAction.next, + style: const TextStyle(fontSize: UiUtils.sizeM), + decoration: const InputDecoration( + labelText: 'price', + labelStyle: TextStyle(fontSize: UiUtils.sizeM), + suffixIcon: Icon(Icons.attach_money, size: UiUtils.sizeXL), + ), + onChanged: (value) => setState(() { + if (value == '') + price = null; + else { + try { + price = double.parse(value); + } on Exception catch (_) { + showDialog( + context: context, + builder: (BuildContext context) { + return const ErrorDialog(message: 'Invalid price value'); + }); + price = null; + } + } + }), + ); + final OutlinedButton uploadButtonArea = OutlinedButton( + onPressed: () async { + try { + final pickedFile = await _pickFile(); + setState(() { + plugin = pickedFile; + }); + } on Exception catch (e) { + debugPrint(e.toString()); + } + }, + style: ButtonStyle( + foregroundColor: MaterialStateProperty.all(Colors.white), + ), + child: SizedBox( + width: double.infinity, + height: 200, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Icon( + Icons.folder, + size: UiUtils.sizeXL * 2, + ), + const SizedBox(height: UiUtils.sizeL), + Text(plugin != null ? plugin!.name : 'Select a zip file to upload'), + ], + ), + ), + ); + + final Row uploadedFileInfo = Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Expanded( + child: TextField( + enabled: false, // Disable input + controller: + TextEditingController(text: plugin == null ? '' : plugin?.name), + decoration: const InputDecoration( + border: UnderlineInputBorder(), + ), + ), + ), + const SizedBox(width: UiUtils.sizeL), + SizedBox( + width: 100, + child: priceInput, + ), + ], + ); + + final AuthState userState = context.read().state; + late final User? user; + if (userState is AuthenticateSuccess) { + if (userState.user == null) { + throw Exception('user is undefined'); + } + user = UserFactory.fromAuth(userState.user!); + } + return AlertDialog( + title: const Text('Upload file'), + content: SizedBox( + width: 400, + height: 300, + child: BlocConsumer( + builder: (BuildContext context, StorageState state) { + if (state is StorageUploading) { + return const Center( + child: CircularProgressIndicator(), + ); + } else { + return Column( + children: [ + uploadButtonArea, + uploadedFileInfo, + ], + ); + } + }, + listener: (BuildContext context, StorageState state) { + if (state is StorageUploadSuccess) { + Navigator.pop(context); + final errorSnackbar = SnackbarSuccess(message: 'Upload Succeed'); + ScaffoldMessenger.of(context).showSnackBar(errorSnackbar); + } else if (state is StorageFailed) { + Navigator.pop(context); + final errorSnackbar = SnackbarError(message: state.message); + ScaffoldMessenger.of(context).showSnackBar(errorSnackbar); + } + }, + ), + ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: const Text('Cancel'), + ), + TextButton( + onPressed: (plugin != null && user != null) + ? () { + if (user == null) return; + context + .read() + .add(UploadFileRequested(user: user, plugin: plugin!, price: price!)); + } + : null, + child: const Text('Upload'), + ), + ], + ); + } +} diff --git a/lib/src/user/presentation/user_dashboard_page/widgets/upload_form.dart b/lib/src/user/presentation/user_dashboard_page/widgets/upload_form.dart new file mode 100644 index 0000000..f5d6756 --- /dev/null +++ b/lib/src/user/presentation/user_dashboard_page/widgets/upload_form.dart @@ -0,0 +1,60 @@ +import 'package:appflowy_theme_marketplace/src/plugins/application/plugin/plugin_bloc.dart'; +import 'package:appflowy_theme_marketplace/src/widgets/popup_dialog.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import '../../../../widgets/ui_utils.dart'; +import '../../../application/bloc/user_bloc/user_bloc.dart'; +import '../../../domain/models/plugin_file_object.dart'; +import '../../../domain/models/user.dart'; + +class UploadForm extends StatefulWidget { + const UploadForm({super.key, required this.file}); + + final PluginFileObject file; + + @override + State createState() => _UploadFormState(); +} + +class _UploadFormState extends State { + @override + Widget build(BuildContext context) { + final UserState userState = context.read().state; + final dialogWidth = MediaQuery.of(context).size.width / 2; + final dialogHeight = MediaQuery.of(context).size.height / 3; + return PopupDialog( + content: BlocBuilder( + builder: (context, state) { + return SizedBox( + width: dialogWidth, + height: dialogHeight, + child: Column( + children: [ + + ], + ), + ); + }, + ), + actions: [ + ElevatedButton( + onPressed: () => Navigator.of(context).pop(), + child: const Text('No'), + ), + ElevatedButton( + onPressed: () {}, + style: ButtonStyle( + backgroundColor: + MaterialStateProperty.all(UiUtils.error), + foregroundColor: + MaterialStateProperty.all(UiUtils.white), + ), + child: const Text('Yes'), + ), + ], + title: const Text('Are you Sure?'), + ); + } +} diff --git a/lib/src/widgets/error_dialog.dart b/lib/src/widgets/error_dialog.dart new file mode 100644 index 0000000..de408b4 --- /dev/null +++ b/lib/src/widgets/error_dialog.dart @@ -0,0 +1,34 @@ +import 'package:flutter/material.dart'; + +class ErrorDialog extends StatelessWidget { + const ErrorDialog({super.key, required this.message, title}) : title = title ?? 'Error message'; + + final String message; + final String title; + + @override + Widget build(BuildContext context) { + return AlertDialog( + title: Text(title), + content: Text( + message, + ), + actions: [ + TextButton( + style: TextButton.styleFrom( + textStyle: Theme.of(context).textTheme.labelLarge, + ), + child: const Text('Cancel'), + onPressed: () => Navigator.of(context).pop(), + ), + TextButton( + style: TextButton.styleFrom( + textStyle: Theme.of(context).textTheme.labelLarge, + ), + child: const Text('Understood'), + onPressed: () => Navigator.of(context).pop(), + ), + ], + ); + } +} diff --git a/lib/src/widgets/hover_opacity_widget.dart b/lib/src/widgets/hover_opacity_widget.dart new file mode 100644 index 0000000..1334a8d --- /dev/null +++ b/lib/src/widgets/hover_opacity_widget.dart @@ -0,0 +1,47 @@ + +import 'package:flutter/material.dart'; + +class HoverOpacityWidget extends StatefulWidget { + const HoverOpacityWidget({super.key, this.onTap, required this.child, padding}) : padding = padding ?? 0; + + final void Function()? onTap; + final Widget child; + final double padding; + + @override + State createState() => _HoverOpacityWidgetState(); +} + +class _HoverOpacityWidgetState extends State { + bool isHovered = false; + double opacity = 1; + + @override + Widget build(BuildContext context) { + return MouseRegion( + onEnter: (_) { + setState(() { + isHovered = true; + opacity = 0.5; + }); + }, + onExit: (_) { + setState(() { + isHovered = false; + opacity = 1; + }); + }, + child: GestureDetector( + onTap: widget.onTap, + child: Container( + padding: EdgeInsets.all(widget.padding), + child: AnimatedOpacity( + opacity: opacity, + duration: const Duration(milliseconds: 100), + child: widget.child, + ), + ) + ) + ); + } +} \ No newline at end of file diff --git a/lib/src/widgets/icon_button.dart b/lib/src/widgets/icon_button.dart new file mode 100644 index 0000000..79d7bff --- /dev/null +++ b/lib/src/widgets/icon_button.dart @@ -0,0 +1,20 @@ + +import 'package:flutter/material.dart'; +import 'ui_utils.dart'; + +class DefaulIconButton extends StatelessWidget { + const DefaulIconButton({super.key, this.onPressed, required this.icon}); + + final void Function()? onPressed; + final Icon icon; + + @override + Widget build(BuildContext context) { + return IconButton( + onPressed: onPressed, + icon: icon, + splashRadius: UiUtils.sizeXL, + splashColor: UiUtils.transparent, + ); + } +} \ No newline at end of file diff --git a/lib/src/widgets/page_layout.dart b/lib/src/widgets/page_layout.dart new file mode 100644 index 0000000..dd7d996 --- /dev/null +++ b/lib/src/widgets/page_layout.dart @@ -0,0 +1,119 @@ +import 'package:appflowy_theme_marketplace/src/widgets/ui_utils.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +import '../authentication/application/auth_bloc/auth_bloc.dart'; +import '../plugins/presentation/widgets/search_input.dart'; +import '../plugins/presentation/widgets/user_dropdown/user_dropdown.dart'; +import '../user/application/factories/user_factory.dart'; +import 'hover_opacity_widget.dart'; + +class PageLayout extends StatefulWidget { + const PageLayout({ + super.key, + this.body, + this.floatingActionButton, + this.drawerContent, + this.scaffoldKey, + }); + + final Widget? body; + final Widget? floatingActionButton; + final Widget? drawerContent; + final GlobalKey? scaffoldKey; + + @override + State createState() => _PageLayoutState(); +} + +class _PageLayoutState extends State { + + @override + void initState() { + super.initState(); + } + + @override + Widget build(BuildContext context) { + final curRoute = ModalRoute.of(context)?.settings.name; + final screenSize = MediaQuery.of(context).size.width; + String appTitle = screenSize > 800 ? 'Appflowy Marketplace' : ''; + + final List actions = [ + Padding( + padding: const EdgeInsets.all(UiUtils.sizeS), + child: BlocBuilder( + builder: (BuildContext context, AuthState state) { + if (state is Loading) { + return TextButton( + onPressed: () => {}, + child: const CircularProgressIndicator(), + ); + } else if (state is UnAuthenticated || + state is RegistrationSuccess) { + return TextButton( + onPressed: () => Navigator.pushNamed(context, '/signin'), + child: const Text('Signin'), + ); + } else if (state is AuthenticateSuccess) { + return Container( + padding: const EdgeInsets.symmetric( + vertical: UiUtils.sizeL, + horizontal: 0, + ), + width: 80, + child: UserDropDown( + user: UserFactory.authToPlugin(state.user!), + ), + ); + } else { + return const Text('Error'); + } + }, + ), + ) + ]; + + final AppBar appBar = AppBar( + automaticallyImplyLeading: false, + leading: null, + centerTitle: true, + toolbarHeight: 80, + title: Row( + children: [ + HoverOpacityWidget( + onTap: () => + curRoute != '/' ? Navigator.of(context).pushNamed('/') : null, + child: Row( + children: [ + const Icon(Icons.extension_sharp), + const SizedBox(width: 8), + Text( + appTitle, + style: const TextStyle( + fontSize: 18, + fontWeight: FontWeight.normal, + ), + ), + ], + ), + ), + const Spacer(), + curRoute == '/' ? const SearchInput() : const SizedBox(), + const Spacer(), + ], + ), + actions: actions, + ); + + return SelectionArea( + child: Scaffold( + key: widget.scaffoldKey, + appBar: appBar, + body: widget.body, + floatingActionButton: widget.floatingActionButton, + endDrawer: Drawer(child: widget.drawerContent), + ), + ); + } +} \ No newline at end of file diff --git a/lib/src/widgets/popup_dialog.dart b/lib/src/widgets/popup_dialog.dart new file mode 100644 index 0000000..8e86287 --- /dev/null +++ b/lib/src/widgets/popup_dialog.dart @@ -0,0 +1,22 @@ +import 'package:flutter/material.dart'; + +class PopupDialog extends StatelessWidget { + const PopupDialog({super.key, required this.content, required this.actions, required this.title}); + + final Widget content; + final Widget title; + final List? actions; + + @override + Widget build(BuildContext context) { + return AlertDialog( + title: SelectionArea(child: title), + content: SelectionArea( + child: SingleChildScrollView( + child: content, + ), + ), + actions: actions, + ); + } +} diff --git a/lib/src/widgets/snackbar_status.dart b/lib/src/widgets/snackbar_status.dart new file mode 100644 index 0000000..8b897f2 --- /dev/null +++ b/lib/src/widgets/snackbar_status.dart @@ -0,0 +1,54 @@ +import 'package:flutter/material.dart'; + +class SnackbarSuccess extends SnackBar { + SnackbarSuccess({ + Key? key, + required String message, + IconData iconData = Icons.check_circle, + }) : super( + key: key, + content: Row( + children: [ + Icon( + iconData, + color: Colors.white, + ), + Text( + message, + style: const TextStyle( + color: Colors.white, + ), + ), + ] + ), + backgroundColor: Colors.green[400], + ); + +} + +class SnackbarError extends SnackBar { + SnackbarError({ + Key? key, + required String message, + IconData iconData = Icons.error, + }) : super( + key: key, + content: Row( + children: [ + Icon( + iconData, + color: Colors.white, + ), + const SizedBox(width:8), + Text( + message, + style: const TextStyle( + color: Colors.white, + ), + ), + ], + ), + backgroundColor: Colors.red, + ); + +} \ No newline at end of file diff --git a/lib/src/widgets/star_rating_bar.dart b/lib/src/widgets/star_rating_bar.dart new file mode 100644 index 0000000..f7ade26 --- /dev/null +++ b/lib/src/widgets/star_rating_bar.dart @@ -0,0 +1,33 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_rating_bar/flutter_rating_bar.dart'; + +import 'ui_utils.dart'; + +class StarRatingBar extends StatelessWidget { + const StarRatingBar({super.key, required this.rating, this.onRatingUpdate}); + + final double rating; + final void Function(double)? onRatingUpdate; + + @override + Widget build(BuildContext context) { + return RatingBar.builder( + initialRating: rating, + minRating: 1, + direction: Axis.horizontal, + ignoreGestures: true, + allowHalfRating: true, + itemCount: 5, + itemSize: UiUtils.sizeM, + itemPadding: const EdgeInsets.symmetric( + horizontal: 0.0, + vertical: 0.0, + ), + itemBuilder: (context, _) => const Icon( + Icons.star, + color: Colors.amber, + ), + onRatingUpdate: (r) {}, + ); + } +} \ No newline at end of file diff --git a/lib/src/widgets/ui_utils.dart b/lib/src/widgets/ui_utils.dart new file mode 100644 index 0000000..624af60 --- /dev/null +++ b/lib/src/widgets/ui_utils.dart @@ -0,0 +1,163 @@ +import 'package:flutter/material.dart'; +import 'package:intl/intl.dart'; + +class HeaderSize { + static const double h1 = 12; + static const double h2 = 16; + static const double h3 = 20; + static const double h4 = 24; + static const double h5 = 32; + static const double h6 = 36; +} + +class IconSize { + static const double size_8 = 8; + static const double size_12 = 12; + static const double size_16 = 16; + static const double size_20 = 20; + static const double size_24 = 24; + static const double size_32 = 32; +} + +class FontText { + static const TextStyle font_12 = TextStyle(fontSize: 12); + static const TextStyle font_14 = TextStyle(fontSize: 14); + static const TextStyle font_16 = TextStyle(fontSize: 16); + static const TextStyle font_18 = TextStyle(fontSize: 18); + static const TextStyle font_20 = TextStyle(fontSize: 20); + static const TextStyle font_24 = TextStyle(fontSize: 24); +} + +class FontSize { + static const double xs = 8; + static const double s = 12; + static const double m = 16; + static const double l = 20; + static const double xl = 24; + static const double xxl = 32; +} + +class ContentPadding { + static const double padding_4 = 4; + static const double padding_8 = 8; + static const double padding_12 = 12; + static const double padding_16 = 16; + static const double padding_20 = 20; + static const double padding_24 = 24; +} + +class ContentSpacer { + static const Widget verticalSpacer_16 = SizedBox(height: 16); + static const Widget verticalSpacer_24 = SizedBox(height: 24); + static const Widget verticalSpacer_32 = SizedBox(height: 32); + static const Widget verticalSpacer_48 = SizedBox(height: 48); + static const Widget verticalSpacer_64 = SizedBox(height: 64); + + static const Widget horizontalSpacer_16 = SizedBox(width: 16); + static const Widget horizontalSpacer_24 = SizedBox(width: 24); + static const Widget horizontalSpacer_32 = SizedBox(width: 32); + static const Widget horizontalSpacer_48 = SizedBox(width: 48); + static const Widget horizontalSpacer_64 = SizedBox(width: 64); +} + +enum ScreenSize{ + small(size: 420,numCards: 1), + medium(size: 600, numCards: 2), + large(size: 1000, numCards: 3), + largest(size: 1001, numCards: 5); + + const ScreenSize({ + required this.size, + required this.numCards, + }); + + final int size; + final int numCards; + + factory ScreenSize.from(double size) { + for (final value in ScreenSize.values) { + if (size < value.size) return value; + } + return largest; + } +} + + +class UiUtils { + + static const String lightJsonTheme = '.light.json'; + static const String darkJsonTheme = '.dark.json'; + static const String plugins = '.plugin'; + + static const String defaultEmail = 'Guest@appflowy.io'; + static const double screenSizeSmall = 420; + static const double screenSizeMedium = 600; + static const double screenSizeLarge = 1000; + + static const double sizeXS = 4; + static const double sizeS = 8; + static const double sizeM = 12; + static const double sizeL = 16; + static const double sizeXL = 24; + static const double sizeXXL = 32; + + static const Color color = Colors.black; + static const Color gray = Color.fromRGBO(66, 66, 66, 1); + static const Color blue = Colors.blue; + static const Color error = Colors.red; + static const Color success = Colors.green; + static const Color white = Colors.white; + static const Color transparent = Colors.transparent; + + static const double leftNavBarWidth = 300; + static const double borderWidth_1 = 1; + + static String formatDate(DateTime date) { + final DateFormat formatter = DateFormat('MM/dd/yyyy'); + return(formatter.format(date).toString()); + } + + static double calculateCardSize(double screenSize) { + if (screenSize < screenSizeSmall) { + return screenSize; + } else if (screenSize < screenSizeMedium) { + return screenSize / 2; + } else if (screenSize < screenSizeLarge) { + return screenSize / 3; + } else { + return screenSize / 5; + } + } + + static int calCardsPerpage(double screenSize) { + if (screenSize < screenSizeSmall) { + return 1; + } else if (screenSize < screenSizeMedium) { + return 2; + } else if (screenSize < screenSizeLarge) { + return 3; + } else { + return 5; + } + } + + static String screenType(double screenSize) { + if (screenSize < screenSizeSmall) { + return 'S'; + } else if (screenSize < screenSizeMedium) { + return 'M'; + } else if (screenSize < screenSizeLarge) { + return 'L'; + } else { + return 'XL'; + } + } + + static Future delayLoading([int milliseconds = 300]) async { + await Future.delayed(Duration(milliseconds: milliseconds), () {}); + } + + static String defaultUsername(String email) { + return email.split('@')[0]; + } +} diff --git a/linux/.gitignore b/linux/.gitignore new file mode 100644 index 0000000..d3896c9 --- /dev/null +++ b/linux/.gitignore @@ -0,0 +1 @@ +flutter/ephemeral diff --git a/linux/CMakeLists.txt b/linux/CMakeLists.txt new file mode 100644 index 0000000..1c11b1a --- /dev/null +++ b/linux/CMakeLists.txt @@ -0,0 +1,138 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.10) +project(runner LANGUAGES CXX) + +# The name of the executable created for the application. Change this to change +# the on-disk name of your application. +set(BINARY_NAME "appflowy_theme_marketplace") +# The unique GTK application identifier for this application. See: +# https://wiki.gnome.org/HowDoI/ChooseApplicationID +set(APPLICATION_ID "com.example.appflowy_theme_marketplace") + +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. +cmake_policy(SET CMP0063 NEW) + +# Load bundled libraries from the lib/ directory relative to the binary. +set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") + +# Root filesystem for cross-building. +if(FLUTTER_TARGET_PLATFORM_SYSROOT) + set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT}) + set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT}) + set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) + set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) +endif() + +# Define build configuration options. +if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") +endif() + +# Compilation settings that should be applied to most targets. +# +# Be cautious about adding new options here, as plugins use this function by +# default. In most cases, you should add new options to specific targets instead +# of modifying this function. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_14) + target_compile_options(${TARGET} PRIVATE -Wall -Werror) + target_compile_options(${TARGET} PRIVATE "$<$>:-O3>") + target_compile_definitions(${TARGET} PRIVATE "$<$>:NDEBUG>") +endfunction() + +# Flutter library and tool build rules. +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# System-level dependencies. +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) + +add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}") + +# Define the application target. To change its name, change BINARY_NAME above, +# not the value here, or `flutter run` will no longer work. +# +# Any new source files that you add to the application should be added here. +add_executable(${BINARY_NAME} + "main.cc" + "my_application.cc" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" +) + +# Apply the standard set of build settings. This can be removed for applications +# that need different build settings. +apply_standard_settings(${BINARY_NAME}) + +# Add dependency libraries. Add any application-specific dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter) +target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK) + +# Run the Flutter tool portions of the build. This must not be removed. +add_dependencies(${BINARY_NAME} flutter_assemble) + +# Only the install-generated bundle's copy of the executable will launch +# correctly, since the resources must in the right relative locations. To avoid +# people trying to run the unbundled copy, put it in a subdirectory instead of +# the default top-level location. +set_target_properties(${BINARY_NAME} + PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run" +) + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# By default, "installing" just makes a relocatable bundle in the build +# directory. +set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle") +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +# Start with a clean build bundle directory every time. +install(CODE " + file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\") + " COMPONENT Runtime) + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES}) + install(FILES "${bundled_library}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endforeach(bundled_library) + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +if(NOT CMAKE_BUILD_TYPE MATCHES "Debug") + install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() diff --git a/linux/flutter/CMakeLists.txt b/linux/flutter/CMakeLists.txt new file mode 100644 index 0000000..d5bd016 --- /dev/null +++ b/linux/flutter/CMakeLists.txt @@ -0,0 +1,88 @@ +# This file controls Flutter-level build steps. It should not be edited. +cmake_minimum_required(VERSION 3.10) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. + +# Serves the same purpose as list(TRANSFORM ... PREPEND ...), +# which isn't available in 3.10. +function(list_prepend LIST_NAME PREFIX) + set(NEW_LIST "") + foreach(element ${${LIST_NAME}}) + list(APPEND NEW_LIST "${PREFIX}${element}") + endforeach(element) + set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE) +endfunction() + +# === Flutter Library === +# System-level dependencies. +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) +pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0) +pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0) + +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "fl_basic_message_channel.h" + "fl_binary_codec.h" + "fl_binary_messenger.h" + "fl_dart_project.h" + "fl_engine.h" + "fl_json_message_codec.h" + "fl_json_method_codec.h" + "fl_message_codec.h" + "fl_method_call.h" + "fl_method_channel.h" + "fl_method_codec.h" + "fl_method_response.h" + "fl_plugin_registrar.h" + "fl_plugin_registry.h" + "fl_standard_message_codec.h" + "fl_standard_method_codec.h" + "fl_string_codec.h" + "fl_value.h" + "fl_view.h" + "flutter_linux.h" +) +list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}") +target_link_libraries(flutter INTERFACE + PkgConfig::GTK + PkgConfig::GLIB + PkgConfig::GIO +) +add_dependencies(flutter flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CMAKE_CURRENT_BINARY_DIR}/_phony_ + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh" + ${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE} + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} +) diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc new file mode 100644 index 0000000..f6f23bf --- /dev/null +++ b/linux/flutter/generated_plugin_registrant.cc @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + +#include + +void fl_register_plugins(FlPluginRegistry* registry) { + g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); + url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); +} diff --git a/linux/flutter/generated_plugin_registrant.h b/linux/flutter/generated_plugin_registrant.h new file mode 100644 index 0000000..e0f0a47 --- /dev/null +++ b/linux/flutter/generated_plugin_registrant.h @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void fl_register_plugins(FlPluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake new file mode 100644 index 0000000..f16b4c3 --- /dev/null +++ b/linux/flutter/generated_plugins.cmake @@ -0,0 +1,24 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST + url_launcher_linux +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/linux/main.cc b/linux/main.cc new file mode 100644 index 0000000..e7c5c54 --- /dev/null +++ b/linux/main.cc @@ -0,0 +1,6 @@ +#include "my_application.h" + +int main(int argc, char** argv) { + g_autoptr(MyApplication) app = my_application_new(); + return g_application_run(G_APPLICATION(app), argc, argv); +} diff --git a/linux/my_application.cc b/linux/my_application.cc new file mode 100644 index 0000000..18e7f0b --- /dev/null +++ b/linux/my_application.cc @@ -0,0 +1,104 @@ +#include "my_application.h" + +#include +#ifdef GDK_WINDOWING_X11 +#include +#endif + +#include "flutter/generated_plugin_registrant.h" + +struct _MyApplication { + GtkApplication parent_instance; + char** dart_entrypoint_arguments; +}; + +G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION) + +// Implements GApplication::activate. +static void my_application_activate(GApplication* application) { + MyApplication* self = MY_APPLICATION(application); + GtkWindow* window = + GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application))); + + // Use a header bar when running in GNOME as this is the common style used + // by applications and is the setup most users will be using (e.g. Ubuntu + // desktop). + // If running on X and not using GNOME then just use a traditional title bar + // in case the window manager does more exotic layout, e.g. tiling. + // If running on Wayland assume the header bar will work (may need changing + // if future cases occur). + gboolean use_header_bar = TRUE; +#ifdef GDK_WINDOWING_X11 + GdkScreen* screen = gtk_window_get_screen(window); + if (GDK_IS_X11_SCREEN(screen)) { + const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen); + if (g_strcmp0(wm_name, "GNOME Shell") != 0) { + use_header_bar = FALSE; + } + } +#endif + if (use_header_bar) { + GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); + gtk_widget_show(GTK_WIDGET(header_bar)); + gtk_header_bar_set_title(header_bar, "appflowy_theme_marketplace"); + gtk_header_bar_set_show_close_button(header_bar, TRUE); + gtk_window_set_titlebar(window, GTK_WIDGET(header_bar)); + } else { + gtk_window_set_title(window, "appflowy_theme_marketplace"); + } + + gtk_window_set_default_size(window, 1280, 720); + gtk_widget_show(GTK_WIDGET(window)); + + g_autoptr(FlDartProject) project = fl_dart_project_new(); + fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments); + + FlView* view = fl_view_new(project); + gtk_widget_show(GTK_WIDGET(view)); + gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view)); + + fl_register_plugins(FL_PLUGIN_REGISTRY(view)); + + gtk_widget_grab_focus(GTK_WIDGET(view)); +} + +// Implements GApplication::local_command_line. +static gboolean my_application_local_command_line(GApplication* application, gchar*** arguments, int* exit_status) { + MyApplication* self = MY_APPLICATION(application); + // Strip out the first argument as it is the binary name. + self->dart_entrypoint_arguments = g_strdupv(*arguments + 1); + + g_autoptr(GError) error = nullptr; + if (!g_application_register(application, nullptr, &error)) { + g_warning("Failed to register: %s", error->message); + *exit_status = 1; + return TRUE; + } + + g_application_activate(application); + *exit_status = 0; + + return TRUE; +} + +// Implements GObject::dispose. +static void my_application_dispose(GObject* object) { + MyApplication* self = MY_APPLICATION(object); + g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev); + G_OBJECT_CLASS(my_application_parent_class)->dispose(object); +} + +static void my_application_class_init(MyApplicationClass* klass) { + G_APPLICATION_CLASS(klass)->activate = my_application_activate; + G_APPLICATION_CLASS(klass)->local_command_line = my_application_local_command_line; + G_OBJECT_CLASS(klass)->dispose = my_application_dispose; +} + +static void my_application_init(MyApplication* self) {} + +MyApplication* my_application_new() { + return MY_APPLICATION(g_object_new(my_application_get_type(), + "application-id", APPLICATION_ID, + "flags", G_APPLICATION_NON_UNIQUE, + nullptr)); +} diff --git a/linux/my_application.h b/linux/my_application.h new file mode 100644 index 0000000..72271d5 --- /dev/null +++ b/linux/my_application.h @@ -0,0 +1,18 @@ +#ifndef FLUTTER_MY_APPLICATION_H_ +#define FLUTTER_MY_APPLICATION_H_ + +#include + +G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION, + GtkApplication) + +/** + * my_application_new: + * + * Creates a new Flutter-based application. + * + * Returns: a new #MyApplication. + */ +MyApplication* my_application_new(); + +#endif // FLUTTER_MY_APPLICATION_H_ diff --git a/macos/.gitignore b/macos/.gitignore new file mode 100644 index 0000000..746adbb --- /dev/null +++ b/macos/.gitignore @@ -0,0 +1,7 @@ +# Flutter-related +**/Flutter/ephemeral/ +**/Pods/ + +# Xcode-related +**/dgph +**/xcuserdata/ diff --git a/macos/Flutter/Flutter-Debug.xcconfig b/macos/Flutter/Flutter-Debug.xcconfig new file mode 100644 index 0000000..c2efd0b --- /dev/null +++ b/macos/Flutter/Flutter-Debug.xcconfig @@ -0,0 +1 @@ +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/macos/Flutter/Flutter-Release.xcconfig b/macos/Flutter/Flutter-Release.xcconfig new file mode 100644 index 0000000..c2efd0b --- /dev/null +++ b/macos/Flutter/Flutter-Release.xcconfig @@ -0,0 +1 @@ +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift new file mode 100644 index 0000000..b7428fe --- /dev/null +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -0,0 +1,30 @@ +// +// Generated file. Do not edit. +// + +import FlutterMacOS +import Foundation + +import app_links +import cloud_firestore +import cloud_functions +import firebase_auth +import firebase_core +import firebase_storage +import path_provider_foundation +import shared_preferences_foundation +import sign_in_with_apple +import url_launcher_macos + +func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + AppLinksMacosPlugin.register(with: registry.registrar(forPlugin: "AppLinksMacosPlugin")) + FLTFirebaseFirestorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseFirestorePlugin")) + FLTFirebaseFunctionsPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseFunctionsPlugin")) + FLTFirebaseAuthPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseAuthPlugin")) + FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin")) + FLTFirebaseStoragePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseStoragePlugin")) + PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) + SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) + SignInWithApplePlugin.register(with: registry.registrar(forPlugin: "SignInWithApplePlugin")) + UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) +} diff --git a/macos/Runner.xcodeproj/project.pbxproj b/macos/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000..689ee32 --- /dev/null +++ b/macos/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,573 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXAggregateTarget section */ + 33CC111A2044C6BA0003C045 /* Flutter Assemble */ = { + isa = PBXAggregateTarget; + buildConfigurationList = 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */; + buildPhases = ( + 33CC111E2044C6BF0003C045 /* ShellScript */, + ); + dependencies = ( + ); + name = "Flutter Assemble"; + productName = FLX; + }; +/* End PBXAggregateTarget section */ + +/* Begin PBXBuildFile section */ + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; }; + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC111A2044C6BA0003C045; + remoteInfo = FLX; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 33CC110E2044A8840003C045 /* Bundle Framework */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Bundle Framework"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; + 33CC10ED2044A3C60003C045 /* appflowy_theme_marketplace.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "appflowy_theme_marketplace.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; + 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; + 33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = ""; }; + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = ""; }; + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = ""; }; + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = ""; }; + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = ""; }; + 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; }; + 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; + 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 33CC10EA2044A3C60003C045 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 33BA886A226E78AF003329D5 /* Configs */ = { + isa = PBXGroup; + children = ( + 33E5194F232828860026EE4D /* AppInfo.xcconfig */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */, + ); + path = Configs; + sourceTree = ""; + }; + 33CC10E42044A3C60003C045 = { + isa = PBXGroup; + children = ( + 33FAB671232836740065AC1E /* Runner */, + 33CEB47122A05771004F2AC0 /* Flutter */, + 33CC10EE2044A3C60003C045 /* Products */, + D73912EC22F37F3D000D13A0 /* Frameworks */, + ); + sourceTree = ""; + }; + 33CC10EE2044A3C60003C045 /* Products */ = { + isa = PBXGroup; + children = ( + 33CC10ED2044A3C60003C045 /* appflowy_theme_marketplace.app */, + ); + name = Products; + sourceTree = ""; + }; + 33CC11242044D66E0003C045 /* Resources */ = { + isa = PBXGroup; + children = ( + 33CC10F22044A3C60003C045 /* Assets.xcassets */, + 33CC10F42044A3C60003C045 /* MainMenu.xib */, + 33CC10F72044A3C60003C045 /* Info.plist */, + ); + name = Resources; + path = ..; + sourceTree = ""; + }; + 33CEB47122A05771004F2AC0 /* Flutter */ = { + isa = PBXGroup; + children = ( + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */, + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */, + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */, + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */, + ); + path = Flutter; + sourceTree = ""; + }; + 33FAB671232836740065AC1E /* Runner */ = { + isa = PBXGroup; + children = ( + 33CC10F02044A3C60003C045 /* AppDelegate.swift */, + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */, + 33E51913231747F40026EE4D /* DebugProfile.entitlements */, + 33E51914231749380026EE4D /* Release.entitlements */, + 33CC11242044D66E0003C045 /* Resources */, + 33BA886A226E78AF003329D5 /* Configs */, + ); + path = Runner; + sourceTree = ""; + }; + D73912EC22F37F3D000D13A0 /* Frameworks */ = { + isa = PBXGroup; + children = ( + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 33CC10EC2044A3C60003C045 /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 33CC10E92044A3C60003C045 /* Sources */, + 33CC10EA2044A3C60003C045 /* Frameworks */, + 33CC10EB2044A3C60003C045 /* Resources */, + 33CC110E2044A8840003C045 /* Bundle Framework */, + 3399D490228B24CF009A79C7 /* ShellScript */, + ); + buildRules = ( + ); + dependencies = ( + 33CC11202044C79F0003C045 /* PBXTargetDependency */, + ); + name = Runner; + productName = Runner; + productReference = 33CC10ED2044A3C60003C045 /* appflowy_theme_marketplace.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 33CC10E52044A3C60003C045 /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 0920; + LastUpgradeCheck = 1300; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 33CC10EC2044A3C60003C045 = { + CreatedOnToolsVersion = 9.2; + LastSwiftMigration = 1100; + ProvisioningStyle = Automatic; + SystemCapabilities = { + com.apple.Sandbox = { + enabled = 1; + }; + }; + }; + 33CC111A2044C6BA0003C045 = { + CreatedOnToolsVersion = 9.2; + ProvisioningStyle = Manual; + }; + }; + }; + buildConfigurationList = 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 33CC10E42044A3C60003C045; + productRefGroup = 33CC10EE2044A3C60003C045 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 33CC10EC2044A3C60003C045 /* Runner */, + 33CC111A2044C6BA0003C045 /* Flutter Assemble */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 33CC10EB2044A3C60003C045 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */, + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3399D490228B24CF009A79C7 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n"; + }; + 33CC111E2044C6BF0003C045 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + Flutter/ephemeral/FlutterInputs.xcfilelist, + ); + inputPaths = ( + Flutter/ephemeral/tripwire, + ); + outputFileListPaths = ( + Flutter/ephemeral/FlutterOutputs.xcfilelist, + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 33CC10E92044A3C60003C045 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */, + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */, + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 33CC11202044C79F0003C045 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */; + targetProxy = 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 33CC10F42044A3C60003C045 /* MainMenu.xib */ = { + isa = PBXVariantGroup; + children = ( + 33CC10F52044A3C60003C045 /* Base */, + ); + name = MainMenu.xib; + path = Runner; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 338D0CE9231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.14; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Profile; + }; + 338D0CEA231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Profile; + }; + 338D0CEB231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Profile; + }; + 33CC10F92044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.14; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 33CC10FA2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.14; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Release; + }; + 33CC10FC2044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 33CC10FD2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; + 33CC111C2044C6BA0003C045 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 33CC111D2044C6BA0003C045 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10F92044A3C60003C045 /* Debug */, + 33CC10FA2044A3C60003C045 /* Release */, + 338D0CE9231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10FC2044A3C60003C045 /* Debug */, + 33CC10FD2044A3C60003C045 /* Release */, + 338D0CEA231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC111C2044C6BA0003C045 /* Debug */, + 33CC111D2044C6BA0003C045 /* Release */, + 338D0CEB231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 33CC10E52044A3C60003C045 /* Project object */; +} diff --git a/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000..f094031 --- /dev/null +++ b/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/macos/Runner.xcworkspace/contents.xcworkspacedata b/macos/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..1d526a1 --- /dev/null +++ b/macos/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/macos/Runner/AppDelegate.swift b/macos/Runner/AppDelegate.swift new file mode 100644 index 0000000..d53ef64 --- /dev/null +++ b/macos/Runner/AppDelegate.swift @@ -0,0 +1,9 @@ +import Cocoa +import FlutterMacOS + +@NSApplicationMain +class AppDelegate: FlutterAppDelegate { + override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { + return true + } +} diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000..a2ec33f --- /dev/null +++ b/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,68 @@ +{ + "images" : [ + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_16.png", + "scale" : "1x" + }, + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "2x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "1x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_64.png", + "scale" : "2x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_128.png", + "scale" : "1x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "2x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "1x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "2x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "1x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_1024.png", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png new file mode 100644 index 0000000..82b6f9d Binary files /dev/null and b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png differ diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png new file mode 100644 index 0000000..13b35eb Binary files /dev/null and b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png differ diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png new file mode 100644 index 0000000..0a3f5fa Binary files /dev/null and b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png differ diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png new file mode 100644 index 0000000..bdb5722 Binary files /dev/null and b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png differ diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png new file mode 100644 index 0000000..f083318 Binary files /dev/null and b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png differ diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png new file mode 100644 index 0000000..326c0e7 Binary files /dev/null and b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png differ diff --git a/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png new file mode 100644 index 0000000..2f1632c Binary files /dev/null and b/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png differ diff --git a/macos/Runner/Base.lproj/MainMenu.xib b/macos/Runner/Base.lproj/MainMenu.xib new file mode 100644 index 0000000..80e867a --- /dev/null +++ b/macos/Runner/Base.lproj/MainMenu.xib @@ -0,0 +1,343 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/macos/Runner/Configs/AppInfo.xcconfig b/macos/Runner/Configs/AppInfo.xcconfig new file mode 100644 index 0000000..10dff83 --- /dev/null +++ b/macos/Runner/Configs/AppInfo.xcconfig @@ -0,0 +1,14 @@ +// Application-level settings for the Runner target. +// +// This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the +// future. If not, the values below would default to using the project name when this becomes a +// 'flutter create' template. + +// The application's name. By default this is also the title of the Flutter window. +PRODUCT_NAME = appflowy_theme_marketplace + +// The application's bundle identifier +PRODUCT_BUNDLE_IDENTIFIER = com.example.appflowyThemeMarketplace + +// The copyright displayed in application information +PRODUCT_COPYRIGHT = Copyright © 2023 com.example. All rights reserved. diff --git a/macos/Runner/Configs/Debug.xcconfig b/macos/Runner/Configs/Debug.xcconfig new file mode 100644 index 0000000..36b0fd9 --- /dev/null +++ b/macos/Runner/Configs/Debug.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Debug.xcconfig" +#include "Warnings.xcconfig" diff --git a/macos/Runner/Configs/Release.xcconfig b/macos/Runner/Configs/Release.xcconfig new file mode 100644 index 0000000..dff4f49 --- /dev/null +++ b/macos/Runner/Configs/Release.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Release.xcconfig" +#include "Warnings.xcconfig" diff --git a/macos/Runner/Configs/Warnings.xcconfig b/macos/Runner/Configs/Warnings.xcconfig new file mode 100644 index 0000000..42bcbf4 --- /dev/null +++ b/macos/Runner/Configs/Warnings.xcconfig @@ -0,0 +1,13 @@ +WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings +GCC_WARN_UNDECLARED_SELECTOR = YES +CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES +CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE +CLANG_WARN__DUPLICATE_METHOD_MATCH = YES +CLANG_WARN_PRAGMA_PACK = YES +CLANG_WARN_STRICT_PROTOTYPES = YES +CLANG_WARN_COMMA = YES +GCC_WARN_STRICT_SELECTOR_MATCH = YES +CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES +CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES +GCC_WARN_SHADOW = YES +CLANG_WARN_UNREACHABLE_CODE = YES diff --git a/macos/Runner/DebugProfile.entitlements b/macos/Runner/DebugProfile.entitlements new file mode 100644 index 0000000..dddb8a3 --- /dev/null +++ b/macos/Runner/DebugProfile.entitlements @@ -0,0 +1,12 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.cs.allow-jit + + com.apple.security.network.server + + + diff --git a/macos/Runner/GoogleService-Info.plist b/macos/Runner/GoogleService-Info.plist new file mode 100644 index 0000000..e4a9db6 --- /dev/null +++ b/macos/Runner/GoogleService-Info.plist @@ -0,0 +1,34 @@ + + + + + CLIENT_ID + 806787763701-en62uhjnm7lr09j09u49lg9d70hfkl6l.apps.googleusercontent.com + REVERSED_CLIENT_ID + com.googleusercontent.apps.806787763701-en62uhjnm7lr09j09u49lg9d70hfkl6l + API_KEY + AIzaSyCI77BNBYn4xhi6jlz0oNimd5IPz6mgye4 + GCM_SENDER_ID + 806787763701 + PLIST_VERSION + 1 + BUNDLE_ID + com.example.appflowyThemeMarketplace + PROJECT_ID + appflowy-theme-marketpla-e8f9b + STORAGE_BUCKET + appflowy-theme-marketpla-e8f9b.appspot.com + IS_ADS_ENABLED + + IS_ANALYTICS_ENABLED + + IS_APPINVITE_ENABLED + + IS_GCM_ENABLED + + IS_SIGNIN_ENABLED + + GOOGLE_APP_ID + 1:806787763701:ios:6519fad4c098674b9664bc + + \ No newline at end of file diff --git a/macos/Runner/Info.plist b/macos/Runner/Info.plist new file mode 100644 index 0000000..4789daa --- /dev/null +++ b/macos/Runner/Info.plist @@ -0,0 +1,32 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIconFile + + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSMinimumSystemVersion + $(MACOSX_DEPLOYMENT_TARGET) + NSHumanReadableCopyright + $(PRODUCT_COPYRIGHT) + NSMainNibFile + MainMenu + NSPrincipalClass + NSApplication + + diff --git a/macos/Runner/MainFlutterWindow.swift b/macos/Runner/MainFlutterWindow.swift new file mode 100644 index 0000000..2722837 --- /dev/null +++ b/macos/Runner/MainFlutterWindow.swift @@ -0,0 +1,15 @@ +import Cocoa +import FlutterMacOS + +class MainFlutterWindow: NSWindow { + override func awakeFromNib() { + let flutterViewController = FlutterViewController.init() + let windowFrame = self.frame + self.contentViewController = flutterViewController + self.setFrame(windowFrame, display: true) + + RegisterGeneratedPlugins(registry: flutterViewController) + + super.awakeFromNib() + } +} diff --git a/macos/Runner/Release.entitlements b/macos/Runner/Release.entitlements new file mode 100644 index 0000000..852fa1a --- /dev/null +++ b/macos/Runner/Release.entitlements @@ -0,0 +1,8 @@ + + + + + com.apple.security.app-sandbox + + + diff --git a/macos/firebase_app_id_file.json b/macos/firebase_app_id_file.json new file mode 100644 index 0000000..1604dbb --- /dev/null +++ b/macos/firebase_app_id_file.json @@ -0,0 +1,7 @@ +{ + "file_generated_by": "FlutterFire CLI", + "purpose": "FirebaseAppID & ProjectID for this Firebase app in this directory", + "GOOGLE_APP_ID": "1:806787763701:ios:6519fad4c098674b9664bc", + "FIREBASE_PROJECT_ID": "appflowy-theme-marketpla-e8f9b", + "GCM_SENDER_ID": "806787763701" +} \ No newline at end of file diff --git a/pubspec.lock b/pubspec.lock new file mode 100644 index 0000000..6c8ee26 --- /dev/null +++ b/pubspec.lock @@ -0,0 +1,1218 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + _fe_analyzer_shared: + dependency: transitive + description: + name: _fe_analyzer_shared + sha256: ae92f5d747aee634b87f89d9946000c2de774be1d6ac3e58268224348cd0101a + url: "https://pub.dev" + source: hosted + version: "61.0.0" + _flutterfire_internals: + dependency: transitive + description: + name: _flutterfire_internals + sha256: a742f71d7f3484253a623b30e19256aa4668ecbb3de6ad1beb0bcf8d4777ecd8 + url: "https://pub.dev" + source: hosted + version: "1.3.3" + analyzer: + dependency: transitive + description: + name: analyzer + sha256: ea3d8652bda62982addfd92fdc2d0214e5f82e43325104990d4f4c4a2a313562 + url: "https://pub.dev" + source: hosted + version: "5.13.0" + app_links: + dependency: transitive + description: + name: app_links + sha256: "16725e716afd0634a5441654b1dda2b6c5557aa230884b5e1f41a5aa546a4cb6" + url: "https://pub.dev" + source: hosted + version: "3.4.3" + archive: + dependency: "direct main" + description: + name: archive + sha256: "0c8368c9b3f0abbc193b9d6133649a614204b528982bebc7026372d61677ce3a" + url: "https://pub.dev" + source: hosted + version: "3.3.7" + args: + dependency: transitive + description: + name: args + sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596 + url: "https://pub.dev" + source: hosted + version: "2.4.2" + async: + dependency: transitive + description: + name: async + sha256: bfe67ef28df125b7dddcea62755991f807aa39a2492a23e1550161692950bbe0 + url: "https://pub.dev" + source: hosted + version: "2.10.0" + bloc: + dependency: "direct main" + description: + name: bloc + sha256: "3820f15f502372d979121de1f6b97bfcf1630ebff8fe1d52fb2b0bfa49be5b49" + url: "https://pub.dev" + source: hosted + version: "8.1.2" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + build: + dependency: transitive + description: + name: build + sha256: "3fbda25365741f8251b39f3917fb3c8e286a96fd068a5a242e11c2012d495777" + url: "https://pub.dev" + source: hosted + version: "2.3.1" + build_config: + dependency: transitive + description: + name: build_config + sha256: bf80fcfb46a29945b423bd9aad884590fb1dc69b330a4d4700cac476af1708d1 + url: "https://pub.dev" + source: hosted + version: "1.1.1" + build_daemon: + dependency: transitive + description: + name: build_daemon + sha256: "757153e5d9cd88253cb13f28c2fb55a537dc31fefd98137549895b5beb7c6169" + url: "https://pub.dev" + source: hosted + version: "3.1.1" + build_resolvers: + dependency: transitive + description: + name: build_resolvers + sha256: a7417cc44d9edb3f2c8760000270c99dba8c72ff66d0146772b8326565780745 + url: "https://pub.dev" + source: hosted + version: "2.3.1" + build_runner: + dependency: "direct dev" + description: + name: build_runner + sha256: b0a8a7b8a76c493e85f1b84bffa0588859a06197863dba8c9036b15581fd9727 + url: "https://pub.dev" + source: hosted + version: "2.3.3" + build_runner_core: + dependency: transitive + description: + name: build_runner_core + sha256: "0671ad4162ed510b70d0eb4ad6354c249f8429cab4ae7a4cec86bbc2886eb76e" + url: "https://pub.dev" + source: hosted + version: "7.2.7+1" + built_collection: + dependency: transitive + description: + name: built_collection + sha256: "376e3dd27b51ea877c28d525560790aee2e6fbb5f20e2f85d5081027d94e2100" + url: "https://pub.dev" + source: hosted + version: "5.1.1" + built_value: + dependency: transitive + description: + name: built_value + sha256: ff627b645b28fb8bdb69e645f910c2458fd6b65f6585c3a53e0626024897dedf + url: "https://pub.dev" + source: hosted + version: "8.6.2" + characters: + dependency: transitive + description: + name: characters + sha256: e6a326c8af69605aec75ed6c187d06b349707a27fbff8222ca9cc2cff167975c + url: "https://pub.dev" + source: hosted + version: "1.2.1" + checked_yaml: + dependency: transitive + description: + name: checked_yaml + sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff + url: "https://pub.dev" + source: hosted + version: "2.0.3" + clock: + dependency: transitive + description: + name: clock + sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf + url: "https://pub.dev" + source: hosted + version: "1.1.1" + cloud_firestore: + dependency: "direct main" + description: + name: cloud_firestore + sha256: "988351d4fcc58c47578d95d014018888b2ce7a228f84ce322fea4a127707a0d4" + url: "https://pub.dev" + source: hosted + version: "4.8.1" + cloud_firestore_platform_interface: + dependency: transitive + description: + name: cloud_firestore_platform_interface + sha256: b6652ce95507e604f00cb0c9c9be2363d21746e82667f2f3d61edf2d33cad3bf + url: "https://pub.dev" + source: hosted + version: "5.15.1" + cloud_firestore_web: + dependency: transitive + description: + name: cloud_firestore_web + sha256: "22d02595eb7a304c0f1b4a717e78cc054522e8f237eb7b1122886f93130f3f7a" + url: "https://pub.dev" + source: hosted + version: "3.6.1" + cloud_functions: + dependency: "direct main" + description: + name: cloud_functions + sha256: "61560d62b19f43e1d65b352ae121fc1b7046ceb6efce4e69ecd28fb110c84784" + url: "https://pub.dev" + source: hosted + version: "4.3.4" + cloud_functions_platform_interface: + dependency: transitive + description: + name: cloud_functions_platform_interface + sha256: edfa0a929baec47df698ca28c37d03c27c020545326eeea1ed0f8b3a47b073ca + url: "https://pub.dev" + source: hosted + version: "5.4.4" + cloud_functions_web: + dependency: transitive + description: + name: cloud_functions_web + sha256: c2b73ef1bc9b0948bfb701a252e1a4019af4266e204270803ee7fa0f1641c74f + url: "https://pub.dev" + source: hosted + version: "4.5.4" + code_builder: + dependency: transitive + description: + name: code_builder + sha256: "315a598c7fbe77f22de1c9da7cfd6fd21816312f16ffa124453b4fc679e540f1" + url: "https://pub.dev" + source: hosted + version: "4.6.0" + collection: + dependency: transitive + description: + name: collection + sha256: cfc915e6923fe5ce6e153b0723c753045de46de1b4d63771530504004a45fae0 + url: "https://pub.dev" + source: hosted + version: "1.17.0" + convert: + dependency: transitive + description: + name: convert + sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592" + url: "https://pub.dev" + source: hosted + version: "3.1.1" + crypto: + dependency: transitive + description: + name: crypto + sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab + url: "https://pub.dev" + source: hosted + version: "3.0.3" + cupertino_icons: + dependency: "direct main" + description: + name: cupertino_icons + sha256: e35129dc44c9118cee2a5603506d823bab99c68393879edb440e0090d07586be + url: "https://pub.dev" + source: hosted + version: "1.0.5" + dart_style: + dependency: transitive + description: + name: dart_style + sha256: "1efa911ca7086affd35f463ca2fc1799584fb6aa89883cf0af8e3664d6a02d55" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + equatable: + dependency: "direct main" + description: + name: equatable + sha256: c2b87cb7756efdf69892005af546c56c0b5037f54d2a88269b4f347a505e3ca2 + url: "https://pub.dev" + source: hosted + version: "2.0.5" + fake_async: + dependency: transitive + description: + name: fake_async + sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" + url: "https://pub.dev" + source: hosted + version: "1.3.1" + ffi: + dependency: transitive + description: + name: ffi + sha256: ed5337a5660c506388a9f012be0288fb38b49020ce2b45fe1f8b8323fe429f99 + url: "https://pub.dev" + source: hosted + version: "2.0.2" + file: + dependency: transitive + description: + name: file + sha256: "1b92bec4fc2a72f59a8e15af5f52cd441e4a7860b49499d69dfa817af20e925d" + url: "https://pub.dev" + source: hosted + version: "6.1.4" + file_picker: + dependency: "direct main" + description: + name: file_picker + sha256: "9d6e95ec73abbd31ec54d0e0df8a961017e165aba1395e462e5b31ea0c165daf" + url: "https://pub.dev" + source: hosted + version: "5.3.1" + firebase_auth: + dependency: "direct main" + description: + name: firebase_auth + sha256: f693c0aa998b1101453878951b171b69f0db5199003df1c943b33493a1de7917 + url: "https://pub.dev" + source: hosted + version: "4.6.3" + firebase_auth_platform_interface: + dependency: transitive + description: + name: firebase_auth_platform_interface + sha256: "689ae048b78ad088ba31acdec45f5badb56201e749ed8b534947a7303ddb32aa" + url: "https://pub.dev" + source: hosted + version: "6.15.3" + firebase_auth_web: + dependency: transitive + description: + name: firebase_auth_web + sha256: f35d637a1707afd51f30090bb5234b381d5071ccbfef09b8c393bc7c65e440cd + url: "https://pub.dev" + source: hosted + version: "5.5.3" + firebase_core: + dependency: "direct main" + description: + name: firebase_core + sha256: "2e9324f719e90200dc7d3c4f5d2abc26052f9f2b995d3b6626c47a0dfe1c8192" + url: "https://pub.dev" + source: hosted + version: "2.15.0" + firebase_core_platform_interface: + dependency: transitive + description: + name: firebase_core_platform_interface + sha256: b63e3be6c96ef5c33bdec1aab23c91eb00696f6452f0519401d640938c94cba2 + url: "https://pub.dev" + source: hosted + version: "4.8.0" + firebase_core_web: + dependency: transitive + description: + name: firebase_core_web + sha256: "0fd5c4b228de29b55fac38aed0d9e42514b3d3bd47675de52bf7f8fccaf922fa" + url: "https://pub.dev" + source: hosted + version: "2.6.0" + firebase_storage: + dependency: "direct main" + description: + name: firebase_storage + sha256: e9e889adc839a1a68e48762457bd91e247c3b02e4a065842037c039ebb51fa27 + url: "https://pub.dev" + source: hosted + version: "11.2.3" + firebase_storage_platform_interface: + dependency: transitive + description: + name: firebase_storage_platform_interface + sha256: be0f4254cae3ccaefd5d1435b82c1fa3bda33187387b241c55b27d7516ea9b93 + url: "https://pub.dev" + source: hosted + version: "4.4.3" + firebase_storage_web: + dependency: transitive + description: + name: firebase_storage_web + sha256: a5aae39af27ecce997ea53e61a35e0a61b3d33f3c247d74748de0bc40f1c5c5a + url: "https://pub.dev" + source: hosted + version: "3.6.3" + fixnum: + dependency: transitive + description: + name: fixnum + sha256: "25517a4deb0c03aa0f32fd12db525856438902d9c16536311e76cdc57b31d7d1" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_bloc: + dependency: "direct main" + description: + name: flutter_bloc + sha256: e74efb89ee6945bcbce74a5b3a5a3376b088e5f21f55c263fc38cbdc6237faae + url: "https://pub.dev" + source: hosted + version: "8.1.3" + flutter_dotenv: + dependency: "direct main" + description: + name: flutter_dotenv + sha256: "9357883bdd153ab78cbf9ffa07656e336b8bbb2b5a3ca596b0b27e119f7c7d77" + url: "https://pub.dev" + source: hosted + version: "5.1.0" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + sha256: aeb0b80a8b3709709c9cc496cdc027c5b3216796bc0af0ce1007eaf24464fd4c + url: "https://pub.dev" + source: hosted + version: "2.0.1" + flutter_plugin_android_lifecycle: + dependency: transitive + description: + name: flutter_plugin_android_lifecycle + sha256: "950e77c2bbe1692bc0874fc7fb491b96a4dc340457f4ea1641443d0a6c1ea360" + url: "https://pub.dev" + source: hosted + version: "2.0.15" + flutter_rating_bar: + dependency: "direct main" + description: + name: flutter_rating_bar + sha256: d2af03469eac832c591a1eba47c91ecc871fe5708e69967073c043b2d775ed93 + url: "https://pub.dev" + source: hosted + version: "4.0.1" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + font_awesome_flutter: + dependency: "direct main" + description: + name: font_awesome_flutter + sha256: "959ef4add147753f990b4a7c6cccb746d5792dbdc81b1cde99e62e7edb31b206" + url: "https://pub.dev" + source: hosted + version: "10.4.0" + freezed: + dependency: "direct dev" + description: + name: freezed + sha256: "2df89855fe181baae3b6d714dc3c4317acf4fccd495a6f36e5e00f24144c6c3b" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + freezed_annotation: + dependency: "direct main" + description: + name: freezed_annotation + sha256: c3fd9336eb55a38cc1bbd79ab17573113a8deccd0ecbbf926cca3c62803b5c2d + url: "https://pub.dev" + source: hosted + version: "2.4.1" + frontend_server_client: + dependency: transitive + description: + name: frontend_server_client + sha256: "408e3ca148b31c20282ad6f37ebfa6f4bdc8fede5b74bc2f08d9d92b55db3612" + url: "https://pub.dev" + source: hosted + version: "3.2.0" + functions_client: + dependency: transitive + description: + name: functions_client + sha256: "3b157b4d3ae9e38614fd80fab76d1ef1e0e39ff3412a45de2651f27cecb9d2d2" + url: "https://pub.dev" + source: hosted + version: "1.3.2" + get_it: + dependency: "direct main" + description: + name: get_it + sha256: "529de303c739fca98cd7ece5fca500d8ff89649f1bb4b4e94fb20954abcd7468" + url: "https://pub.dev" + source: hosted + version: "7.6.0" + glob: + dependency: transitive + description: + name: glob + sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + gotrue: + dependency: transitive + description: + name: gotrue + sha256: "6ba95e38c06af30d4a365112b433567df70f83d5853923274cb894ea9702c5fa" + url: "https://pub.dev" + source: hosted + version: "1.11.2" + graphs: + dependency: transitive + description: + name: graphs + sha256: aedc5a15e78fc65a6e23bcd927f24c64dd995062bcd1ca6eda65a3cff92a4d19 + url: "https://pub.dev" + source: hosted + version: "2.3.1" + hive: + dependency: transitive + description: + name: hive + sha256: "8dcf6db979d7933da8217edcec84e9df1bdb4e4edc7fc77dbd5aa74356d6d941" + url: "https://pub.dev" + source: hosted + version: "2.2.3" + hive_flutter: + dependency: transitive + description: + name: hive_flutter + sha256: dca1da446b1d808a51689fb5d0c6c9510c0a2ba01e22805d492c73b68e33eecc + url: "https://pub.dev" + source: hosted + version: "1.1.0" + http: + dependency: "direct main" + description: + name: http + sha256: "5895291c13fa8a3bd82e76d5627f69e0d85ca6a30dcac95c4ea19a5d555879c2" + url: "https://pub.dev" + source: hosted + version: "0.13.6" + http_multi_server: + dependency: transitive + description: + name: http_multi_server + sha256: "97486f20f9c2f7be8f514851703d0119c3596d14ea63227af6f7a481ef2b2f8b" + url: "https://pub.dev" + source: hosted + version: "3.2.1" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" + url: "https://pub.dev" + source: hosted + version: "4.0.2" + intl: + dependency: "direct main" + description: + name: intl + sha256: "3bc132a9dbce73a7e4a21a17d06e1878839ffbf975568bc875c60537824b0c4d" + url: "https://pub.dev" + source: hosted + version: "0.18.1" + io: + dependency: transitive + description: + name: io + sha256: "2ec25704aba361659e10e3e5f5d672068d332fc8ac516421d483a11e5cbd061e" + url: "https://pub.dev" + source: hosted + version: "1.0.4" + js: + dependency: transitive + description: + name: js + sha256: "5528c2f391ededb7775ec1daa69e65a2d61276f7552de2b5f7b8d34ee9fd4ab7" + url: "https://pub.dev" + source: hosted + version: "0.6.5" + json_annotation: + dependency: "direct main" + description: + name: json_annotation + sha256: b10a7b2ff83d83c777edba3c6a0f97045ddadd56c944e1a23a3fdf43a1bf4467 + url: "https://pub.dev" + source: hosted + version: "4.8.1" + json_serializable: + dependency: "direct dev" + description: + name: json_serializable + sha256: "43793352f90efa5d8b251893a63d767b2f7c833120e3cc02adad55eefec04dc7" + url: "https://pub.dev" + source: hosted + version: "6.6.2" + jwt_decode: + dependency: transitive + description: + name: jwt_decode + sha256: d2e9f68c052b2225130977429d30f187aa1981d789c76ad104a32243cfdebfbb + url: "https://pub.dev" + source: hosted + version: "0.3.1" + lints: + dependency: transitive + description: + name: lints + sha256: "5e4a9cd06d447758280a8ac2405101e0e2094d2a1dbdd3756aec3fe7775ba593" + url: "https://pub.dev" + source: hosted + version: "2.0.1" + logging: + dependency: transitive + description: + name: logging + sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340" + url: "https://pub.dev" + source: hosted + version: "1.2.0" + matcher: + dependency: transitive + description: + name: matcher + sha256: "16db949ceee371e9b99d22f88fa3a73c4e59fd0afed0bd25fc336eb76c198b72" + url: "https://pub.dev" + source: hosted + version: "0.12.13" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724 + url: "https://pub.dev" + source: hosted + version: "0.2.0" + meta: + dependency: transitive + description: + name: meta + sha256: "6c268b42ed578a53088d834796959e4a1814b5e9e164f147f580a386e5decf42" + url: "https://pub.dev" + source: hosted + version: "1.8.0" + mime: + dependency: transitive + description: + name: mime + sha256: e4ff8e8564c03f255408decd16e7899da1733852a9110a58fe6d1b817684a63e + url: "https://pub.dev" + source: hosted + version: "1.0.4" + mockito: + dependency: "direct dev" + description: + name: mockito + sha256: dd61809f04da1838a680926de50a9e87385c1de91c6579629c3d1723946e8059 + url: "https://pub.dev" + source: hosted + version: "5.4.0" + nested: + dependency: transitive + description: + name: nested + sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20" + url: "https://pub.dev" + source: hosted + version: "1.0.0" + open_file: + dependency: "direct main" + description: + name: open_file + sha256: a5a32d44acb7c899987d0999e1e3cbb0a0f1adebbf41ac813ec6d2d8faa0af20 + url: "https://pub.dev" + source: hosted + version: "3.3.2" + package_config: + dependency: transitive + description: + name: package_config + sha256: "1c5b77ccc91e4823a5af61ee74e6b972db1ef98c2ff5a18d3161c982a55448bd" + url: "https://pub.dev" + source: hosted + version: "2.1.0" + path: + dependency: transitive + description: + name: path + sha256: db9d4f58c908a4ba5953fcee2ae317c94889433e5024c27ce74a37f94267945b + url: "https://pub.dev" + source: hosted + version: "1.8.2" + path_provider: + dependency: transitive + description: + name: path_provider + sha256: "909b84830485dbcd0308edf6f7368bc8fd76afa26a270420f34cabea2a6467a0" + url: "https://pub.dev" + source: hosted + version: "2.1.0" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + sha256: "5d44fc3314d969b84816b569070d7ace0f1dea04bd94a83f74c4829615d22ad8" + url: "https://pub.dev" + source: hosted + version: "2.1.0" + path_provider_foundation: + dependency: transitive + description: + name: path_provider_foundation + sha256: "1b744d3d774e5a879bb76d6cd1ecee2ba2c6960c03b1020cd35212f6aa267ac5" + url: "https://pub.dev" + source: hosted + version: "2.3.0" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + sha256: ba2b77f0c52a33db09fc8caf85b12df691bf28d983e84cf87ff6d693cfa007b3 + url: "https://pub.dev" + source: hosted + version: "2.2.0" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + sha256: bced5679c7df11190e1ddc35f3222c858f328fff85c3942e46e7f5589bf9eb84 + url: "https://pub.dev" + source: hosted + version: "2.1.0" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + sha256: ee0e0d164516b90ae1f970bdf29f726f1aa730d7cfc449ecc74c495378b705da + url: "https://pub.dev" + source: hosted + version: "2.2.0" + platform: + dependency: transitive + description: + name: platform + sha256: "4a451831508d7d6ca779f7ac6e212b4023dd5a7d08a27a63da33756410e32b76" + url: "https://pub.dev" + source: hosted + version: "3.1.0" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "6a2128648c854906c53fa8e33986fc0247a1116122f9534dd20e3ab9e16a32bc" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + pointycastle: + dependency: transitive + description: + name: pointycastle + sha256: "7c1e5f0d23c9016c5bbd8b1473d0d3fb3fc851b876046039509e18e0c7485f2c" + url: "https://pub.dev" + source: hosted + version: "3.7.3" + pool: + dependency: transitive + description: + name: pool + sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a" + url: "https://pub.dev" + source: hosted + version: "1.5.1" + postgrest: + dependency: transitive + description: + name: postgrest + sha256: d6cc0f60c7dc761f84d1c6d11d9e02b3ad90399bd84639a28c1c024adbaa9bde + url: "https://pub.dev" + source: hosted + version: "1.5.0" + profanity_filter: + dependency: "direct main" + description: + name: profanity_filter + sha256: afe466d42a2763a5b86849192bee3337de45a9566d5915a0fe4aa5f9cd2e8ac4 + url: "https://pub.dev" + source: hosted + version: "2.0.0" + provider: + dependency: transitive + description: + name: provider + sha256: cdbe7530b12ecd9eb455bdaa2fcb8d4dad22e80b8afb4798b41479d5ce26847f + url: "https://pub.dev" + source: hosted + version: "6.0.5" + pub_semver: + dependency: transitive + description: + name: pub_semver + sha256: "40d3ab1bbd474c4c2328c91e3a7df8c6dd629b79ece4c4bd04bee496a224fb0c" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + pubspec_parse: + dependency: transitive + description: + name: pubspec_parse + sha256: c63b2876e58e194e4b0828fcb080ad0e06d051cb607a6be51a9e084f47cb9367 + url: "https://pub.dev" + source: hosted + version: "1.2.3" + realtime_client: + dependency: transitive + description: + name: realtime_client + sha256: ff743de9bb0f46fcfffcfe64ae93062702dcd0f83a2ce8adc40d5fb7f542af90 + url: "https://pub.dev" + source: hosted + version: "1.1.3" + retry: + dependency: transitive + description: + name: retry + sha256: a8a1e475a100a0bdc73f529ca8ea1e9c9c76bec8ad86a1f47780139a34ce7aea + url: "https://pub.dev" + source: hosted + version: "3.1.1" + rxdart: + dependency: "direct main" + description: + name: rxdart + sha256: "0c7c0cedd93788d996e33041ffecda924cc54389199cde4e6a34b440f50044cb" + url: "https://pub.dev" + source: hosted + version: "0.27.7" + shared_preferences: + dependency: transitive + description: + name: shared_preferences + sha256: "0344316c947ffeb3a529eac929e1978fcd37c26be4e8468628bac399365a3ca1" + url: "https://pub.dev" + source: hosted + version: "2.2.0" + shared_preferences_android: + dependency: transitive + description: + name: shared_preferences_android + sha256: fe8401ec5b6dcd739a0fe9588802069e608c3fdbfd3c3c93e546cf2f90438076 + url: "https://pub.dev" + source: hosted + version: "2.2.0" + shared_preferences_foundation: + dependency: transitive + description: + name: shared_preferences_foundation + sha256: d29753996d8eb8f7619a1f13df6ce65e34bc107bef6330739ed76f18b22310ef + url: "https://pub.dev" + source: hosted + version: "2.3.3" + shared_preferences_linux: + dependency: transitive + description: + name: shared_preferences_linux + sha256: "71d6806d1449b0a9d4e85e0c7a917771e672a3d5dc61149cc9fac871115018e1" + url: "https://pub.dev" + source: hosted + version: "2.3.0" + shared_preferences_platform_interface: + dependency: transitive + description: + name: shared_preferences_platform_interface + sha256: "23b052f17a25b90ff2b61aad4cc962154da76fb62848a9ce088efe30d7c50ab1" + url: "https://pub.dev" + source: hosted + version: "2.3.0" + shared_preferences_web: + dependency: transitive + description: + name: shared_preferences_web + sha256: "7347b194fb0bbeb4058e6a4e87ee70350b6b2b90f8ac5f8bd5b3a01548f6d33a" + url: "https://pub.dev" + source: hosted + version: "2.2.0" + shared_preferences_windows: + dependency: transitive + description: + name: shared_preferences_windows + sha256: f95e6a43162bce43c9c3405f3eb6f39e5b5d11f65fab19196cf8225e2777624d + url: "https://pub.dev" + source: hosted + version: "2.3.0" + shelf: + dependency: transitive + description: + name: shelf + sha256: ad29c505aee705f41a4d8963641f91ac4cee3c8fad5947e033390a7bd8180fa4 + url: "https://pub.dev" + source: hosted + version: "1.4.1" + shelf_web_socket: + dependency: transitive + description: + name: shelf_web_socket + sha256: "9ca081be41c60190ebcb4766b2486a7d50261db7bd0f5d9615f2d653637a84c1" + url: "https://pub.dev" + source: hosted + version: "1.0.4" + sign_in_with_apple: + dependency: transitive + description: + name: sign_in_with_apple + sha256: "0975c23b9f8b30a80e27d5659a75993a093d4cb5f4eb7d23a9ccc586fea634e0" + url: "https://pub.dev" + source: hosted + version: "5.0.0" + sign_in_with_apple_platform_interface: + dependency: transitive + description: + name: sign_in_with_apple_platform_interface + sha256: a5883edee09ed6be19de19e7d9f618a617fe41a6fa03f76d082dfb787e9ea18d + url: "https://pub.dev" + source: hosted + version: "1.0.0" + sign_in_with_apple_web: + dependency: transitive + description: + name: sign_in_with_apple_web + sha256: "44b66528f576e77847c14999d5e881e17e7223b7b0625a185417829e5306f47a" + url: "https://pub.dev" + source: hosted + version: "1.0.1" + skeletons: + dependency: "direct main" + description: + name: skeletons + sha256: "5b2d08ae7f908ee1f7007ca99f8dcebb4bfc1d3cb2143dec8d112a5be5a45c8f" + url: "https://pub.dev" + source: hosted + version: "0.0.3" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.99" + source_gen: + dependency: transitive + description: + name: source_gen + sha256: "373f96cf5a8744bc9816c1ff41cf5391bbdbe3d7a96fe98c622b6738a8a7bd33" + url: "https://pub.dev" + source: hosted + version: "1.3.2" + source_helper: + dependency: transitive + description: + name: source_helper + sha256: "6adebc0006c37dd63fe05bca0a929b99f06402fc95aa35bf36d67f5c06de01fd" + url: "https://pub.dev" + source: hosted + version: "1.3.4" + source_span: + dependency: transitive + description: + name: source_span + sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250 + url: "https://pub.dev" + source: hosted + version: "1.9.1" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5 + url: "https://pub.dev" + source: hosted + version: "1.11.0" + storage_client: + dependency: transitive + description: + name: storage_client + sha256: a3024569213b064587d616827747b766f9bc796e80cec99bd5ffb597b8aeb018 + url: "https://pub.dev" + source: hosted + version: "1.5.1" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + stream_transform: + dependency: transitive + description: + name: stream_transform + sha256: "14a00e794c7c11aa145a170587321aedce29769c08d7f58b1d141da75e3b1c6f" + url: "https://pub.dev" + source: hosted + version: "2.1.0" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + url: "https://pub.dev" + source: hosted + version: "1.2.0" + supabase: + dependency: transitive + description: + name: supabase + sha256: "291e065aa8c9be06a0348743c184beafd038e109960bc3da201affd3eda811fc" + url: "https://pub.dev" + source: hosted + version: "1.10.0" + supabase_flutter: + dependency: "direct main" + description: + name: supabase_flutter + sha256: "696763552513ca140ddecd47062bd0fe05a8d042ef9fd20a1660f4264b5d069a" + url: "https://pub.dev" + source: hosted + version: "1.10.12" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 + url: "https://pub.dev" + source: hosted + version: "1.2.1" + test_api: + dependency: transitive + description: + name: test_api + sha256: ad540f65f92caa91bf21dfc8ffb8c589d6e4dc0c2267818b4cc2792857706206 + url: "https://pub.dev" + source: hosted + version: "0.4.16" + timing: + dependency: transitive + description: + name: timing + sha256: "70a3b636575d4163c477e6de42f247a23b315ae20e86442bebe32d3cabf61c32" + url: "https://pub.dev" + source: hosted + version: "1.0.1" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c + url: "https://pub.dev" + source: hosted + version: "1.3.2" + universal_io: + dependency: transitive + description: + name: universal_io + sha256: "06866290206d196064fd61df4c7aea1ffe9a4e7c4ccaa8fcded42dd41948005d" + url: "https://pub.dev" + source: hosted + version: "2.2.0" + url_launcher: + dependency: "direct main" + description: + name: url_launcher + sha256: eb1e00ab44303d50dd487aab67ebc575456c146c6af44422f9c13889984c00f3 + url: "https://pub.dev" + source: hosted + version: "6.1.11" + url_launcher_android: + dependency: transitive + description: + name: url_launcher_android + sha256: "15f5acbf0dce90146a0f5a2c4a002b1814a6303c4c5c075aa2623b2d16156f03" + url: "https://pub.dev" + source: hosted + version: "6.0.36" + url_launcher_ios: + dependency: transitive + description: + name: url_launcher_ios + sha256: "9af7ea73259886b92199f9e42c116072f05ff9bea2dcb339ab935dfc957392c2" + url: "https://pub.dev" + source: hosted + version: "6.1.4" + url_launcher_linux: + dependency: transitive + description: + name: url_launcher_linux + sha256: "207f4ddda99b95b4d4868320a352d374b0b7e05eefad95a4a26f57da413443f5" + url: "https://pub.dev" + source: hosted + version: "3.0.5" + url_launcher_macos: + dependency: transitive + description: + name: url_launcher_macos + sha256: "91ee3e75ea9dadf38036200c5d3743518f4a5eb77a8d13fda1ee5764373f185e" + url: "https://pub.dev" + source: hosted + version: "3.0.5" + url_launcher_platform_interface: + dependency: transitive + description: + name: url_launcher_platform_interface + sha256: bfdfa402f1f3298637d71ca8ecfe840b4696698213d5346e9d12d4ab647ee2ea + url: "https://pub.dev" + source: hosted + version: "2.1.3" + url_launcher_web: + dependency: transitive + description: + name: url_launcher_web + sha256: "6bb1e5d7fe53daf02a8fee85352432a40b1f868a81880e99ec7440113d5cfcab" + url: "https://pub.dev" + source: hosted + version: "2.0.17" + url_launcher_windows: + dependency: transitive + description: + name: url_launcher_windows + sha256: "254708f17f7c20a9c8c471f67d86d76d4a3f9c1591aad1e15292008aceb82771" + url: "https://pub.dev" + source: hosted + version: "3.0.6" + uuid: + dependency: "direct main" + description: + name: uuid + sha256: "648e103079f7c64a36dc7d39369cabb358d377078a051d6ae2ad3aa539519313" + url: "https://pub.dev" + source: hosted + version: "3.0.7" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + watcher: + dependency: transitive + description: + name: watcher + sha256: "6a7f46926b01ce81bfc339da6a7f20afbe7733eff9846f6d6a5466aa4c6667c0" + url: "https://pub.dev" + source: hosted + version: "1.0.2" + web_socket_channel: + dependency: transitive + description: + name: web_socket_channel + sha256: d88238e5eac9a42bb43ca4e721edba3c08c6354d4a53063afaa568516217621b + url: "https://pub.dev" + source: hosted + version: "2.4.0" + webview_flutter: + dependency: transitive + description: + name: webview_flutter + sha256: "789d52bd789373cc1e100fb634af2127e86c99cf9abde09499743270c5de8d00" + url: "https://pub.dev" + source: hosted + version: "4.2.2" + webview_flutter_android: + dependency: transitive + description: + name: webview_flutter_android + sha256: bca797abba472868655b5f1a6029c1132385685ee9db4713cb0e7f33076210c6 + url: "https://pub.dev" + source: hosted + version: "3.9.3" + webview_flutter_platform_interface: + dependency: transitive + description: + name: webview_flutter_platform_interface + sha256: "564ef378cafc1a0e29f1d76ce175ef517a0a6115875dff7b43fccbef2b0aeb30" + url: "https://pub.dev" + source: hosted + version: "2.4.0" + webview_flutter_wkwebview: + dependency: transitive + description: + name: webview_flutter_wkwebview + sha256: ed749f94ac9e814d04a258a9255cf69cfa4cc6006ff59542aea7fb4590144972 + url: "https://pub.dev" + source: hosted + version: "3.7.3" + win32: + dependency: transitive + description: + name: win32 + sha256: "5a751eddf9db89b3e5f9d50c20ab8612296e4e8db69009788d6c8b060a84191c" + url: "https://pub.dev" + source: hosted + version: "4.1.4" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + sha256: f0c26453a2d47aa4c2570c6a033246a3fc62da2fe23c7ffdd0a7495086dc0247 + url: "https://pub.dev" + source: hosted + version: "1.0.2" + yaml: + dependency: transitive + description: + name: yaml + sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5" + url: "https://pub.dev" + source: hosted + version: "3.1.2" + yet_another_json_isolate: + dependency: transitive + description: + name: yet_another_json_isolate + sha256: "86fad76026c4241a32831d6c7febd8f9bded5019e2cd36c5b148499808d8307d" + url: "https://pub.dev" + source: hosted + version: "1.1.1" +sdks: + dart: ">=2.19.6 <3.0.0" + flutter: ">=3.7.6" diff --git a/pubspec.yaml b/pubspec.yaml new file mode 100644 index 0000000..4ac668a --- /dev/null +++ b/pubspec.yaml @@ -0,0 +1,122 @@ +name: appflowy_theme_marketplace +description: A new Flutter project. +# The following line prevents the package from being accidentally published to +# pub.dev using `flutter pub publish`. This is preferred for private packages. +publish_to: 'none' # Remove this line if you wish to publish to pub.dev + +# The following defines the version and build number for your application. +# A version number is three numbers separated by dots, like 1.2.43 +# followed by an optional build number separated by a +. +# Both the version and the builder number may be overridden in flutter +# build by specifying --build-name and --build-number, respectively. +# In Android, build-name is used as versionName while build-number used as versionCode. +# Read more about Android versioning at https://developer.android.com/studio/publish/versioning +# In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion. +# Read more about iOS versioning at +# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html +# In Windows, build-name is used as the major, minor, and patch parts +# of the product and file versions while build-number is used as the build suffix. +version: 1.0.0+1 + +environment: + sdk: '>=2.19.6 <3.0.0' + +# Dependencies specify other packages that your package needs in order to work. +# To automatically upgrade your package dependencies to the latest versions +# consider running `flutter pub upgrade --major-versions`. Alternatively, +# dependencies can be manually updated by changing the version numbers below to +# the latest version available on pub.dev. To see which dependencies have newer +# versions available, run `flutter pub outdated`. +dependencies: + flutter: + sdk: flutter + + + # The following adds the Cupertino Icons font to your application. + # Use with the CupertinoIcons class for iOS style icons. + font_awesome_flutter: ^10.4.0 + cupertino_icons: ^1.0.2 + firebase_core: ^2.14.0 + firebase_auth: ^4.6.3 + firebase_storage: ^11.2.3 + file_picker: ^5.3.1 + open_file: ^3.3.1 + url_launcher: ^6.0.12 + profanity_filter: ^2.0.0 + archive: ^3.3.7 + cloud_firestore: ^4.8.1 + flutter_rating_bar: ^4.0.1 + flutter_bloc: ^8.1.3 + equatable: ^2.0.5 + skeletons: ^0.0.3 + http: ^0.13.6 + flutter_dotenv: ^5.1.0 + cloud_functions: ^4.3.4 + bloc: ^8.1.2 + rxdart: ^0.27.7 + get_it: ^7.6.0 + uuid: ^3.0.7 + intl: ^0.18.1 + supabase_flutter: ^1.10.12 + freezed_annotation: ^2.4.1 + json_annotation: ^4.8.1 + +dev_dependencies: + flutter_test: + sdk: flutter + + # The "flutter_lints" package below contains a set of recommended lints to + # encourage good coding practices. The lint set provided by the package is + # activated in the `analysis_options.yaml` file located at the root of your + # package. See that file for information about deactivating specific lint + # rules and activating additional ones. + flutter_lints: ^2.0.0 + build_runner: ^2.3.3 + freezed: ^2.4.1 + json_serializable: ^6.6.2 + mockito: ^5.4.0 + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter packages. +flutter: + + + # The following line ensures that the Material Icons font is + # included with your application, so that you can use the icons in + # the material Icons class. + uses-material-design: true + + # To add assets to your application, add an assets section, like this: + assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + - assets/json-file-icon.jpg + - dotenv + + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/assets-and-images/#resolution-aware + + # For details regarding adding assets from package dependencies, see + # https://flutter.dev/assets-and-images/#from-packages + + # To add custom fonts to your application, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts from package dependencies, + # see https://flutter.dev/custom-fonts/#from-packages diff --git a/storage.rules b/storage.rules new file mode 100644 index 0000000..ba7e82d --- /dev/null +++ b/storage.rules @@ -0,0 +1,23 @@ +rules_version = '2'; +service firebase.storage { + match /b/{bucket}/o { + match /public/{allPaths=**} { + allow read: if true + allow create: if request.auth != null + // update & delete rule is not working right now + allow update, delete: if firestore.get( + /databases/(default)/documents/Users/$(request.auth.uid)) + .data.uid == request.auth.uid; + } + match /private/{fileName} { + allow read: if request.auth != null && fileName in firestore.get( + /databases/(default)/documents/Users/$(request.auth.uid)) + .data.purchasedItems; + allow create: if request.auth != null + // update & delete rule is not working right now + allow update, delete: if firestore.get( + /databases/(default)/documents/Users/$(request.auth.uid)) + .data.uid == request.auth.uid; + } + } +} \ No newline at end of file diff --git a/supabase/.vscode/settings.json b/supabase/.vscode/settings.json new file mode 100644 index 0000000..2c7ddc5 --- /dev/null +++ b/supabase/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "deno.enable": true, + "deno.lint": true, + "deno.unstable": true +} \ No newline at end of file diff --git a/supabase/package-lock.json b/supabase/package-lock.json new file mode 100644 index 0000000..f98cb04 --- /dev/null +++ b/supabase/package-lock.json @@ -0,0 +1,298 @@ +{ + "name": "supabase", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "devDependencies": { + "supabase": "^1.86.2" + } + }, + "node_modules/bin-links": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/bin-links/-/bin-links-4.0.2.tgz", + "integrity": "sha512-jxJ0PbXR8eQyPlExCvCs3JFnikvs1Yp4gUJt6nmgathdOwvur+q22KWC3h20gvWl4T/14DXKj2IlkJwwZkZPOw==", + "dev": true, + "dependencies": { + "cmd-shim": "^6.0.0", + "npm-normalize-package-bin": "^3.0.0", + "read-cmd-shim": "^4.0.0", + "write-file-atomic": "^5.0.0" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/cmd-shim": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/cmd-shim/-/cmd-shim-6.0.1.tgz", + "integrity": "sha512-S9iI9y0nKR4hwEQsVWpyxld/6kRfGepGfzff83FcaiEBpmvlbA2nnGe7Cylgrx2f/p1P5S5wpRm9oL8z1PbS3Q==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "dev": true, + "engines": { + "node": ">= 12" + } + }, + "node_modules/fetch-blob": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", + "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "paypal", + "url": "https://paypal.me/jimmywarting" + } + ], + "dependencies": { + "node-domexception": "^1.0.0", + "web-streams-polyfill": "^3.0.3" + }, + "engines": { + "node": "^12.20 || >= 14.13" + } + }, + "node_modules/formdata-polyfill": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", + "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "dev": true, + "dependencies": { + "fetch-blob": "^3.1.2" + }, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dev": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/fs-minipass/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dev": true, + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minizlib/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true, + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-fetch": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", + "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "dev": true, + "dependencies": { + "data-uri-to-buffer": "^4.0.0", + "fetch-blob": "^3.1.4", + "formdata-polyfill": "^4.0.10" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-fetch" + } + }, + "node_modules/npm-normalize-package-bin": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-3.0.1.tgz", + "integrity": "sha512-dMxCf+zZ+3zeQZXKxmyuCKlIDPGuv8EF940xbkC4kQVDTtqoh6rJFO+JTKSA6/Rwi0getWmtuy4Itup0AMcaDQ==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/read-cmd-shim": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/read-cmd-shim/-/read-cmd-shim-4.0.0.tgz", + "integrity": "sha512-yILWifhaSEEytfXI76kB9xEEiG1AiozaCJZ83A87ytjRiN+jVibXjedjCRNjoZviinhG+4UkalO3mWTd8u5O0Q==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/supabase": { + "version": "1.86.2", + "resolved": "https://registry.npmjs.org/supabase/-/supabase-1.86.2.tgz", + "integrity": "sha512-8tgQYTRxoo+XVnQPSsbEYeb7Ag8GSqvFTDdO/KiegIsBOiN5y4UV+s2G9/BJMdiAU+wyGmSStmy29X+yRHwS5g==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "bin-links": "^4.0.1", + "node-fetch": "^3.2.10", + "tar": "6.1.15" + }, + "bin": { + "supabase": "bin/supabase" + }, + "engines": { + "npm": ">=8" + } + }, + "node_modules/tar": { + "version": "6.1.15", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.15.tgz", + "integrity": "sha512-/zKt9UyngnxIT/EAGYuxaMYgOIJiP81ab9ZfkILq4oNLPFX50qyYmu7jRj9qeXoxmJHjGlbH0+cm2uy1WCs10A==", + "dev": true, + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/web-streams-polyfill": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz", + "integrity": "sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/write-file-atomic": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz", + "integrity": "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==", + "dev": true, + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } + } +} diff --git a/supabase/package.json b/supabase/package.json new file mode 100644 index 0000000..bb4b955 --- /dev/null +++ b/supabase/package.json @@ -0,0 +1,5 @@ +{ + "devDependencies": { + "supabase": "^1.86.2" + } +} diff --git a/supabase/supabase.code-workspace b/supabase/supabase.code-workspace new file mode 100644 index 0000000..a9ad457 --- /dev/null +++ b/supabase/supabase.code-workspace @@ -0,0 +1,17 @@ +{ + "folders": [ + { + "name": "project-root", + "path": "./" + }, + { + "name": "supabase-functions", + "path": "supabase/functions" + } + ], + "settings": { + "files.exclude": { + "supabase/functions/": true + } + } +} diff --git a/supabase/supabase/.gitignore b/supabase/supabase/.gitignore new file mode 100644 index 0000000..773c7c3 --- /dev/null +++ b/supabase/supabase/.gitignore @@ -0,0 +1,3 @@ +# Supabase +.branches +.temp diff --git a/supabase/supabase/config.toml b/supabase/supabase/config.toml new file mode 100644 index 0000000..e637f89 --- /dev/null +++ b/supabase/supabase/config.toml @@ -0,0 +1,110 @@ +# A string used to distinguish different Supabase projects on the same host. Defaults to the +# working directory name when running `supabase init`. +project_id = "supabase" + +[api] +# Port to use for the API URL. +port = 54321 +# Schemas to expose in your API. Tables, views and stored procedures in this schema will get API +# endpoints. public and storage are always included. +schemas = ["public", "storage", "graphql_public"] +# Extra schemas to add to the search_path of every request. public is always included. +extra_search_path = ["public", "extensions"] +# The maximum number of rows returns from a view, table, or stored procedure. Limits payload size +# for accidental or malicious requests. +max_rows = 1000 + +[db] +# Port to use for the local database URL. +port = 54322 +# Port used by db diff command to initialise the shadow database. +shadow_port = 54320 +# The database major version to use. This has to be the same as your remote database's. Run `SHOW +# server_version;` on the remote database to check. +major_version = 15 + +[studio] +enabled = true +# Port to use for Supabase Studio. +port = 54323 +# External URL of the API server that frontend connects to. +api_url = "http://localhost" + +# Email testing server. Emails sent with the local dev setup are not actually sent - rather, they +# are monitored, and you can view the emails that would have been sent from the web interface. +[inbucket] +enabled = true +# Port to use for the email testing server web interface. +port = 54324 +# Uncomment to expose additional ports for testing user applications that send emails. +# smtp_port = 54325 +# pop3_port = 54326 + +[storage] +# The maximum file size allowed (e.g. "5MB", "500KB"). +file_size_limit = "50MiB" + +[auth] +# The base URL of your website. Used as an allow-list for redirects and for constructing URLs used +# in emails. +site_url = "http://localhost:3000" +# A list of *exact* URLs that auth providers are permitted to redirect to post authentication. +additional_redirect_urls = ["https://localhost:3000"] +# How long tokens are valid for, in seconds. Defaults to 3600 (1 hour), maximum 604,800 (1 week). +jwt_expiry = 3600 +# If disabled, the refresh token will never expire. +enable_refresh_token_rotation = true +# Allows refresh tokens to be reused after expiry, up to the specified interval in seconds. +# Requires enable_refresh_token_rotation = true. +refresh_token_reuse_interval = 10 +# Allow/disallow new user signups to your project. +enable_signup = true + +[auth.email] +# Allow/disallow new user signups via email to your project. +enable_signup = true +# If enabled, a user will be required to confirm any email change on both the old, and new email +# addresses. If disabled, only the new email is required to confirm. +double_confirm_changes = true +# If enabled, users need to confirm their email address before signing in. +enable_confirmations = false + +# Uncomment to customize email template +# [auth.email.template.invite] +# subject = "You have been invited" +# content_path = "./supabase/templates/invite.html" + +[auth.sms] +# Allow/disallow new user signups via SMS to your project. +enable_signup = true +# If enabled, users need to confirm their phone number before signing in. +enable_confirmations = false + +# Configure one of the supported SMS providers: `twilio`, `messagebird`, `textlocal`, `vonage`. +[auth.sms.twilio] +enabled = false +account_sid = "" +message_service_sid = "" +# DO NOT commit your Twilio auth token to git. Use environment variable substitution instead: +auth_token = "env(SUPABASE_AUTH_SMS_TWILIO_AUTH_TOKEN)" + +# Use an external OAuth provider. The full list of providers are: `apple`, `azure`, `bitbucket`, +# `discord`, `facebook`, `github`, `gitlab`, `google`, `keycloak`, `linkedin`, `notion`, `twitch`, +# `twitter`, `slack`, `spotify`, `workos`, `zoom`. +[auth.external.apple] +enabled = false +client_id = "" +# DO NOT commit your OAuth provider secret to git. Use environment variable substitution instead: +secret = "env(SUPABASE_AUTH_EXTERNAL_APPLE_SECRET)" +# Overrides the default auth redirectUrl. +redirect_uri = "" +# Overrides the default auth provider URL. Used to support self-hosted gitlab, single-tenant Azure, +# or any other third-party OIDC providers. +url = "" + +[analytics] +enabled = false +port = 54327 +vector_port = 54328 +# Configure one of the supported backends: `postgres`, `bigquery` +backend = "postgres" diff --git a/supabase/supabase/functions/.vscode/extensions.json b/supabase/supabase/functions/.vscode/extensions.json new file mode 100644 index 0000000..74baffc --- /dev/null +++ b/supabase/supabase/functions/.vscode/extensions.json @@ -0,0 +1,3 @@ +{ + "recommendations": ["denoland.vscode-deno"] +} diff --git a/supabase/supabase/functions/.vscode/settings.json b/supabase/supabase/functions/.vscode/settings.json new file mode 100644 index 0000000..6f4f84f --- /dev/null +++ b/supabase/supabase/functions/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "deno.enable": true, + "deno.lint": true, + "editor.defaultFormatter": "denoland.vscode-deno" +} diff --git a/supabase/supabase/functions/_utils/cors.ts b/supabase/supabase/functions/_utils/cors.ts new file mode 100644 index 0000000..778a89d --- /dev/null +++ b/supabase/supabase/functions/_utils/cors.ts @@ -0,0 +1,10 @@ +export const corsHeaders = { + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type, Origin, X-Requested-With, Accept', + 'Content-Type': 'application/json', + 'Access-Control-Max-Age': '86400', // 24 hours +} + +export const headers = { + 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS', +}; \ No newline at end of file diff --git a/supabase/supabase/functions/_utils/db.ts b/supabase/supabase/functions/_utils/db.ts new file mode 100644 index 0000000..1e371fb --- /dev/null +++ b/supabase/supabase/functions/_utils/db.ts @@ -0,0 +1,84 @@ +import { createClient } from 'https://esm.sh/@supabase/supabase-js@2.33.1' + +const supUrl = Deno.env.get('_SUPABASE_URL') as string; +const supKey = Deno.env.get('_SUPABASE_SERVICE_KEY') as string; +const supabase = createClient(supUrl, supKey); + +const duplicateCheck = (data: any[] | null) => { + if(data == null) + return null + if(data.length == 0) + return null + if(data.length > 1) + throw Error('Duplicate user with the same email'); + return data[0]; +} + +const findUserByMail = async (email:string) => { + const { data } = await supabase.from('users').select('*').eq('email', email) + return duplicateCheck(data); +} + +const findUserByStripeId = async (stripeId:string) => { + const { data } = await supabase.from('users').select('*').eq('stripe_id', stripeId) + return duplicateCheck(data); +} + +const updateUserByMail = async (email:string, stripeId:string) => { + await supabase + .from('users') + .update({ stripe_id: stripeId }) + .eq('email', email) +} + +const newOrder = async(reqData:any) => { + const { data } = await supabase.from('users').select('email').eq('uid', reqData.metadata.customerUid) + const customer = duplicateCheck(data) + const res = await supabase.from('orders').insert({ + 'customer_id': reqData.metadata.customerUid, + 'plugin_id': reqData.metadata.productId, + 'order_detail': reqData, + 'product_name': reqData.metadata.productName, + 'customer_email': customer.email, + }); + return res; +} + +const findPurchasedOrder = async(customerUid: string, pluginId: string) => { + const { data } = await supabase + .from('orders') + .select('customer_id, plugin_id, product_name, customer_email') + .eq('customer_id', customerUid) + .eq('plugin_id', pluginId) + const res = duplicateCheck(data) + return res; +} + +const findUploader = async(pluginId: string) => { + const { data } = await supabase + .from('files') + .select('uploader_name, uploader_id, uploader_email, name') + .eq('plugin_id', pluginId) + const res = duplicateCheck(data) + return res; +} + +const getSignedUrl = async (sellerUid: string, pluginName: string) => { + const path = `${sellerUid}/${pluginName}` + const bucket = 'paid_plugins' + const { data, error } = await supabase.storage.from(bucket).createSignedUrl(path, 60) + if(error != null || error != undefined) + console.error(`error: ${error}`); + const downloadUrl: string | undefined = data?.signedUrl + return downloadUrl +} + +export { + findUserByMail, + findUserByStripeId, + updateUserByMail, + newOrder, + findPurchasedOrder, + findUploader, + getSignedUrl, +} \ No newline at end of file diff --git a/supabase/supabase/functions/_utils/stripe.ts b/supabase/supabase/functions/_utils/stripe.ts new file mode 100644 index 0000000..e5f6757 --- /dev/null +++ b/supabase/supabase/functions/_utils/stripe.ts @@ -0,0 +1,99 @@ +import { Stripe } from "https://esm.sh/stripe@13.2.0?target=deno&no-check"; + +const redirectUrl = Deno.env.get('_REDIRECT_URL') as string; +const stripeKey = Deno.env.get('_STRIPE_SECRET') as string; + +const stripe: any = Stripe(stripeKey, { + httpClient: Stripe.createFetchHttpClient(), +}); + +if(stripe == null || stripe == undefined) + throw Error('Error constructing Stripe object'); + +const cryptoProvider = Stripe.createSubtleCryptoProvider(); + +const createAccount = async (email:string, accountType:string) => { + const accountsList = await stripe.accounts.list() + const duplicatesList = accountsList.data.filter((account:any) => account.email === email); + if (duplicatesList.length > 0){ + console.error('Duplicate stripe accounts') + throw new Error('Account with the same email already exists.'); + } + const customer = await stripe.accounts.create({ email: email, type: accountType }); + return customer; +} + +const createAccountLink = async (stripeId:string) => { + const accountLink = await stripe.accountLinks.create({ + account: stripeId, + refresh_url: redirectUrl, + return_url: redirectUrl, + type: 'account_onboarding', + }); + return accountLink; +} + +const createCheckoutSession = async (reqData:any, sellerData:any) => { + const cutPercentage = 10 //NOTE: the server decides how much the cut should be + const cutAmount = cutPercentage/100 * parseInt(reqData.price, 10); + const timeNow = new Date() + const newCheckoutSession = await stripe.checkout.sessions.create({ + payment_method_types: ['card'], + mode: 'payment', + invoice_creation: { + enabled: true, + }, + line_items: [ + { + quantity: 1, + price_data: { + currency: 'usd', + unit_amount: reqData.price * 100, + product_data: { + name: reqData.name, + description: reqData.description ?? 'No description', + } + } + } + ], + payment_intent_data: { + application_fee_amount: cutAmount * 100, + transfer_data: { + destination: sellerData.stripe_id + }, + }, + metadata: { + 'productName': reqData.name, + 'productId': reqData.productId, + 'customerUid': reqData.customerUid ?? 'Guest checkout', + 'purchaseDate': timeNow.toLocaleDateString(), + }, + allow_promotion_codes: true, + 'success_url': redirectUrl, + 'cancel_url': redirectUrl, + }) + return newCheckoutSession; +} + +const getAccountInfo = async (accountId:any) => { + const accountData = await stripe.accounts.retrieve(accountId) + console.log(accountData) + return accountData; +} + +const getAccountBalance = async (accountId:any) => { + const balance = await stripe.balance.retrieve({ + stripeAccount: accountId, + }); + return balance; +} + +export { + stripe, + cryptoProvider, + createAccount, + createAccountLink, + createCheckoutSession, + getAccountInfo, + getAccountBalance, +} \ No newline at end of file diff --git a/supabase/supabase/functions/create-stripe-account-link/index.ts b/supabase/supabase/functions/create-stripe-account-link/index.ts new file mode 100644 index 0000000..2d1ef03 --- /dev/null +++ b/supabase/supabase/functions/create-stripe-account-link/index.ts @@ -0,0 +1,19 @@ +import { serve } from "https://deno.land/std@0.168.0/http/server.ts" +import { createAccountLink } from '../_utils/stripe.ts' +import { corsHeaders } from "../_utils/cors.ts"; + +serve(async (req) => { + if (req.method === 'OPTIONS') { + return new Response('ok', { headers: corsHeaders }); + } + + try { + const data = await req.json() + console.log(data) + const res = await createAccountLink(data.stripeId); + return new Response(JSON.stringify(res), { status: 200, headers: corsHeaders }) + } catch (e) { + console.error(e); + return new Response(JSON.stringify(e), { status: 400, headers: corsHeaders }); + } +}) \ No newline at end of file diff --git a/supabase/supabase/functions/create-stripe-account/index.ts b/supabase/supabase/functions/create-stripe-account/index.ts new file mode 100644 index 0000000..4293624 --- /dev/null +++ b/supabase/supabase/functions/create-stripe-account/index.ts @@ -0,0 +1,21 @@ +import { serve } from 'https://deno.land/std@0.168.0/http/server.ts' +import { createAccount } from '../_utils/stripe.ts' +import { updateUserByMail } from '../_utils/db.ts' +import { corsHeaders } from '../_utils/cors.ts'; + +serve(async (req) => { + if (req.method === 'OPTIONS') { + return new Response('ok', { status: 200, headers: corsHeaders }); + } + + //TODO(a-wallen): test duplicate stripe account registration + try { + const data = await req.json(); + const res = await createAccount(data.email, 'standard'); + await updateUserByMail(data.email, res.id) + return new Response(JSON.stringify(res), { status: 200, headers: corsHeaders }) + } catch (e) { + console.error(e); + return new Response(JSON.stringify(e), { status: 400, headers: corsHeaders }); + } +}) diff --git a/supabase/supabase/functions/download-plugin/index.ts b/supabase/supabase/functions/download-plugin/index.ts new file mode 100644 index 0000000..2897760 --- /dev/null +++ b/supabase/supabase/functions/download-plugin/index.ts @@ -0,0 +1,48 @@ +import { serve } from "https://deno.land/std@0.168.0/http/server.ts" +import { findPurchasedOrder, getSignedUrl, findUploader } from '../_utils/db.ts' +import { corsHeaders } from "../_utils/cors.ts"; + +serve(async (req) => { + if (req.method === 'OPTIONS') { + return new Response('ok', { headers: corsHeaders }); + } + + try { + const { customer_id, plugin_id } = await req.json() + const apiKey = req.headers.get('apikey'); + const authorizationHeader = req.headers.get('Authorization'); + if(apiKey === null || apiKey === undefined) + return new Response(JSON.stringify({ message: 'Missing api key' }), { status: 400 }) + if(authorizationHeader === null || authorizationHeader === undefined) + return new Response(JSON.stringify({ message: 'Missing credential to perform action' }), { status: 400, headers: corsHeaders }) + + const token = authorizationHeader.replace('Bearer ', ''); + const decodedToken = JSON.parse(atob(token.split('.')[1])); // Decode and parse JWT payload + if(customer_id !== decodedToken.sub) + return new Response(JSON.stringify({ message: 'Not authorized to perform action' }), { status: 400, headers: corsHeaders }) + const { uploader_id, name } = await findUploader(plugin_id) + + let downloadUrl + if(customer_id === uploader_id) { + console.log('is uploader') + downloadUrl = await getSignedUrl(uploader_id, name) + } + + else { + console.log('is buyer') + const findOrder = await findPurchasedOrder(customer_id, plugin_id) + if(findOrder === null || findOrder === undefined) + return new Response(JSON.stringify({message: 'Cannot find order with given data'}), { status: 400, headers: corsHeaders }) + const { product_name } = findOrder + downloadUrl = await getSignedUrl(uploader_id, product_name) + } + + return new Response(JSON.stringify({ signedUrl: downloadUrl }), { + headers: corsHeaders, + status: 200, + }) + } catch (e) { + console.error(e) + return new Response(JSON.stringify({ message: 'Not authorized to perform action' }), { status: 400, headers: corsHeaders }) + } +}) diff --git a/supabase/supabase/functions/get-account-info/index.ts b/supabase/supabase/functions/get-account-info/index.ts new file mode 100644 index 0000000..031d277 --- /dev/null +++ b/supabase/supabase/functions/get-account-info/index.ts @@ -0,0 +1,45 @@ +import { serve } from 'https://deno.land/std@0.168.0/http/server.ts' +import { corsHeaders } from '../_utils/cors.ts' +import { getAccountInfo } from '../_utils/stripe.ts' +import { findUserByStripeId } from "../_utils/db.ts"; + +serve(async (req:Request) => { + if (req.method === 'OPTIONS') { + return new Response('ok', { headers: corsHeaders }); + } + + try { + const apiKey = req.headers.get('apikey'); + const authorizationHeader = req.headers.get('Authorization'); + if(apiKey === null || apiKey === undefined) + return new Response(JSON.stringify({ message: 'Missing api key' }), { status: 400, headers: corsHeaders }) + if(authorizationHeader === null || authorizationHeader === undefined) + return new Response(JSON.stringify({ message: 'Missing credential to perform action' }), { status: 400, headers: corsHeaders }) + + const taskPattern = new URLPattern({ pathname: '/get-account-info/:id' }) + const matchingPath = taskPattern.exec(req.url) + const stripeId = matchingPath ? matchingPath.pathname.groups.id : null + if(stripeId === null || stripeId == undefined) + return new Response(JSON.stringify({ message: 'Missing stripe id' }), { status: 400, headers: corsHeaders }) + + const token = authorizationHeader.replace('Bearer ', ''); + const decodedToken = JSON.parse(atob(token.split('.')[1])); // Decode and parse JWT payload + const { uid } = await findUserByStripeId(stripeId!); + if(uid !== decodedToken.sub) + return new Response(JSON.stringify({ message: 'Not authorized to perform action' }), { status: 400, headers: corsHeaders }) + + if(stripeId && req.method === 'GET') { + console.log(stripeId); + const accountData = await getAccountInfo(stripeId); + console.log('acctData: ' + accountData) + return new Response( + JSON.stringify(accountData), + { headers: corsHeaders, status: 200 }, + ) + } + return new Response(JSON.stringify('Bad Request'), { status: 400 , headers: corsHeaders }) + } catch (e) { + console.error(e) + return new Response(JSON.stringify('Not authorized to perform action'), { status: 400 , headers: corsHeaders }) + } +}) \ No newline at end of file diff --git a/supabase/supabase/functions/stripe-checkout-session/index.ts b/supabase/supabase/functions/stripe-checkout-session/index.ts new file mode 100644 index 0000000..6579b5a --- /dev/null +++ b/supabase/supabase/functions/stripe-checkout-session/index.ts @@ -0,0 +1,20 @@ +import { serve } from 'https://deno.land/std@0.168.0/http/server.ts' +import { createCheckoutSession } from '../_utils/stripe.ts' +import { findUserByMail } from '../_utils/db.ts' +import { corsHeaders } from "../_utils/cors.ts"; + +serve(async (req) => { + if (req.method === 'OPTIONS') { + return new Response('ok', { headers: corsHeaders }); + } + + try { + const reqData = await req.json() + const sellerData = await findUserByMail(reqData.uploaderEmail) + const newCheckoutSession = await createCheckoutSession(reqData, sellerData); + return new Response(JSON .stringify(newCheckoutSession), { status: 200, headers: corsHeaders }) + } catch (e) { + console.log(e) + return new Response(JSON.stringify(e), { status: 400, headers: corsHeaders }); + } +}) \ No newline at end of file diff --git a/supabase/supabase/functions/stripe-webhook/index.ts b/supabase/supabase/functions/stripe-webhook/index.ts new file mode 100644 index 0000000..5bc5264 --- /dev/null +++ b/supabase/supabase/functions/stripe-webhook/index.ts @@ -0,0 +1,26 @@ +import { serve } from 'https://deno.land/std@0.168.0/http/server.ts' +import { newOrder } from '../_utils/db.ts' +import { stripe, cryptoProvider } from '../_utils/stripe.ts' +import { corsHeaders } from '../_utils/cors.ts' + +serve(async (req) => { + const webhook_secret = Deno.env.get('_STRIPE_WEBHOOK_SECRET') as string + const signature = req.headers.get('Stripe-Signature') + + if (req.method === 'OPTIONS') return new Response('ok', { headers: corsHeaders }) + + const parameters = req.clone() + const { data } = await parameters.json() + const body = await req.text() + + try { + await stripe.webhooks.constructEventAsync(body, signature, webhook_secret, undefined, cryptoProvider); + } catch (e) { + return new Response(JSON.stringify(e), { status: 400, headers: corsHeaders }) + } + await newOrder(data.object) + return new Response( + JSON.stringify("OK"), + { headers: { "Content-Type": "application/json" } }, + ) +}) \ No newline at end of file diff --git a/supabase/supabase/seed.sql b/supabase/supabase/seed.sql new file mode 100644 index 0000000..e69de29 diff --git a/test/auth/supabase_auth_repo_test.dart b/test/auth/supabase_auth_repo_test.dart new file mode 100644 index 0000000..1800022 --- /dev/null +++ b/test/auth/supabase_auth_repo_test.dart @@ -0,0 +1,173 @@ +import 'package:appflowy_theme_marketplace/src/authentication/data/repositories/supabase_authentication_repository.dart'; +import 'package:appflowy_theme_marketplace/src/authentication/domain/models/user.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; +import 'package:supabase_flutter/supabase_flutter.dart' as supabase; +import 'package:http/http.dart' as http; + +import 'supabase_auth_repo_test.mocks.dart'; + +@GenerateNiceMocks([ + MockSpec(), + MockSpec(), + MockSpec(), + MockSpec(), +]) +Future main() async { + late MockGoTrueClient auth; + late MockAuthResponse authResponse; + late SupabaseAuthenticationRepository authRepo; + late MockClient client; + setUp(() { + auth = MockGoTrueClient(); + authResponse = MockAuthResponse(); + client = MockClient(); + authRepo = SupabaseAuthenticationRepository(auth: auth, client: client); + }); + + group('auth register test', () { + test('test register success', () async { + when(auth.signUp( + email: anyNamed('email'), + password: anyNamed('password'), + emailRedirectTo: anyNamed('emailRedirectTo'), + phone: anyNamed('phone'), + data: anyNamed('data'), + captchaToken: anyNamed('captchaToken'), + )) + .thenAnswer((realInvocation) => Future(() => authResponse)); + expect(auth.signUp(email: 'someemail@gmail.com', password: '123456'), completion(authResponse)); + + supabase.AuthResponse response = supabase.AuthResponse( + user: const supabase.User( + id: '', + appMetadata: {}, + userMetadata: {}, + aud: '', + createdAt: '', + ), + ); + when(client.post(any, headers: anyNamed('headers'), body: anyNamed('body'))).thenAnswer((_) async => http.Response('', 200)); + when(authResponse.session).thenReturn(response.session); + when(authResponse.user).thenReturn(response.user); + expect(await authRepo.register(emailAddress: 'someemail@gmail.com', password: '123456', key: ''), isA()); + }); + + test('test register fail', () async { + when(auth.signUp( + email: anyNamed('email'), + password: anyNamed('password'), + emailRedirectTo: anyNamed('emailRedirectTo'), + phone: anyNamed('phone'), + data: anyNamed('data'), + captchaToken: anyNamed('captchaToken'), + )) + .thenAnswer((realInvocation) => Future(() => throw Exception())); + when(auth.signUp(email: '', password: '123456')).thenAnswer((realInvocation) async { + throw Exception(); + }); + when(auth.signUp(email: 'test', password: '123456')).thenAnswer((realInvocation) async { + throw Exception(); + }); + when(auth.signUp(email: 'test@gmail.com', password: '')).thenAnswer((realInvocation) async { + throw Exception(); + }); + + when(authResponse.user).thenReturn(null); + when(authResponse.session).thenReturn(null); + expect(authResponse.user, null); + expect(authResponse.session, null); + expect(auth.signUp(email: 'someemail@gmail.com', password: '123456'), throwsException); + expect(auth.signUp(email: '', password: '123456'), throwsException); + expect(auth.signUp(email: 'test', password: '123456'), throwsException); + expect(auth.signUp(email: 'test@gmail.com', password: ''), throwsException); + expect(authRepo.register(emailAddress: 'someemail@gmail.com', password: '123456', key: ''), throwsException); + }); + }); + + group('auth signin test', () { + test('test signin success', () { + when(auth.signInWithPassword( + email: anyNamed('email'), + password: anyNamed('password'), + phone: anyNamed('phone'), + captchaToken: anyNamed('captchaToken'), + )) + .thenAnswer((realInvocation) => Future(() => authResponse)); + expect(auth.signInWithPassword(email: 'someemail@gmail.com', password: '123456'), completion(authResponse)); + // expect(auth.signInWithPassword(email: 'someemail@gmail.com', password: '123456', data: any), completion(authResponse)); + supabase.AuthResponse response = supabase.AuthResponse( + user: const supabase.User( + id: '', + appMetadata: {}, + userMetadata: {}, + aud: '', + createdAt: '', + ), + ); + when(authResponse.session).thenReturn(response.session); + when(authResponse.user).thenReturn(response.user); + expect(authRepo.signIn(emailAddress: 'someemail@gmail.com', password: '123456'), completion(isA())); + }); + + test('test signin fail', () { + when(auth.signInWithPassword( + email: anyNamed('email'), + password: anyNamed('password'), + phone: anyNamed('phone'), + captchaToken: anyNamed('captchaToken'), + )) + .thenAnswer((realInvocation) => Future(() => throw Exception())); + when(auth.signInWithPassword(email: '', password: '123456',)).thenAnswer((realInvocation) async { + throw Exception(); + }); + when(auth.signInWithPassword(email: 'test', password: '123456')).thenAnswer((realInvocation) async { + throw Exception(); + }); + when(auth.signInWithPassword(email: 'test@gmail.com', password: '')).thenAnswer((realInvocation) async { + throw Exception(); + }); + when(auth.signInWithPassword(email: 'test@gmail.com', phone: anyNamed('phone'), password: '123456', captchaToken: anyNamed('captchaToken'),),).thenAnswer((realInvocation) async { + throw Exception(); + }); + expect(auth.signInWithPassword(email: '', password: '123456'), throwsException); + expect(auth.signInWithPassword(email: 'test', password: '123456'), throwsException); + expect(auth.signInWithPassword(email: 'test@gmail.com', password: ''), throwsException); + when(authResponse.session).thenReturn(null); + when(authResponse.user).thenReturn(null); + expect(authRepo.signIn(emailAddress: 'someemail@gmail.com', password: '123456'), throwsException); + }); + }); + + group('auth signout test', () { + test('signout success', () { + when(auth.signOut( + scope: anyNamed('scope') + )) + .thenAnswer((_) async => null); + expect(auth.signOut(), isA>()); + expect(authRepo.signOut(), isA>()); + }); + + test('signout fail', () { + when(auth.signOut( + scope: anyNamed('scope') + )) + .thenAnswer((realInvocation) => Future(() => throw Exception())); + + expect(auth.signOut(), throwsException); + expect(authRepo.signOut(), throwsException); + }); + }); + + group('auth googld Signin test', () { + test('google signin success', () { + + }); + + test('google signin fail', () { + + }); + }); +} \ No newline at end of file diff --git a/test/auth/supabase_auth_repo_test.mocks.dart b/test/auth/supabase_auth_repo_test.mocks.dart new file mode 100644 index 0000000..bcb7497 --- /dev/null +++ b/test/auth/supabase_auth_repo_test.mocks.dart @@ -0,0 +1,1020 @@ +// Mocks generated by Mockito 5.4.0 from annotations +// in appflowy_theme_marketplace/test/auth/supabase_auth_repo_test.dart. +// Do not manually edit this file. + +// ignore_for_file: no_leading_underscores_for_library_prefixes +import 'dart:async' as _i4; +import 'dart:convert' as _i6; +import 'dart:typed_data' as _i7; + +import 'package:appflowy_theme_marketplace/src/authentication/domain/models/user.dart' + as _i5; +import 'package:gotrue/gotrue.dart' as _i2; +import 'package:http/http.dart' as _i3; +import 'package:mockito/mockito.dart' as _i1; + +// ignore_for_file: type=lint +// ignore_for_file: avoid_redundant_argument_values +// ignore_for_file: avoid_setters_without_getters +// ignore_for_file: comment_references +// ignore_for_file: implementation_imports +// ignore_for_file: invalid_use_of_visible_for_testing_member +// ignore_for_file: prefer_const_constructors +// ignore_for_file: unnecessary_parenthesis +// ignore_for_file: camel_case_types +// ignore_for_file: subtype_of_sealed_class + +class _FakeGoTrueAdminApi_0 extends _i1.SmartFake + implements _i2.GoTrueAdminApi { + _FakeGoTrueAdminApi_0( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeGoTrueMFAApi_1 extends _i1.SmartFake implements _i2.GoTrueMFAApi { + _FakeGoTrueMFAApi_1( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeAuthResponse_2 extends _i1.SmartFake implements _i2.AuthResponse { + _FakeAuthResponse_2( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeOAuthResponse_3 extends _i1.SmartFake implements _i2.OAuthResponse { + _FakeOAuthResponse_3( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeResendResponse_4 extends _i1.SmartFake + implements _i2.ResendResponse { + _FakeResendResponse_4( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeUserResponse_5 extends _i1.SmartFake implements _i2.UserResponse { + _FakeUserResponse_5( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeAuthSessionUrlResponse_6 extends _i1.SmartFake + implements _i2.AuthSessionUrlResponse { + _FakeAuthSessionUrlResponse_6( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeResponse_7 extends _i1.SmartFake implements _i3.Response { + _FakeResponse_7( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeStreamedResponse_8 extends _i1.SmartFake + implements _i3.StreamedResponse { + _FakeStreamedResponse_8( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +/// A class which mocks [GoTrueClient]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockGoTrueClient extends _i1.Mock implements _i2.GoTrueClient { + @override + _i2.GoTrueAdminApi get admin => (super.noSuchMethod( + Invocation.getter(#admin), + returnValue: _FakeGoTrueAdminApi_0( + this, + Invocation.getter(#admin), + ), + returnValueForMissingStub: _FakeGoTrueAdminApi_0( + this, + Invocation.getter(#admin), + ), + ) as _i2.GoTrueAdminApi); + @override + set admin(_i2.GoTrueAdminApi? _admin) => super.noSuchMethod( + Invocation.setter( + #admin, + _admin, + ), + returnValueForMissingStub: null, + ); + @override + _i2.GoTrueMFAApi get mfa => (super.noSuchMethod( + Invocation.getter(#mfa), + returnValue: _FakeGoTrueMFAApi_1( + this, + Invocation.getter(#mfa), + ), + returnValueForMissingStub: _FakeGoTrueMFAApi_1( + this, + Invocation.getter(#mfa), + ), + ) as _i2.GoTrueMFAApi); + @override + set mfa(_i2.GoTrueMFAApi? _mfa) => super.noSuchMethod( + Invocation.setter( + #mfa, + _mfa, + ), + returnValueForMissingStub: null, + ); + @override + _i4.Stream<_i2.AuthState> get onAuthStateChange => (super.noSuchMethod( + Invocation.getter(#onAuthStateChange), + returnValue: _i4.Stream<_i2.AuthState>.empty(), + returnValueForMissingStub: _i4.Stream<_i2.AuthState>.empty(), + ) as _i4.Stream<_i2.AuthState>); + @override + _i4.Stream<_i2.AuthState> get onAuthStateChangeSync => (super.noSuchMethod( + Invocation.getter(#onAuthStateChangeSync), + returnValue: _i4.Stream<_i2.AuthState>.empty(), + returnValueForMissingStub: _i4.Stream<_i2.AuthState>.empty(), + ) as _i4.Stream<_i2.AuthState>); + @override + Map get headers => (super.noSuchMethod( + Invocation.getter(#headers), + returnValue: {}, + returnValueForMissingStub: {}, + ) as Map); + @override + _i4.Future<_i2.AuthResponse> signUp({ + String? email, + String? phone, + required String? password, + String? emailRedirectTo, + Map? data, + String? captchaToken, + }) => + (super.noSuchMethod( + Invocation.method( + #signUp, + [], + { + #email: email, + #phone: phone, + #password: password, + #emailRedirectTo: emailRedirectTo, + #data: data, + #captchaToken: captchaToken, + }, + ), + returnValue: _i4.Future<_i2.AuthResponse>.value(_FakeAuthResponse_2( + this, + Invocation.method( + #signUp, + [], + { + #email: email, + #phone: phone, + #password: password, + #emailRedirectTo: emailRedirectTo, + #data: data, + #captchaToken: captchaToken, + }, + ), + )), + returnValueForMissingStub: + _i4.Future<_i2.AuthResponse>.value(_FakeAuthResponse_2( + this, + Invocation.method( + #signUp, + [], + { + #email: email, + #phone: phone, + #password: password, + #emailRedirectTo: emailRedirectTo, + #data: data, + #captchaToken: captchaToken, + }, + ), + )), + ) as _i4.Future<_i2.AuthResponse>); + @override + _i4.Future<_i2.AuthResponse> signInWithPassword({ + String? email, + String? phone, + required String? password, + String? captchaToken, + }) => + (super.noSuchMethod( + Invocation.method( + #signInWithPassword, + [], + { + #email: email, + #phone: phone, + #password: password, + #captchaToken: captchaToken, + }, + ), + returnValue: _i4.Future<_i2.AuthResponse>.value(_FakeAuthResponse_2( + this, + Invocation.method( + #signInWithPassword, + [], + { + #email: email, + #phone: phone, + #password: password, + #captchaToken: captchaToken, + }, + ), + )), + returnValueForMissingStub: + _i4.Future<_i2.AuthResponse>.value(_FakeAuthResponse_2( + this, + Invocation.method( + #signInWithPassword, + [], + { + #email: email, + #phone: phone, + #password: password, + #captchaToken: captchaToken, + }, + ), + )), + ) as _i4.Future<_i2.AuthResponse>); + @override + _i4.Future<_i2.OAuthResponse> getOAuthSignInUrl({ + required _i2.Provider? provider, + String? redirectTo, + String? scopes, + Map? queryParams, + }) => + (super.noSuchMethod( + Invocation.method( + #getOAuthSignInUrl, + [], + { + #provider: provider, + #redirectTo: redirectTo, + #scopes: scopes, + #queryParams: queryParams, + }, + ), + returnValue: _i4.Future<_i2.OAuthResponse>.value(_FakeOAuthResponse_3( + this, + Invocation.method( + #getOAuthSignInUrl, + [], + { + #provider: provider, + #redirectTo: redirectTo, + #scopes: scopes, + #queryParams: queryParams, + }, + ), + )), + returnValueForMissingStub: + _i4.Future<_i2.OAuthResponse>.value(_FakeOAuthResponse_3( + this, + Invocation.method( + #getOAuthSignInUrl, + [], + { + #provider: provider, + #redirectTo: redirectTo, + #scopes: scopes, + #queryParams: queryParams, + }, + ), + )), + ) as _i4.Future<_i2.OAuthResponse>); + @override + _i4.Future<_i2.AuthResponse> exchangeCodeForSession(String? authCode) => + (super.noSuchMethod( + Invocation.method( + #exchangeCodeForSession, + [authCode], + ), + returnValue: _i4.Future<_i2.AuthResponse>.value(_FakeAuthResponse_2( + this, + Invocation.method( + #exchangeCodeForSession, + [authCode], + ), + )), + returnValueForMissingStub: + _i4.Future<_i2.AuthResponse>.value(_FakeAuthResponse_2( + this, + Invocation.method( + #exchangeCodeForSession, + [authCode], + ), + )), + ) as _i4.Future<_i2.AuthResponse>); + @override + _i4.Future<_i2.AuthResponse> signInWithIdToken({ + required _i2.Provider? provider, + required String? idToken, + String? accessToken, + String? nonce, + String? captchaToken, + }) => + (super.noSuchMethod( + Invocation.method( + #signInWithIdToken, + [], + { + #provider: provider, + #idToken: idToken, + #accessToken: accessToken, + #nonce: nonce, + #captchaToken: captchaToken, + }, + ), + returnValue: _i4.Future<_i2.AuthResponse>.value(_FakeAuthResponse_2( + this, + Invocation.method( + #signInWithIdToken, + [], + { + #provider: provider, + #idToken: idToken, + #accessToken: accessToken, + #nonce: nonce, + #captchaToken: captchaToken, + }, + ), + )), + returnValueForMissingStub: + _i4.Future<_i2.AuthResponse>.value(_FakeAuthResponse_2( + this, + Invocation.method( + #signInWithIdToken, + [], + { + #provider: provider, + #idToken: idToken, + #accessToken: accessToken, + #nonce: nonce, + #captchaToken: captchaToken, + }, + ), + )), + ) as _i4.Future<_i2.AuthResponse>); + @override + _i4.Future signInWithOtp({ + String? email, + String? phone, + String? emailRedirectTo, + bool? shouldCreateUser, + Map? data, + String? captchaToken, + }) => + (super.noSuchMethod( + Invocation.method( + #signInWithOtp, + [], + { + #email: email, + #phone: phone, + #emailRedirectTo: emailRedirectTo, + #shouldCreateUser: shouldCreateUser, + #data: data, + #captchaToken: captchaToken, + }, + ), + returnValue: _i4.Future.value(), + returnValueForMissingStub: _i4.Future.value(), + ) as _i4.Future); + @override + _i4.Future<_i2.AuthResponse> verifyOTP({ + String? email, + String? phone, + required String? token, + required _i2.OtpType? type, + String? redirectTo, + String? captchaToken, + }) => + (super.noSuchMethod( + Invocation.method( + #verifyOTP, + [], + { + #email: email, + #phone: phone, + #token: token, + #type: type, + #redirectTo: redirectTo, + #captchaToken: captchaToken, + }, + ), + returnValue: _i4.Future<_i2.AuthResponse>.value(_FakeAuthResponse_2( + this, + Invocation.method( + #verifyOTP, + [], + { + #email: email, + #phone: phone, + #token: token, + #type: type, + #redirectTo: redirectTo, + #captchaToken: captchaToken, + }, + ), + )), + returnValueForMissingStub: + _i4.Future<_i2.AuthResponse>.value(_FakeAuthResponse_2( + this, + Invocation.method( + #verifyOTP, + [], + { + #email: email, + #phone: phone, + #token: token, + #type: type, + #redirectTo: redirectTo, + #captchaToken: captchaToken, + }, + ), + )), + ) as _i4.Future<_i2.AuthResponse>); + @override + _i4.Future<_i2.AuthResponse> refreshSession() => (super.noSuchMethod( + Invocation.method( + #refreshSession, + [], + ), + returnValue: _i4.Future<_i2.AuthResponse>.value(_FakeAuthResponse_2( + this, + Invocation.method( + #refreshSession, + [], + ), + )), + returnValueForMissingStub: + _i4.Future<_i2.AuthResponse>.value(_FakeAuthResponse_2( + this, + Invocation.method( + #refreshSession, + [], + ), + )), + ) as _i4.Future<_i2.AuthResponse>); + @override + _i4.Future reauthenticate() => (super.noSuchMethod( + Invocation.method( + #reauthenticate, + [], + ), + returnValue: _i4.Future.value(), + returnValueForMissingStub: _i4.Future.value(), + ) as _i4.Future); + @override + _i4.Future<_i2.ResendResponse> resend({ + String? email, + String? phone, + required _i2.OtpType? type, + String? emailRedirectTo, + String? captchaToken, + }) => + (super.noSuchMethod( + Invocation.method( + #resend, + [], + { + #email: email, + #phone: phone, + #type: type, + #emailRedirectTo: emailRedirectTo, + #captchaToken: captchaToken, + }, + ), + returnValue: _i4.Future<_i2.ResendResponse>.value(_FakeResendResponse_4( + this, + Invocation.method( + #resend, + [], + { + #email: email, + #phone: phone, + #type: type, + #emailRedirectTo: emailRedirectTo, + #captchaToken: captchaToken, + }, + ), + )), + returnValueForMissingStub: + _i4.Future<_i2.ResendResponse>.value(_FakeResendResponse_4( + this, + Invocation.method( + #resend, + [], + { + #email: email, + #phone: phone, + #type: type, + #emailRedirectTo: emailRedirectTo, + #captchaToken: captchaToken, + }, + ), + )), + ) as _i4.Future<_i2.ResendResponse>); + @override + _i4.Future<_i2.UserResponse> updateUser( + _i2.UserAttributes? attributes, { + String? emailRedirectTo, + }) => + (super.noSuchMethod( + Invocation.method( + #updateUser, + [attributes], + {#emailRedirectTo: emailRedirectTo}, + ), + returnValue: _i4.Future<_i2.UserResponse>.value(_FakeUserResponse_5( + this, + Invocation.method( + #updateUser, + [attributes], + {#emailRedirectTo: emailRedirectTo}, + ), + )), + returnValueForMissingStub: + _i4.Future<_i2.UserResponse>.value(_FakeUserResponse_5( + this, + Invocation.method( + #updateUser, + [attributes], + {#emailRedirectTo: emailRedirectTo}, + ), + )), + ) as _i4.Future<_i2.UserResponse>); + @override + _i4.Future<_i2.AuthResponse> setSession(String? refreshToken) => + (super.noSuchMethod( + Invocation.method( + #setSession, + [refreshToken], + ), + returnValue: _i4.Future<_i2.AuthResponse>.value(_FakeAuthResponse_2( + this, + Invocation.method( + #setSession, + [refreshToken], + ), + )), + returnValueForMissingStub: + _i4.Future<_i2.AuthResponse>.value(_FakeAuthResponse_2( + this, + Invocation.method( + #setSession, + [refreshToken], + ), + )), + ) as _i4.Future<_i2.AuthResponse>); + @override + _i4.Future<_i2.AuthSessionUrlResponse> getSessionFromUrl( + Uri? originUrl, { + bool? storeSession = true, + }) => + (super.noSuchMethod( + Invocation.method( + #getSessionFromUrl, + [originUrl], + {#storeSession: storeSession}, + ), + returnValue: _i4.Future<_i2.AuthSessionUrlResponse>.value( + _FakeAuthSessionUrlResponse_6( + this, + Invocation.method( + #getSessionFromUrl, + [originUrl], + {#storeSession: storeSession}, + ), + )), + returnValueForMissingStub: _i4.Future<_i2.AuthSessionUrlResponse>.value( + _FakeAuthSessionUrlResponse_6( + this, + Invocation.method( + #getSessionFromUrl, + [originUrl], + {#storeSession: storeSession}, + ), + )), + ) as _i4.Future<_i2.AuthSessionUrlResponse>); + @override + _i4.Future signOut( + {_i2.SignOutScope? scope = _i2.SignOutScope.global}) => + (super.noSuchMethod( + Invocation.method( + #signOut, + [], + {#scope: scope}, + ), + returnValue: _i4.Future.value(), + returnValueForMissingStub: _i4.Future.value(), + ) as _i4.Future); + @override + _i4.Future resetPasswordForEmail( + String? email, { + String? redirectTo, + String? captchaToken, + }) => + (super.noSuchMethod( + Invocation.method( + #resetPasswordForEmail, + [email], + { + #redirectTo: redirectTo, + #captchaToken: captchaToken, + }, + ), + returnValue: _i4.Future.value(), + returnValueForMissingStub: _i4.Future.value(), + ) as _i4.Future); + @override + _i4.Future<_i2.AuthResponse> recoverSession(String? jsonStr) => + (super.noSuchMethod( + Invocation.method( + #recoverSession, + [jsonStr], + ), + returnValue: _i4.Future<_i2.AuthResponse>.value(_FakeAuthResponse_2( + this, + Invocation.method( + #recoverSession, + [jsonStr], + ), + )), + returnValueForMissingStub: + _i4.Future<_i2.AuthResponse>.value(_FakeAuthResponse_2( + this, + Invocation.method( + #recoverSession, + [jsonStr], + ), + )), + ) as _i4.Future<_i2.AuthResponse>); +} + +/// A class which mocks [AuthResponse]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockAuthResponse extends _i1.Mock implements _i2.AuthResponse {} + +/// A class which mocks [User]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockUser extends _i1.Mock implements _i5.User { + @override + String get uid => (super.noSuchMethod( + Invocation.getter(#uid), + returnValue: '', + returnValueForMissingStub: '', + ) as String); + @override + Map toJson() => (super.noSuchMethod( + Invocation.method( + #toJson, + [], + ), + returnValue: {}, + returnValueForMissingStub: {}, + ) as Map); +} + +/// A class which mocks [Client]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockClient extends _i1.Mock implements _i3.Client { + @override + _i4.Future<_i3.Response> head( + Uri? url, { + Map? headers, + }) => + (super.noSuchMethod( + Invocation.method( + #head, + [url], + {#headers: headers}, + ), + returnValue: _i4.Future<_i3.Response>.value(_FakeResponse_7( + this, + Invocation.method( + #head, + [url], + {#headers: headers}, + ), + )), + returnValueForMissingStub: + _i4.Future<_i3.Response>.value(_FakeResponse_7( + this, + Invocation.method( + #head, + [url], + {#headers: headers}, + ), + )), + ) as _i4.Future<_i3.Response>); + @override + _i4.Future<_i3.Response> get( + Uri? url, { + Map? headers, + }) => + (super.noSuchMethod( + Invocation.method( + #get, + [url], + {#headers: headers}, + ), + returnValue: _i4.Future<_i3.Response>.value(_FakeResponse_7( + this, + Invocation.method( + #get, + [url], + {#headers: headers}, + ), + )), + returnValueForMissingStub: + _i4.Future<_i3.Response>.value(_FakeResponse_7( + this, + Invocation.method( + #get, + [url], + {#headers: headers}, + ), + )), + ) as _i4.Future<_i3.Response>); + @override + _i4.Future<_i3.Response> post( + Uri? url, { + Map? headers, + Object? body, + _i6.Encoding? encoding, + }) => + (super.noSuchMethod( + Invocation.method( + #post, + [url], + { + #headers: headers, + #body: body, + #encoding: encoding, + }, + ), + returnValue: _i4.Future<_i3.Response>.value(_FakeResponse_7( + this, + Invocation.method( + #post, + [url], + { + #headers: headers, + #body: body, + #encoding: encoding, + }, + ), + )), + returnValueForMissingStub: + _i4.Future<_i3.Response>.value(_FakeResponse_7( + this, + Invocation.method( + #post, + [url], + { + #headers: headers, + #body: body, + #encoding: encoding, + }, + ), + )), + ) as _i4.Future<_i3.Response>); + @override + _i4.Future<_i3.Response> put( + Uri? url, { + Map? headers, + Object? body, + _i6.Encoding? encoding, + }) => + (super.noSuchMethod( + Invocation.method( + #put, + [url], + { + #headers: headers, + #body: body, + #encoding: encoding, + }, + ), + returnValue: _i4.Future<_i3.Response>.value(_FakeResponse_7( + this, + Invocation.method( + #put, + [url], + { + #headers: headers, + #body: body, + #encoding: encoding, + }, + ), + )), + returnValueForMissingStub: + _i4.Future<_i3.Response>.value(_FakeResponse_7( + this, + Invocation.method( + #put, + [url], + { + #headers: headers, + #body: body, + #encoding: encoding, + }, + ), + )), + ) as _i4.Future<_i3.Response>); + @override + _i4.Future<_i3.Response> patch( + Uri? url, { + Map? headers, + Object? body, + _i6.Encoding? encoding, + }) => + (super.noSuchMethod( + Invocation.method( + #patch, + [url], + { + #headers: headers, + #body: body, + #encoding: encoding, + }, + ), + returnValue: _i4.Future<_i3.Response>.value(_FakeResponse_7( + this, + Invocation.method( + #patch, + [url], + { + #headers: headers, + #body: body, + #encoding: encoding, + }, + ), + )), + returnValueForMissingStub: + _i4.Future<_i3.Response>.value(_FakeResponse_7( + this, + Invocation.method( + #patch, + [url], + { + #headers: headers, + #body: body, + #encoding: encoding, + }, + ), + )), + ) as _i4.Future<_i3.Response>); + @override + _i4.Future<_i3.Response> delete( + Uri? url, { + Map? headers, + Object? body, + _i6.Encoding? encoding, + }) => + (super.noSuchMethod( + Invocation.method( + #delete, + [url], + { + #headers: headers, + #body: body, + #encoding: encoding, + }, + ), + returnValue: _i4.Future<_i3.Response>.value(_FakeResponse_7( + this, + Invocation.method( + #delete, + [url], + { + #headers: headers, + #body: body, + #encoding: encoding, + }, + ), + )), + returnValueForMissingStub: + _i4.Future<_i3.Response>.value(_FakeResponse_7( + this, + Invocation.method( + #delete, + [url], + { + #headers: headers, + #body: body, + #encoding: encoding, + }, + ), + )), + ) as _i4.Future<_i3.Response>); + @override + _i4.Future read( + Uri? url, { + Map? headers, + }) => + (super.noSuchMethod( + Invocation.method( + #read, + [url], + {#headers: headers}, + ), + returnValue: _i4.Future.value(''), + returnValueForMissingStub: _i4.Future.value(''), + ) as _i4.Future); + @override + _i4.Future<_i7.Uint8List> readBytes( + Uri? url, { + Map? headers, + }) => + (super.noSuchMethod( + Invocation.method( + #readBytes, + [url], + {#headers: headers}, + ), + returnValue: _i4.Future<_i7.Uint8List>.value(_i7.Uint8List(0)), + returnValueForMissingStub: + _i4.Future<_i7.Uint8List>.value(_i7.Uint8List(0)), + ) as _i4.Future<_i7.Uint8List>); + @override + _i4.Future<_i3.StreamedResponse> send(_i3.BaseRequest? request) => + (super.noSuchMethod( + Invocation.method( + #send, + [request], + ), + returnValue: + _i4.Future<_i3.StreamedResponse>.value(_FakeStreamedResponse_8( + this, + Invocation.method( + #send, + [request], + ), + )), + returnValueForMissingStub: + _i4.Future<_i3.StreamedResponse>.value(_FakeStreamedResponse_8( + this, + Invocation.method( + #send, + [request], + ), + )), + ) as _i4.Future<_i3.StreamedResponse>); + @override + void close() => super.noSuchMethod( + Invocation.method( + #close, + [], + ), + returnValueForMissingStub: null, + ); +} diff --git a/test/main.dart b/test/main.dart new file mode 100644 index 0000000..5b0302f --- /dev/null +++ b/test/main.dart @@ -0,0 +1,11 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:http/http.dart' as http; +import 'package:mockito/annotations.dart'; +import 'package:supabase_flutter/supabase_flutter.dart'; + +@GenerateMocks([SupabaseClient]) +@GenerateMocks([SupabaseStorageClient]) +@GenerateMocks([http.Client]) +Future main() async { + +} \ No newline at end of file diff --git a/test/main.mocks.dart b/test/main.mocks.dart new file mode 100644 index 0000000..aa40d3f --- /dev/null +++ b/test/main.mocks.dart @@ -0,0 +1,741 @@ +// Mocks generated by Mockito 5.4.0 from annotations +// in appflowy_theme_marketplace/test/main.dart. +// Do not manually edit this file. + +// ignore_for_file: no_leading_underscores_for_library_prefixes +import 'dart:async' as _i10; +import 'dart:convert' as _i11; +import 'dart:typed_data' as _i12; + +import 'package:functions_client/functions_client.dart' as _i3; +import 'package:gotrue/gotrue.dart' as _i2; +import 'package:http/http.dart' as _i8; +import 'package:mockito/mockito.dart' as _i1; +import 'package:postgrest/postgrest.dart' as _i6; +import 'package:realtime_client/realtime_client.dart' as _i5; +import 'package:storage_client/storage_client.dart' as _i4; +import 'package:supabase/src/supabase_client.dart' as _i9; +import 'package:supabase/src/supabase_query_builder.dart' as _i7; + +// ignore_for_file: type=lint +// ignore_for_file: avoid_redundant_argument_values +// ignore_for_file: avoid_setters_without_getters +// ignore_for_file: comment_references +// ignore_for_file: implementation_imports +// ignore_for_file: invalid_use_of_visible_for_testing_member +// ignore_for_file: prefer_const_constructors +// ignore_for_file: unnecessary_parenthesis +// ignore_for_file: camel_case_types +// ignore_for_file: subtype_of_sealed_class + +class _FakeGoTrueClient_0 extends _i1.SmartFake implements _i2.GoTrueClient { + _FakeGoTrueClient_0( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeFunctionsClient_1 extends _i1.SmartFake + implements _i3.FunctionsClient { + _FakeFunctionsClient_1( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeSupabaseStorageClient_2 extends _i1.SmartFake + implements _i4.SupabaseStorageClient { + _FakeSupabaseStorageClient_2( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeRealtimeClient_3 extends _i1.SmartFake + implements _i5.RealtimeClient { + _FakeRealtimeClient_3( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakePostgrestClient_4 extends _i1.SmartFake + implements _i6.PostgrestClient { + _FakePostgrestClient_4( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeSupabaseQueryBuilder_5 extends _i1.SmartFake + implements _i7.SupabaseQueryBuilder { + _FakeSupabaseQueryBuilder_5( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakePostgrestFilterBuilder_6 extends _i1.SmartFake + implements _i6.PostgrestFilterBuilder { + _FakePostgrestFilterBuilder_6( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeRealtimeChannel_7 extends _i1.SmartFake + implements _i5.RealtimeChannel { + _FakeRealtimeChannel_7( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeStorageFileApi_8 extends _i1.SmartFake + implements _i4.StorageFileApi { + _FakeStorageFileApi_8( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeBucket_9 extends _i1.SmartFake implements _i4.Bucket { + _FakeBucket_9( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeResponse_10 extends _i1.SmartFake implements _i8.Response { + _FakeResponse_10( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +class _FakeStreamedResponse_11 extends _i1.SmartFake + implements _i8.StreamedResponse { + _FakeStreamedResponse_11( + Object parent, + Invocation parentInvocation, + ) : super( + parent, + parentInvocation, + ); +} + +/// A class which mocks [SupabaseClient]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockSupabaseClient extends _i1.Mock implements _i9.SupabaseClient { + MockSupabaseClient() { + _i1.throwOnMissingStub(this); + } + + @override + String get supabaseUrl => (super.noSuchMethod( + Invocation.getter(#supabaseUrl), + returnValue: '', + ) as String); + @override + String get supabaseKey => (super.noSuchMethod( + Invocation.getter(#supabaseKey), + returnValue: '', + ) as String); + @override + String get schema => (super.noSuchMethod( + Invocation.getter(#schema), + returnValue: '', + ) as String); + @override + String get restUrl => (super.noSuchMethod( + Invocation.getter(#restUrl), + returnValue: '', + ) as String); + @override + String get realtimeUrl => (super.noSuchMethod( + Invocation.getter(#realtimeUrl), + returnValue: '', + ) as String); + @override + String get authUrl => (super.noSuchMethod( + Invocation.getter(#authUrl), + returnValue: '', + ) as String); + @override + String get storageUrl => (super.noSuchMethod( + Invocation.getter(#storageUrl), + returnValue: '', + ) as String); + @override + String get functionsUrl => (super.noSuchMethod( + Invocation.getter(#functionsUrl), + returnValue: '', + ) as String); + @override + _i2.GoTrueClient get auth => (super.noSuchMethod( + Invocation.getter(#auth), + returnValue: _FakeGoTrueClient_0( + this, + Invocation.getter(#auth), + ), + ) as _i2.GoTrueClient); + @override + set auth(_i2.GoTrueClient? _auth) => super.noSuchMethod( + Invocation.setter( + #auth, + _auth, + ), + returnValueForMissingStub: null, + ); + @override + _i3.FunctionsClient get functions => (super.noSuchMethod( + Invocation.getter(#functions), + returnValue: _FakeFunctionsClient_1( + this, + Invocation.getter(#functions), + ), + ) as _i3.FunctionsClient); + @override + set functions(_i3.FunctionsClient? _functions) => super.noSuchMethod( + Invocation.setter( + #functions, + _functions, + ), + returnValueForMissingStub: null, + ); + @override + _i4.SupabaseStorageClient get storage => (super.noSuchMethod( + Invocation.getter(#storage), + returnValue: _FakeSupabaseStorageClient_2( + this, + Invocation.getter(#storage), + ), + ) as _i4.SupabaseStorageClient); + @override + set storage(_i4.SupabaseStorageClient? _storage) => super.noSuchMethod( + Invocation.setter( + #storage, + _storage, + ), + returnValueForMissingStub: null, + ); + @override + _i5.RealtimeClient get realtime => (super.noSuchMethod( + Invocation.getter(#realtime), + returnValue: _FakeRealtimeClient_3( + this, + Invocation.getter(#realtime), + ), + ) as _i5.RealtimeClient); + @override + set realtime(_i5.RealtimeClient? _realtime) => super.noSuchMethod( + Invocation.setter( + #realtime, + _realtime, + ), + returnValueForMissingStub: null, + ); + @override + _i6.PostgrestClient get rest => (super.noSuchMethod( + Invocation.getter(#rest), + returnValue: _FakePostgrestClient_4( + this, + Invocation.getter(#rest), + ), + ) as _i6.PostgrestClient); + @override + set rest(_i6.PostgrestClient? _rest) => super.noSuchMethod( + Invocation.setter( + #rest, + _rest, + ), + returnValueForMissingStub: null, + ); + @override + Map get headers => (super.noSuchMethod( + Invocation.getter(#headers), + returnValue: {}, + ) as Map); + @override + set headers(Map? headers) => super.noSuchMethod( + Invocation.setter( + #headers, + headers, + ), + returnValueForMissingStub: null, + ); + @override + _i7.SupabaseQueryBuilder from(String? table) => (super.noSuchMethod( + Invocation.method( + #from, + [table], + ), + returnValue: _FakeSupabaseQueryBuilder_5( + this, + Invocation.method( + #from, + [table], + ), + ), + ) as _i7.SupabaseQueryBuilder); + @override + _i6.PostgrestClient useSchema(String? schema) => (super.noSuchMethod( + Invocation.method( + #useSchema, + [schema], + ), + returnValue: _FakePostgrestClient_4( + this, + Invocation.method( + #useSchema, + [schema], + ), + ), + ) as _i6.PostgrestClient); + @override + _i6.PostgrestFilterBuilder rpc( + String? fn, { + Map? params, + _i6.FetchOptions? options = const _i6.FetchOptions(), + }) => + (super.noSuchMethod( + Invocation.method( + #rpc, + [fn], + { + #params: params, + #options: options, + }, + ), + returnValue: _FakePostgrestFilterBuilder_6( + this, + Invocation.method( + #rpc, + [fn], + { + #params: params, + #options: options, + }, + ), + ), + ) as _i6.PostgrestFilterBuilder); + @override + _i5.RealtimeChannel channel( + String? name, { + _i5.RealtimeChannelConfig? opts = const _i5.RealtimeChannelConfig(), + }) => + (super.noSuchMethod( + Invocation.method( + #channel, + [name], + {#opts: opts}, + ), + returnValue: _FakeRealtimeChannel_7( + this, + Invocation.method( + #channel, + [name], + {#opts: opts}, + ), + ), + ) as _i5.RealtimeChannel); + @override + List<_i5.RealtimeChannel> getChannels() => (super.noSuchMethod( + Invocation.method( + #getChannels, + [], + ), + returnValue: <_i5.RealtimeChannel>[], + ) as List<_i5.RealtimeChannel>); + @override + _i10.Future removeChannel(_i5.RealtimeChannel? channel) => + (super.noSuchMethod( + Invocation.method( + #removeChannel, + [channel], + ), + returnValue: _i10.Future.value(''), + ) as _i10.Future); + @override + _i10.Future> removeAllChannels() => (super.noSuchMethod( + Invocation.method( + #removeAllChannels, + [], + ), + returnValue: _i10.Future>.value([]), + ) as _i10.Future>); + @override + _i10.Future dispose() => (super.noSuchMethod( + Invocation.method( + #dispose, + [], + ), + returnValue: _i10.Future.value(), + returnValueForMissingStub: _i10.Future.value(), + ) as _i10.Future); +} + +/// A class which mocks [SupabaseStorageClient]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockSupabaseStorageClient extends _i1.Mock + implements _i4.SupabaseStorageClient { + MockSupabaseStorageClient() { + _i1.throwOnMissingStub(this); + } + + @override + String get url => (super.noSuchMethod( + Invocation.getter(#url), + returnValue: '', + ) as String); + @override + Map get headers => (super.noSuchMethod( + Invocation.getter(#headers), + returnValue: {}, + ) as Map); + @override + _i4.StorageFileApi from(String? id) => (super.noSuchMethod( + Invocation.method( + #from, + [id], + ), + returnValue: _FakeStorageFileApi_8( + this, + Invocation.method( + #from, + [id], + ), + ), + ) as _i4.StorageFileApi); + @override + void setAuth(String? jwt) => super.noSuchMethod( + Invocation.method( + #setAuth, + [jwt], + ), + returnValueForMissingStub: null, + ); + @override + _i10.Future> listBuckets() => (super.noSuchMethod( + Invocation.method( + #listBuckets, + [], + ), + returnValue: _i10.Future>.value(<_i4.Bucket>[]), + ) as _i10.Future>); + @override + _i10.Future<_i4.Bucket> getBucket(String? id) => (super.noSuchMethod( + Invocation.method( + #getBucket, + [id], + ), + returnValue: _i10.Future<_i4.Bucket>.value(_FakeBucket_9( + this, + Invocation.method( + #getBucket, + [id], + ), + )), + ) as _i10.Future<_i4.Bucket>); + @override + _i10.Future createBucket( + String? id, [ + _i4.BucketOptions? bucketOptions = const _i4.BucketOptions(public: false), + ]) => + (super.noSuchMethod( + Invocation.method( + #createBucket, + [ + id, + bucketOptions, + ], + ), + returnValue: _i10.Future.value(''), + ) as _i10.Future); + @override + _i10.Future updateBucket( + String? id, + _i4.BucketOptions? bucketOptions, + ) => + (super.noSuchMethod( + Invocation.method( + #updateBucket, + [ + id, + bucketOptions, + ], + ), + returnValue: _i10.Future.value(''), + ) as _i10.Future); + @override + _i10.Future emptyBucket(String? id) => (super.noSuchMethod( + Invocation.method( + #emptyBucket, + [id], + ), + returnValue: _i10.Future.value(''), + ) as _i10.Future); + @override + _i10.Future deleteBucket(String? id) => (super.noSuchMethod( + Invocation.method( + #deleteBucket, + [id], + ), + returnValue: _i10.Future.value(''), + ) as _i10.Future); +} + +/// A class which mocks [Client]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockClient extends _i1.Mock implements _i8.Client { + MockClient() { + _i1.throwOnMissingStub(this); + } + + @override + _i10.Future<_i8.Response> head( + Uri? url, { + Map? headers, + }) => + (super.noSuchMethod( + Invocation.method( + #head, + [url], + {#headers: headers}, + ), + returnValue: _i10.Future<_i8.Response>.value(_FakeResponse_10( + this, + Invocation.method( + #head, + [url], + {#headers: headers}, + ), + )), + ) as _i10.Future<_i8.Response>); + @override + _i10.Future<_i8.Response> get( + Uri? url, { + Map? headers, + }) => + (super.noSuchMethod( + Invocation.method( + #get, + [url], + {#headers: headers}, + ), + returnValue: _i10.Future<_i8.Response>.value(_FakeResponse_10( + this, + Invocation.method( + #get, + [url], + {#headers: headers}, + ), + )), + ) as _i10.Future<_i8.Response>); + @override + _i10.Future<_i8.Response> post( + Uri? url, { + Map? headers, + Object? body, + _i11.Encoding? encoding, + }) => + (super.noSuchMethod( + Invocation.method( + #post, + [url], + { + #headers: headers, + #body: body, + #encoding: encoding, + }, + ), + returnValue: _i10.Future<_i8.Response>.value(_FakeResponse_10( + this, + Invocation.method( + #post, + [url], + { + #headers: headers, + #body: body, + #encoding: encoding, + }, + ), + )), + ) as _i10.Future<_i8.Response>); + @override + _i10.Future<_i8.Response> put( + Uri? url, { + Map? headers, + Object? body, + _i11.Encoding? encoding, + }) => + (super.noSuchMethod( + Invocation.method( + #put, + [url], + { + #headers: headers, + #body: body, + #encoding: encoding, + }, + ), + returnValue: _i10.Future<_i8.Response>.value(_FakeResponse_10( + this, + Invocation.method( + #put, + [url], + { + #headers: headers, + #body: body, + #encoding: encoding, + }, + ), + )), + ) as _i10.Future<_i8.Response>); + @override + _i10.Future<_i8.Response> patch( + Uri? url, { + Map? headers, + Object? body, + _i11.Encoding? encoding, + }) => + (super.noSuchMethod( + Invocation.method( + #patch, + [url], + { + #headers: headers, + #body: body, + #encoding: encoding, + }, + ), + returnValue: _i10.Future<_i8.Response>.value(_FakeResponse_10( + this, + Invocation.method( + #patch, + [url], + { + #headers: headers, + #body: body, + #encoding: encoding, + }, + ), + )), + ) as _i10.Future<_i8.Response>); + @override + _i10.Future<_i8.Response> delete( + Uri? url, { + Map? headers, + Object? body, + _i11.Encoding? encoding, + }) => + (super.noSuchMethod( + Invocation.method( + #delete, + [url], + { + #headers: headers, + #body: body, + #encoding: encoding, + }, + ), + returnValue: _i10.Future<_i8.Response>.value(_FakeResponse_10( + this, + Invocation.method( + #delete, + [url], + { + #headers: headers, + #body: body, + #encoding: encoding, + }, + ), + )), + ) as _i10.Future<_i8.Response>); + @override + _i10.Future read( + Uri? url, { + Map? headers, + }) => + (super.noSuchMethod( + Invocation.method( + #read, + [url], + {#headers: headers}, + ), + returnValue: _i10.Future.value(''), + ) as _i10.Future); + @override + _i10.Future<_i12.Uint8List> readBytes( + Uri? url, { + Map? headers, + }) => + (super.noSuchMethod( + Invocation.method( + #readBytes, + [url], + {#headers: headers}, + ), + returnValue: _i10.Future<_i12.Uint8List>.value(_i12.Uint8List(0)), + ) as _i10.Future<_i12.Uint8List>); + @override + _i10.Future<_i8.StreamedResponse> send(_i8.BaseRequest? request) => + (super.noSuchMethod( + Invocation.method( + #send, + [request], + ), + returnValue: + _i10.Future<_i8.StreamedResponse>.value(_FakeStreamedResponse_11( + this, + Invocation.method( + #send, + [request], + ), + )), + ) as _i10.Future<_i8.StreamedResponse>); + @override + void close() => super.noSuchMethod( + Invocation.method( + #close, + [], + ), + returnValueForMissingStub: null, + ); +} diff --git a/test/payment/stripe_payment_repo_test.dart b/test/payment/stripe_payment_repo_test.dart new file mode 100644 index 0000000..b85f859 --- /dev/null +++ b/test/payment/stripe_payment_repo_test.dart @@ -0,0 +1,16 @@ +import 'package:appflowy_theme_marketplace/src/authentication/data/repositories/supabase_authentication_repository.dart'; +import 'package:appflowy_theme_marketplace/src/payment/domain/models/user.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/annotations.dart'; +import 'package:mockito/mockito.dart'; +import 'package:supabase_flutter/supabase_flutter.dart' as supabase; + +import '../main.mocks.dart'; +// import 'stripe_payment_repo_test.mocks.dart'; + +@GenerateMocks([User]) + +Future main() async { + + +} \ No newline at end of file diff --git a/test/payment/stripe_payment_repo_test.mocks.dart b/test/payment/stripe_payment_repo_test.mocks.dart new file mode 100644 index 0000000..8401116 --- /dev/null +++ b/test/payment/stripe_payment_repo_test.mocks.dart @@ -0,0 +1,52 @@ +// Mocks generated by Mockito 5.4.0 from annotations +// in appflowy_theme_marketplace/test/payment/stripe_payment_repo_test.dart. +// Do not manually edit this file. + +// ignore_for_file: no_leading_underscores_for_library_prefixes +import 'package:appflowy_theme_marketplace/src/payment/domain/models/user.dart' + as _i2; +import 'package:mockito/mockito.dart' as _i1; + +// ignore_for_file: type=lint +// ignore_for_file: avoid_redundant_argument_values +// ignore_for_file: avoid_setters_without_getters +// ignore_for_file: comment_references +// ignore_for_file: implementation_imports +// ignore_for_file: invalid_use_of_visible_for_testing_member +// ignore_for_file: prefer_const_constructors +// ignore_for_file: unnecessary_parenthesis +// ignore_for_file: camel_case_types +// ignore_for_file: subtype_of_sealed_class + +/// A class which mocks [User]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockUser extends _i1.Mock implements _i2.User { + MockUser() { + _i1.throwOnMissingStub(this); + } + + @override + String get uid => (super.noSuchMethod( + Invocation.getter(#uid), + returnValue: '', + ) as String); + @override + String get email => (super.noSuchMethod( + Invocation.getter(#email), + returnValue: '', + ) as String); + @override + List get purchasedItems => (super.noSuchMethod( + Invocation.getter(#purchasedItems), + returnValue: [], + ) as List); + @override + Map toJson() => (super.noSuchMethod( + Invocation.method( + #toJson, + [], + ), + returnValue: {}, + ) as Map); +} diff --git a/test/plugins/supabase_plugins_repo_test.dart b/test/plugins/supabase_plugins_repo_test.dart new file mode 100644 index 0000000..e69de29 diff --git a/test/ratings/supabase_ratings_repo_test.dart b/test/ratings/supabase_ratings_repo_test.dart new file mode 100644 index 0000000..e69de29 diff --git a/test/storage/supabase_storage_repo_test.dart b/test/storage/supabase_storage_repo_test.dart new file mode 100644 index 0000000..e69de29 diff --git a/test/user/supabase_user_repo_test.dart b/test/user/supabase_user_repo_test.dart new file mode 100644 index 0000000..e69de29 diff --git a/web/favicon.png b/web/favicon.png new file mode 100644 index 0000000..8aaa46a Binary files /dev/null and b/web/favicon.png differ diff --git a/web/icons/Icon-192.png b/web/icons/Icon-192.png new file mode 100644 index 0000000..b749bfe Binary files /dev/null and b/web/icons/Icon-192.png differ diff --git a/web/icons/Icon-512.png b/web/icons/Icon-512.png new file mode 100644 index 0000000..88cfd48 Binary files /dev/null and b/web/icons/Icon-512.png differ diff --git a/web/icons/Icon-maskable-192.png b/web/icons/Icon-maskable-192.png new file mode 100644 index 0000000..eb9b4d7 Binary files /dev/null and b/web/icons/Icon-maskable-192.png differ diff --git a/web/icons/Icon-maskable-512.png b/web/icons/Icon-maskable-512.png new file mode 100644 index 0000000..d69c566 Binary files /dev/null and b/web/icons/Icon-maskable-512.png differ diff --git a/web/index.html b/web/index.html new file mode 100644 index 0000000..f644166 --- /dev/null +++ b/web/index.html @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + appflowy_theme_marketplace + + + + + + + + + + diff --git a/web/manifest.json b/web/manifest.json new file mode 100644 index 0000000..c7b127e --- /dev/null +++ b/web/manifest.json @@ -0,0 +1,35 @@ +{ + "name": "appflowy_theme_marketplace", + "short_name": "appflowy_theme_marketplace", + "start_url": ".", + "display": "standalone", + "background_color": "#0175C2", + "theme_color": "#0175C2", + "description": "A new Flutter project.", + "orientation": "portrait-primary", + "prefer_related_applications": false, + "icons": [ + { + "src": "icons/Icon-192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "icons/Icon-512.png", + "sizes": "512x512", + "type": "image/png" + }, + { + "src": "icons/Icon-maskable-192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "icons/Icon-maskable-512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + } + ] +} diff --git a/windows/.gitignore b/windows/.gitignore new file mode 100644 index 0000000..d492d0d --- /dev/null +++ b/windows/.gitignore @@ -0,0 +1,17 @@ +flutter/ephemeral/ + +# Visual Studio user-specific files. +*.suo +*.user +*.userosscache +*.sln.docstates + +# Visual Studio build-related files. +x64/ +x86/ + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ diff --git a/windows/CMakeLists.txt b/windows/CMakeLists.txt new file mode 100644 index 0000000..0f4db68 --- /dev/null +++ b/windows/CMakeLists.txt @@ -0,0 +1,101 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.14) +project(appflowy_theme_marketplace LANGUAGES CXX) + +# The name of the executable created for the application. Change this to change +# the on-disk name of your application. +set(BINARY_NAME "appflowy_theme_marketplace") + +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. +cmake_policy(SET CMP0063 NEW) + +# Define build configuration option. +get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) +if(IS_MULTICONFIG) + set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release" + CACHE STRING "" FORCE) +else() + if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") + endif() +endif() +# Define settings for the Profile build mode. +set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}") +set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}") +set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}") +set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}") + +# Use Unicode for all projects. +add_definitions(-DUNICODE -D_UNICODE) + +# Compilation settings that should be applied to most targets. +# +# Be cautious about adding new options here, as plugins use this function by +# default. In most cases, you should add new options to specific targets instead +# of modifying this function. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_17) + target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100") + target_compile_options(${TARGET} PRIVATE /EHsc) + target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0") + target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>") +endfunction() + +# Flutter library and tool build rules. +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# Application build; see runner/CMakeLists.txt. +add_subdirectory("runner") + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# Support files are copied into place next to the executable, so that it can +# run in place. This is done instead of making a separate bundle (as on Linux) +# so that building and running from within Visual Studio will work. +set(BUILD_BUNDLE_DIR "$") +# Make the "install" step default, as it's required to run. +set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +if(PLUGIN_BUNDLED_LIBRARIES) + install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + CONFIGURATIONS Profile;Release + COMPONENT Runtime) diff --git a/windows/flutter/CMakeLists.txt b/windows/flutter/CMakeLists.txt new file mode 100644 index 0000000..930d207 --- /dev/null +++ b/windows/flutter/CMakeLists.txt @@ -0,0 +1,104 @@ +# This file controls Flutter-level build steps. It should not be edited. +cmake_minimum_required(VERSION 3.14) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. +set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") + +# === Flutter Library === +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "flutter_export.h" + "flutter_windows.h" + "flutter_messenger.h" + "flutter_plugin_registrar.h" + "flutter_texture_registrar.h" +) +list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib") +add_dependencies(flutter flutter_assemble) + +# === Wrapper === +list(APPEND CPP_WRAPPER_SOURCES_CORE + "core_implementations.cc" + "standard_codec.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_PLUGIN + "plugin_registrar.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_APP + "flutter_engine.cc" + "flutter_view_controller.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/") + +# Wrapper sources needed for a plugin. +add_library(flutter_wrapper_plugin STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} +) +apply_standard_settings(flutter_wrapper_plugin) +set_target_properties(flutter_wrapper_plugin PROPERTIES + POSITION_INDEPENDENT_CODE ON) +set_target_properties(flutter_wrapper_plugin PROPERTIES + CXX_VISIBILITY_PRESET hidden) +target_link_libraries(flutter_wrapper_plugin PUBLIC flutter) +target_include_directories(flutter_wrapper_plugin PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_plugin flutter_assemble) + +# Wrapper sources needed for the runner. +add_library(flutter_wrapper_app STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_APP} +) +apply_standard_settings(flutter_wrapper_app) +target_link_libraries(flutter_wrapper_app PUBLIC flutter) +target_include_directories(flutter_wrapper_app PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_app flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_") +set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE) +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} + ${PHONY_OUTPUT} + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" + windows-x64 $ + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} +) diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc new file mode 100644 index 0000000..c87e8b8 --- /dev/null +++ b/windows/flutter/generated_plugin_registrant.cc @@ -0,0 +1,20 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + +#include +#include +#include + +void RegisterPlugins(flutter::PluginRegistry* registry) { + AppLinksPluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("AppLinksPluginCApi")); + FirebaseCorePluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("FirebaseCorePluginCApi")); + UrlLauncherWindowsRegisterWithRegistrar( + registry->GetRegistrarForPlugin("UrlLauncherWindows")); +} diff --git a/windows/flutter/generated_plugin_registrant.h b/windows/flutter/generated_plugin_registrant.h new file mode 100644 index 0000000..dc139d8 --- /dev/null +++ b/windows/flutter/generated_plugin_registrant.h @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void RegisterPlugins(flutter::PluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake new file mode 100644 index 0000000..19833e4 --- /dev/null +++ b/windows/flutter/generated_plugins.cmake @@ -0,0 +1,26 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST + app_links + firebase_core + url_launcher_windows +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/windows/runner/CMakeLists.txt b/windows/runner/CMakeLists.txt new file mode 100644 index 0000000..394917c --- /dev/null +++ b/windows/runner/CMakeLists.txt @@ -0,0 +1,40 @@ +cmake_minimum_required(VERSION 3.14) +project(runner LANGUAGES CXX) + +# Define the application target. To change its name, change BINARY_NAME in the +# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer +# work. +# +# Any new source files that you add to the application should be added here. +add_executable(${BINARY_NAME} WIN32 + "flutter_window.cpp" + "main.cpp" + "utils.cpp" + "win32_window.cpp" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" + "Runner.rc" + "runner.exe.manifest" +) + +# Apply the standard set of build settings. This can be removed for applications +# that need different build settings. +apply_standard_settings(${BINARY_NAME}) + +# Add preprocessor definitions for the build version. +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION=\"${FLUTTER_VERSION}\"") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MAJOR=${FLUTTER_VERSION_MAJOR}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MINOR=${FLUTTER_VERSION_MINOR}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTTER_VERSION_BUILD}") + +# Disable Windows macros that collide with C++ standard library functions. +target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") + +# Add dependency libraries and include directories. Add any application-specific +# dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) +target_link_libraries(${BINARY_NAME} PRIVATE "dwmapi.lib") +target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") + +# Run the Flutter tool portions of the build. This must not be removed. +add_dependencies(${BINARY_NAME} flutter_assemble) diff --git a/windows/runner/Runner.rc b/windows/runner/Runner.rc new file mode 100644 index 0000000..25624ae --- /dev/null +++ b/windows/runner/Runner.rc @@ -0,0 +1,121 @@ +// Microsoft Visual C++ generated resource script. +// +#pragma code_page(65001) +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "winres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (United States) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""winres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_APP_ICON ICON "resources\\app_icon.ico" + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +#if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD) +#define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD +#else +#define VERSION_AS_NUMBER 1,0,0,0 +#endif + +#if defined(FLUTTER_VERSION) +#define VERSION_AS_STRING FLUTTER_VERSION +#else +#define VERSION_AS_STRING "1.0.0" +#endif + +VS_VERSION_INFO VERSIONINFO + FILEVERSION VERSION_AS_NUMBER + PRODUCTVERSION VERSION_AS_NUMBER + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK +#ifdef _DEBUG + FILEFLAGS VS_FF_DEBUG +#else + FILEFLAGS 0x0L +#endif + FILEOS VOS__WINDOWS32 + FILETYPE VFT_APP + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904e4" + BEGIN + VALUE "CompanyName", "com.example" "\0" + VALUE "FileDescription", "appflowy_theme_marketplace" "\0" + VALUE "FileVersion", VERSION_AS_STRING "\0" + VALUE "InternalName", "appflowy_theme_marketplace" "\0" + VALUE "LegalCopyright", "Copyright (C) 2023 com.example. All rights reserved." "\0" + VALUE "OriginalFilename", "appflowy_theme_marketplace.exe" "\0" + VALUE "ProductName", "appflowy_theme_marketplace" "\0" + VALUE "ProductVersion", VERSION_AS_STRING "\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1252 + END +END + +#endif // English (United States) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED diff --git a/windows/runner/flutter_window.cpp b/windows/runner/flutter_window.cpp new file mode 100644 index 0000000..b25e363 --- /dev/null +++ b/windows/runner/flutter_window.cpp @@ -0,0 +1,66 @@ +#include "flutter_window.h" + +#include + +#include "flutter/generated_plugin_registrant.h" + +FlutterWindow::FlutterWindow(const flutter::DartProject& project) + : project_(project) {} + +FlutterWindow::~FlutterWindow() {} + +bool FlutterWindow::OnCreate() { + if (!Win32Window::OnCreate()) { + return false; + } + + RECT frame = GetClientArea(); + + // The size here must match the window dimensions to avoid unnecessary surface + // creation / destruction in the startup path. + flutter_controller_ = std::make_unique( + frame.right - frame.left, frame.bottom - frame.top, project_); + // Ensure that basic setup of the controller was successful. + if (!flutter_controller_->engine() || !flutter_controller_->view()) { + return false; + } + RegisterPlugins(flutter_controller_->engine()); + SetChildContent(flutter_controller_->view()->GetNativeWindow()); + + flutter_controller_->engine()->SetNextFrameCallback([&]() { + this->Show(); + }); + + return true; +} + +void FlutterWindow::OnDestroy() { + if (flutter_controller_) { + flutter_controller_ = nullptr; + } + + Win32Window::OnDestroy(); +} + +LRESULT +FlutterWindow::MessageHandler(HWND hwnd, UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + // Give Flutter, including plugins, an opportunity to handle window messages. + if (flutter_controller_) { + std::optional result = + flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, + lparam); + if (result) { + return *result; + } + } + + switch (message) { + case WM_FONTCHANGE: + flutter_controller_->engine()->ReloadSystemFonts(); + break; + } + + return Win32Window::MessageHandler(hwnd, message, wparam, lparam); +} diff --git a/windows/runner/flutter_window.h b/windows/runner/flutter_window.h new file mode 100644 index 0000000..6da0652 --- /dev/null +++ b/windows/runner/flutter_window.h @@ -0,0 +1,33 @@ +#ifndef RUNNER_FLUTTER_WINDOW_H_ +#define RUNNER_FLUTTER_WINDOW_H_ + +#include +#include + +#include + +#include "win32_window.h" + +// A window that does nothing but host a Flutter view. +class FlutterWindow : public Win32Window { + public: + // Creates a new FlutterWindow hosting a Flutter view running |project|. + explicit FlutterWindow(const flutter::DartProject& project); + virtual ~FlutterWindow(); + + protected: + // Win32Window: + bool OnCreate() override; + void OnDestroy() override; + LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, + LPARAM const lparam) noexcept override; + + private: + // The project to run. + flutter::DartProject project_; + + // The Flutter instance hosted by this window. + std::unique_ptr flutter_controller_; +}; + +#endif // RUNNER_FLUTTER_WINDOW_H_ diff --git a/windows/runner/main.cpp b/windows/runner/main.cpp new file mode 100644 index 0000000..5c0e5d5 --- /dev/null +++ b/windows/runner/main.cpp @@ -0,0 +1,43 @@ +#include +#include +#include + +#include "flutter_window.h" +#include "utils.h" + +int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, + _In_ wchar_t *command_line, _In_ int show_command) { + // Attach to console when present (e.g., 'flutter run') or create a + // new console when running with a debugger. + if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { + CreateAndAttachConsole(); + } + + // Initialize COM, so that it is available for use in the library and/or + // plugins. + ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); + + flutter::DartProject project(L"data"); + + std::vector command_line_arguments = + GetCommandLineArguments(); + + project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); + + FlutterWindow window(project); + Win32Window::Point origin(10, 10); + Win32Window::Size size(1280, 720); + if (!window.Create(L"appflowy_theme_marketplace", origin, size)) { + return EXIT_FAILURE; + } + window.SetQuitOnClose(true); + + ::MSG msg; + while (::GetMessage(&msg, nullptr, 0, 0)) { + ::TranslateMessage(&msg); + ::DispatchMessage(&msg); + } + + ::CoUninitialize(); + return EXIT_SUCCESS; +} diff --git a/windows/runner/resource.h b/windows/runner/resource.h new file mode 100644 index 0000000..66a65d1 --- /dev/null +++ b/windows/runner/resource.h @@ -0,0 +1,16 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by Runner.rc +// +#define IDI_APP_ICON 101 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 102 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/windows/runner/resources/app_icon.ico b/windows/runner/resources/app_icon.ico new file mode 100644 index 0000000..c04e20c Binary files /dev/null and b/windows/runner/resources/app_icon.ico differ diff --git a/windows/runner/runner.exe.manifest b/windows/runner/runner.exe.manifest new file mode 100644 index 0000000..a42ea76 --- /dev/null +++ b/windows/runner/runner.exe.manifest @@ -0,0 +1,20 @@ + + + + + PerMonitorV2 + + + + + + + + + + + + + + + diff --git a/windows/runner/utils.cpp b/windows/runner/utils.cpp new file mode 100644 index 0000000..f5bf9fa --- /dev/null +++ b/windows/runner/utils.cpp @@ -0,0 +1,64 @@ +#include "utils.h" + +#include +#include +#include +#include + +#include + +void CreateAndAttachConsole() { + if (::AllocConsole()) { + FILE *unused; + if (freopen_s(&unused, "CONOUT$", "w", stdout)) { + _dup2(_fileno(stdout), 1); + } + if (freopen_s(&unused, "CONOUT$", "w", stderr)) { + _dup2(_fileno(stdout), 2); + } + std::ios::sync_with_stdio(); + FlutterDesktopResyncOutputStreams(); + } +} + +std::vector GetCommandLineArguments() { + // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. + int argc; + wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); + if (argv == nullptr) { + return std::vector(); + } + + std::vector command_line_arguments; + + // Skip the first argument as it's the binary name. + for (int i = 1; i < argc; i++) { + command_line_arguments.push_back(Utf8FromUtf16(argv[i])); + } + + ::LocalFree(argv); + + return command_line_arguments; +} + +std::string Utf8FromUtf16(const wchar_t* utf16_string) { + if (utf16_string == nullptr) { + return std::string(); + } + int target_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + -1, nullptr, 0, nullptr, nullptr); + std::string utf8_string; + if (target_length == 0 || target_length > utf8_string.max_size()) { + return utf8_string; + } + utf8_string.resize(target_length); + int converted_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + -1, utf8_string.data(), + target_length, nullptr, nullptr); + if (converted_length == 0) { + return std::string(); + } + return utf8_string; +} diff --git a/windows/runner/utils.h b/windows/runner/utils.h new file mode 100644 index 0000000..3879d54 --- /dev/null +++ b/windows/runner/utils.h @@ -0,0 +1,19 @@ +#ifndef RUNNER_UTILS_H_ +#define RUNNER_UTILS_H_ + +#include +#include + +// Creates a console for the process, and redirects stdout and stderr to +// it for both the runner and the Flutter library. +void CreateAndAttachConsole(); + +// Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string +// encoded in UTF-8. Returns an empty std::string on failure. +std::string Utf8FromUtf16(const wchar_t* utf16_string); + +// Gets the command line arguments passed in as a std::vector, +// encoded in UTF-8. Returns an empty std::vector on failure. +std::vector GetCommandLineArguments(); + +#endif // RUNNER_UTILS_H_ diff --git a/windows/runner/win32_window.cpp b/windows/runner/win32_window.cpp new file mode 100644 index 0000000..041a385 --- /dev/null +++ b/windows/runner/win32_window.cpp @@ -0,0 +1,288 @@ +#include "win32_window.h" + +#include +#include + +#include "resource.h" + +namespace { + +/// Window attribute that enables dark mode window decorations. +/// +/// Redefined in case the developer's machine has a Windows SDK older than +/// version 10.0.22000.0. +/// See: https://docs.microsoft.com/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute +#ifndef DWMWA_USE_IMMERSIVE_DARK_MODE +#define DWMWA_USE_IMMERSIVE_DARK_MODE 20 +#endif + +constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW"; + +/// Registry key for app theme preference. +/// +/// A value of 0 indicates apps should use dark mode. A non-zero or missing +/// value indicates apps should use light mode. +constexpr const wchar_t kGetPreferredBrightnessRegKey[] = + L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize"; +constexpr const wchar_t kGetPreferredBrightnessRegValue[] = L"AppsUseLightTheme"; + +// The number of Win32Window objects that currently exist. +static int g_active_window_count = 0; + +using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd); + +// Scale helper to convert logical scaler values to physical using passed in +// scale factor +int Scale(int source, double scale_factor) { + return static_cast(source * scale_factor); +} + +// Dynamically loads the |EnableNonClientDpiScaling| from the User32 module. +// This API is only needed for PerMonitor V1 awareness mode. +void EnableFullDpiSupportIfAvailable(HWND hwnd) { + HMODULE user32_module = LoadLibraryA("User32.dll"); + if (!user32_module) { + return; + } + auto enable_non_client_dpi_scaling = + reinterpret_cast( + GetProcAddress(user32_module, "EnableNonClientDpiScaling")); + if (enable_non_client_dpi_scaling != nullptr) { + enable_non_client_dpi_scaling(hwnd); + } + FreeLibrary(user32_module); +} + +} // namespace + +// Manages the Win32Window's window class registration. +class WindowClassRegistrar { + public: + ~WindowClassRegistrar() = default; + + // Returns the singleton registar instance. + static WindowClassRegistrar* GetInstance() { + if (!instance_) { + instance_ = new WindowClassRegistrar(); + } + return instance_; + } + + // Returns the name of the window class, registering the class if it hasn't + // previously been registered. + const wchar_t* GetWindowClass(); + + // Unregisters the window class. Should only be called if there are no + // instances of the window. + void UnregisterWindowClass(); + + private: + WindowClassRegistrar() = default; + + static WindowClassRegistrar* instance_; + + bool class_registered_ = false; +}; + +WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr; + +const wchar_t* WindowClassRegistrar::GetWindowClass() { + if (!class_registered_) { + WNDCLASS window_class{}; + window_class.hCursor = LoadCursor(nullptr, IDC_ARROW); + window_class.lpszClassName = kWindowClassName; + window_class.style = CS_HREDRAW | CS_VREDRAW; + window_class.cbClsExtra = 0; + window_class.cbWndExtra = 0; + window_class.hInstance = GetModuleHandle(nullptr); + window_class.hIcon = + LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON)); + window_class.hbrBackground = 0; + window_class.lpszMenuName = nullptr; + window_class.lpfnWndProc = Win32Window::WndProc; + RegisterClass(&window_class); + class_registered_ = true; + } + return kWindowClassName; +} + +void WindowClassRegistrar::UnregisterWindowClass() { + UnregisterClass(kWindowClassName, nullptr); + class_registered_ = false; +} + +Win32Window::Win32Window() { + ++g_active_window_count; +} + +Win32Window::~Win32Window() { + --g_active_window_count; + Destroy(); +} + +bool Win32Window::Create(const std::wstring& title, + const Point& origin, + const Size& size) { + Destroy(); + + const wchar_t* window_class = + WindowClassRegistrar::GetInstance()->GetWindowClass(); + + const POINT target_point = {static_cast(origin.x), + static_cast(origin.y)}; + HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST); + UINT dpi = FlutterDesktopGetDpiForMonitor(monitor); + double scale_factor = dpi / 96.0; + + HWND window = CreateWindow( + window_class, title.c_str(), WS_OVERLAPPEDWINDOW, + Scale(origin.x, scale_factor), Scale(origin.y, scale_factor), + Scale(size.width, scale_factor), Scale(size.height, scale_factor), + nullptr, nullptr, GetModuleHandle(nullptr), this); + + if (!window) { + return false; + } + + UpdateTheme(window); + + return OnCreate(); +} + +bool Win32Window::Show() { + return ShowWindow(window_handle_, SW_SHOWNORMAL); +} + +// static +LRESULT CALLBACK Win32Window::WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + if (message == WM_NCCREATE) { + auto window_struct = reinterpret_cast(lparam); + SetWindowLongPtr(window, GWLP_USERDATA, + reinterpret_cast(window_struct->lpCreateParams)); + + auto that = static_cast(window_struct->lpCreateParams); + EnableFullDpiSupportIfAvailable(window); + that->window_handle_ = window; + } else if (Win32Window* that = GetThisFromHandle(window)) { + return that->MessageHandler(window, message, wparam, lparam); + } + + return DefWindowProc(window, message, wparam, lparam); +} + +LRESULT +Win32Window::MessageHandler(HWND hwnd, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + switch (message) { + case WM_DESTROY: + window_handle_ = nullptr; + Destroy(); + if (quit_on_close_) { + PostQuitMessage(0); + } + return 0; + + case WM_DPICHANGED: { + auto newRectSize = reinterpret_cast(lparam); + LONG newWidth = newRectSize->right - newRectSize->left; + LONG newHeight = newRectSize->bottom - newRectSize->top; + + SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth, + newHeight, SWP_NOZORDER | SWP_NOACTIVATE); + + return 0; + } + case WM_SIZE: { + RECT rect = GetClientArea(); + if (child_content_ != nullptr) { + // Size and position the child window. + MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left, + rect.bottom - rect.top, TRUE); + } + return 0; + } + + case WM_ACTIVATE: + if (child_content_ != nullptr) { + SetFocus(child_content_); + } + return 0; + + case WM_DWMCOLORIZATIONCOLORCHANGED: + UpdateTheme(hwnd); + return 0; + } + + return DefWindowProc(window_handle_, message, wparam, lparam); +} + +void Win32Window::Destroy() { + OnDestroy(); + + if (window_handle_) { + DestroyWindow(window_handle_); + window_handle_ = nullptr; + } + if (g_active_window_count == 0) { + WindowClassRegistrar::GetInstance()->UnregisterWindowClass(); + } +} + +Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept { + return reinterpret_cast( + GetWindowLongPtr(window, GWLP_USERDATA)); +} + +void Win32Window::SetChildContent(HWND content) { + child_content_ = content; + SetParent(content, window_handle_); + RECT frame = GetClientArea(); + + MoveWindow(content, frame.left, frame.top, frame.right - frame.left, + frame.bottom - frame.top, true); + + SetFocus(child_content_); +} + +RECT Win32Window::GetClientArea() { + RECT frame; + GetClientRect(window_handle_, &frame); + return frame; +} + +HWND Win32Window::GetHandle() { + return window_handle_; +} + +void Win32Window::SetQuitOnClose(bool quit_on_close) { + quit_on_close_ = quit_on_close; +} + +bool Win32Window::OnCreate() { + // No-op; provided for subclasses. + return true; +} + +void Win32Window::OnDestroy() { + // No-op; provided for subclasses. +} + +void Win32Window::UpdateTheme(HWND const window) { + DWORD light_mode; + DWORD light_mode_size = sizeof(light_mode); + LSTATUS result = RegGetValue(HKEY_CURRENT_USER, kGetPreferredBrightnessRegKey, + kGetPreferredBrightnessRegValue, + RRF_RT_REG_DWORD, nullptr, &light_mode, + &light_mode_size); + + if (result == ERROR_SUCCESS) { + BOOL enable_dark_mode = light_mode == 0; + DwmSetWindowAttribute(window, DWMWA_USE_IMMERSIVE_DARK_MODE, + &enable_dark_mode, sizeof(enable_dark_mode)); + } +} diff --git a/windows/runner/win32_window.h b/windows/runner/win32_window.h new file mode 100644 index 0000000..c86632d --- /dev/null +++ b/windows/runner/win32_window.h @@ -0,0 +1,102 @@ +#ifndef RUNNER_WIN32_WINDOW_H_ +#define RUNNER_WIN32_WINDOW_H_ + +#include + +#include +#include +#include + +// A class abstraction for a high DPI-aware Win32 Window. Intended to be +// inherited from by classes that wish to specialize with custom +// rendering and input handling +class Win32Window { + public: + struct Point { + unsigned int x; + unsigned int y; + Point(unsigned int x, unsigned int y) : x(x), y(y) {} + }; + + struct Size { + unsigned int width; + unsigned int height; + Size(unsigned int width, unsigned int height) + : width(width), height(height) {} + }; + + Win32Window(); + virtual ~Win32Window(); + + // Creates a win32 window with |title| that is positioned and sized using + // |origin| and |size|. New windows are created on the default monitor. Window + // sizes are specified to the OS in physical pixels, hence to ensure a + // consistent size this function will scale the inputted width and height as + // as appropriate for the default monitor. The window is invisible until + // |Show| is called. Returns true if the window was created successfully. + bool Create(const std::wstring& title, const Point& origin, const Size& size); + + // Show the current window. Returns true if the window was successfully shown. + bool Show(); + + // Release OS resources associated with window. + void Destroy(); + + // Inserts |content| into the window tree. + void SetChildContent(HWND content); + + // Returns the backing Window handle to enable clients to set icon and other + // window properties. Returns nullptr if the window has been destroyed. + HWND GetHandle(); + + // If true, closing this window will quit the application. + void SetQuitOnClose(bool quit_on_close); + + // Return a RECT representing the bounds of the current client area. + RECT GetClientArea(); + + protected: + // Processes and route salient window messages for mouse handling, + // size change and DPI. Delegates handling of these to member overloads that + // inheriting classes can handle. + virtual LRESULT MessageHandler(HWND window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Called when CreateAndShow is called, allowing subclass window-related + // setup. Subclasses should return false if setup fails. + virtual bool OnCreate(); + + // Called when Destroy is called. + virtual void OnDestroy(); + + private: + friend class WindowClassRegistrar; + + // OS callback called by message pump. Handles the WM_NCCREATE message which + // is passed when the non-client area is being created and enables automatic + // non-client DPI scaling so that the non-client area automatically + // responsponds to changes in DPI. All other messages are handled by + // MessageHandler. + static LRESULT CALLBACK WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Retrieves a class instance pointer for |window| + static Win32Window* GetThisFromHandle(HWND const window) noexcept; + + // Update the window frame's theme to match the system theme. + static void UpdateTheme(HWND const window); + + bool quit_on_close_ = false; + + // window handle for top level window. + HWND window_handle_ = nullptr; + + // window handle for hosted content. + HWND child_content_ = nullptr; +}; + +#endif // RUNNER_WIN32_WINDOW_H_