diff --git a/Gemfile b/Gemfile index 3f3b2533..86a1cf99 100644 --- a/Gemfile +++ b/Gemfile @@ -3,13 +3,13 @@ ruby "~> 2.7" # Server gem 'rails' -gem 'puma', '~> 5.3' +gem 'puma', '~> 5.5' gem 'puma-heroku' # gem 'bootsnap', require: false gem 'sprockets', '~> 3.7.2' # Storage -gem 'aws-sdk', '~> 3.0' +gem 'aws-sdk', '~> 3.1' gem 'aws-sdk-s3' gem 'filesize' @@ -42,7 +42,6 @@ gem 'paranoia' # Javascript gem 'coffee-rails' gem 'rails-jquery-autocomplete' -gem 'animate-rails' gem 'webpacker' gem 'react-rails' @@ -91,7 +90,7 @@ gem 'redis' gem 'csv' # Admin -gem 'rails_admin', '~> 2.1' +gem 'rails_admin', '~> 2.2' # Tech debt & hacks gem 'binding_of_caller' # see has_changelog.rb diff --git a/Gemfile.lock b/Gemfile.lock index a1195fde..25c26e3f 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -13,13 +13,13 @@ GIT GIT remote: https://github.com/indentlabs/thredded.git - revision: b9a3976e310bb4eeb3f9a4b30d6c176d15a28ce1 + revision: 6ed40273e4696fc4f32013e4f2a101ff8658b960 branch: feature/report-posts specs: thredded (0.16.16) active_record_union (>= 1.3.0) autoprefixer-rails - db_text_search (~> 0.3.0) + db_text_search (~> 0.3.2) friendly_id html-pipeline htmlentities @@ -28,10 +28,10 @@ GIT kramdown (>= 2.0.0) kramdown-parser-gfm nokogiri - onebox (~> 1.8, >= 1.8.99) + onebox (>= 1.8.99) pundit (>= 1.1.0) rails (>= 4.2.10, != 6.0.0.rc2) - rb-gravatar + rails_gravatar rinku sanitize sassc-rails (>= 2.0.0) @@ -41,40 +41,40 @@ GIT GEM remote: https://rubygems.org/ specs: - actioncable (6.1.4) - actionpack (= 6.1.4) - activesupport (= 6.1.4) + actioncable (6.1.4.1) + actionpack (= 6.1.4.1) + activesupport (= 6.1.4.1) nio4r (~> 2.0) websocket-driver (>= 0.6.1) - actionmailbox (6.1.4) - actionpack (= 6.1.4) - activejob (= 6.1.4) - activerecord (= 6.1.4) - activestorage (= 6.1.4) - activesupport (= 6.1.4) + actionmailbox (6.1.4.1) + actionpack (= 6.1.4.1) + activejob (= 6.1.4.1) + activerecord (= 6.1.4.1) + activestorage (= 6.1.4.1) + activesupport (= 6.1.4.1) mail (>= 2.7.1) - actionmailer (6.1.4) - actionpack (= 6.1.4) - actionview (= 6.1.4) - activejob (= 6.1.4) - activesupport (= 6.1.4) + actionmailer (6.1.4.1) + actionpack (= 6.1.4.1) + actionview (= 6.1.4.1) + activejob (= 6.1.4.1) + activesupport (= 6.1.4.1) mail (~> 2.5, >= 2.5.4) rails-dom-testing (~> 2.0) - actionpack (6.1.4) - actionview (= 6.1.4) - activesupport (= 6.1.4) + actionpack (6.1.4.1) + actionview (= 6.1.4.1) + activesupport (= 6.1.4.1) rack (~> 2.0, >= 2.0.9) rack-test (>= 0.6.3) rails-dom-testing (~> 2.0) rails-html-sanitizer (~> 1.0, >= 1.2.0) - actiontext (6.1.4) - actionpack (= 6.1.4) - activerecord (= 6.1.4) - activestorage (= 6.1.4) - activesupport (= 6.1.4) + actiontext (6.1.4.1) + actionpack (= 6.1.4.1) + activerecord (= 6.1.4.1) + activestorage (= 6.1.4.1) + activesupport (= 6.1.4.1) nokogiri (>= 1.8.5) - actionview (6.1.4) - activesupport (= 6.1.4) + actionview (6.1.4.1) + activesupport (= 6.1.4.1) builder (~> 3.1) erubi (~> 1.4) rails-dom-testing (~> 2.0) @@ -83,26 +83,26 @@ GEM activerecord (>= 4.0) active_storage_validations (0.9.5) rails (>= 5.2.0) - activejob (6.1.4) - activesupport (= 6.1.4) + activejob (6.1.4.1) + activesupport (= 6.1.4.1) globalid (>= 0.3.6) - activemodel (6.1.4) - activesupport (= 6.1.4) + activemodel (6.1.4.1) + activesupport (= 6.1.4.1) activemodel-serializers-xml (1.0.2) activemodel (> 5.x) activesupport (> 5.x) builder (~> 3.1) - activerecord (6.1.4) - activemodel (= 6.1.4) - activesupport (= 6.1.4) - activestorage (6.1.4) - actionpack (= 6.1.4) - activejob (= 6.1.4) - activerecord (= 6.1.4) - activesupport (= 6.1.4) + activerecord (6.1.4.1) + activemodel (= 6.1.4.1) + activesupport (= 6.1.4.1) + activestorage (6.1.4.1) + actionpack (= 6.1.4.1) + activejob (= 6.1.4.1) + activerecord (= 6.1.4.1) + activesupport (= 6.1.4.1) marcel (~> 1.0.0) mini_mime (>= 1.1.0) - activesupport (6.1.4) + activesupport (6.1.4.1) concurrent-ruby (~> 1.0, >= 1.0.2) i18n (>= 1.6, < 2) minitest (>= 5.1) @@ -110,669 +110,682 @@ GEM zeitwerk (~> 2.3) acts_as_list (1.0.4) activerecord (>= 4.2) - addressable (2.7.0) + addressable (2.8.0) public_suffix (>= 2.0.2, < 5.0) - animate-rails (1.0.10) - rails authority (3.3.0) activesupport (>= 3.0.0) autoprefixer-rails (10.3.3.0) execjs (~> 2) - aws-eventstream (1.1.1) - aws-partitions (1.480.0) - aws-sdk (3.0.2) + aws-eventstream (1.2.0) + aws-partitions (1.503.0) + aws-sdk (3.1.0) aws-sdk-resources (~> 3) - aws-sdk-accessanalyzer (1.20.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-accessanalyzer (1.23.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-acm (1.43.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-acm (1.45.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-acmpca (1.37.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-acmpca (1.40.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-alexaforbusiness (1.48.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-alexaforbusiness (1.50.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-amplify (1.30.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-amplify (1.32.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-amplifybackend (1.6.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-amplifybackend (1.8.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-apigateway (1.63.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-apigateway (1.67.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-apigatewaymanagementapi (1.22.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-apigatewaymanagementapi (1.24.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-apigatewayv2 (1.33.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-apigatewayv2 (1.36.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-appconfig (1.15.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-appconfig (1.17.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-appflow (1.12.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-appflow (1.15.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-appintegrationsservice (1.4.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-appintegrationsservice (1.6.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-applicationautoscaling (1.52.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-applicationautoscaling (1.55.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-applicationcostprofiler (1.1.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-applicationcostprofiler (1.3.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-applicationdiscoveryservice (1.36.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-applicationdiscoveryservice (1.38.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-applicationinsights (1.19.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-applicationinsights (1.21.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-appmesh (1.37.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-appmesh (1.39.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-appregistry (1.6.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-appregistry (1.8.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-apprunner (1.1.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-apprunner (1.3.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-appstream (1.53.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-appstream (1.55.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-appsync (1.41.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-appsync (1.43.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-athena (1.38.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-athena (1.41.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-auditmanager (1.9.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-auditmanager (1.11.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-augmentedairuntime (1.14.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-augmentedairuntime (1.16.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-autoscaling (1.65.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-autoscaling (1.68.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-autoscalingplans (1.32.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-autoscalingplans (1.34.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-backup (1.29.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-backup (1.32.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-batch (1.49.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-batch (1.51.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-braket (1.9.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-braket (1.11.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-budgets (1.39.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-budgets (1.41.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-chime (1.52.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-chime (1.57.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-cloud9 (1.36.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-chimesdkidentity (1.2.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-clouddirectory (1.32.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-chimesdkmessaging (1.2.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-cloudformation (1.54.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-cloud9 (1.39.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-cloudfront (1.54.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-clouddirectory (1.35.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-cloudhsm (1.31.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-cloudformation (1.58.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-cloudhsmv2 (1.34.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-cloudfront (1.56.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-cloudsearch (1.31.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-cloudhsm (1.33.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-cloudsearchdomain (1.25.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-cloudhsmv2 (1.36.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-cloudtrail (1.36.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-cloudsearch (1.33.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-cloudwatch (1.53.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-cloudsearchdomain (1.27.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-cloudwatchevents (1.48.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-cloudtrail (1.38.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-cloudwatchlogs (1.42.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-cloudwatch (1.55.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-codeartifact (1.11.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-cloudwatchevents (1.51.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-codebuild (1.75.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-cloudwatchlogs (1.45.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-codecommit (1.43.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-codeartifact (1.13.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-codedeploy (1.41.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-codebuild (1.81.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-codeguruprofiler (1.16.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-codecommit (1.45.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-codegurureviewer (1.19.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-codedeploy (1.43.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-codepipeline (1.45.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-codeguruprofiler (1.18.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-codestar (1.30.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-codegurureviewer (1.23.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-codestarconnections (1.16.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-codepipeline (1.47.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-codestarnotifications (1.11.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-codestar (1.32.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-cognitoidentity (1.32.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-codestarconnections (1.18.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-cognitoidentityprovider (1.55.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-codestarnotifications (1.13.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-cognitosync (1.28.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-cognitoidentity (1.34.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-comprehend (1.47.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-cognitoidentityprovider (1.57.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-comprehendmedical (1.27.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-cognitosync (1.30.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-computeoptimizer (1.20.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-comprehend (1.51.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-configservice (1.63.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-comprehendmedical (1.29.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-connect (1.47.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-computeoptimizer (1.24.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-connectcontactlens (1.3.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-configservice (1.66.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-connectparticipant (1.12.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-connect (1.50.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-core (3.118.0) + aws-sdk-connectcontactlens (1.5.0) + aws-sdk-core (~> 3, >= 3.120.0) + aws-sigv4 (~> 1.1) + aws-sdk-connectparticipant (1.14.0) + aws-sdk-core (~> 3, >= 3.120.0) + aws-sigv4 (~> 1.1) + aws-sdk-core (3.121.0) aws-eventstream (~> 1, >= 1.0.2) aws-partitions (~> 1, >= 1.239.0) aws-sigv4 (~> 1.1) jmespath (~> 1.0) - aws-sdk-costandusagereportservice (1.32.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-costandusagereportservice (1.34.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-costexplorer (1.63.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-costexplorer (1.66.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-customerprofiles (1.8.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-customerprofiles (1.11.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-databasemigrationservice (1.55.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-databasemigrationservice (1.59.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-dataexchange (1.14.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-dataexchange (1.16.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-datapipeline (1.28.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-datapipeline (1.30.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-datasync (1.33.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-datasync (1.36.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-dax (1.31.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-dax (1.33.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-detective (1.19.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-detective (1.21.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-devicefarm (1.43.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-devicefarm (1.45.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-devopsguru (1.9.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-devopsguru (1.11.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-directconnect (1.44.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-directconnect (1.46.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-directoryservice (1.40.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-directoryservice (1.43.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-dlm (1.41.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-dlm (1.44.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-docdb (1.33.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-docdb (1.35.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-dynamodb (1.61.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-dynamodb (1.63.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-dynamodbstreams (1.30.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-dynamodbstreams (1.32.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-ebs (1.14.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-ebs (1.18.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-ec2 (1.252.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-ec2 (1.263.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-ec2instanceconnect (1.15.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-ec2instanceconnect (1.17.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-ecr (1.43.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-ecr (1.46.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-ecrpublic (1.4.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-ecrpublic (1.6.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-ecs (1.82.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-ecs (1.85.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-efs (1.41.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-efs (1.44.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-eks (1.59.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-eks (1.63.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-elasticache (1.58.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-elasticache (1.62.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-elasticbeanstalk (1.43.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-elasticbeanstalk (1.45.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-elasticinference (1.13.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-elasticinference (1.15.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-elasticloadbalancing (1.32.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-elasticloadbalancing (1.34.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-elasticloadbalancingv2 (1.65.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-elasticloadbalancingv2 (1.67.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-elasticsearchservice (1.53.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-elasticsearchservice (1.56.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-elastictranscoder (1.30.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-elastictranscoder (1.32.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-emr (1.47.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-emr (1.52.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-emrcontainers (1.5.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-emrcontainers (1.7.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-eventbridge (1.26.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-eventbridge (1.30.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-finspace (1.3.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-finspace (1.5.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-finspacedata (1.2.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-finspacedata (1.4.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-firehose (1.38.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-firehose (1.41.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-fis (1.2.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-fis (1.4.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-fms (1.38.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-fms (1.41.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-forecastqueryservice (1.13.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-forecastqueryservice (1.15.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-forecastservice (1.22.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-forecastservice (1.25.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-frauddetector (1.20.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-frauddetector (1.24.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-fsx (1.39.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-fsx (1.42.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-gamelift (1.45.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-gamelift (1.47.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-glacier (1.38.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-glacier (1.40.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-globalaccelerator (1.31.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-globalaccelerator (1.33.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-glue (1.91.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-glue (1.95.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-gluedatabrew (1.10.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-gluedatabrew (1.13.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-greengrass (1.41.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-greengrass (1.43.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-greengrassv2 (1.5.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-greengrassv2 (1.8.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-groundstation (1.19.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-groundstation (1.21.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-guardduty (1.46.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-guardduty (1.48.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-health (1.37.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-health (1.39.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-healthlake (1.5.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-healthlake (1.7.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-honeycode (1.7.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-honeycode (1.9.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-iam (1.58.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-iam (1.60.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-identitystore (1.7.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-identitystore (1.9.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-imagebuilder (1.26.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-imagebuilder (1.29.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-importexport (1.27.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-importexport (1.29.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv2 (~> 1.0) - aws-sdk-inspector (1.35.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-inspector (1.37.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-iot (1.70.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-iot (1.75.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-iot1clickdevicesservice (1.29.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-iot1clickdevicesservice (1.31.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-iot1clickprojects (1.29.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-iot1clickprojects (1.31.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-iotanalytics (1.41.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-iotanalytics (1.43.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-iotdataplane (1.29.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-iotdataplane (1.32.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-iotdeviceadvisor (1.4.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-iotdeviceadvisor (1.6.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-iotevents (1.25.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-iotevents (1.27.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-ioteventsdata (1.17.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-ioteventsdata (1.19.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-iotfleethub (1.3.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-iotfleethub (1.5.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-iotjobsdataplane (1.28.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-iotjobsdataplane (1.30.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-iotsecuretunneling (1.12.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-iotsecuretunneling (1.14.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-iotsitewise (1.26.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-iotsitewise (1.32.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-iotthingsgraph (1.15.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-iotthingsgraph (1.17.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-iotwireless (1.12.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-iotwireless (1.14.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-ivs (1.10.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-ivs (1.12.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-kafka (1.37.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-kafka (1.40.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-kendra (1.32.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-kafkaconnect (1.0.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-kinesis (1.33.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-kendra (1.34.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-kinesisanalytics (1.32.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-kinesis (1.35.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-kinesisanalyticsv2 (1.31.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-kinesisanalytics (1.34.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-kinesisvideo (1.33.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-kinesisanalyticsv2 (1.33.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-kinesisvideoarchivedmedia (1.35.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-kinesisvideo (1.35.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-kinesisvideomedia (1.29.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-kinesisvideoarchivedmedia (1.37.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-kinesisvideosignalingchannels (1.11.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-kinesisvideomedia (1.31.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-kms (1.45.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-kinesisvideosignalingchannels (1.13.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-lakeformation (1.15.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-kms (1.48.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-lambda (1.65.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-lakeformation (1.17.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-lambdapreview (1.27.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-lambda (1.68.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-lex (1.37.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-lambdapreview (1.29.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-lexmodelbuildingservice (1.48.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-lex (1.39.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-lexmodelsv2 (1.7.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-lexmodelbuildingservice (1.51.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-lexruntimev2 (1.4.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-lexmodelsv2 (1.10.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-licensemanager (1.29.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-lexruntimev2 (1.6.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-lightsail (1.53.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-licensemanager (1.32.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-locationservice (1.6.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-lightsail (1.56.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-lookoutequipment (1.1.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-locationservice (1.8.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-lookoutforvision (1.4.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-lookoutequipment (1.4.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-lookoutmetrics (1.5.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-lookoutforvision (1.6.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-machinelearning (1.29.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-lookoutmetrics (1.7.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-macie (1.29.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-machinelearning (1.31.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-macie2 (1.31.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-macie (1.31.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-managedblockchain (1.24.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-macie2 (1.34.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-marketplacecatalog (1.13.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-managedblockchain (1.26.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-marketplacecommerceanalytics (1.33.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-marketplacecatalog (1.15.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-marketplaceentitlementservice (1.27.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-marketplacecommerceanalytics (1.35.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-marketplacemetering (1.35.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-marketplaceentitlementservice (1.29.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-mediaconnect (1.35.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-marketplacemetering (1.37.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-mediaconvert (1.70.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-mediaconnect (1.37.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-medialive (1.74.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-mediaconvert (1.74.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-mediapackage (1.41.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-medialive (1.76.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-mediapackagevod (1.25.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-mediapackage (1.44.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-mediastore (1.33.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-mediapackagevod (1.27.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-mediastoredata (1.30.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-mediastore (1.35.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-mediatailor (1.42.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-mediastoredata (1.32.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-mgn (1.2.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-mediatailor (1.44.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-migrationhub (1.32.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-memorydb (1.2.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-migrationhubconfig (1.12.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-mgn (1.4.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-mobile (1.27.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-migrationhub (1.34.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-mq (1.38.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-migrationhubconfig (1.14.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-mturk (1.32.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-mobile (1.29.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-mwaa (1.6.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-mq (1.40.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-neptune (1.36.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-mturk (1.34.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-networkfirewall (1.5.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-mwaa (1.8.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-networkmanager (1.12.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-neptune (1.38.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-nimblestudio (1.2.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-networkfirewall (1.7.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-opsworks (1.33.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-networkmanager (1.14.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-opsworkscm (1.44.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-nimblestudio (1.5.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-organizations (1.60.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-opensearchservice (1.1.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-outposts (1.18.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-opsworks (1.35.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-personalize (1.29.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-opsworkscm (1.46.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-personalizeevents (1.19.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-organizations (1.62.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-personalizeruntime (1.23.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-outposts (1.21.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-pi (1.29.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-personalize (1.31.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-pinpoint (1.54.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-personalizeevents (1.21.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-pinpointemail (1.27.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-personalizeruntime (1.25.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-pinpointsmsvoice (1.24.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-pi (1.31.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-polly (1.42.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-pinpoint (1.57.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-pricing (1.29.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-pinpointemail (1.29.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-prometheusservice (1.4.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-pinpointsmsvoice (1.26.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-proton (1.4.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-polly (1.46.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-qldb (1.17.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-pricing (1.31.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-qldbsession (1.14.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-prometheusservice (1.7.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-quicksight (1.49.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-proton (1.7.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-ram (1.27.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-qldb (1.19.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-rds (1.123.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-qldbsession (1.16.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-rdsdataservice (1.26.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-quicksight (1.54.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-redshift (1.66.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-ram (1.30.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-redshiftdataapiservice (1.9.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-rds (1.127.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-rekognition (1.52.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-rdsdataservice (1.28.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-resourcegroups (1.37.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-redshift (1.69.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-resourcegroupstaggingapi (1.38.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-redshiftdataapiservice (1.11.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-resources (3.106.0) + aws-sdk-rekognition (1.56.0) + aws-sdk-core (~> 3, >= 3.120.0) + aws-sigv4 (~> 1.1) + aws-sdk-resourcegroups (1.39.0) + aws-sdk-core (~> 3, >= 3.120.0) + aws-sigv4 (~> 1.1) + aws-sdk-resourcegroupstaggingapi (1.40.0) + aws-sdk-core (~> 3, >= 3.120.0) + aws-sigv4 (~> 1.1) + aws-sdk-resources (3.112.0) aws-sdk-accessanalyzer (~> 1) aws-sdk-acm (~> 1) aws-sdk-acmpca (~> 1) @@ -804,6 +817,8 @@ GEM aws-sdk-braket (~> 1) aws-sdk-budgets (~> 1) aws-sdk-chime (~> 1) + aws-sdk-chimesdkidentity (~> 1) + aws-sdk-chimesdkmessaging (~> 1) aws-sdk-cloud9 (~> 1) aws-sdk-clouddirectory (~> 1) aws-sdk-cloudformation (~> 1) @@ -913,6 +928,7 @@ GEM aws-sdk-iotwireless (~> 1) aws-sdk-ivs (~> 1) aws-sdk-kafka (~> 1) + aws-sdk-kafkaconnect (~> 1) aws-sdk-kendra (~> 1) aws-sdk-kinesis (~> 1) aws-sdk-kinesisanalytics (~> 1) @@ -951,6 +967,7 @@ GEM aws-sdk-mediastore (~> 1) aws-sdk-mediastoredata (~> 1) aws-sdk-mediatailor (~> 1) + aws-sdk-memorydb (~> 1) aws-sdk-mgn (~> 1) aws-sdk-migrationhub (~> 1) aws-sdk-migrationhubconfig (~> 1) @@ -962,6 +979,7 @@ GEM aws-sdk-networkfirewall (~> 1) aws-sdk-networkmanager (~> 1) aws-sdk-nimblestudio (~> 1) + aws-sdk-opensearchservice (~> 1) aws-sdk-opsworks (~> 1) aws-sdk-opsworkscm (~> 1) aws-sdk-organizations (~> 1) @@ -1017,6 +1035,7 @@ GEM aws-sdk-simpledb (~> 1) aws-sdk-sms (~> 1) aws-sdk-snowball (~> 1) + aws-sdk-snowdevicemanagement (~> 1) aws-sdk-sns (~> 1) aws-sdk-sqs (~> 1) aws-sdk-ssm (~> 1) @@ -1046,183 +1065,186 @@ GEM aws-sdk-workmailmessageflow (~> 1) aws-sdk-workspaces (~> 1) aws-sdk-xray (~> 1) - aws-sdk-robomaker (1.38.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-robomaker (1.42.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-route53 (1.51.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-route53 (1.55.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-route53domains (1.31.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-route53domains (1.33.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-route53recoverycluster (1.1.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-route53recoverycluster (1.3.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-route53recoverycontrolconfig (1.1.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-route53recoverycontrolconfig (1.3.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-route53recoveryreadiness (1.1.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-route53recoveryreadiness (1.3.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-route53resolver (1.27.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-route53resolver (1.30.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-s3 (1.97.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-s3 (1.103.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sdk-kms (~> 1) + aws-sigv4 (~> 1.4) + aws-sdk-s3control (1.40.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-s3control (1.37.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-s3outposts (1.6.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-s3outposts (1.4.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-sagemaker (1.101.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-sagemaker (1.94.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-sagemakeredgemanager (1.5.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-sagemakeredgemanager (1.3.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-sagemakerfeaturestoreruntime (1.6.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-sagemakerfeaturestoreruntime (1.4.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-sagemakerruntime (1.35.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-sagemakerruntime (1.32.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-savingsplans (1.19.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-savingsplans (1.16.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-schemas (1.16.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-schemas (1.13.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-secretsmanager (1.49.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-secretsmanager (1.47.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-securityhub (1.52.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-securityhub (1.49.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-serverlessapplicationrepository (1.37.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-serverlessapplicationrepository (1.35.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-servicecatalog (1.63.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-servicecatalog (1.61.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-servicediscovery (1.40.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-servicediscovery (1.38.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-servicequotas (1.17.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-servicequotas (1.15.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-ses (1.41.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-ses (1.39.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-sesv2 (1.20.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-sesv2 (1.18.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-shield (1.41.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-shield (1.39.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-signer (1.32.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-signer (1.30.0) - aws-sdk-core (~> 3, >= 3.118.0) - aws-sigv4 (~> 1.1) - aws-sdk-simpledb (1.27.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-simpledb (1.29.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv2 (~> 1.0) - aws-sdk-sms (1.30.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-sms (1.32.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-snowball (1.40.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-snowball (1.42.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-sns (1.43.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-snowdevicemanagement (1.1.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-sqs (1.41.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-sns (1.45.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-ssm (1.113.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-sqs (1.44.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-ssmcontacts (1.3.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-ssm (1.117.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-ssmincidents (1.1.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-ssmcontacts (1.7.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-ssoadmin (1.8.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-ssmincidents (1.4.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-ssooidc (1.11.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-ssoadmin (1.10.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-states (1.40.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-ssooidc (1.13.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-storagegateway (1.57.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-states (1.42.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-support (1.32.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-storagegateway (1.59.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-swf (1.28.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-support (1.34.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-synthetics (1.14.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-swf (1.30.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-textract (1.26.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-synthetics (1.17.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-timestreamquery (1.5.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-textract (1.28.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-timestreamwrite (1.5.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-timestreamquery (1.7.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-transcribeservice (1.56.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-timestreamwrite (1.7.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-transcribestreamingservice (1.30.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-transcribeservice (1.63.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-transfer (1.36.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-transcribestreamingservice (1.33.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-translate (1.32.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-transfer (1.39.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-waf (1.39.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-translate (1.34.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-wafregional (1.40.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-waf (1.41.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-wafv2 (1.22.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-wafregional (1.42.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-wellarchitected (1.6.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-wafv2 (1.26.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-workdocs (1.31.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-wellarchitected (1.8.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-worklink (1.24.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-workdocs (1.33.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-workmail (1.38.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-worklink (1.26.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-workmailmessageflow (1.13.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-workmail (1.40.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-workspaces (1.54.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-workmailmessageflow (1.15.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sdk-xray (1.38.0) - aws-sdk-core (~> 3, >= 3.118.0) + aws-sdk-workspaces (1.56.0) + aws-sdk-core (~> 3, >= 3.120.0) aws-sigv4 (~> 1.1) - aws-sigv2 (1.0.2) - aws-sigv4 (1.2.4) + aws-sdk-xray (1.41.0) + aws-sdk-core (~> 3, >= 3.120.0) + aws-sigv4 (~> 1.1) + aws-sigv2 (1.1.0) + aws-sigv4 (1.4.0) aws-eventstream (~> 1, >= 1.0.2) babel-source (5.8.35) babel-transpiler (0.7.0) @@ -1236,10 +1258,10 @@ GEM binding_of_caller (1.0.0) debug_inspector (>= 0.0.1) builder (3.2.4) - bullet (6.1.4) + bullet (6.1.5) activesupport (>= 3.0.0) uniform_notifier (~> 1.11) - bundler-audit (0.8.0) + bundler-audit (0.9.0.1) bundler (>= 1.2.0, < 3) thor (~> 1.0) chartkick (4.0.5) @@ -1293,7 +1315,7 @@ GEM faye-websocket (0.11.1) eventmachine (>= 0.12.0) websocket-driver (>= 0.5.1) - ffi (1.15.3) + ffi (1.15.4) ffi-compiler (1.0.1) ffi (>= 1.0.0) rake @@ -1303,7 +1325,7 @@ GEM railties (>= 3.2, < 7) friendly_id (5.4.2) activerecord (>= 4.0.0) - globalid (0.5.1) + globalid (0.5.2) activesupport (>= 5.0) haml (5.2.2) temple (>= 0.8.0) @@ -1328,7 +1350,7 @@ GEM concurrent-ruby (~> 1.0) http (~> 4.4.0) jwt (~> 2.2.1) - ibm_watson (2.1.1) + ibm_watson (2.1.3) concurrent-ruby (~> 1.0) eventmachine (~> 1.2) faye-websocket (~> 0.11) @@ -1368,22 +1390,22 @@ GEM kramdown (~> 2.0) language_filter (0.3.01) libv8-node (15.14.0.1) - listen (3.6.0) + listen (3.7.0) rb-fsevent (~> 0.10, >= 0.10.3) rb-inotify (~> 0.9, >= 0.9.10) - loofah (2.10.0) + loofah (2.12.0) crass (~> 1.0.2) nokogiri (>= 1.5.9) mail (2.7.1) mini_mime (>= 0.1.1) - marcel (1.0.1) + marcel (1.0.2) material_icons (2.2.1) railties (>= 3.2) medium-editor-rails (2.3.1) railties (>= 3.0) memory_profiler (1.0.0) - meta-tags (2.14.0) - actionpack (>= 3.2.0, < 6.2) + meta-tags (2.16.0) + actionpack (>= 3.2.0, < 7.1) method_source (1.0.0) mime-types (3.3.1) mime-types-data (~> 3.2015) @@ -1392,8 +1414,8 @@ GEM nokogiri (~> 1) rake mini_magick (4.11.0) - mini_mime (1.1.0) - mini_portile2 (2.5.3) + mini_mime (1.1.2) + mini_portile2 (2.6.1) mini_racer (0.4.0) libv8-node (~> 15.14.0.0) minitest (5.14.4) @@ -1401,15 +1423,13 @@ GEM multipart-post (2.1.1) mustache (1.1.1) nested_form (0.3.2) - newrelic_rpm (7.2.0) - nio4r (2.5.7) - nokogiri (1.11.7) - mini_portile2 (~> 2.5.0) + newrelic_rpm (8.0.0) + nio4r (2.5.8) + nokogiri (1.12.5) + mini_portile2 (~> 2.6.1) racc (~> 1.4) - nokogumbo (2.0.5) - nokogiri (~> 1.8, >= 1.8.4) - onebox (1.9.30) - addressable (~> 2.7.0) + onebox (2.2.19) + addressable (~> 2.8.0) htmlentities (~> 4.3) multi_json (~> 1.11) mustache @@ -1424,27 +1444,27 @@ GEM terrapin (~> 0.6.0) paranoia (2.4.3) activerecord (>= 4.0, < 6.2) - paypal-checkout-sdk (1.0.3) - paypalhttp (~> 1.0.0) + paypal-checkout-sdk (1.0.4) + paypalhttp (~> 1.0.1) paypal_client (0.4.0) activesupport (> 4.2.8) faraday (~> 0.15) faraday_middleware (~> 0.12) - paypalhttp (1.0.0) + paypalhttp (1.0.1) pg (1.2.3) pry (0.14.1) coderay (~> 1.1) method_source (~> 1.0) public_suffix (4.0.6) - puma (5.3.2) + puma (5.5.0) nio4r (~> 2.0) puma-heroku (2.0.0) puma (>= 5.0, < 6.0) pundit (2.1.1) activesupport (>= 3.0.0) - racc (1.5.2) + racc (1.6.0) rack (2.2.3) - rack-mini-profiler (2.3.2) + rack-mini-profiler (2.3.3) rack (>= 1.2.0) rack-pjax (1.1.0) nokogiri (~> 1.5) @@ -1453,31 +1473,31 @@ GEM rack rack-test (1.1.0) rack (>= 1.0, < 3) - rails (6.1.4) - actioncable (= 6.1.4) - actionmailbox (= 6.1.4) - actionmailer (= 6.1.4) - actionpack (= 6.1.4) - actiontext (= 6.1.4) - actionview (= 6.1.4) - activejob (= 6.1.4) - activemodel (= 6.1.4) - activerecord (= 6.1.4) - activestorage (= 6.1.4) - activesupport (= 6.1.4) + rails (6.1.4.1) + actioncable (= 6.1.4.1) + actionmailbox (= 6.1.4.1) + actionmailer (= 6.1.4.1) + actionpack (= 6.1.4.1) + actiontext (= 6.1.4.1) + actionview (= 6.1.4.1) + activejob (= 6.1.4.1) + activemodel (= 6.1.4.1) + activerecord (= 6.1.4.1) + activestorage (= 6.1.4.1) + activesupport (= 6.1.4.1) bundler (>= 1.15.0) - railties (= 6.1.4) + railties (= 6.1.4.1) sprockets-rails (>= 2.0.0) rails-dom-testing (2.0.3) activesupport (>= 4.2.0) nokogiri (>= 1.6) - rails-html-sanitizer (1.3.0) + rails-html-sanitizer (1.4.2) loofah (~> 2.3) rails-jquery-autocomplete (1.0.5) rails (>= 3.2) rails-ujs (0.1.0) railties (>= 3.1) - rails_admin (2.2.0) + rails_admin (2.2.1) activemodel-serializers-xml (>= 1.0) builder (~> 3.1) haml (>= 4.0, < 6) @@ -1489,15 +1509,16 @@ GEM rails (>= 5.0, < 7) remotipart (~> 1.3) sassc-rails (>= 1.3, < 3) - railties (6.1.4) - actionpack (= 6.1.4) - activesupport (= 6.1.4) + rails_gravatar (1.0.4) + actionview + railties (6.1.4.1) + actionpack (= 6.1.4.1) + activesupport (= 6.1.4.1) method_source rake (>= 0.13) thor (~> 1.0) rake (13.0.6) rb-fsevent (0.11.0) - rb-gravatar (1.0.6) rb-inotify (0.10.1) ffi (~> 1.0) react-rails (2.6.1) @@ -1515,13 +1536,12 @@ GEM rexml (3.2.5) rinku (2.0.6) rmagick (4.2.2) - ruby-vips (2.1.2) - ffi (~> 1.12) - rubyzip (2.3.2) - sanitize (5.2.3) + ruby-vips (2.0.17) + ffi (~> 1.9) + rubyzip (2.0.0) + sanitize (6.0.0) crass (~> 1.0.2) - nokogiri (>= 1.8.0) - nokogumbo (~> 2.0) + nokogiri (>= 1.12.0) sass-rails (6.0.0) sassc-rails (~> 2.1, >= 2.1.1) sassc (2.4.0) @@ -1536,7 +1556,7 @@ GEM childprocess (>= 0.5, < 4.0) rubyzip (>= 1.2.2) semantic_range (3.0.0) - sidekiq (6.2.1) + sidekiq (6.2.2) connection_pool (>= 2.2.2) rack (~> 2.0) redis (>= 4.2.0) @@ -1560,7 +1580,7 @@ GEM sqlite3 (1.4.2) stackprof (0.2.17) statsd-ruby (1.5.0) - stripe (5.37.0) + stripe (5.38.0) stripe_event (2.3.1) activesupport (>= 3.1) stripe (>= 2.8, < 6) @@ -1580,7 +1600,7 @@ GEM execjs (>= 0.3.0, < 3) unf (0.1.4) unf_ext - unf_ext (0.0.7.7) + unf_ext (0.0.8) uniform_notifier (1.14.2) warden (1.2.9) rack (>= 2.0.9) @@ -1599,7 +1619,7 @@ GEM websocket-extensions (0.1.5) word_count_analyzer (1.0.1) engtagger - zeitwerk (2.4.2) + zeitwerk (2.5.1) PLATFORMS ruby @@ -1607,9 +1627,8 @@ PLATFORMS DEPENDENCIES active_storage_validations acts_as_list - animate-rails authority - aws-sdk (~> 3.0) + aws-sdk (~> 3.1) aws-sdk-s3 barnes binding_of_caller @@ -1646,13 +1665,13 @@ DEPENDENCIES paypal_client pg (~> 1.2) pry - puma (~> 5.3) + puma (~> 5.5) puma-heroku rack-mini-profiler rails rails-jquery-autocomplete rails-ujs - rails_admin (~> 2.1) + rails_admin (~> 2.2) react-rails redcarpet redis diff --git a/README.rdoc b/README.rdoc index 5a337e3d..009d2de7 100644 --- a/README.rdoc +++ b/README.rdoc @@ -1,4 +1,4 @@ -= Notebook.ai += Notebook.ai {}[https://codeclimate.com/github/indentlabs/notebook] {}[https://codeclimate.com/github/indentlabs/notebook/coverage] {Inline docs}[http://inch-ci.org/github/indentlabs/notebook] @@ -34,54 +34,7 @@ TL;DR Milestones are independent of each other -- work on whatever you want to s == Installing the notebook development stack locally -Install ruby 2.6.6 (using `rbenv`, `rvm`, any other Ruby version manager, or just plain ol' ruby) - - rbenv install 2.6.6 - -Install necessary libraries - - sudo apt install imagemagick libmagickwand-dev - sudo apt install libpq-dev - -Clone the code - - git clone git@github.com:indentlabs/notebook.git - -Install gems - - bundle install - -Create database - - rake db:create - -Run initial database migrations - - rake db:migrate - rake billing_plans:initialize_defaults - rake data_migrations:create_default_billing_plans - rake db:seed - -Finally, run the server with - - bundle exec rails server - -You should now see a copy of the site running locally at http://localhost:3000/! - -You can also run background workers with sidekiq (and can run specific queues with the `-q ` flag): - - bundle exec sidekiq -C config/sidekiq.yml - -== Running the notebook stack locally with Docker - -Please note that the Docker installation is managed by the community, so it may not always be up to date. I recommend installing the stack manually. - -- install {Docker}[https://www.docker.com/products/overview] -- install {Docker Compose}[https://docs.docker.com/compose/install] -- clone this git repo -- cd into the root of this repo, and then run - docker-compose up -- You should now see a copy of the site running locally at http://localhost:3000/ +Please see the {installation Guide}[https://github.com/indentlabs/notebook/wiki/Setup-Instructions] in the wiki for setup instructions. == Testing diff --git a/app/assets/javascripts/content.js b/app/assets/javascripts/content.js index 6eac7528..8de06125 100644 --- a/app/assets/javascripts/content.js +++ b/app/assets/javascripts/content.js @@ -104,13 +104,35 @@ $(document).ready(function () { // Replace this element's content with the name of the page var tag = $(this); - $.get( - '/api/internal/' + tag.data('klass') + '/' + tag.data('id') + '/name' - ).done(function (response) { - tag.find('.name-container').text(response); - }).fail(function() { - tag.find('.name-conainer').text("Unknown " + tag.data('klass')); - }); + // Instantiate a cache for all page lookup queries (if not already created) + window.load_page_name_cache = window.load_page_name_cache || {}; + var page_name_key = tag.data('klass') + '/' + tag.data('id'); + + if (page_name_key in window.load_page_name_cache) { + // If we've already made a request for this klass+id, we can just insta-load the + // cached result instead of requesting it again. + tag.find('.name-container').text(window.load_page_name_cache[page_name_key]); + + } else { + // If we haven't made a request for this klass+id, look it up and cache it + $.get( + '/api/internal/' + page_name_key + '/name' + ).done(function (response) { + tag.find('.name-container').text(response); + window.load_page_name_cache[page_name_key] = response; + + // Go ahead and pre-fill all tags on the page for this klass+id, too + $('.js-load-page-name[data-klass=' + tag.data('klass') + '][data-id=' + tag.data('id') + ']') + .find('.name-container') + .text(response); + + }).fail(function() { + tag.find('.name-container').text("Unknown " + tag.data('klass')); + }); + } + + + }); }); diff --git a/app/assets/javascripts/page_tags.coffee b/app/assets/javascripts/page_tags.coffee new file mode 100644 index 00000000..24f83d18 --- /dev/null +++ b/app/assets/javascripts/page_tags.coffee @@ -0,0 +1,3 @@ +# Place all the behaviors and hooks related to the matching controller here. +# All this logic will automatically be available in application.js. +# You can use CoffeeScript in this file: http://coffeescript.org/ diff --git a/app/assets/stylesheets/application.css b/app/assets/stylesheets/application.css index 670b96cc..e0b1691b 100644 --- a/app/assets/stylesheets/application.css +++ b/app/assets/stylesheets/application.css @@ -13,7 +13,6 @@ *= require font-awesome *= require medium-editor/medium-editor *= require medium-editor/themes/beagle - *= require animate *= require tribute *= require_tree . */ diff --git a/app/assets/stylesheets/layout.scss b/app/assets/stylesheets/layout.scss index adaf9eeb..1f974cc2 100644 --- a/app/assets/stylesheets/layout.scss +++ b/app/assets/stylesheets/layout.scss @@ -9,4 +9,5 @@ .card-panel-title { font-size: 1.3em; font-weight: bold; + margin-top: 0.5em; } \ No newline at end of file diff --git a/app/authorizers/content_page_authorizer.rb b/app/authorizers/content_page_authorizer.rb new file mode 100644 index 00000000..3bc57016 --- /dev/null +++ b/app/authorizers/content_page_authorizer.rb @@ -0,0 +1,48 @@ +class ContentPageAuthorizer < CoreContentAuthorizer + def self.creatable_by?(user) + return false unless user.present? + return false if ENV.key?('CONTENT_BLACKLIST') && ENV['CONTENT_BLACKLIST'].split(',').include?(user.email) + + if resource.page_type == 'Universe' + return true if PermissionService.user_has_fewer_owned_universes_than_plan_limit?(user: user) + + else + is_premium_page = Rails.application.config.content_types[:premium].include?(resource.page_type) + return true if !is_premium_page + return true if is_premium_page && PermissionService.user_is_on_premium_plan?(user: user) + end + + return false + end + + def readable_by?(user) + return true if PermissionService.content_is_public?(content: resource) + return true if PermissionService.user_owns_content?(user: user, content: resource) + + if resource.page_type == 'Universe' + return true if PermissionService.user_can_contribute_to_universe?(user: user, universe: resource) + else + return true if PermissionService.user_can_contribute_to_containing_universe?(user: user, content: resource) + end + + return false + end + + def updatable_by?(user) + return true if PermissionService.user_owns_content?(user: user, content: resource) + + if resource.page_type == 'Universe' + return true if PermissionService.user_can_contribute_to_universe?(user: user, universe: resource) + else + return true if PermissionService.user_can_contribute_to_containing_universe?(user: user, content: resource) + end + + return false + end + + def deletable_by?(user) + [ + PermissionService.user_owns_content?(user: user, content: resource) + ].any? + end +end diff --git a/app/authorizers/document_authorizer.rb b/app/authorizers/document_authorizer.rb index c8f56644..d30f5c96 100644 --- a/app/authorizers/document_authorizer.rb +++ b/app/authorizers/document_authorizer.rb @@ -7,6 +7,7 @@ class DocumentAuthorizer < ApplicationAuthorizer def readable_by?(user) return true if user && resource.user_id == user.id + return true if user && user.site_administrator? return true if resource.privacy == 'public' return true if resource.universe.present? && resource.universe.privacy == 'public' return true if user && resource.universe.present? && resource.universe.contributors.pluck(:user_id).include?(user.id) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 547bdd67..cd3e10b2 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -1,3 +1,4 @@ +# frozen_string_literal: true class ApplicationController < ActionController::Base protect_from_forgery @@ -27,7 +28,7 @@ class ApplicationController < ActionController::Base session.delete(:universe_id) elsif params[:universe].is_a?(String) && params[:universe].to_i.to_s == params[:universe] found_universe = Universe.find_by(id: params[:universe]) - found_universe = nil unless current_user.universes.include?(found_universe) || current_user.contributable_universes.include?(found_universe) + found_universe = nil unless found_universe.user_id == current_user.id || found_universe.contributors.pluck(:user_id).include?(current_user.id) session[:universe_id] = found_universe.id if found_universe end end @@ -36,52 +37,115 @@ class ApplicationController < ActionController::Base def set_universe_scope if user_signed_in? && session.key?(:universe_id) @universe_scope = Universe.find_by(id: session[:universe_id]) - @universe_scope = nil unless current_user.universes.include?(@universe_scope) || current_user.contributable_universes.include?(@universe_scope) + + if @universe_scope && @universe_scope.user_id != current_user.try(:id) + # Verify the current user has access to this universe by looking up their + # universe contributorship + contributorship = Contributor.find_by( + user: current_user, + universe: @universe_scope + ) + + if contributorship.nil? + # If the user doesn't have current contributor access to this universe, + # then revert back to unscoped universe actions + @universe_scope = nil + end + end + else @universe_scope = nil end end - # Cache some super-common stuff we need for every page. For example, content lists for the side nav. + # Cache some super-common stuff we need for every page. For example, content lists for the side nav. This is a catch-all for most pages that render + # UI, but methods are also free to skip this filter and call the individual cache methods they need instead. def cache_most_used_page_information + return unless user_signed_in? + + cache_activated_content_types + cache_current_user_content + cache_notifications + cache_recently_edited_pages + end + + def cache_activated_content_types + @activated_content_types ||= if user_signed_in? + ( + # Use config to dictate order, but AND to only include what a user has turned on + Rails.application.config.content_type_names[:all] & current_user.user_content_type_activators.pluck(:content_type) + ) + else + [] + end + end + + def cache_current_user_content + return if @current_user_content + @current_user_content = {} return unless user_signed_in? - @activated_content_types = ( - Rails.application.config.content_types[:all].map(&:name) & # Use config to dictate order, but AND to only include what a user has turned on - current_user.user_content_type_activators.pluck(:content_type) - ) + cache_activated_content_types # We always want to cache Universes, even if they aren't explicitly turned on. - @current_user_content = current_user.content(content_types: @activated_content_types + ['Universe'], universe_id: @universe_scope.try(:id)) + @current_user_content = current_user.content( + content_types: @activated_content_types + [Universe.name], + universe_id: @universe_scope.try(:id) + ) # Likewise, we should also always cache Timelines & Documents if @universe_scope @current_user_content['Timeline'] = current_user.timelines.where(universe_id: @universe_scope.try(:id)).to_a - @current_user_content['Document'] = current_user.linkable_documents.includes([:user]).where(universe_id: @universe_scope.try(:id)).order('updated_at DESC').to_a + @current_user_content['Document'] = current_user.documents.where(universe_id: @universe_scope.try(:id)).order('updated_at DESC').to_a else @current_user_content['Timeline'] = current_user.timelines.to_a - @current_user_content['Document'] = current_user.linkable_documents.includes([:user]).order('updated_at DESC').to_a + @current_user_content['Document'] = current_user.documents.order('updated_at DESC').to_a end + end - # Fetch notifications - @user_notifications = current_user.notifications.order('happened_at DESC').limit(100) + def cache_notifications + @user_notifications ||= if user_signed_in? + current_user.notifications.order('happened_at DESC').limit(100) + else + [] + end + end - # Cache recently-edited pages - @recently_edited_pages = @current_user_content.values.flatten - .sort_by(&:updated_at) - .last(50) - .reverse + def cache_recently_created_pages(amount=50) + cache_current_user_content + + @recently_created_pages = if user_signed_in? + @current_user_content.values.flatten + .sort_by(&:created_at) + .last(amount) + .reverse + else + [] + end + end + + def cache_recently_edited_pages(amount=50) + cache_current_user_content + + @recently_edited_pages ||= if user_signed_in? + @current_user_content.values.flatten + .sort_by(&:updated_at) + .last(amount) + .reverse + else + [] + end end def cache_forums_unread_counts - @unread_threads = if user_signed_in? + @unread_threads ||= if user_signed_in? Thredded::Topic.unread_followed_by(current_user).count else 0 end - @unread_private_messages = if user_signed_in? + @unread_private_messages ||= if user_signed_in? Thredded::PrivateTopic .for_user(current_user) .unread(current_user) @@ -91,33 +155,69 @@ class ApplicationController < ActionController::Base end end + def cache_contributable_universe_ids + cache_current_user_content + + @contributable_universe_ids ||= if user_signed_in? + current_user.contributable_universe_ids + else + [] + end + end + def cache_linkable_content_for_each_content_type - linkable_classes = Rails.application.config.content_types[:all].map(&:name) & current_user.user_content_type_activators.pluck(:content_type) + cache_contributable_universe_ids + cache_current_user_content + + linkable_classes = @activated_content_types linkable_classes += %w(Document Timeline) - @linkables_cache = {} - @linkables_raw = {} - linkable_classes.each do |class_name| - # class_name = "Character" + @linkables_cache = {} # Cache is list of [[page_name, page_id], [page_name, page_id], ...] + @linkables_raw = {} # Raw is list of objects [#{page}, #{page}, ...] - @linkables_cache[class_name] = current_user - .send("linkable_#{class_name.downcase.pluralize}") - .in_universe(@universe_scope) + @current_user_content.each do |page_type, content_list| + # We already have our own list of content by the current user in @current_user_content, + # so all we need to grab is additional pages in contributable universes + @linkables_raw[page_type] = @current_user_content[page_type] - if @content.present? && @content.persisted? - @linkables_cache[class_name] = @linkables_cache[class_name] - .in_universe(@content.universe) - .reject { |content| content.class.name == class_name && content.id == @content.id } + # Add contributor content + if @contributable_universe_ids.any? + existing_page_ids = @linkables_raw[page_type].map(&:id) + + pages_to_add = if page_type == Universe.name + page_type.constantize.where(id: @contributable_universe_ids) + .where.not(id: existing_page_ids) + .where.not(user_id: current_user.id) + else + page_type.constantize.where(universe_id: @contributable_universe_ids) + .where.not(id: existing_page_ids) + .where.not(user_id: current_user.id) + end + + # If we're scoped to a universe, also scope contributor content pulled to that + # universe. If we're not, leave it as all contributor content. + if @universe_scope && pages_to_add.klass.respond_to?(:universe) + pages_to_add = pages_to_add.where(universe: @universe_scope) + end + + filtered_fields = ContentPage.polymorphic_content_fields.map(&:to_s) + filtered_fields.push 'universe_id' unless page_type == Universe.name + pages_to_add.each do |page_data| + filtered_page_data = page_data.attributes.slice(*filtered_fields) + @linkables_raw[page_type].push ContentPage.new(filtered_page_data) + end end - @linkables_raw[class_name] = @linkables_cache[class_name] - .sort_by { |p| p.name.downcase } - .compact + # We can't properly display or @-mention content without a name set, so we explicitly + # reject it here. However, this is a bit of a code-smell: why is there content without + # a name set? + @linkables_raw[page_type].reject! { |page| page.name.nil? } - @linkables_cache[class_name] = @linkables_cache[class_name] - .sort_by { |p| p.name.downcase } - .map { |c| [c.name, c.id] } - .compact + # Finally, we want to sort our linkables cache once so we don't have to sort it again + @linkables_raw[page_type].sort_by! { |page| page.name.downcase }.compact! + + # Lastly, build our name/id cache as well + @linkables_cache[page_type] = @linkables_raw[page_type].map { |page| [page.name, page.id] } end end end diff --git a/app/controllers/attribute_fields_controller.rb b/app/controllers/attribute_fields_controller.rb index 21e377fb..bd717aa3 100644 --- a/app/controllers/attribute_fields_controller.rb +++ b/app/controllers/attribute_fields_controller.rb @@ -26,7 +26,7 @@ class AttributeFieldsController < ContentController return redirect_back fallback_location: root_path end - if @attribute_field.update(attribute_field_params.merge({ migrated_from_legacy: true })) + if @attribute_field.update(content_params.merge({ migrated_from_legacy: true })) @content = @attribute_field successful_response( @attribute_field, diff --git a/app/controllers/content_controller.rb b/app/controllers/content_controller.rb index fa7eafec..9b99761c 100644 --- a/app/controllers/content_controller.rb +++ b/app/controllers/content_controller.rb @@ -1,9 +1,15 @@ -class ContentController < ApplicationController - # todo we should probably spin off an Api::ContentController for #api_sort and anything else api-wise we need +# frozen_string_literal: true +# TODO: we should probably spin off an Api::ContentController for #api_sort and anything else +# api-wise we need +class ContentController < ApplicationController before_action :authenticate_user!, except: [:show, :changelog, :api_sort] \ + Rails.application.config.content_types[:all_non_universe].map { |type| type.name.downcase.pluralize.to_sym } + skip_before_action :cache_most_used_page_information, only: [ + :name_field_update, :text_field_update, :tags_field_update, :universe_field_update, :api_sort + ] + before_action :migrate_old_style_field_values, only: [:show, :edit] before_action :cache_linkable_content_for_each_content_type, only: [:new, :edit, :index] @@ -21,49 +27,35 @@ class ContentController < ApplicationController @page_title = "My #{pluralized_content_name}" # Create the default fields for this user if they don't have any already + # TODO: uh, this probably doesn't belong here! @content_type_class.attribute_categories(current_user) - if @universe_scope.present? && @content_type_class != Universe - @content = @universe_scope.send(pluralized_content_name) - .includes(:page_tags, :image_uploads) - .unarchived + # Linkables cache is already scoped per-universe, includes contributor pages + @content = @linkables_raw.fetch(@content_type_class.name, []) - @show_scope_notice = true - else - @content = ( - current_user.send(pluralized_content_name).unarchived.includes(:page_tags, :image_uploads) + - current_user.send("contributable_#{pluralized_content_name}").unarchived.includes(:page_tags, :image_uploads) - ) - - if @content_type_class != Universe - my_universe_ids = current_user.universes.pluck(:id) - @content.concat(@content_type_class.where(universe_id: my_universe_ids).unarchived) - end - end - - @content = @content.to_a.flatten.uniq + @show_scope_notice = @universe_scope.present? && @content_type_class != Universe # Filters @page_tags = PageTag.where( page_type: @content_type_class.name, page_id: @content.pluck(:id) ).order(:tag) - if params.key?(:tag) - @filtered_page_tags = @page_tags.where(slug: params[:tag]) + if params.key?(:slug) + @filtered_page_tags = @page_tags.where(slug: params[:slug]) @content.select! { |content| @filtered_page_tags.pluck(:page_id).include?(content.id) } end + @page_tags = @page_tags.uniq(&:tag) if params.key?(:favorite_only) @content.select!(&:favorite?) end - @page_tags = @page_tags.uniq(&:tag) - @content = @content.sort_by {|x| [x.favorite? ? 0 : 1, x.name] } @questioned_content = @content.sample @attribute_field_to_question = SerendipitousService.question_for(@questioned_content) + # Uh, do we ever actually make JSON requests to logged-in user pages? respond_to do |format| format.html { render 'content/index' } format.json { render json: @content } @@ -72,7 +64,7 @@ class ContentController < ApplicationController def show content_type = content_type_from_controller(self.class) - return redirect_to(root_path, notice: "That page doesn't exist!") unless valid_content_types.map(&:name).include?(content_type.name) + return redirect_to(root_path, notice: "That page doesn't exist!") unless valid_content_types.include?(content_type.name) @content = content_type.find_by(id: params[:id]) return redirect_to(root_path, notice: "You don't have permission to view that content.") if @content.nil? @@ -126,6 +118,10 @@ class ContentController < ApplicationController end @content.save! + + # If the user doesn't have this content type enabled, go ahead and automatically enable it for them + current_user.user_content_type_activators.find_or_create_by(content_type: @content.class.name) + return redirect_to edit_polymorphic_path(@content) else return redirect_to(subscription_path, notice: "#{@content.class.name.pluralize} require a Premium subscription to create.") @@ -194,6 +190,9 @@ class ContentController < ApplicationController end end + # If the user doesn't have this content type enabled, go ahead and automatically enable it for them + current_user.user_content_type_activators.find_or_create_by(content_type: content_type.name) + successful_response(content_creation_redirect_url, t(:create_success, model_name: @content.try(:name).presence || humanized_model_name)) else failed_response('new', :unprocessable_entity, "Unable to save page. Error code: " + @content.errors.to_json.to_s) @@ -289,7 +288,7 @@ class ContentController < ApplicationController def changelog content_type = content_type_from_controller(self.class) - return redirect_to root_path unless valid_content_types.map(&:name).include?(content_type.name) + return redirect_to root_path unless valid_content_types.include?(content_type.name) @content = content_type.find_by(id: params[:id]) return redirect_to(root_path, notice: "You don't have permission to view that content.") if @content.nil? @serialized_content = ContentSerializer.new(@content) @@ -421,6 +420,7 @@ class ContentController < ApplicationController set_entity referencing_page = @entity + # TODO: move this into a link mention update job valid_reference_ids = [] referenced_page_codes = JSON.parse(attribute_value.value) referenced_page_codes.each do |page_code| @@ -455,7 +455,7 @@ class ContentController < ApplicationController # We also need to update the cached `name` field on the content page itself entity_type = entity_params.fetch(:entity_type) - raise "Invalid entity type: #{entity_params.fetch(:entity_type)}" unless valid_content_types.map(&:name).include?(entity_params.fetch('entity_type')) + raise "Invalid entity type: #{entity_params.fetch(:entity_type)}" unless valid_content_types.include?(entity_params.fetch('entity_type')) entity = entity_type.constantize.find_by(id: entity_params.fetch(:entity_id).to_i) entity.update(name: field_params.fetch('value', '')) end @@ -469,35 +469,16 @@ class ContentController < ApplicationController attribute_value.value = text attribute_value.save! - # Create PageReferences for mentioned pages - tokens = ContentFormatterService.tokens_to_replace(text) - if tokens.any? - set_entity + UpdateTextAttributeReferencesJob.perform_later(attribute_value.id) - valid_reference_ids = [] - tokens.each do |token| - reference = @entity.outgoing_page_references.find_or_initialize_by( - referenced_page_type: token[:content_type], - referenced_page_id: token[:content_id], - attribute_field_id: @attribute_field.id, - reference_type: 'mentioned' - ) - reference.cached_relation_title = @attribute_field.label - reference.save! - - valid_reference_ids << reference.reload.id - end - - # Delete all other references still attached to this field, but not present in this request - @entity.outgoing_page_references - .where(attribute_field_id: @attribute_field.id) - .where.not(id: valid_reference_ids) - .destroy_all + respond_to do |format| + format.html { redirect_back(fallback_location: root_path, notice: "#{@attribute_field.label} updated!") } + format.json { render json: attribute_value, status: :success } end end def tags_field_update - return unless valid_content_types.map(&:name).include?(entity_params.fetch('entity_type')) + return unless valid_content_types.include?(entity_params.fetch('entity_type')) @attribute_field = AttributeField.find_by(id: params[:field_id].to_i) attribute_value = @attribute_field.attribute_values.order('created_at desc').find_or_initialize_by(entity_params) @@ -512,7 +493,7 @@ class ContentController < ApplicationController end def universe_field_update - return unless valid_content_types.map(&:name).include?(entity_params.fetch('entity_type')) + return unless valid_content_types.include?(entity_params.fetch('entity_type')) @attribute_field = AttributeField.find_by(id: params[:field_id].to_i) attribute_value = @attribute_field.attribute_values.order('created_at desc').find_or_initialize_by(entity_params) @@ -580,7 +561,7 @@ class ContentController < ApplicationController end def valid_content_types - Rails.application.config.content_types[:all] + Rails.application.config.content_type_names[:all] end def initialize_object @@ -656,7 +637,7 @@ class ContentController < ApplicationController def set_attributes_content_type @content_type = params[:content_type] # todo make this a before_action load_content_type - unless valid_content_types.map { |c| c.name.downcase }.include?(@content_type) + unless valid_content_types.map(&:downcase).include?(@content_type) raise "Invalid content type on attributes customization page: #{@content_type}" end @content_type_class = @content_type.titleize.constantize @@ -671,7 +652,7 @@ class ContentController < ApplicationController entity_page_type = entity_params.fetch(:entity_type) entity_page_id = entity_params.fetch(:entity_id) - return unless valid_content_types.map(&:name).include?(entity_page_type) + return unless valid_content_types.include?(entity_page_type) @entity = entity_page_type.constantize.find_by(id: entity_page_id) end diff --git a/app/controllers/data_controller.rb b/app/controllers/data_controller.rb index ac5737f4..b6897575 100644 --- a/app/controllers/data_controller.rb +++ b/app/controllers/data_controller.rb @@ -115,6 +115,10 @@ class DataController < ApplicationController @content = current_user.content end + def tags + @tags = current_user.page_tags + end + def discussions @topics = Thredded::Topic.where(user_id: current_user.id) @posts = Thredded::Post.where(user_id: current_user.id) @@ -140,6 +144,9 @@ class DataController < ApplicationController @share_link = "https://www.notebook.ai/?referral=#{current_user.referral_code.code}" end + def green + end + private def set_sidenav_expansion diff --git a/app/controllers/documents_controller.rb b/app/controllers/documents_controller.rb index 4ded2037..f484adfa 100644 --- a/app/controllers/documents_controller.rb +++ b/app/controllers/documents_controller.rb @@ -4,12 +4,18 @@ class DocumentsController < ApplicationController # todo Uh, this is a hack. The CSRF token on document editor model to add entities is being rejected... for whatever reason. skip_before_action :verify_authenticity_token, only: [:link_entity] + skip_before_action :cache_most_used_page_information, only: [:update] + before_action :set_document, only: [:show, :analysis, :plaintext, :queue_analysis, :edit, :destroy] before_action :set_sidenav_expansion, except: [:plaintext] before_action :set_navbar_color, except: [:plaintext] before_action :set_navbar_actions, except: [:edit, :plaintext] before_action :set_footer_visibility, only: [:edit] + # Skip UI-heavy calls for API endpoints + skip_before_action :cache_most_used_page_information, only: [:update] + skip_before_action :cache_forums_unread_counts, only: [:update] + # TODO: verify_user_can_read, verify_user_can_edit, etc before_actions instead of inlining them before_action :cache_linkable_content_for_each_content_type, only: [:edit] @@ -181,21 +187,21 @@ class DocumentsController < ApplicationController def update document = Document.with_deleted.find_or_initialize_by(id: params[:id]) - d_params = document_params.clone # TODO: why are we duplicating the params here? unless document.updatable_by?(current_user) redirect_to(dashboard_path, notice: "You don't have permission to do that!") return end - # Only queue document mentions for analysis if the document body has changed - DocumentMentionJob.perform_later(document.id) if d_params.key?(:body) - # We can't pass actual-nil from HTML (for no universe), so we pass a string instead and convert it back here. + d_params = document_params.clone if d_params.fetch(:universe_id, nil) == "nil" d_params[:universe_id] = nil end + # Only queue document mentions for analysis if the document body has changed + DocumentMentionJob.perform_later(document.id) if d_params.key?(:body) + update_page_tags(document) if document_tag_params if document.update(d_params) diff --git a/app/controllers/main_controller.rb b/app/controllers/main_controller.rb index bbee013a..3c44b5cd 100644 --- a/app/controllers/main_controller.rb +++ b/app/controllers/main_controller.rb @@ -25,8 +25,7 @@ class MainController < ApplicationController def dashboard @page_title = "My notebook" - set_random_content # for questions - @attribute_field_to_question = SerendipitousService.question_for(@content) + set_questionable_content # for questions end def infostack @@ -35,13 +34,37 @@ class MainController < ApplicationController def sascon end + def paper + @navbar_color = '#4CAF50' + + @total_notebook_pages = 0 + @total_pages_equivalent = 0 + @total_trees_saved = 0 + + @per_page_savings = {} + + (Rails.application.config.content_types[:all] + [Timeline, Document]).each do |content_type| + physical_page_equivalent = GreenService.total_physical_pages_equivalent(content_type) + tree_equivalent = physical_page_equivalent.to_f / GreenService::SHEETS_OF_PAPER_PER_TREE + + @per_page_savings[content_type.name] = { + digital: content_type.last.try(:id) || 0, + pages: physical_page_equivalent, + trees: tree_equivalent + } + + @total_notebook_pages += @per_page_savings.dig(content_type.name, :digital) + @total_pages_equivalent += @per_page_savings.dig(content_type.name, :pages) + @total_trees_saved += @per_page_savings.dig(content_type.name, :trees) + end + end + def prompts @sidenav_expansion = 'writing' @navbar_color = '#FF9800' @page_title = "Writing prompts" - set_random_content # for question - @attribute_field_to_question = SerendipitousService.question_for(@content) + set_questionable_content # for question end # deprecated path just kept around for bookmarks for a while @@ -50,13 +73,6 @@ class MainController < ApplicationController end def recent_content - # todo optimize this / use Attributes - return [] if @activated_content_types.nil? - - @recently_created_pages = @current_user_content.values.flatten - .sort_by(&:created_at) - .last(50) - .reverse end def for_writers @@ -80,27 +96,8 @@ class MainController < ApplicationController private - def set_random_content - @activated_content_types.shuffle.each do |content_type| - if content_type == Universe.name - if @universe_scope.present? - @content = content_type.constantize.where(user: current_user, id: @universe_scope.id).includes(:user) - else - @content = content_type.constantize.where(user: current_user).includes(:user) - end - else - if @universe_scope.present? - # when we want to enable prompts for contributing universes we can remove the user: - # selector here, but we will need to verify the user has permission to see the universe - # when we do that, or else prompts could open leak - @content = content_type.constantize.where(user: current_user, universe: @universe_scope).includes(:user, :universe) - else - @content = content_type.constantize.where(user: current_user).includes(:user, :universe) - end - end - - @content = @content.sample - return if @content.present? - end + def set_questionable_content + @content = @current_user_content.except(*%w(Timeline Document)).values.flatten.sample + @attribute_field_to_question = SerendipitousService.question_for(@content) end end diff --git a/app/controllers/page_tags_controller.rb b/app/controllers/page_tags_controller.rb new file mode 100644 index 00000000..55653d39 --- /dev/null +++ b/app/controllers/page_tags_controller.rb @@ -0,0 +1,23 @@ +class PageTagsController < ApplicationController + # Remove a tag and all of its links to a page + def remove + # Params + # {"page_type"=>"Location", "slug"=>"mountains", "controller"=>"page_tags", "action"=>"remove" + return unless params.key?(:page_type) && params.key?(:slug) + + PageTag.where( + page_type: params[:page_type], + slug: params[:slug], + user_id: current_user.id + ).destroy_all + + return redirect_back fallback_location: root_path, notice: 'Tag(s) deleted successfully.' + end + + # Destroy a specific tag by ID + def destroy + PageTag.find_by(id: params[:id], user_id: current_user.id).destroy! + + return redirect_back fallback_location: root_path, notice: 'Tag(s) deleted successfully.' + end +end diff --git a/app/controllers/timelines_controller.rb b/app/controllers/timelines_controller.rb index 2bd4f351..3e243cce 100644 --- a/app/controllers/timelines_controller.rb +++ b/app/controllers/timelines_controller.rb @@ -7,6 +7,8 @@ class TimelinesController < ApplicationController # GET /timelines def index + cache_linkable_content_for_each_content_type + @timelines = current_user.timelines @page_title = "My timelines" diff --git a/app/helpers/page_tags_helper.rb b/app/helpers/page_tags_helper.rb new file mode 100644 index 00000000..96c4808b --- /dev/null +++ b/app/helpers/page_tags_helper.rb @@ -0,0 +1,2 @@ +module PageTagsHelper +end diff --git a/app/jobs/cache_attribute_word_count_job.rb b/app/jobs/cache_attribute_word_count_job.rb new file mode 100644 index 00000000..fd1238dc --- /dev/null +++ b/app/jobs/cache_attribute_word_count_job.rb @@ -0,0 +1,30 @@ +class CacheAttributeWordCountJob < ApplicationJob + queue_as :cache + + def perform(*args) + attribute_id = args.shift + attribute = Attribute.find_by(id: attribute_id) + + return if attribute.nil? + return if attribute.value.nil? || attribute.value.blank? + + word_count = WordCountAnalyzer::Counter.new( + ellipsis: 'no_special_treatment', + hyperlink: 'count_as_one', + contraction: 'count_as_one', + hyphenated_word: 'count_as_one', + date: 'no_special_treatment', + number: 'count', + numbered_list: 'ignore', + xhtml: 'remove', + forward_slash: 'count_as_multiple_except_dates', + backslash: 'count_as_one', + dotted_line: 'ignore', + dashed_line: 'ignore', + underscore: 'ignore', + stray_punctuation: 'ignore' + ).count(attribute.value) + + attribute.update!(word_count_cache: word_count) + end +end diff --git a/app/jobs/cache_sum_attribute_word_count_job.rb b/app/jobs/cache_sum_attribute_word_count_job.rb new file mode 100644 index 00000000..63dad69a --- /dev/null +++ b/app/jobs/cache_sum_attribute_word_count_job.rb @@ -0,0 +1,20 @@ +class CacheSumAttributeWordCountJob < ApplicationJob + queue_as :cache + + def perform(*args) + entity_type = args.shift + entity_id = args.shift + + entity = entity_type.constantize.find_by(id: entity_id) + return if entity.nil? + + sum_attribute_word_count = Attribute.where(entity_type: entity_type, entity_id: entity_id).sum(:word_count_cache) + update = entity.word_count_updates.find_or_initialize_by( + for_date: DateTime.current, + ) + update.word_count = sum_attribute_word_count + update.user_id ||= entity.user_id + + update.save! + end +end diff --git a/app/jobs/document_entity_analysis_job.rb b/app/jobs/document_entity_analysis_job.rb index 35ffb337..f91f1c1d 100644 --- a/app/jobs/document_entity_analysis_job.rb +++ b/app/jobs/document_entity_analysis_job.rb @@ -4,7 +4,7 @@ class DocumentEntityAnalysisJob < ApplicationJob def perform(*args) document_entity_id = args.shift - entity = DocumentEntity.find(document_entity_id) + entity = DocumentEntity.find_by(id: document_entity_id) return unless entity.present? Documents::Analysis::ThirdParty::IbmWatsonService.analyze_entity(document_entity_id) diff --git a/app/jobs/save_document_revision_job.rb b/app/jobs/save_document_revision_job.rb index d52d737f..95fc4101 100644 --- a/app/jobs/save_document_revision_job.rb +++ b/app/jobs/save_document_revision_job.rb @@ -4,13 +4,21 @@ class SaveDocumentRevisionJob < ApplicationJob def perform(*args) document_id = args.shift - document = Document.find(document_id) - return unless document.present? + document = Document.find_by(id: document_id) + return unless document # Update cached word count for the document regardless of how often this is called new_word_count = document.computed_word_count document.update(cached_word_count: new_word_count) + # Save a WordCountUpdate for this document for today + update = document.word_count_updates.find_or_initialize_by( + for_date: DateTime.current, + ) + update.word_count = new_word_count + update.user_id ||= document.user_id + update.save! + # Make sure we're only storing revisions at least every 5 min latest_revision = document.document_revisions.order('created_at DESC').limit(1).first if latest_revision.present? && latest_revision.created_at > 5.minutes.ago diff --git a/app/jobs/update_text_attribute_references_job.rb b/app/jobs/update_text_attribute_references_job.rb new file mode 100644 index 00000000..598d7eaa --- /dev/null +++ b/app/jobs/update_text_attribute_references_job.rb @@ -0,0 +1,36 @@ +class UpdateTextAttributeReferencesJob < ApplicationJob + queue_as :mentions + + def perform(*args) + attribute_id = args.shift + + attribute = Attribute.find_by(id: attribute_id) + return unless attribute.present? + + # Create PageReferences for mentioned pages + tokens = ContentFormatterService.tokens_to_replace(attribute.value) + if tokens.any? + entity = attribute.entity + + valid_reference_ids = [] + tokens.each do |token| + reference = entity.outgoing_page_references.find_or_initialize_by( + referenced_page_type: token[:content_type], + referenced_page_id: token[:content_id], + attribute_field_id: attribute.attribute_field_id, + reference_type: 'mentioned' + ) + reference.cached_relation_title = AttributeField.find_by(id: attribute.attribute_field_id).try(:label) + reference.save! + + valid_reference_ids << reference.reload.id + end + + # Delete all other references still attached to this field, but not present in this request + entity.outgoing_page_references + .where(attribute_field_id: attribute.attribute_field_id) + .where.not(id: valid_reference_ids) + .destroy_all + end + end +end diff --git a/app/models/concerns/has_attributes.rb b/app/models/concerns/has_attributes.rb index 00c28e76..b7a07108 100644 --- a/app/models/concerns/has_attributes.rb +++ b/app/models/concerns/has_attributes.rb @@ -42,17 +42,28 @@ module HasAttributes end def self.attribute_categories(user, show_hidden: false) + # TODO: this is a code smell; we should probably either be whitelisting or fixing whatever is calling + # this with the wrong models return [] if ['attribute_category', 'attribute_field'].include?(content_name) # Cache the result in case we call this function multiple times this request @cached_attribute_categories_for_this_content = begin # Always include the flatfile categories (but create AR versions if they don't exist) + categories_list = AttributeCategory.with_deleted.where(user: user).to_a + full_fields_list = AttributeField.with_deleted.where(user: user, attribute_category_id: categories_list.map(&:id)) categories = YAML.load_file(Rails.root.join('config', 'attributes', "#{content_name}.yml")).map do |category_name, details| - category = ::AttributeCategory.with_deleted.find_or_initialize_by( - entity_type: self.content_name, - name: category_name.to_s, - user: user - ) + category = categories_list.detect do |persisted_category| + persisted_category.entity_type == self.content_name && + persisted_category.name == category_name.to_s + end + if category.nil? + category = AttributeCategory.new( + entity_type: self.content_name, + name: category_name.to_s, + user: user + ) + end + # Default new categories to some sane defaults unless category.persisted? category.icon = details[:icon] @@ -60,13 +71,21 @@ module HasAttributes end category.save! if user && category.new_record? + fields_list = full_fields_list.select do |field| + field.attribute_category_id == category.id + end category.attribute_fields << details[:attributes].map do |field| - af_field = category.attribute_fields.with_deleted.find_or_initialize_by( - # label: field[:label], - old_column_source: field[:name], - user: user, - field_type: field[:field_type].presence || "text_area" - ) + af_field = fields_list.detect do |persisted_field| + persisted_field.old_column_source == field[:name] && + persisted_field.field_type == field[:field_type].presence || "text_area" + end + if af_field.nil? + af_field = category.attribute_fields.new( + old_column_source: field[:name], + user: user, + field_type: field[:field_type].presence || "text_area" + ) + end if af_field.label.nil? af_field.label = field[:label] end @@ -180,6 +199,7 @@ module HasAttributes end end + # All of these helpers are spooky and rife for N+1s def name_field category_ids = AttributeCategory.where( user_id: user_id, diff --git a/app/models/concerns/has_content.rb b/app/models/concerns/has_content.rb index eff6dd03..c12f6f4c 100644 --- a/app/models/concerns/has_content.rb +++ b/app/models/concerns/has_content.rb @@ -16,18 +16,49 @@ module HasContent has_many :attribute_categories has_many :attribute_values, class_name: 'Attribute', dependent: :destroy + # Alternative approach to #content for benchmarking in prod + def content_with_multiple_queries( + content_types: Rails.application.config.content_type_names[:all], + page_scoping: { user_id: self.id }, + universe_id: nil + ) + @content_by_page_type = {} + content_types.each do |content_type| + type_specific_fields = ContentPage.polymorphic_content_fields + type_specific_fields.push 'universe_id' unless content_type == 'Universe' + + pages_of_this_type = content_type.constantize + .where(page_scoping) + .select(type_specific_fields) + + if content_type != 'Universe' && universe_id.present? + pages_of_this_type = pages_of_this_type.where(universe_id: universe_id) + end + + @content_by_page_type[content_type] = [] + pages_of_this_type.each do |page_data| + @content_by_page_type[content_type].push ContentPage.new(page_data.attributes) + end + end + + @content_by_page_type + end + # { # characters: [...], # locations: [...] # } def content( - content_types: Rails.application.config.content_types[:all].map(&:name), + content_types: Rails.application.config.content_type_names[:all], page_scoping: { user_id: self.id }, universe_id: nil ) - return {} if content_types.empty? + # return content_with_multiple_queries(content_types: content_types, page_scoping: page_scoping, universe_id: universe_id) - polymorphic_content_fields = [:id, :name, :page_type, :user_id, :created_at, :updated_at, :deleted_at, :archived_at, :privacy] + return {} if content_types.empty? + # TODO: we should return early if we already have @content_by_page_type!!! + + polymorphic_content_fields = ContentPage.polymorphic_content_fields where_conditions = page_scoping.map { |key, value| "#{key} = #{value}" }.join(' AND ') + ' AND deleted_at IS NULL AND archived_at IS NULL' sql = content_types.uniq.map do |page_type| @@ -67,7 +98,7 @@ module HasContent ) # todo we can't select for universe_id here which kind of sucks, so we need to research 1) the repercussions, 2) what to do instead - polymorphic_content_fields = [:id, :name, :page_type, :user_id, :created_at, :updated_at, :deleted_at, :archived_at, :privacy] + polymorphic_content_fields = ContentPage.polymorphic_content_fields where_conditions = page_scoping.map { |key, value| "#{key} = #{value}" }.join(' AND ') + ' AND deleted_at IS NULL AND archived_at IS NULL' sql = content_types.uniq.map do |page_type| diff --git a/app/models/concerns/has_image_uploads.rb b/app/models/concerns/has_image_uploads.rb index a59df6d2..392350eb 100644 --- a/app/models/concerns/has_image_uploads.rb +++ b/app/models/concerns/has_image_uploads.rb @@ -9,23 +9,34 @@ module HasImageUploads # todo: destroy from s3 on destroy def public_image_uploads - self.image_uploads.where(privacy: 'public').presence || ["card-headers/#{self.class.name.downcase.pluralize}.jpg"] + self.image_uploads.where(privacy: 'public').presence || [header_asset_for(self.class.name)] end def private_image_uploads - self.image.uploads.where(privacy: 'private').presence || ["card-headers/#{self.class.name.downcase.pluralize}.jpg"] + self.image.uploads.where(privacy: 'private').presence || [header_asset_for(self.class.name)] end def random_image_including_private(format: :medium) - image_uploads.sample.try(:src, format).presence || "card-headers/#{self.class.name.downcase.pluralize}.jpg" + @random_image_including_private_cache ||= {} + key = self.class.name + self.id.to_s + return @random_image_including_private_cache[key] if @random_image_including_private_cache.key?(key) + + result = image_uploads.sample.try(:src, format).presence || header_asset_for(self.class.name) + @random_image_including_private_cache[key] = result + + result end def first_public_image(format: :medium) - public_image_uploads.first.try(:src, format).presence || "card-headers/#{self.class.name.downcase.pluralize}.jpg" + public_image_uploads.first.try(:src, format).presence || header_asset_for(self.class.name) end def random_public_image(format: :medium) - public_image_uploads.sample.try(:src, format).presence || "card-headers/#{self.class.name.downcase.pluralize}.jpg" + public_image_uploads.sample.try(:src, format).presence || header_asset_for(self.class.name) + end + + def header_asset_for(class_name) + ActionController::Base.helpers.asset_path("card-headers/#{class_name.downcase.pluralize}.jpg") end end end diff --git a/app/models/concerns/has_page_tags.rb b/app/models/concerns/has_page_tags.rb index b1ada5cb..cfcdb1eb 100644 --- a/app/models/concerns/has_page_tags.rb +++ b/app/models/concerns/has_page_tags.rb @@ -4,6 +4,6 @@ module HasPageTags extend ActiveSupport::Concern included do - has_many :page_tags, as: :page + has_many :page_tags, as: :page, dependent: :destroy end end diff --git a/app/models/concerns/is_content_page.rb b/app/models/concerns/is_content_page.rb index ad0b69fd..718797a4 100644 --- a/app/models/concerns/is_content_page.rb +++ b/app/models/concerns/is_content_page.rb @@ -18,6 +18,11 @@ module IsContentPage has_many :timeline_events, through: :timeline_event_entities has_many :timelines, -> { distinct }, through: :timeline_events + has_many :word_count_updates, as: :entity, dependent: :destroy + def latest_word_count_cache + word_count_updates.order('for_date DESC').limit(1).first.try(:word_count) || 0 + end + scope :unarchived, -> { where(archived_at: nil) } def archive! update!(archived_at: DateTime.now) diff --git a/app/models/documents/document.rb b/app/models/documents/document.rb index a28e3eb8..cbbd1027 100644 --- a/app/models/documents/document.rb +++ b/app/models/documents/document.rb @@ -26,6 +26,12 @@ class Document < ApplicationRecord attr_accessor :tagged_text + # Duplicated from is_content_page since we don't include that here yet + has_many :word_count_updates, as: :entity, dependent: :destroy + def latest_word_count_cache + word_count_updates.order('for_date DESC').limit(1).first.try(:word_count) || 0 + end + KEYS_TO_TRIGGER_REVISION_ON_CHANGE = %w(title body synopsis notes_text) def self.color diff --git a/app/models/page_collections/page_collection.rb b/app/models/page_collections/page_collection.rb index 36e9e9a2..aa3d53a6 100644 --- a/app/models/page_collections/page_collection.rb +++ b/app/models/page_collections/page_collection.rb @@ -51,7 +51,7 @@ class PageCollection < ApplicationRecord end # If all else fails, fall back on default header - "card-headers/#{self.class.name.downcase.pluralize}.jpg" + ActionController::Base.helpers.asset_path("card-headers/#{self.class.name.downcase.pluralize}.jpg") end def first_public_image diff --git a/app/models/page_data/attribute.rb b/app/models/page_data/attribute.rb index cdd17cb1..a08f3cc2 100644 --- a/app/models/page_data/attribute.rb +++ b/app/models/page_data/attribute.rb @@ -19,6 +19,16 @@ class Attribute < ApplicationRecord end end + after_commit do + if saved_changes.key?('value') + # Cache the updated word count on this attribute + CacheAttributeWordCountJob.perform_later(self.id) + + # Cache the updated word count on the page this attribute belongs to + CacheSumAttributeWordCountJob.perform_later(self.entity_type, self.entity_id) + end + end + after_save do entity.touch end diff --git a/app/models/page_types/content_page.rb b/app/models/page_types/content_page.rb index 41581e4a..201b40f9 100644 --- a/app/models/page_types/content_page.rb +++ b/app/models/page_types/content_page.rb @@ -1,9 +1,16 @@ class ContentPage < ApplicationRecord + include Rails.application.routes.url_helpers + belongs_to :user belongs_to :universe + attr_accessor :favorite + + include Authority::Abilities + self.authorizer_name = 'ContentPageAuthorizer' + def random_image_including_private(format: :small) - ImageUpload.where(content_type: self.page_type, content_id: self.id).sample.try(:src, format) || "card-headers/#{self.page_type.downcase.pluralize}.jpg" + ImageUpload.where(content_type: self.page_type, content_id: self.id).sample.try(:src, format) || ActionController::Base.helpers.asset_path("card-headers/#{self.page_type.downcase.pluralize}.jpg") end def icon @@ -17,4 +24,20 @@ class ContentPage < ApplicationRecord def text_color self.page_type.constantize.text_color end + + def favorite? + !!favorite + end + + def view_path + send("#{self.page_type.downcase}_path", self.id) + end + + def edit_path + send("edit_#{self.page_type.downcase}_path", self.id) + end + + def self.polymorphic_content_fields + [:id, :name, :favorite, :page_type, :user_id, :created_at, :updated_at, :deleted_at, :archived_at, :privacy] + end end diff --git a/app/models/page_types/creature.rb b/app/models/page_types/creature.rb index 61925486..19ad5da3 100644 --- a/app/models/page_types/creature.rb +++ b/app/models/page_types/creature.rb @@ -20,7 +20,7 @@ class Creature < ApplicationRecord include Serendipitous::Concern include Authority::Abilities - self.authorizer_name = 'ExtendedContentAuthorizer' + self.authorizer_name = 'CoreContentAuthorizer' # Locations relates :habitats, with: :wildlifeships diff --git a/app/models/timelines/timeline_event.rb b/app/models/timelines/timeline_event.rb index fc68f032..9cbe6c9e 100644 --- a/app/models/timelines/timeline_event.rb +++ b/app/models/timelines/timeline_event.rb @@ -1,7 +1,7 @@ class TimelineEvent < ApplicationRecord acts_as_paranoid - belongs_to :timeline + belongs_to :timeline, touch: true has_many :timeline_event_entities, dependent: :destroy diff --git a/app/models/users/user.rb b/app/models/users/user.rb index 2a321067..3c5528d1 100644 --- a/app/models/users/user.rb +++ b/app/models/users/user.rb @@ -119,13 +119,12 @@ class User < ApplicationRecord @cached_user_contributable_universes ||= Universe.where(id: contributable_universe_ids) end - def linkable_universes - @cached_linkable_universes ||= Universe.where(id: my_universe_ids + contributable_universes) - end - def contributable_universe_ids # TODO: email confirmation needs to happen for data safety / privacy (only verified emails) @contributable_universe_ids ||= Contributor.where('email = ? OR user_id = ?', self.email, self.id).pluck(:universe_id) + @contributable_universe_ids += Contributor.where(universe_id: my_universe_ids).pluck(:universe_id) + + @contributable_universe_ids.uniq end # TODO: rename this to #{content_type}_shared_with_me @@ -156,7 +155,7 @@ class User < ApplicationRecord universe_id IN (#{(my_universe_ids + contributable_universe_ids + [-1]).uniq.join(',')}) OR (universe_id IS NULL AND user_id = #{self.id.to_i}) - """) + """).includes([:user]) end def linkable_timelines diff --git a/app/models/word_count_update.rb b/app/models/word_count_update.rb new file mode 100644 index 00000000..1f26dccc --- /dev/null +++ b/app/models/word_count_update.rb @@ -0,0 +1,4 @@ +class WordCountUpdate < ApplicationRecord + belongs_to :user + belongs_to :entity, polymorphic: true +end diff --git a/app/services/field_type_service.rb b/app/services/field_type_service.rb index 19f7b197..0d5798e0 100644 --- a/app/services/field_type_service.rb +++ b/app/services/field_type_service.rb @@ -15,4 +15,13 @@ class FieldTypeService < Service raise "Unexpected/unhandled field type: #{field[:type]}" end end + + def self.form_path_from_attribute_field(field) + field_data = { + type: field.field_type, + internal_id: field.id + } + + form_path(field_data) + end end diff --git a/app/services/green_service.rb b/app/services/green_service.rb new file mode 100644 index 00000000..ccf9fd18 --- /dev/null +++ b/app/services/green_service.rb @@ -0,0 +1,107 @@ +class GreenService < Service + AVERAGE_WORDS_PER_PAGE = 500 + AVERAGE_TIMELINE_EVENTS_PER_PAGE = 3 + SHEETS_OF_PAPER_PER_TREE = 11_000 # source: https://ribble-pack.co.uk/blog/much-paper-comes-one-tree + + def self.physical_pages_equivalent_for(worldbuilding_page_type) + # TODO: This would be better estimated with [average] word counts from pages (or a real total), + # but we don't have that data computed (and definitely don't want to do so on each page load). + # Until we have a better solution, these page counts come from printing out notebook pages + # from http://www.notebook-paper.com/ + + case worldbuilding_page_type + when "Universe" then 2 + when "Character" then 6 + when "Location" then 4 + when "Item" then 2 + when "Building" then 8 + when "Condition" then 4 + when "Continent" then 6 + when "Country" then 5 + when "Creature" then 8 + when "Deity" then 5 + when "Flora" then 4 + when "Food" then 5 + when "Government" then 6 + when "Group" then 4 + when "Job" then 4 + when "Landmark" then 3 + when "Language" then 5 + when "Lore" then 8 + when "Magic" then 4 + when "Planet" then 6 + when "Race" then 4 + when "Religion" then 3 + when "Scene" then 2 + when "School" then 6 + when "Sport" then 4 + when "Technology" then 4 + when "Town" then 4 + when "Tradition" then 3 + when "Vehicle" then 4 + else + raise "Unknown green estimate: #{worldbuilding_page_type}" + end + end + + def self.trees_saved_by(worldbuilding_page_type) + (physical_pages_equivalent_for(worldbuilding_page_type) * (worldbuilding_page_type.constantize.last.try(:id) || 0)) / SHEETS_OF_PAPER_PER_TREE.to_f + end + + def self.total_document_pages_equivalent + total_pages = 0 + + # Treat all <1-page documents as 1 page per document, since they'd print on separate pages + total_pages += Document.with_deleted.where('cached_word_count <= ?', AVERAGE_WORDS_PER_PAGE).count + + # For all >1-page documents, do a quick estimate of word count sum + num docs to also cover EOD page breaks + docs = Document.with_deleted.where.not(cached_word_count: nil).where('cached_word_count > ?', AVERAGE_WORDS_PER_PAGE) + total_pages += (docs.sum(:cached_word_count) / AVERAGE_WORDS_PER_PAGE.to_f).round + total_pages += docs.count + + total_pages + end + + def self.total_timeline_pages_equivalent + ((TimelineEvent.last.try(:id) || 0) / AVERAGE_TIMELINE_EVENTS_PER_PAGE.to_f).to_i + end + + def self.total_physical_pages_equivalent(content_type) + case content_type.name + when 'Timeline' + GreenService.total_timeline_pages_equivalent + when 'Document' + GreenService.total_document_pages_equivalent + else + GreenService.physical_pages_equivalent_for(content_type.name) * (content_type.last.try(:id) || 0) + end + end + + def self.total_pages_saved_by(user) + total_pages = 0 + + user.content.each do |content_type, content_list| + physical_page_equivalent_for_content_type = case content_type + when 'Timeline' + AVERAGE_TIMELINE_EVENTS_PER_PAGE * TimelineEvent.where(timeline_id: content_list.map(&:id)).count + + when 'Document' + [ + content_list.inject(0) { |sum, doc| sum + (doc.cached_word_count || 0) } / GreenService::AVERAGE_WORDS_PER_PAGE.to_f, + content_list_count + ].max + + else + physical_pages_equivalent_for(content_type) * content_list.count + end + + total_pages += physical_page_equivalent_for_content_type + end + + total_pages + end + + def self.total_trees_saved_by(user) + total_pages_saved_by(user).to_f / GreenService::SHEETS_OF_PAPER_PER_TREE + end +end \ No newline at end of file diff --git a/app/services/permission_service.rb b/app/services/permission_service.rb index 654e123c..bfc15d90 100644 --- a/app/services/permission_service.rb +++ b/app/services/permission_service.rb @@ -5,7 +5,7 @@ class PermissionService < Service end def self.user_owns_content?(user:, content:) - content.user && user && content.user.try(:id) == user.try(:id) + content.user && user && content.try(:user_id) == user.try(:id) end def self.user_owns_any_containing_universe?(user:, content:) @@ -27,7 +27,11 @@ class PermissionService < Service def self.user_can_contribute_to_containing_universe?(user:, content:) return false if user.nil? return true if [AttributeCategory, AttributeField, Attribute].include?(content.class) #todo audit this - content.universe.present? && user.contributable_universes.pluck(:id).include?(content.universe.id) + + return true if user.contributable_universe_ids.include?(content.universe_id) + return true if user.universes.pluck(:id).include?(content.universe_id) + + return false end def self.content_has_no_containing_universe?(content:) diff --git a/app/services/serendipitous_service.rb b/app/services/serendipitous_service.rb index 0f81cb3d..eb77ea37 100644 --- a/app/services/serendipitous_service.rb +++ b/app/services/serendipitous_service.rb @@ -4,7 +4,7 @@ class SerendipitousService < Service categories_for_this_type = AttributeCategory.where( user: content.user, - entity_type: content.class.name.downcase, + entity_type: content.page_type.downcase, hidden: [nil, false] ) @@ -25,7 +25,7 @@ class SerendipitousService < Service #raise fields_for_these_categories.pluck(:label).inspect attribute_fields_with_values = Attribute.where( - entity_type: content.class.name, + entity_type: content.page_type, entity_id: content.id, attribute_field_id: fields_for_these_categories.pluck(:id) ).where.not( diff --git a/app/services/temporary_field_migration_service.rb b/app/services/temporary_field_migration_service.rb index e7b0e969..cdf4b19c 100644 --- a/app/services/temporary_field_migration_service.rb +++ b/app/services/temporary_field_migration_service.rb @@ -11,6 +11,7 @@ class TemporaryFieldMigrationService < Service def self.migrate_fields_for_content(content_model, user, force: false) return unless content_model.present? && user.present? return unless content_model.user == user + return if content_model.is_a?(ContentPage) return if !force && content_model.persisted? && content_model.created_at > 'May 1, 2018'.to_datetime return if !!content_model.columns_migrated_from_old_style? diff --git a/app/views/api/api_docs/index.html.erb b/app/views/api/api_docs/index.html.erb index 62964333..7481809a 100644 --- a/app/views/api/api_docs/index.html.erb +++ b/app/views/api/api_docs/index.html.erb @@ -100,7 +100,7 @@
- Gain full access to + Gain full access to millions of <% Rails.application.config.content_types[:all_non_universe].each do |type| %> <%= type.name.downcase.pluralize %>, <% end %> @@ -110,7 +110,7 @@

After a user authenticates your application, you'll have full access to integrate their worldbuilding pages into your app. - You can show them their characters, let them edit one of their existing creatures, pin their towns and landmarks to your maps, + You can show them their characters, let them import pictures from their locations, pin their towns and landmarks to your maps, create new items, and more — all without leaving your app.


diff --git a/app/views/cards/serendipitous/_content_question.html.erb b/app/views/cards/serendipitous/_content_question.html.erb index 182875c0..b02d7103 100644 --- a/app/views/cards/serendipitous/_content_question.html.erb +++ b/app/views/cards/serendipitous/_content_question.html.erb @@ -6,22 +6,25 @@ <% if defined?(field) && field.present? %>
  • -
    - help +
    + help <%= t( - "serendipitous_questions.attributes.#{content.class.name.downcase}.#{field.label.downcase}", - name: content.name_field_value, - default: "What is #{content.name_field_value}'s #{field.label.downcase}?" + "serendipitous_questions.attributes.#{content.page_type.downcase}.#{field.label.downcase}", + name: content.name, + default: "What is #{content.name}'s #{field.label.downcase}?" ) %>
    - <%= form_for content do |f| %> + <%= form_for content, url: FieldTypeService.form_path_from_attribute_field(field), method: :patch do |f| %> <%= hidden_field(:override, :redirect_path, value: redirect_path) if defined?(redirect_path) %> + <%= hidden_field_tag "entity[entity_id]", content.id %> + <%= hidden_field_tag "entity[entity_type]", content.page_type %> + <%= - render 'content/form/text_input', + render 'content/form/text_input_for_content_page', f: f, content: content, field: field @@ -32,14 +35,14 @@ <% end %> <% if include_quick_reference %> - <%= link_to content, class: 'entity-trigger sidenav-trigger orange white-text btn tooltipped', data: { target: "quick-reference-#{@content.class.name}-#{@content.id}", tooltip: "View this #{@content.class.name.downcase} without leaving this page" } do %> + <%= link_to content.view_path, class: 'entity-trigger sidenav-trigger orange white-text btn tooltipped', data: { target: "quick-reference-#{content.page_type}-#{content.id}", tooltip: "View this #{content.page_type.downcase} without leaving this page" } do %> vertical_split Quick-reference <% end %> <% end %> <% if !defined?(show_view_button) || !!show_view_button %> - <%= link_to content, class: "btn #{content.class.color} white-text tooltipped", target: '_new', data: { tooltip: "View this #{@content.class.name.downcase} in a new tab" } do %> - <%= content.class.icon %> + <%= link_to content.view_path, class: "btn #{content.color} white-text tooltipped", target: '_new', data: { tooltip: "View this #{content.name.downcase} in a new tab" } do %> + <%= content.icon %> View <% end %> <% end %> diff --git a/app/views/content/components/_list_filter_bar.html.erb b/app/views/content/components/_list_filter_bar.html.erb index 88a01d90..955b2891 100644 --- a/app/views/content/components/_list_filter_bar.html.erb +++ b/app/views/content/components/_list_filter_bar.html.erb @@ -30,8 +30,8 @@
  • <% - linkable_universes_with_this_kind_of_content = current_user.linkable_universes.select do |universe| - @current_user_content[content_type.name].any? { |content| content.universe_id == universe.id } + linkable_universes_with_this_kind_of_content = @linkables_raw.fetch('Universe', []).select do |universe| + @current_user_content.fetch(content_type.name, []).any? { |content| content.universe_id == universe.id } end %> <% linkable_universes_with_this_kind_of_content.each do |universe| %> @@ -60,7 +60,7 @@