diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 08d0a5afe..49687f7af 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -14,41 +14,38 @@ jobs: strategy: fail-fast: false matrix: - ruby: ['2.6', '2.7', '3.0', 'jruby', 'truffleruby'] - gemfile: ['gemfiles/activerecord_5.0.2.gemfile', 'gemfiles/activerecord_5.1.0.gemfile', 'gemfiles/activerecord_5.2.2.gemfile', 'gemfiles/activerecord_6.0.0.gemfile', 'gemfiles/activerecord_6.1.0.gemfile', 'gemfiles/activerecord_main.gemfile'] + ruby: ['2.7', '3.0', '3.1', '3.2', '3.3'] + gemfile: ['gemfiles/activerecord_5.2.2.gemfile', 'gemfiles/activerecord_6.0.0.gemfile', 'gemfiles/activerecord_6.1.0.gemfile', 'gemfiles/activerecord_7.0.0.gemfile', 'gemfiles/activerecord_7.1.0.gemfile', 'gemfiles/activerecord_main.gemfile'] include: + - gemfile: 'gemfiles/activerecord_7.1.0.gemfile' + ruby: '3.3' + - gemfile: 'gemfiles/activerecord_7.1.0.gemfile' + ruby: '3.0' - gemfile: 'gemfiles/activerecord_7.0.0.gemfile' - ruby: '3.1' + ruby: '3.3' - gemfile: 'gemfiles/activerecord_7.0.0.gemfile' ruby: '3.0' - gemfile: 'gemfiles/activerecord_6.1.0.gemfile' - ruby: '3.1' + ruby: '3.3' - gemfile: 'gemfiles/activerecord_6.1.0.gemfile' ruby: '3.0' exclude: - gemfile: 'gemfiles/activerecord_5.2.2.gemfile' - ruby: '3.0' # rails 5.2 can't run on ruby 3.0 - - gemfile: 'gemfiles/activerecord_5.1.0.gemfile' - ruby: '3.0' # rails 5.1 can't run on ruby 3.0 - - gemfile: 'gemfiles/activerecord_5.0.2.gemfile' - ruby: '3.0' # rails 5.0 can't run on ruby 3.0 - - gemfile: 'gemfiles/activerecord_5.0.2.gemfile' - ruby: '3.0' # rails 5.0 can't run on ruby 3.0 - - gemfile: 'gemfiles/activerecord_5.0.2.gemfile' - ruby: 'truffleruby' # TruffleRuby 21.0 targets Ruby 2.7, same as above - - gemfile: 'gemfiles/activerecord_5.1.0.gemfile' - ruby: 'truffleruby' # TruffleRuby 21.0 targets Ruby 2.7, same as above + ruby: '3.0' + - gemfile: 'gemfiles/activerecord_5.2.2.gemfile' + ruby: '3.1' - gemfile: 'gemfiles/activerecord_5.2.2.gemfile' - ruby: 'truffleruby' # TruffleRuby 21.0 targets Ruby 2.7, same as above + ruby: '3.2' + - gemfile: 'gemfiles/activerecord_5.2.2.gemfile' + ruby: '3.3' + - gemfile: 'gemfiles/activerecord_7.0.0.gemfile' + ruby: '2.7' + - gemfile: 'gemfiles/activerecord_7.1.0.gemfile' + ruby: '2.7' - gemfile: 'gemfiles/activerecord_main.gemfile' - ruby: '2.6' # rails 7+ requires ruby 3.0+ - - gemfile: 'gemfiles/activerecord_5.0.2.gemfile' - ruby: 'jruby' # this *should* work - there's a test failure; it's not incompatible like the other excludes. could be an issue in Rails 5.0.2? - - gemfile: 'gemfiles/activerecord_6.1.0.gemfile' - ruby: 'jruby' # this *should* work. it seems like there's an issue with rails 6 on jruby. + ruby: '2.7' - gemfile: 'gemfiles/activerecord_main.gemfile' - ruby: 'jruby' # this *should* work. it seems like there's an issue with rails 6 on jruby. - + ruby: '3.0' env: BUNDLE_GEMFILE: ${{ matrix.gemfile }} @@ -84,10 +81,10 @@ jobs: bundler-cache: true - name: Run tests on sqlite - run: DB=sqlite bundle exec rake + run: DB=sqlite bundle exec rspec - name: Run tests on postgres - run: DB=postgres bundle exec rake + run: DB=postgres bundle exec rspec lint: name: Lint diff --git a/.gitignore b/.gitignore index 5b936d516..4eb7b201c 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,6 @@ Gemfile.lock .ruby-version .ruby-gemset /tmp +.vitepress/cache +node_modules +.vitepress/dist diff --git a/.rubocop.yml b/.rubocop.yml index 8585e75c6..24763717e 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -50,3 +50,4 @@ AllCops: - 'gemfiles/**/*' - 'vendor/**/*' - 'Appraisals' + - 'node_modules/**/*' diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index f6c643ac7..1715d17ee 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -214,3 +214,6 @@ Style/StringChars: Style/StringConcatenation: Exclude: - 'lib/cancan/rule.rb' + +Lint/SafeNavigationChain: + Enabled: false diff --git a/Appraisals b/Appraisals index d025b2d22..2bb71a217 100644 --- a/Appraisals +++ b/Appraisals @@ -1,37 +1,3 @@ -appraise 'activerecord_5.0.2' do - gem 'activerecord', '~> 5.0.2', require: 'active_record' - gem 'activesupport', '~> 5.0.2', require: 'active_support/all' - gem 'actionpack', '~> 5.0.2', require: 'action_pack' - - gemfile.platforms :jruby do - gem 'activerecord-jdbcsqlite3-adapter' - gem 'jdbc-sqlite3' - gem 'jdbc-postgres' - end - - gemfile.platforms :ruby, :mswin, :mingw do - gem 'pg', '~> 1.3.4' - gem 'sqlite3', '~> 1.3.0' - end -end - -appraise 'activerecord_5.1.0' do - gem 'activerecord', '~> 5.1.0', require: 'active_record' - gem 'activesupport', '~> 5.1.0', require: 'active_support/all' - gem 'actionpack', '~> 5.1.0', require: 'action_pack' - - gemfile.platforms :jruby do - gem 'activerecord-jdbcsqlite3-adapter' - gem 'jdbc-sqlite3' - gem 'jdbc-postgres' - end - - gemfile.platforms :ruby, :mswin, :mingw do - gem 'pg', '~> 1.3.4' - gem 'sqlite3', '~> 1.4.2' - end -end - appraise 'activerecord_5.2.2' do gem 'activerecord', '~> 5.2.2', require: 'active_record' gem 'activesupport', '~> 5.2.2', require: 'active_support/all' @@ -45,7 +11,7 @@ appraise 'activerecord_5.2.2' do gemfile.platforms :ruby, :mswin, :mingw do gem 'pg', '~> 1.3.4' - gem 'sqlite3', '~> 1.4.2' + gem 'sqlite3', '~> 1.7.3' end end @@ -62,7 +28,7 @@ appraise 'activerecord_6.0.0' do platforms :ruby, :mswin, :mingw do gem 'pg', '~> 1.3.4' - gem 'sqlite3', '~> 1.4.2' + gem 'sqlite3', '~> 1.7.3' end end @@ -79,7 +45,7 @@ appraise 'activerecord_6.1.0' do platforms :ruby, :mswin, :mingw do gem 'pg', '~> 1.3.4' - gem 'sqlite3', '~> 1.4.2' + gem 'sqlite3', '~> 1.7.3' end end @@ -96,7 +62,24 @@ appraise 'activerecord_7.0.0' do platforms :ruby, :mswin, :mingw do gem 'pg', '~> 1.3.4' - gem 'sqlite3', '~> 1.4.2' + gem 'sqlite3', '~> 1.7.3' + end +end + +appraise 'activerecord_7.1.0' do + gem 'actionpack', '~> 7.1.0', require: 'action_pack' + gem 'activerecord', '~> 7.1.0', require: 'active_record' + gem 'activesupport', '~> 7.1.0', require: 'active_support/all' + + platforms :jruby do + gem 'activerecord-jdbcsqlite3-adapter' + gem 'jdbc-sqlite3' + gem 'jdbc-postgres' + end + + platforms :ruby, :mswin, :mingw do + gem 'pg', '~> 1.5.6' + gem 'sqlite3', '~> 1.7.3' end end @@ -114,7 +97,7 @@ appraise 'activerecord_main' do end platforms :ruby, :mswin, :mingw do - gem 'pg', '~> 1.3.4' - gem 'sqlite3', '~> 1.4.2' + gem 'pg', '~> 1.5.6' + gem 'sqlite3', '~> 1.7.3' end end diff --git a/CHANGELOG.md b/CHANGELOG.md index 5dece0e08..c8a88d2d1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,15 @@ -## Unreleased +## 3.6.0 + +* [#849](https://github.com/CanCanCommunity/cancancan/pull/849): Update tests matrix. ([@coorasse][]) +* [#843](https://github.com/CanCanCommunity/cancancan/pull/843): Compress duplicate rules. ([@MrChoclate][]) +* [#841](https://github.com/CanCanCommunity/cancancan/pull/841): New https://cancancan.dev website. ([@pandermatt][]) +* [#839](https://github.com/CanCanCommunity/cancancan/pull/839): switch from database column detection to Rails attributes detection. ([@kalsan][]) +## 3.5.0 * [#653](https://github.com/CanCanCommunity/cancancan/pull/653): Add support for using an nil relation as a condition. ([@ghiculescu][]) * [#702](https://github.com/CanCanCommunity/cancancan/pull/702): Support scopes of STI classes as ability conditions. ([@honigc][]) * [#798](https://github.com/CanCanCommunity/cancancan/pull/798): Allow disabling of rules compressor via `CanCan.rules_compressor_enabled = false`. ([@coorasse][]) +* [#814](https://github.com/CanCanCommunity/cancancan/pull/814): Fix issue with polymorphic associations. ([@WriterZephos][]) ## 3.4.0 @@ -705,3 +712,7 @@ Please read the [guide on migrating from CanCanCan 2.x to 3.0](https://github.co [@mtoneil]: https://github.com/mtoneil [@Juleffel]: https://github.com/Juleffel [@honigc]: https://github.com/honigc +[@WriterZephos]: https://github.com/WriterZephos +[@MrChoclate]: https://github.com/MrChoclate +[@pandermatt]: https://github.com/pandermatt +[@kalsan]: https://github.com/kalsan diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7252e2f31..54df23d80 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,7 +2,7 @@ ### Reporting an Issue -1. If you have any questions about CanCanCan, search the [Wiki](https://github.com/cancancommunity/cancancan/wiki) or +1. If you have any questions about CanCanCan, search the [Developer guide](./docs/README.md) or use [Stack Overflow](http://stackoverflow.com/questions/tagged/cancancan). Do not post questions here. @@ -25,3 +25,19 @@ Please make sure you have test coverage for anything you add or fix! Please add a CHANGELOG entry with any relevant tags for issues, pull-requests, and authors. Thanks for you contribution! + +### Modify the Documentation + +The documentation is written in Markdown and is located in the `docs` directory. The documentation is built using [VitePress](https://vitepress.dev). +VitePress supports all markdown features but also adds a few enhancements, which are documented in the [Markdown Extensions](https://vitepress.dev/guide/markdown). + +```bash +npm install +npm run dev + +# build for production, resulting in a static site in docs/.vitepress/dist +npm run build +``` + +Before submitting a pull request, please make sure the documentation builds correctly using `npm run build`. +Most likely the build will fail if there are any syntax errors in the markdown files or dead links. diff --git a/README.md b/README.md index 832437480..1e84f0de3 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ [![Code Climate Badge](https://codeclimate.com/github/CanCanCommunity/cancancan.svg)](https://codeclimate.com/github/CanCanCommunity/cancancan) [Developer guide](./docs/README.md) | -[RDocs](http://rdoc.info/projects/CanCanCommunity/cancancan) | +[RDocs](https://www.rubydoc.info/github/CanCanCommunity/cancancan) | [Screencast 1](http://railscasts.com/episodes/192-authorization-with-cancan) | [Screencast 2](https://www.youtube.com/watch?v=cTYu-OjUgDw) @@ -26,20 +26,14 @@ of models automatically and reduce duplicated code. ## Our sponsors
- - Renuo AG + + Pennylane -
-
-
- - Modern Treasury - -
-
-
- - Bullet Train +
+
+
+
+ Honeybadger

@@ -50,20 +44,8 @@ of models automatically and reduce duplicated code.


- - NewRelic - -
-
-
- - Ontra - -
-
-
- - Honeybadger + + Renuo AG

diff --git a/docs/.vitepress/config.mts b/docs/.vitepress/config.mts new file mode 100644 index 000000000..d492843d7 --- /dev/null +++ b/docs/.vitepress/config.mts @@ -0,0 +1,97 @@ +import { defineConfig } from 'vitepress' + +// https://vitepress.dev/reference/site-config +export default defineConfig({ + title: "CanCanCan", + description: "The authorization Gem for Ruby on Rails.", + head: [ + ['link', { rel: "apple-touch-icon", sizes: "180x180", href: "/apple-touch-icon.png" }], + ['link', { rel: "icon", type: "image/png", sizes: "32x32", href: "/favicon-32x32.png" }], + ['link', { rel: "icon", type: "image/png", sizes: "16x16", href: "/favicon-16x16.png" }], + ['link', { rel: "mask-icon", href: "./safari-pinned-tab.svg", color: "#3c3ebf" }], + + ['link', { rel: "icon", href: "/favicon.ico", type: "image/x-icon" }], + ['link', { rel: "shortcut icon", href: "/favicon.ico", type: "image/x-icon" }], + ], + sitemap: { + hostname: 'https://cancancan.dev' + }, + cleanUrls: true, + themeConfig: { + // https://vitepress.dev/reference/default-theme-config + nav: [ + { text: 'Docs', link: '/README' }, + { text: 'Changelog', link: 'https://github.com/CanCanCommunity/cancancan/blob/main/CHANGELOG.md' }, + { + text: 'Screencasts', + items: [ + { text: 'Screencast 1', link: 'http://railscasts.com/episodes/192-authorization-with-cancan' }, + { text: 'Screencast 2', link: 'https://www.youtube.com/watch?v=cTYu-OjUgDw' } + ] + }, + ], + + footer: { + message: 'Made with ❤️ by the CanCanCan community', + copyright: `${new Date().getFullYear()} CanCanCan` + }, + externalLinkIcon: true, + + lastUpdated: { + formatOptions: { + dateStyle: 'medium', + } + }, + editLink: { + pattern: 'https://github.com/CanCanCommunity/cancancan/edit/main/docs/:path' + }, + search: { + provider: 'local' + }, + + logo: '/cancancan.png', + + sidebar: [ + { + text: 'Guide', + items: [ + { text: 'Introduction', link: '/introduction' }, + { text: 'Installation', link: '/installation' }, + { text: 'Define and check abilities', link: '/define_check_abilities' }, + { text: 'Controller helpers', link: '/controller_helpers' }, + { text: 'Fetching records', link: '/fetching_records' }, + { text: 'Cannot', link: '/cannot' }, + { text: 'Hash of conditions', link: '/hash_of_conditions' }, + { text: 'Combine Abilities', link: '/combine_abilities' }, + { text: 'Check abilities - avoid mistakes', link: '/check_abilities_mistakes' }, + { text: 'Handling CanCan::AccessDenied', link: '/handling_access_denied' }, + { text: 'Customize controller helpers', link: '/changing_defaults' }, + { text: 'Accessing request data', link: '/accessing_request_data' }, + { text: 'SQL strategies', link: '/sql_strategies' }, + { text: 'Accessible attributes', link: '/accessible_attributes' }, + { text: 'Testing', link: '/testing' }, + { text: 'Internationalization', link: '/internationalization' } + ] + }, + { + text: 'Further topics', + items: [ + { text: 'Migrating', link: '/migrating' }, + { text: 'Debugging Abilities', link: '/debugging' }, + { text: 'Split your ability file', link: '/split_ability' }, + { text: 'Define Abilities - best practices', link: '/define_abilities_best_practices' }, + { text: 'Abilities in database', link: '/abilities_in_database' }, + { text: 'Role-based Authorization', link: '/role_based_authorization' }, + { text: 'Model Adapter', link: '/model_adapter' }, + { text: 'Rules compression', link: '/rules_compression' }, + { text: 'Inherited Resources', link: '/inherited_resources' }, + { text: 'Devise', link: '/devise' }, + { text: 'FriendlyId', link: '/friendly_id' } + ] + } + ], + socialLinks: [ + { icon: 'github', link: 'https://github.com/CanCanCommunity/cancancan' } + ] + } +}) diff --git a/docs/.vitepress/theme/index.ts b/docs/.vitepress/theme/index.ts new file mode 100644 index 000000000..8462969be --- /dev/null +++ b/docs/.vitepress/theme/index.ts @@ -0,0 +1,16 @@ +// https://vitepress.dev/guide/custom-theme +import { h } from 'vue' +import type { Theme } from 'vitepress' +import DefaultTheme from 'vitepress/theme' +import './style.css' + +export default { + extends: DefaultTheme, + Layout: () => { + return h(DefaultTheme.Layout, null, { + // https://vitepress.dev/guide/extending-default-theme#layout-slots + }) + }, + enhanceApp({ app, router, siteData }) { + } +} satisfies Theme diff --git a/docs/.vitepress/theme/style.css b/docs/.vitepress/theme/style.css new file mode 100644 index 000000000..460b168b0 --- /dev/null +++ b/docs/.vitepress/theme/style.css @@ -0,0 +1,136 @@ +/** + * Customize default theme styling by overriding CSS variables: + * https://github.com/vuejs/vitepress/blob/main/src/client/theme-default/styles/vars.css + */ + +/** + * Colors + * + * Each colors have exact same color scale system with 3 levels of solid + * colors with different brightness, and 1 soft color. + * + * - `XXX-1`: The most solid color used mainly for colored text. It must + * satisfy the contrast ratio against when used on top of `XXX-soft`. + * + * - `XXX-2`: The color used mainly for hover state of the button. + * + * - `XXX-3`: The color for solid background, such as bg color of the button. + * It must satisfy the contrast ratio with pure white (#ffffff) text on + * top of it. + * + * - `XXX-soft`: The color used for subtle background such as custom container + * or badges. It must satisfy the contrast ratio when putting `XXX-1` colors + * on top of it. + * + * The soft color must be semi transparent alpha channel. This is crucial + * because it allows adding multiple "soft" colors on top of each other + * to create a accent, such as when having inline code block inside + * custom containers. + * + * - `default`: The color used purely for subtle indication without any + * special meanings attched to it such as bg color for menu hover state. + * + * - `brand`: Used for primary brand colors, such as link text, button with + * brand theme, etc. + * + * - `tip`: Used to indicate useful information. The default theme uses the + * brand color for this by default. + * + * - `warning`: Used to indicate warning to the users. Used in custom + * container, badges, etc. + * + * - `danger`: Used to show error, or dangerous message to the users. Used + * in custom container, badges, etc. + * -------------------------------------------------------------------------- */ + + :root { + --vp-c-default-1: var(--vp-c-gray-1); + --vp-c-default-2: var(--vp-c-gray-2); + --vp-c-default-3: var(--vp-c-gray-3); + --vp-c-default-soft: var(--vp-c-gray-soft); + + --vp-c-brand-1: var(--vp-c-indigo-1); + --vp-c-brand-2: var(--vp-c-indigo-2); + --vp-c-brand-3: var(--vp-c-indigo-3); + --vp-c-brand-soft: var(--vp-c-indigo-soft); + + --vp-c-tip-1: var(--vp-c-brand-1); + --vp-c-tip-2: var(--vp-c-brand-2); + --vp-c-tip-3: var(--vp-c-brand-3); + --vp-c-tip-soft: var(--vp-c-brand-soft); + + --vp-c-warning-1: var(--vp-c-yellow-1); + --vp-c-warning-2: var(--vp-c-yellow-2); + --vp-c-warning-3: var(--vp-c-yellow-3); + --vp-c-warning-soft: var(--vp-c-yellow-soft); + + --vp-c-danger-1: var(--vp-c-red-1); + --vp-c-danger-2: var(--vp-c-red-2); + --vp-c-danger-3: var(--vp-c-red-3); + --vp-c-danger-soft: var(--vp-c-red-soft); +} + +.dark .VPImage.logo { + filter: brightness(0) invert(1); +} + +/** + * Component: Button + * -------------------------------------------------------------------------- */ + +:root { + --vp-button-brand-border: transparent; + --vp-button-brand-text: var(--vp-c-white); + --vp-button-brand-bg: var(--vp-c-brand-3); + --vp-button-brand-hover-border: transparent; + --vp-button-brand-hover-text: var(--vp-c-white); + --vp-button-brand-hover-bg: var(--vp-c-brand-2); + --vp-button-brand-active-border: transparent; + --vp-button-brand-active-text: var(--vp-c-white); + --vp-button-brand-active-bg: var(--vp-c-brand-1); +} + +/** + * Component: Home + * -------------------------------------------------------------------------- */ + +:root { + --vp-home-hero-name-color: transparent; + --vp-home-hero-name-background: -webkit-linear-gradient( + 120deg, + #bd34fe 30%, + #41d1ff + ); +} + +@media (min-width: 640px) { + :root { + --vp-home-hero-image-filter: blur(56px); + } +} + +@media (min-width: 960px) { + :root { + --vp-home-hero-image-filter: blur(68px); + } +} + +/** + * Component: Custom Block + * -------------------------------------------------------------------------- */ + +:root { + --vp-custom-block-tip-border: transparent; + --vp-custom-block-tip-text: var(--vp-c-text-1); + --vp-custom-block-tip-bg: var(--vp-c-brand-soft); + --vp-custom-block-tip-code-bg: var(--vp-c-brand-soft); +} + +/** + * Component: Algolia + * -------------------------------------------------------------------------- */ + +.DocSearch { + --docsearch-primary-color: var(--vp-c-brand-1) !important; +} + diff --git a/docs/check_abilities_mistakes.md b/docs/check_abilities_mistakes.md index a9118eabb..1b8534808 100644 --- a/docs/check_abilities_mistakes.md +++ b/docs/check_abilities_mistakes.md @@ -14,7 +14,7 @@ cannot? :destroy, @article What we want to explain you in this chapter is that you can also pass the class instead of a single instance: -```rhtml +```erb <% if can? :create, Project %> <%= link_to "New Project", new_project_path %> <% end %> diff --git a/docs/define_check_abilities.md b/docs/define_check_abilities.md index 476b9e149..856232ae4 100644 --- a/docs/define_check_abilities.md +++ b/docs/define_check_abilities.md @@ -227,6 +227,6 @@ end some_user.can? :update, @article ``` -That's everything you know about defining and checking abilities. The DSL is very easy but yet very powerful. There's still a lot you need/should learn about defining abilities. You can [dig deeper](./hash_of_conditions.md) now, but we would suggest to stop, digest it, and proceed on a more Rails-specific topic: [Controller helpers](./controller_helpers.md) where you will learn how to secure your Rails application. +That's everything you need to know about checking abilities. The DSL is very easy but yet very powerful. However, there is still a lot you should learn about defining abilities. You can [dig deeper](./hash_of_conditions.md) now, but we would suggest to stop, digest, and proceed on a more Rails-specific topic: [Controller helpers](./controller_helpers.md) where you will learn how to secure your Rails application. Or you could already take a look at the session about [testing](./testing.md). diff --git a/docs/friendly_id.md b/docs/friendly_id.md index 8f2f92a38..21f3b678f 100644 --- a/docs/friendly_id.md +++ b/docs/friendly_id.md @@ -4,7 +4,7 @@ If you are using [FriendlyId](https://github.com/norman/friendly_id) you will pr You do not have to write `find_by :slug` or something like that, that is always error prone. -You just need to create a `config/initizializers/cancancan.rb` file with: +You just need to create a `config/initializers/cancancan.rb` file with: ```ruby if defined?(CanCanCan) @@ -16,7 +16,7 @@ if defined?(CanCanCan) module CanCan module ModelAdapters - class ActiveRecord4Adapter < AbstractAdapter + class ActiveRecordAdapter < AbstractAdapter @@friendly_support = {} def self.find(model_class, id) diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 000000000..cc22c9663 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,83 @@ +--- +# https://vitepress.dev/reference/default-theme-home-page +layout: home + +hero: + name: CanCanCan + text: Developer guide + tagline: The authorization Gem for Ruby on Rails. + image: + src: /cancancan.png + style: + # for dark mode + backgroundColor: '#fff' + borderRadius: '50%' + padding: '20px' + actions: + - theme: brand + text: Get Started + link: /README + - theme: alt + text: Installation + link: /installation + - theme: alt + text: GitHub + link: https://github.com/CanCanCommunity/cancancan + +features: + - title: "🔐 Secure Your Rails: CanCanCan's Authorization Mastery" + details: "Empowering developers to define and manage user permissions seamlessly." + - title: "🚀 Simplify with CanCanCan: Streamlined Permissions for Ruby" + details: "Dive into efficient, easy-to-manage access control for your Ruby applications." + - title: "✨ Permission Perfection with CanCanCan" + details: "Revolutionizing Ruby on Rails authorization with a unified, easy-to-use system." +--- + +## Our Sponsor + + + + + +_Do you want to sponsor CanCanCan and show your logo here? Check our [Sponsors Page](https://github.com/sponsors/coorasse)._ + +## Questions? + +If you have any question or doubt regarding CanCanCan which you cannot find the solution to in the +[documentation](./README.md), please +[open a question on Stackoverflow](http://stackoverflow.com/questions/ask?tags=cancancan) with tag +[cancancan](http://stackoverflow.com/questions/tagged/cancancan) + +## Bugs? + +If you find a bug please add an [issue on GitHub](https://github.com/CanCanCommunity/cancancan/issues) or fork the project and send a pull request. + +## Special Thanks + +Thanks to our Sponsors and to all the [CanCanCan contributors](https://github.com/CanCanCommunity/cancancan/contributors). +See the [CHANGELOG](https://github.com/CanCanCommunity/cancancan/blob/main/CHANGELOG.md) for the full list. diff --git a/docs/introduction.md b/docs/introduction.md index 74ee76121..76693ec10 100644 --- a/docs/introduction.md +++ b/docs/introduction.md @@ -26,6 +26,23 @@ If you'd like to sponsor this library, head to Head to the [Installation](./installation.md) chapter. + + diff --git a/docs/model_adapter.md b/docs/model_adapter.md index 0ebd36afc..6bed31694 100644 --- a/docs/model_adapter.md +++ b/docs/model_adapter.md @@ -195,15 +195,15 @@ Thus you'd probably be best served with inspecting the actual implementation of #### Implementation -- [ActiveRecord Base](../lib/cancan/model_adapters/active_record_adapter.rb) -- [ActiveRecord 4](../lib/cancan/model_adapters/active_record_4_adapter.rb) -- [ActiveRecord 5](../lib/cancan/model_adapters/active_record_5_adapter.rb) +- [ActiveRecord Base](https://github.com/CanCanCommunity/cancancan/blob/develop/lib/cancan/model_adapters/active_record_adapter.rb) +- [ActiveRecord 4](https://github.com/CanCanCommunity/cancancan/blob/develop/lib/cancan/model_adapters/active_record_4_adapter.rb) +- [ActiveRecord 5](https://github.com/CanCanCommunity/cancancan/blob/develop/lib/cancan/model_adapters/active_record_5_adapter.rb) #### Tests / Specs -- [ActiveRecord Base](../spec/cancan/model_adapters/active_record_adapter_spec.rb) -- [ActiveRecord 4](../spec/cancan/model_adapters/active_record_4_adapter_spec.rb) -- [ActiveRecord 5](../spec/cancan/model_adapters/active_record_5_adapter_spec.rb) +- [ActiveRecord Base](https://github.com/CanCanCommunity/cancancan/blob/develop/spec/cancan/model_adapters/active_record_adapter_spec.rb) +- [ActiveRecord 4](https://github.com/CanCanCommunity/cancancan/blob/develop/spec/cancan/model_adapters/active_record_4_adapter_spec.rb) +- [ActiveRecord 5](https://github.com/CanCanCommunity/cancancan/blob/develop/spec/cancan/model_adapters/active_record_5_adapter_spec.rb) **Mongoid, the adapter used in this entry as an example, can be found at:** diff --git a/docs/public/apple-touch-icon.png b/docs/public/apple-touch-icon.png new file mode 100644 index 000000000..7fd00e20d Binary files /dev/null and b/docs/public/apple-touch-icon.png differ diff --git a/docs/public/cancancan.png b/docs/public/cancancan.png new file mode 100755 index 000000000..11f168710 Binary files /dev/null and b/docs/public/cancancan.png differ diff --git a/docs/public/favicon-16x16.png b/docs/public/favicon-16x16.png new file mode 100644 index 000000000..4366c7924 Binary files /dev/null and b/docs/public/favicon-16x16.png differ diff --git a/docs/public/favicon-32x32.png b/docs/public/favicon-32x32.png new file mode 100644 index 000000000..95343a11d Binary files /dev/null and b/docs/public/favicon-32x32.png differ diff --git a/docs/public/favicon.ico b/docs/public/favicon.ico new file mode 100644 index 000000000..09d71b4f0 Binary files /dev/null and b/docs/public/favicon.ico differ diff --git a/docs/public/goboony.png b/docs/public/goboony.png new file mode 100644 index 000000000..7d77a4782 Binary files /dev/null and b/docs/public/goboony.png differ diff --git a/docs/public/honeybadger.svg b/docs/public/honeybadger.svg new file mode 100644 index 000000000..7d0531674 --- /dev/null +++ b/docs/public/honeybadger.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/public/mstile-150x150.png b/docs/public/mstile-150x150.png new file mode 100644 index 000000000..ecf34ed48 Binary files /dev/null and b/docs/public/mstile-150x150.png differ diff --git a/docs/public/pennylane.svg b/docs/public/pennylane.svg new file mode 100644 index 000000000..eeb8e8fe4 --- /dev/null +++ b/docs/public/pennylane.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/docs/public/renuo.png b/docs/public/renuo.png new file mode 100644 index 000000000..0ce669dc6 Binary files /dev/null and b/docs/public/renuo.png differ diff --git a/docs/public/safari-pinned-tab.svg b/docs/public/safari-pinned-tab.svg new file mode 100644 index 000000000..2a38913c0 --- /dev/null +++ b/docs/public/safari-pinned-tab.svg @@ -0,0 +1,70 @@ + + + + +Created by potrace 1.14, written by Peter Selinger 2001-2017 + + + + + + + + diff --git a/docs/role_based_authorization.md b/docs/role_based_authorization.md index b83f030df..6e45e2349 100644 --- a/docs/role_based_authorization.md +++ b/docs/role_based_authorization.md @@ -39,7 +39,7 @@ If you're using ActiveAdmin don't forget to add `role` to the `user.rb` list of Now you can provide a select-menu for choosing the roles in the view. -```rhtml +```erb <%= f.collection_select(:role, User::ROLES, :to_s, lambda{|i| i.to_s.humanize}) %> ``` @@ -89,7 +89,7 @@ If you're using devise, don't forget to add `attr_accessible :roles` to your use You can use checkboxes in the view for setting these roles. -```rhtml +```erb <% for role in User::ROLES %> <%= check_box_tag "user[roles][#{role}]", role, @user.roles.include?(role), {:name => "user[roles][]"}%> <%= label_tag "user_roles_#{role}", role.to_s.humanize %>
diff --git a/docs/rules_compression.md b/docs/rules_compression.md index a535dbf37..3eb76848a 100644 --- a/docs/rules_compression.md +++ b/docs/rules_compression.md @@ -52,4 +52,4 @@ becomes # nothing ``` -These optimizations allow you to follow the strategy of ["Give Permissions, don't take them"](https://github.com/CanCanCommunity/cancancan/wiki/Defining-Abilities%3A-Best-Practices#give-permissions-dont-take-them-away) and automatically ignore previous rules when they are not needed. +These optimizations allow you to follow the strategy of ["Give Permissions, don't take them"](https://github.com/CanCanCommunity/cancancan/blob/develop/docs/define_abilities_best_practices.md#give-permissions-dont-take-them-away) and automatically ignore previous rules when they are not needed. diff --git a/gemfiles/activerecord_5.1.0.gemfile b/gemfiles/activerecord_5.1.0.gemfile deleted file mode 100644 index fe5514f1a..000000000 --- a/gemfiles/activerecord_5.1.0.gemfile +++ /dev/null @@ -1,20 +0,0 @@ -# This file was generated by Appraisal - -source "https://rubygems.org" - -gem "activerecord", "~> 5.1.0", require: "active_record" -gem "activesupport", "~> 5.1.0", require: "active_support/all" -gem "actionpack", "~> 5.1.0", require: "action_pack" - -platforms :jruby do - gem "activerecord-jdbcsqlite3-adapter" - gem "jdbc-sqlite3" - gem "jdbc-postgres" -end - -platforms :ruby, :mswin, :mingw do - gem "pg", "~> 1.3.4" - gem "sqlite3", "~> 1.4.2" -end - -gemspec path: "../" diff --git a/gemfiles/activerecord_5.2.2.gemfile b/gemfiles/activerecord_5.2.2.gemfile index 63ce61b4e..071167d7e 100644 --- a/gemfiles/activerecord_5.2.2.gemfile +++ b/gemfiles/activerecord_5.2.2.gemfile @@ -14,7 +14,7 @@ end platforms :ruby, :mswin, :mingw do gem "pg", "~> 1.3.4" - gem "sqlite3", "~> 1.4.2" + gem "sqlite3", "~> 1.7.3" end gemspec path: "../" diff --git a/gemfiles/activerecord_6.0.0.gemfile b/gemfiles/activerecord_6.0.0.gemfile index e9f11ddd0..a2777cd19 100644 --- a/gemfiles/activerecord_6.0.0.gemfile +++ b/gemfiles/activerecord_6.0.0.gemfile @@ -14,7 +14,7 @@ end platforms :ruby, :mswin, :mingw do gem "pg", "~> 1.3.4" - gem "sqlite3", "~> 1.4.2" + gem "sqlite3", "~> 1.7.3" end gemspec path: "../" diff --git a/gemfiles/activerecord_6.1.0.gemfile b/gemfiles/activerecord_6.1.0.gemfile index 88580ffa1..fd081871c 100644 --- a/gemfiles/activerecord_6.1.0.gemfile +++ b/gemfiles/activerecord_6.1.0.gemfile @@ -14,7 +14,7 @@ end platforms :ruby, :mswin, :mingw do gem "pg", "~> 1.3.4" - gem "sqlite3", "~> 1.4.2" + gem "sqlite3", "~> 1.7.3" end gemspec path: "../" diff --git a/gemfiles/activerecord_7.0.0.gemfile b/gemfiles/activerecord_7.0.0.gemfile index 1f0c169dd..c0840521c 100644 --- a/gemfiles/activerecord_7.0.0.gemfile +++ b/gemfiles/activerecord_7.0.0.gemfile @@ -14,7 +14,7 @@ end platforms :ruby, :mswin, :mingw do gem "pg", "~> 1.3.4" - gem "sqlite3", "~> 1.4.2" + gem "sqlite3", "~> 1.7.3" end gemspec path: "../" diff --git a/gemfiles/activerecord_5.0.2.gemfile b/gemfiles/activerecord_7.1.0.gemfile similarity index 51% rename from gemfiles/activerecord_5.0.2.gemfile rename to gemfiles/activerecord_7.1.0.gemfile index 8f0cb561a..72952c7b5 100644 --- a/gemfiles/activerecord_5.0.2.gemfile +++ b/gemfiles/activerecord_7.1.0.gemfile @@ -2,9 +2,9 @@ source "https://rubygems.org" -gem "activerecord", "~> 5.0.2", require: "active_record" -gem "activesupport", "~> 5.0.2", require: "active_support/all" -gem "actionpack", "~> 5.0.2", require: "action_pack" +gem "actionpack", "~> 7.1.0", require: "action_pack" +gem "activerecord", "~> 7.1.0", require: "active_record" +gem "activesupport", "~> 7.1.0", require: "active_support/all" platforms :jruby do gem "activerecord-jdbcsqlite3-adapter" @@ -13,8 +13,8 @@ platforms :jruby do end platforms :ruby, :mswin, :mingw do - gem "pg", "~> 1.3.4" - gem "sqlite3", "~> 1.3.0" + gem "pg", "~> 1.5.6" + gem "sqlite3", "~> 1.7.3" end gemspec path: "../" diff --git a/gemfiles/activerecord_main.gemfile b/gemfiles/activerecord_main.gemfile index ba43821e8..cbc6172ca 100644 --- a/gemfiles/activerecord_main.gemfile +++ b/gemfiles/activerecord_main.gemfile @@ -15,8 +15,8 @@ platforms :jruby do end platforms :ruby, :mswin, :mingw do - gem "pg", "~> 1.3.4" - gem "sqlite3", "~> 1.4.2" + gem "pg", "~> 1.5.6" + gem "sqlite3", "~> 1.7.3" end gemspec path: "../" diff --git a/lib/cancan/ability/strong_parameter_support.rb b/lib/cancan/ability/strong_parameter_support.rb index 31da74574..892d250a3 100644 --- a/lib/cancan/ability/strong_parameter_support.rb +++ b/lib/cancan/ability/strong_parameter_support.rb @@ -31,7 +31,7 @@ def get_attributes(rule, subject) klass = subject_class?(subject) ? subject : subject.class # empty attributes is an 'all' if rule.attributes.empty? && klass < ActiveRecord::Base - klass.column_names.map(&:to_sym) - Array(klass.primary_key) + klass.attribute_names.map(&:to_sym) - Array(klass.primary_key) else rule.attributes end diff --git a/lib/cancan/conditions_matcher.rb b/lib/cancan/conditions_matcher.rb index 009c3f6ac..c1a97b96a 100644 --- a/lib/cancan/conditions_matcher.rb +++ b/lib/cancan/conditions_matcher.rb @@ -39,11 +39,13 @@ def matches_non_block_conditions(subject) def nested_subject_matches_conditions?(subject_hash) parent, child = subject_hash.first - matches_base_parent_conditions = matches_conditions_hash?(parent, - @conditions[parent.class.name.downcase.to_sym] || {}) - adapter = model_adapter(parent) + parent_condition_name = adapter.parent_condition_name(parent, child) + + matches_base_parent_conditions = matches_conditions_hash?(parent, + @conditions[parent_condition_name] || {}) + matches_base_parent_conditions && (!adapter.override_nested_subject_conditions_matching?(parent, child, @conditions) || adapter.nested_subject_matches_conditions?(parent, child, @conditions)) @@ -67,16 +69,15 @@ def matches_conditions_hash?(subject, conditions = @conditions) def matches_all_conditions?(adapter, subject, conditions) if conditions.is_a?(Hash) - matches_hash_conditions(adapter, subject, conditions) + matches_hash_conditions?(adapter, subject, conditions) elsif conditions.respond_to?(:include?) conditions.include?(subject) else - puts "does #{subject} match #{conditions}?" subject == conditions end end - def matches_hash_conditions(adapter, subject, conditions) + def matches_hash_conditions?(adapter, subject, conditions) conditions.all? do |name, value| if adapter.override_condition_matching?(subject, name, value) adapter.matches_condition?(subject, name, value) diff --git a/lib/cancan/controller_additions.rb b/lib/cancan/controller_additions.rb index 0c84f83e8..a51f5b96c 100644 --- a/lib/cancan/controller_additions.rb +++ b/lib/cancan/controller_additions.rb @@ -171,6 +171,11 @@ def load_resource(*args) # [:+instance_name+] # The name of the instance variable for this resource. # + # [:+id_param+] + # Find using a param key other than :id. For example: + # + # load_resource :id_param => :url # will use find(params[:url]) + # # [:+through+] # Authorize conditions on this parent resource when instance isn't available. # diff --git a/lib/cancan/model_adapters/abstract_adapter.rb b/lib/cancan/model_adapters/abstract_adapter.rb index 4041dcb49..d934b596e 100644 --- a/lib/cancan/model_adapters/abstract_adapter.rb +++ b/lib/cancan/model_adapters/abstract_adapter.rb @@ -35,6 +35,11 @@ def self.matches_conditions_hash?(_subject, _conditions) raise NotImplemented, 'This model adapter does not support matching on a conditions hash.' end + # Override if parent condition could be under a different key in conditions + def self.parent_condition_name(parent, _child) + parent.class.name.downcase.to_sym + end + # Used above override_conditions_hash_matching to determine if this model adapter will override the # matching behavior for nested subject. # If this returns true then nested_subject_matches_conditions? will be called. diff --git a/lib/cancan/model_adapters/active_record_adapter.rb b/lib/cancan/model_adapters/active_record_adapter.rb index 01ae8063e..048883067 100644 --- a/lib/cancan/model_adapters/active_record_adapter.rb +++ b/lib/cancan/model_adapters/active_record_adapter.rb @@ -1,5 +1,8 @@ # frozen_string_literal: true +# rubocop:disable Metrics/AbcSize +# rubocop:disable Metrics/CyclomaticComplexity +# rubocop:disable Metrics/PerceivedComplexity module CanCan module ModelAdapters class ActiveRecordAdapter < AbstractAdapter @@ -42,11 +45,53 @@ def nested_subject_matches_conditions?(parent, child, all_conditions) def parent_child_conditions(parent, child, all_conditions) child_class = child.is_a?(Class) ? child : child.class + parent_class = parent.is_a?(Class) ? parent : parent.class + foreign_key = child_class.reflect_on_all_associations(:belongs_to).find do |association| - association.klass == parent.class + # Do not match on polymorphic associations or it will throw an error (klass cannot be determined) + !association.polymorphic? && association.klass == parent.class + end&.foreign_key&.to_sym + + # Search again in case of polymorphic associations, this time matching on the :has_many side + # via the :as option, as well as klass + foreign_key ||= parent_class.reflect_on_all_associations(:has_many).find do |has_many_assoc| + matching_parent_child_polymorphic_association(has_many_assoc, child_class) end&.foreign_key&.to_sym + foreign_key.nil? ? nil : all_conditions[foreign_key] end + + def matching_parent_child_polymorphic_association(parent_assoc, child_class) + return nil unless parent_assoc.klass == child_class + return nil if parent_assoc&.options[:as].nil? + + child_class.reflect_on_all_associations(:belongs_to).find do |child_assoc| + # Only match this way for polymorphic associations + child_assoc.polymorphic? && child_assoc.name == parent_assoc.options[:as] + end + end + + def child_association_to_parent(parent, child) + child_class = child.is_a?(Class) ? child : child.class + parent_class = parent.is_a?(Class) ? parent : parent.class + + association = child_class.reflect_on_all_associations(:belongs_to).find do |belongs_to_assoc| + # Do not match on polymorphic associations or it will throw an error (klass cannot be determined) + !belongs_to_assoc.polymorphic? && belongs_to_assoc.klass == parent.class + end + + return association if association + + parent_class.reflect_on_all_associations(:has_many).each do |has_many_assoc| + association ||= matching_parent_child_polymorphic_association(has_many_assoc, child_class) + end + + association + end + + def parent_condition_name(parent, child) + child_association_to_parent(parent, child)&.name || parent.class.name.downcase.to_sym + end end # Returns conditions intended to be used inside a database query. Normally you will not call this @@ -175,6 +220,9 @@ def sanitize_sql(conditions) end end end +# rubocop:enable Metrics/PerceivedComplexity +# rubocop:enable Metrics/CyclomaticComplexity +# rubocop:enable Metrics/AbcSize ActiveSupport.on_load(:active_record) do send :include, CanCan::ModelAdditions diff --git a/lib/cancan/rules_compressor.rb b/lib/cancan/rules_compressor.rb index 01b9e325d..46f5a4cbc 100644 --- a/lib/cancan/rules_compressor.rb +++ b/lib/cancan/rules_compressor.rb @@ -11,6 +11,7 @@ def initialize(rules) end def compress(array) + array = simplify(array) idx = array.rindex(&:catch_all?) return array unless idx @@ -19,5 +20,22 @@ def compress(array) .drop_while { |n| n.base_behavior == value.base_behavior } .tap { |a| a.unshift(value) unless value.cannot_catch_all? } end + + # If we have A OR (!A AND anything ), then we can simplify to A OR anything + # If we have A OR (A OR anything ), then we can simplify to A OR anything + # If we have !A AND (A OR something), then we can simplify it to !A AND something + # If we have !A AND (!A AND something), then we can simplify it to !A AND something + # + # So as soon as we see a condition that is the same as the previous one, + # we can skip it, no matter of the base_behavior + def simplify(rules) + seen = Set.new + rules.reverse_each.filter_map do |rule| + next if seen.include?(rule.conditions) + + seen.add(rule.conditions) + rule + end.reverse + end end end diff --git a/lib/cancan/version.rb b/lib/cancan/version.rb index d5cf6e1f1..7477402a8 100644 --- a/lib/cancan/version.rb +++ b/lib/cancan/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module CanCan - VERSION = '3.4.0'.freeze + VERSION = '3.5.0'.freeze end diff --git a/logo/bullet_train.png b/logo/bullet_train.png deleted file mode 100644 index 0e40a801d..000000000 Binary files a/logo/bullet_train.png and /dev/null differ diff --git a/logo/modern_treasury.svg b/logo/modern_treasury.svg deleted file mode 100644 index 8a15c495a..000000000 --- a/logo/modern_treasury.svg +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - diff --git a/logo/new_relic.png b/logo/new_relic.png deleted file mode 100644 index 6b9f27c11..000000000 Binary files a/logo/new_relic.png and /dev/null differ diff --git a/logo/ontra.png b/logo/ontra.png deleted file mode 100644 index e7f243b70..000000000 Binary files a/logo/ontra.png and /dev/null differ diff --git a/logo/pennylane.svg b/logo/pennylane.svg new file mode 100644 index 000000000..eeb8e8fe4 --- /dev/null +++ b/logo/pennylane.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 000000000..2deac3c0f --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1510 @@ +{ + "name": "cancancan", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "dependencies": { + "vitepress": "^1.2.2" + } + }, + "node_modules/@algolia/autocomplete-core": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-core/-/autocomplete-core-1.9.3.tgz", + "integrity": "sha512-009HdfugtGCdC4JdXUbVJClA0q0zh24yyePn+KUGk3rP7j8FEe/m5Yo/z65gn6nP/cM39PxpzqKrL7A6fP6PPw==", + "dependencies": { + "@algolia/autocomplete-plugin-algolia-insights": "1.9.3", + "@algolia/autocomplete-shared": "1.9.3" + } + }, + "node_modules/@algolia/autocomplete-plugin-algolia-insights": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-plugin-algolia-insights/-/autocomplete-plugin-algolia-insights-1.9.3.tgz", + "integrity": "sha512-a/yTUkcO/Vyy+JffmAnTWbr4/90cLzw+CC3bRbhnULr/EM0fGNvM13oQQ14f2moLMcVDyAx/leczLlAOovhSZg==", + "dependencies": { + "@algolia/autocomplete-shared": "1.9.3" + }, + "peerDependencies": { + "search-insights": ">= 1 < 3" + } + }, + "node_modules/@algolia/autocomplete-preset-algolia": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-preset-algolia/-/autocomplete-preset-algolia-1.9.3.tgz", + "integrity": "sha512-d4qlt6YmrLMYy95n5TB52wtNDr6EgAIPH81dvvvW8UmuWRgxEtY0NJiPwl/h95JtG2vmRM804M0DSwMCNZlzRA==", + "dependencies": { + "@algolia/autocomplete-shared": "1.9.3" + }, + "peerDependencies": { + "@algolia/client-search": ">= 4.9.1 < 6", + "algoliasearch": ">= 4.9.1 < 6" + } + }, + "node_modules/@algolia/autocomplete-shared": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-shared/-/autocomplete-shared-1.9.3.tgz", + "integrity": "sha512-Wnm9E4Ye6Rl6sTTqjoymD+l8DjSTHsHboVRYrKgEt8Q7UHm9nYbqhN/i0fhUYA3OAEH7WA8x3jfpnmJm3rKvaQ==", + "peerDependencies": { + "@algolia/client-search": ">= 4.9.1 < 6", + "algoliasearch": ">= 4.9.1 < 6" + } + }, + "node_modules/@algolia/cache-browser-local-storage": { + "version": "4.23.3", + "resolved": "https://registry.npmjs.org/@algolia/cache-browser-local-storage/-/cache-browser-local-storage-4.23.3.tgz", + "integrity": "sha512-vRHXYCpPlTDE7i6UOy2xE03zHF2C8MEFjPN2v7fRbqVpcOvAUQK81x3Kc21xyb5aSIpYCjWCZbYZuz8Glyzyyg==", + "dependencies": { + "@algolia/cache-common": "4.23.3" + } + }, + "node_modules/@algolia/cache-common": { + "version": "4.23.3", + "resolved": "https://registry.npmjs.org/@algolia/cache-common/-/cache-common-4.23.3.tgz", + "integrity": "sha512-h9XcNI6lxYStaw32pHpB1TMm0RuxphF+Ik4o7tcQiodEdpKK+wKufY6QXtba7t3k8eseirEMVB83uFFF3Nu54A==" + }, + "node_modules/@algolia/cache-in-memory": { + "version": "4.23.3", + "resolved": "https://registry.npmjs.org/@algolia/cache-in-memory/-/cache-in-memory-4.23.3.tgz", + "integrity": "sha512-yvpbuUXg/+0rbcagxNT7un0eo3czx2Uf0y4eiR4z4SD7SiptwYTpbuS0IHxcLHG3lq22ukx1T6Kjtk/rT+mqNg==", + "dependencies": { + "@algolia/cache-common": "4.23.3" + } + }, + "node_modules/@algolia/client-account": { + "version": "4.23.3", + "resolved": "https://registry.npmjs.org/@algolia/client-account/-/client-account-4.23.3.tgz", + "integrity": "sha512-hpa6S5d7iQmretHHF40QGq6hz0anWEHGlULcTIT9tbUssWUriN9AUXIFQ8Ei4w9azD0hc1rUok9/DeQQobhQMA==", + "dependencies": { + "@algolia/client-common": "4.23.3", + "@algolia/client-search": "4.23.3", + "@algolia/transporter": "4.23.3" + } + }, + "node_modules/@algolia/client-analytics": { + "version": "4.23.3", + "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-4.23.3.tgz", + "integrity": "sha512-LBsEARGS9cj8VkTAVEZphjxTjMVCci+zIIiRhpFun9jGDUlS1XmhCW7CTrnaWeIuCQS/2iPyRqSy1nXPjcBLRA==", + "dependencies": { + "@algolia/client-common": "4.23.3", + "@algolia/client-search": "4.23.3", + "@algolia/requester-common": "4.23.3", + "@algolia/transporter": "4.23.3" + } + }, + "node_modules/@algolia/client-common": { + "version": "4.23.3", + "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-4.23.3.tgz", + "integrity": "sha512-l6EiPxdAlg8CYhroqS5ybfIczsGUIAC47slLPOMDeKSVXYG1n0qGiz4RjAHLw2aD0xzh2EXZ7aRguPfz7UKDKw==", + "dependencies": { + "@algolia/requester-common": "4.23.3", + "@algolia/transporter": "4.23.3" + } + }, + "node_modules/@algolia/client-personalization": { + "version": "4.23.3", + "resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-4.23.3.tgz", + "integrity": "sha512-3E3yF3Ocr1tB/xOZiuC3doHQBQ2zu2MPTYZ0d4lpfWads2WTKG7ZzmGnsHmm63RflvDeLK/UVx7j2b3QuwKQ2g==", + "dependencies": { + "@algolia/client-common": "4.23.3", + "@algolia/requester-common": "4.23.3", + "@algolia/transporter": "4.23.3" + } + }, + "node_modules/@algolia/client-search": { + "version": "4.23.3", + "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-4.23.3.tgz", + "integrity": "sha512-P4VAKFHqU0wx9O+q29Q8YVuaowaZ5EM77rxfmGnkHUJggh28useXQdopokgwMeYw2XUht49WX5RcTQ40rZIabw==", + "dependencies": { + "@algolia/client-common": "4.23.3", + "@algolia/requester-common": "4.23.3", + "@algolia/transporter": "4.23.3" + } + }, + "node_modules/@algolia/logger-common": { + "version": "4.23.3", + "resolved": "https://registry.npmjs.org/@algolia/logger-common/-/logger-common-4.23.3.tgz", + "integrity": "sha512-y9kBtmJwiZ9ZZ+1Ek66P0M68mHQzKRxkW5kAAXYN/rdzgDN0d2COsViEFufxJ0pb45K4FRcfC7+33YB4BLrZ+g==" + }, + "node_modules/@algolia/logger-console": { + "version": "4.23.3", + "resolved": "https://registry.npmjs.org/@algolia/logger-console/-/logger-console-4.23.3.tgz", + "integrity": "sha512-8xoiseoWDKuCVnWP8jHthgaeobDLolh00KJAdMe9XPrWPuf1by732jSpgy2BlsLTaT9m32pHI8CRfrOqQzHv3A==", + "dependencies": { + "@algolia/logger-common": "4.23.3" + } + }, + "node_modules/@algolia/recommend": { + "version": "4.23.3", + "resolved": "https://registry.npmjs.org/@algolia/recommend/-/recommend-4.23.3.tgz", + "integrity": "sha512-9fK4nXZF0bFkdcLBRDexsnGzVmu4TSYZqxdpgBW2tEyfuSSY54D4qSRkLmNkrrz4YFvdh2GM1gA8vSsnZPR73w==", + "dependencies": { + "@algolia/cache-browser-local-storage": "4.23.3", + "@algolia/cache-common": "4.23.3", + "@algolia/cache-in-memory": "4.23.3", + "@algolia/client-common": "4.23.3", + "@algolia/client-search": "4.23.3", + "@algolia/logger-common": "4.23.3", + "@algolia/logger-console": "4.23.3", + "@algolia/requester-browser-xhr": "4.23.3", + "@algolia/requester-common": "4.23.3", + "@algolia/requester-node-http": "4.23.3", + "@algolia/transporter": "4.23.3" + } + }, + "node_modules/@algolia/requester-browser-xhr": { + "version": "4.23.3", + "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-4.23.3.tgz", + "integrity": "sha512-jDWGIQ96BhXbmONAQsasIpTYWslyjkiGu0Quydjlowe+ciqySpiDUrJHERIRfELE5+wFc7hc1Q5hqjGoV7yghw==", + "dependencies": { + "@algolia/requester-common": "4.23.3" + } + }, + "node_modules/@algolia/requester-common": { + "version": "4.23.3", + "resolved": "https://registry.npmjs.org/@algolia/requester-common/-/requester-common-4.23.3.tgz", + "integrity": "sha512-xloIdr/bedtYEGcXCiF2muajyvRhwop4cMZo+K2qzNht0CMzlRkm8YsDdj5IaBhshqfgmBb3rTg4sL4/PpvLYw==" + }, + "node_modules/@algolia/requester-node-http": { + "version": "4.23.3", + "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-4.23.3.tgz", + "integrity": "sha512-zgu++8Uj03IWDEJM3fuNl34s746JnZOWn1Uz5taV1dFyJhVM/kTNw9Ik7YJWiUNHJQXcaD8IXD1eCb0nq/aByA==", + "dependencies": { + "@algolia/requester-common": "4.23.3" + } + }, + "node_modules/@algolia/transporter": { + "version": "4.23.3", + "resolved": "https://registry.npmjs.org/@algolia/transporter/-/transporter-4.23.3.tgz", + "integrity": "sha512-Wjl5gttqnf/gQKJA+dafnD0Y6Yw97yvfY8R9h0dQltX1GXTgNs1zWgvtWW0tHl1EgMdhAyw189uWiZMnL3QebQ==", + "dependencies": { + "@algolia/cache-common": "4.23.3", + "@algolia/logger-common": "4.23.3", + "@algolia/requester-common": "4.23.3" + } + }, + "node_modules/@babel/parser": { + "version": "7.24.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.5.tgz", + "integrity": "sha512-EOv5IK8arwh3LI47dz1b0tKUb/1uhHAnHJOrjgtQMIpu1uXd9mlFrJg9IUgGUgZ41Ch0K8REPTYpO7B76b4vJg==", + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@docsearch/css": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/@docsearch/css/-/css-3.6.0.tgz", + "integrity": "sha512-+sbxb71sWre+PwDK7X2T8+bhS6clcVMLwBPznX45Qu6opJcgRjAp7gYSDzVFp187J+feSj5dNBN1mJoi6ckkUQ==" + }, + "node_modules/@docsearch/js": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/@docsearch/js/-/js-3.6.0.tgz", + "integrity": "sha512-QujhqINEElrkIfKwyyyTfbsfMAYCkylInLYMRqHy7PHc8xTBQCow73tlo/Kc7oIwBrCLf0P3YhjlOeV4v8hevQ==", + "dependencies": { + "@docsearch/react": "3.6.0", + "preact": "^10.0.0" + } + }, + "node_modules/@docsearch/react": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/@docsearch/react/-/react-3.6.0.tgz", + "integrity": "sha512-HUFut4ztcVNmqy9gp/wxNbC7pTOHhgVVkHVGCACTuLhUKUhKAF9KYHJtMiLUJxEqiFLQiuri1fWF8zqwM/cu1w==", + "dependencies": { + "@algolia/autocomplete-core": "1.9.3", + "@algolia/autocomplete-preset-algolia": "1.9.3", + "@docsearch/css": "3.6.0", + "algoliasearch": "^4.19.1" + }, + "peerDependencies": { + "@types/react": ">= 16.8.0 < 19.0.0", + "react": ">= 16.8.0 < 19.0.0", + "react-dom": ">= 16.8.0 < 19.0.0", + "search-insights": ">= 1 < 3" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + }, + "search-insights": { + "optional": true + } + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz", + "integrity": "sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==", + "cpu": [ + "ppc64" + ], + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.2.tgz", + "integrity": "sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz", + "integrity": "sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.2.tgz", + "integrity": "sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz", + "integrity": "sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz", + "integrity": "sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz", + "integrity": "sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz", + "integrity": "sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz", + "integrity": "sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz", + "integrity": "sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz", + "integrity": "sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz", + "integrity": "sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==", + "cpu": [ + "loong64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz", + "integrity": "sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==", + "cpu": [ + "mips64el" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz", + "integrity": "sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==", + "cpu": [ + "ppc64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz", + "integrity": "sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==", + "cpu": [ + "riscv64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz", + "integrity": "sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==", + "cpu": [ + "s390x" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz", + "integrity": "sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz", + "integrity": "sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz", + "integrity": "sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz", + "integrity": "sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz", + "integrity": "sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz", + "integrity": "sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz", + "integrity": "sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.18.0.tgz", + "integrity": "sha512-Tya6xypR10giZV1XzxmH5wr25VcZSncG0pZIjfePT0OVBvqNEurzValetGNarVrGiq66EBVAFn15iYX4w6FKgQ==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.18.0.tgz", + "integrity": "sha512-avCea0RAP03lTsDhEyfy+hpfr85KfyTctMADqHVhLAF3MlIkq83CP8UfAHUssgXTYd+6er6PaAhx/QGv4L1EiA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.18.0.tgz", + "integrity": "sha512-IWfdwU7KDSm07Ty0PuA/W2JYoZ4iTj3TUQjkVsO/6U+4I1jN5lcR71ZEvRh52sDOERdnNhhHU57UITXz5jC1/w==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.18.0.tgz", + "integrity": "sha512-n2LMsUz7Ynu7DoQrSQkBf8iNrjOGyPLrdSg802vk6XT3FtsgX6JbE8IHRvposskFm9SNxzkLYGSq9QdpLYpRNA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.18.0.tgz", + "integrity": "sha512-C/zbRYRXFjWvz9Z4haRxcTdnkPt1BtCkz+7RtBSuNmKzMzp3ZxdM28Mpccn6pt28/UWUCTXa+b0Mx1k3g6NOMA==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.18.0.tgz", + "integrity": "sha512-l3m9ewPgjQSXrUMHg93vt0hYCGnrMOcUpTz6FLtbwljo2HluS4zTXFy2571YQbisTnfTKPZ01u/ukJdQTLGh9A==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.18.0.tgz", + "integrity": "sha512-rJ5D47d8WD7J+7STKdCUAgmQk49xuFrRi9pZkWoRD1UeSMakbcepWXPF8ycChBoAqs1pb2wzvbY6Q33WmN2ftw==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.18.0.tgz", + "integrity": "sha512-be6Yx37b24ZwxQ+wOQXXLZqpq4jTckJhtGlWGZs68TgdKXJgw54lUUoFYrg6Zs/kjzAQwEwYbp8JxZVzZLRepQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.18.0.tgz", + "integrity": "sha512-hNVMQK+qrA9Todu9+wqrXOHxFiD5YmdEi3paj6vP02Kx1hjd2LLYR2eaN7DsEshg09+9uzWi2W18MJDlG0cxJA==", + "cpu": [ + "ppc64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.18.0.tgz", + "integrity": "sha512-ROCM7i+m1NfdrsmvwSzoxp9HFtmKGHEqu5NNDiZWQtXLA8S5HBCkVvKAxJ8U+CVctHwV2Gb5VUaK7UAkzhDjlg==", + "cpu": [ + "riscv64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.18.0.tgz", + "integrity": "sha512-0UyyRHyDN42QL+NbqevXIIUnKA47A+45WyasO+y2bGJ1mhQrfrtXUpTxCOrfxCR4esV3/RLYyucGVPiUsO8xjg==", + "cpu": [ + "s390x" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.18.0.tgz", + "integrity": "sha512-xuglR2rBVHA5UsI8h8UbX4VJ470PtGCf5Vpswh7p2ukaqBGFTnsfzxUBetoWBWymHMxbIG0Cmx7Y9qDZzr648w==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.18.0.tgz", + "integrity": "sha512-LKaqQL9osY/ir2geuLVvRRs+utWUNilzdE90TpyoX0eNqPzWjRm14oMEE+YLve4k/NAqCdPkGYDaDF5Sw+xBfg==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.18.0.tgz", + "integrity": "sha512-7J6TkZQFGo9qBKH0pk2cEVSRhJbL6MtfWxth7Y5YmZs57Pi+4x6c2dStAUvaQkHQLnEQv1jzBUW43GvZW8OFqA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.18.0.tgz", + "integrity": "sha512-Txjh+IxBPbkUB9+SXZMpv+b/vnTEtFyfWZgJ6iyCmt2tdx0OF5WhFowLmnh8ENGNpfUlUZkdI//4IEmhwPieNg==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.18.0.tgz", + "integrity": "sha512-UOo5FdvOL0+eIVTgS4tIdbW+TtnBLWg1YBCcU2KWM7nuNwRz9bksDX1bekJJCpu25N1DVWaCwnT39dVQxzqS8g==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@shikijs/core": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-1.6.0.tgz", + "integrity": "sha512-NIEAi5U5R7BLkbW1pG/ZKu3eb1lzc3/+jD0lFsuxMT7zjaf9bbNwdNyMr7zh/Zl8EXQtQ+MYBAt5G+JLu+5DlA==" + }, + "node_modules/@shikijs/transformers": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@shikijs/transformers/-/transformers-1.6.0.tgz", + "integrity": "sha512-qGfHe1ECiqfE2STPWvfogIj/9Q0SK+MCRJdoITkW7AmFuB7DmbFnBT2US84+zklJOB51MzNO8RUXZiauWssJlQ==", + "dependencies": { + "shiki": "1.6.0" + } + }, + "node_modules/@types/estree": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==" + }, + "node_modules/@types/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==" + }, + "node_modules/@types/markdown-it": { + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.1.tgz", + "integrity": "sha512-4NpsnpYl2Gt1ljyBGrKMxFYAYvpqbnnkgP/i/g+NLpjEUa3obn1XJCur9YbEXKDAkaXqsR1LbDnGEJ0MmKFxfg==", + "dependencies": { + "@types/linkify-it": "^5", + "@types/mdurl": "^2" + } + }, + "node_modules/@types/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==" + }, + "node_modules/@types/web-bluetooth": { + "version": "0.0.20", + "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.20.tgz", + "integrity": "sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==" + }, + "node_modules/@vitejs/plugin-vue": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.0.4.tgz", + "integrity": "sha512-WS3hevEszI6CEVEx28F8RjTX97k3KsrcY6kvTg7+Whm5y3oYvcqzVeGCU3hxSAn4uY2CLCkeokkGKpoctccilQ==", + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "vite": "^5.0.0", + "vue": "^3.2.25" + } + }, + "node_modules/@vue/compiler-core": { + "version": "3.4.27", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.27.tgz", + "integrity": "sha512-E+RyqY24KnyDXsCuQrI+mlcdW3ALND6U7Gqa/+bVwbcpcR3BRRIckFoz7Qyd4TTlnugtwuI7YgjbvsLmxb+yvg==", + "dependencies": { + "@babel/parser": "^7.24.4", + "@vue/shared": "3.4.27", + "entities": "^4.5.0", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.0" + } + }, + "node_modules/@vue/compiler-dom": { + "version": "3.4.27", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.27.tgz", + "integrity": "sha512-kUTvochG/oVgE1w5ViSr3KUBh9X7CWirebA3bezTbB5ZKBQZwR2Mwj9uoSKRMFcz4gSMzzLXBPD6KpCLb9nvWw==", + "dependencies": { + "@vue/compiler-core": "3.4.27", + "@vue/shared": "3.4.27" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.4.27", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.27.tgz", + "integrity": "sha512-nDwntUEADssW8e0rrmE0+OrONwmRlegDA1pD6QhVeXxjIytV03yDqTey9SBDiALsvAd5U4ZrEKbMyVXhX6mCGA==", + "dependencies": { + "@babel/parser": "^7.24.4", + "@vue/compiler-core": "3.4.27", + "@vue/compiler-dom": "3.4.27", + "@vue/compiler-ssr": "3.4.27", + "@vue/shared": "3.4.27", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.10", + "postcss": "^8.4.38", + "source-map-js": "^1.2.0" + } + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.4.27", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.27.tgz", + "integrity": "sha512-CVRzSJIltzMG5FcidsW0jKNQnNRYC8bT21VegyMMtHmhW3UOI7knmUehzswXLrExDLE6lQCZdrhD4ogI7c+vuw==", + "dependencies": { + "@vue/compiler-dom": "3.4.27", + "@vue/shared": "3.4.27" + } + }, + "node_modules/@vue/devtools-api": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-7.2.1.tgz", + "integrity": "sha512-6oNCtyFOrNdqm6GUkFujsCgFlpbsHLnZqq7edeM/+cxAbMyCWvsaCsIMUaz7AiluKLccCGEM8fhOsjaKgBvb7g==", + "dependencies": { + "@vue/devtools-kit": "^7.2.1" + } + }, + "node_modules/@vue/devtools-kit": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-7.2.1.tgz", + "integrity": "sha512-Wak/fin1X0Q8LLIfCAHBrdaaB+R6IdpSXsDByPHbQ3BmkCP0/cIo/oEGp9i0U2+gEqD4L3V9RDjNf1S34DTzQQ==", + "dependencies": { + "@vue/devtools-shared": "^7.2.1", + "hookable": "^5.5.3", + "mitt": "^3.0.1", + "perfect-debounce": "^1.0.0", + "speakingurl": "^14.0.1" + }, + "peerDependencies": { + "vue": "^3.0.0" + } + }, + "node_modules/@vue/devtools-shared": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-7.2.1.tgz", + "integrity": "sha512-PCJF4UknJmOal68+X9XHyVeQ+idv0LFujkTOIW30+GaMJqwFVN9LkQKX4gLqn61KkGMdJTzQ1bt7EJag3TI6AA==", + "dependencies": { + "rfdc": "^1.3.1" + } + }, + "node_modules/@vue/reactivity": { + "version": "3.4.27", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.27.tgz", + "integrity": "sha512-kK0g4NknW6JX2yySLpsm2jlunZJl2/RJGZ0H9ddHdfBVHcNzxmQ0sS0b09ipmBoQpY8JM2KmUw+a6sO8Zo+zIA==", + "dependencies": { + "@vue/shared": "3.4.27" + } + }, + "node_modules/@vue/runtime-core": { + "version": "3.4.27", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.27.tgz", + "integrity": "sha512-7aYA9GEbOOdviqVvcuweTLe5Za4qBZkUY7SvET6vE8kyypxVgaT1ixHLg4urtOlrApdgcdgHoTZCUuTGap/5WA==", + "dependencies": { + "@vue/reactivity": "3.4.27", + "@vue/shared": "3.4.27" + } + }, + "node_modules/@vue/runtime-dom": { + "version": "3.4.27", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.27.tgz", + "integrity": "sha512-ScOmP70/3NPM+TW9hvVAz6VWWtZJqkbdf7w6ySsws+EsqtHvkhxaWLecrTorFxsawelM5Ys9FnDEMt6BPBDS0Q==", + "dependencies": { + "@vue/runtime-core": "3.4.27", + "@vue/shared": "3.4.27", + "csstype": "^3.1.3" + } + }, + "node_modules/@vue/server-renderer": { + "version": "3.4.27", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.27.tgz", + "integrity": "sha512-dlAMEuvmeA3rJsOMJ2J1kXU7o7pOxgsNHVr9K8hB3ImIkSuBrIdy0vF66h8gf8Tuinf1TK3mPAz2+2sqyf3KzA==", + "dependencies": { + "@vue/compiler-ssr": "3.4.27", + "@vue/shared": "3.4.27" + }, + "peerDependencies": { + "vue": "3.4.27" + } + }, + "node_modules/@vue/shared": { + "version": "3.4.27", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.27.tgz", + "integrity": "sha512-DL3NmY2OFlqmYYrzp39yi3LDkKxa5vZVwxWdQ3rG0ekuWscHraeIbnI8t+aZK7qhYqEqWKTUdijadunb9pnrgA==" + }, + "node_modules/@vueuse/core": { + "version": "10.9.0", + "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-10.9.0.tgz", + "integrity": "sha512-/1vjTol8SXnx6xewDEKfS0Ra//ncg4Hb0DaZiwKf7drgfMsKFExQ+FnnENcN6efPen+1kIzhLQoGSy0eDUVOMg==", + "dependencies": { + "@types/web-bluetooth": "^0.0.20", + "@vueuse/metadata": "10.9.0", + "@vueuse/shared": "10.9.0", + "vue-demi": ">=0.14.7" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/core/node_modules/vue-demi": { + "version": "0.14.7", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.7.tgz", + "integrity": "sha512-EOG8KXDQNwkJILkx/gPcoL/7vH+hORoBaKgGe+6W7VFMvCYJfmF2dGbvgDroVnI8LU7/kTu8mbjRZGBU1z9NTA==", + "hasInstallScript": true, + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, + "node_modules/@vueuse/integrations": { + "version": "10.9.0", + "resolved": "https://registry.npmjs.org/@vueuse/integrations/-/integrations-10.9.0.tgz", + "integrity": "sha512-acK+A01AYdWSvL4BZmCoJAcyHJ6EqhmkQEXbQLwev1MY7NBnS+hcEMx/BzVoR9zKI+UqEPMD9u6PsyAuiTRT4Q==", + "dependencies": { + "@vueuse/core": "10.9.0", + "@vueuse/shared": "10.9.0", + "vue-demi": ">=0.14.7" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "async-validator": "*", + "axios": "*", + "change-case": "*", + "drauu": "*", + "focus-trap": "*", + "fuse.js": "*", + "idb-keyval": "*", + "jwt-decode": "*", + "nprogress": "*", + "qrcode": "*", + "sortablejs": "*", + "universal-cookie": "*" + }, + "peerDependenciesMeta": { + "async-validator": { + "optional": true + }, + "axios": { + "optional": true + }, + "change-case": { + "optional": true + }, + "drauu": { + "optional": true + }, + "focus-trap": { + "optional": true + }, + "fuse.js": { + "optional": true + }, + "idb-keyval": { + "optional": true + }, + "jwt-decode": { + "optional": true + }, + "nprogress": { + "optional": true + }, + "qrcode": { + "optional": true + }, + "sortablejs": { + "optional": true + }, + "universal-cookie": { + "optional": true + } + } + }, + "node_modules/@vueuse/integrations/node_modules/vue-demi": { + "version": "0.14.7", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.7.tgz", + "integrity": "sha512-EOG8KXDQNwkJILkx/gPcoL/7vH+hORoBaKgGe+6W7VFMvCYJfmF2dGbvgDroVnI8LU7/kTu8mbjRZGBU1z9NTA==", + "hasInstallScript": true, + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, + "node_modules/@vueuse/metadata": { + "version": "10.9.0", + "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-10.9.0.tgz", + "integrity": "sha512-iddNbg3yZM0X7qFY2sAotomgdHK7YJ6sKUvQqbvwnf7TmaVPxS4EJydcNsVejNdS8iWCtDk+fYXr7E32nyTnGA==", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/shared": { + "version": "10.9.0", + "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-10.9.0.tgz", + "integrity": "sha512-Uud2IWncmAfJvRaFYzv5OHDli+FbOzxiVEQdLCKQKLyhz94PIyFC3CHcH7EDMwIn8NPtD06+PNbC/PiO0LGLtw==", + "dependencies": { + "vue-demi": ">=0.14.7" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/shared/node_modules/vue-demi": { + "version": "0.14.7", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.7.tgz", + "integrity": "sha512-EOG8KXDQNwkJILkx/gPcoL/7vH+hORoBaKgGe+6W7VFMvCYJfmF2dGbvgDroVnI8LU7/kTu8mbjRZGBU1z9NTA==", + "hasInstallScript": true, + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, + "node_modules/algoliasearch": { + "version": "4.23.3", + "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-4.23.3.tgz", + "integrity": "sha512-Le/3YgNvjW9zxIQMRhUHuhiUjAlKY/zsdZpfq4dlLqg6mEm0nL6yk+7f2hDOtLpxsgE4jSzDmvHL7nXdBp5feg==", + "dependencies": { + "@algolia/cache-browser-local-storage": "4.23.3", + "@algolia/cache-common": "4.23.3", + "@algolia/cache-in-memory": "4.23.3", + "@algolia/client-account": "4.23.3", + "@algolia/client-analytics": "4.23.3", + "@algolia/client-common": "4.23.3", + "@algolia/client-personalization": "4.23.3", + "@algolia/client-search": "4.23.3", + "@algolia/logger-common": "4.23.3", + "@algolia/logger-console": "4.23.3", + "@algolia/recommend": "4.23.3", + "@algolia/requester-browser-xhr": "4.23.3", + "@algolia/requester-common": "4.23.3", + "@algolia/requester-node-http": "4.23.3", + "@algolia/transporter": "4.23.3" + } + }, + "node_modules/csstype": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/esbuild": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz", + "integrity": "sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==", + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.20.2", + "@esbuild/android-arm": "0.20.2", + "@esbuild/android-arm64": "0.20.2", + "@esbuild/android-x64": "0.20.2", + "@esbuild/darwin-arm64": "0.20.2", + "@esbuild/darwin-x64": "0.20.2", + "@esbuild/freebsd-arm64": "0.20.2", + "@esbuild/freebsd-x64": "0.20.2", + "@esbuild/linux-arm": "0.20.2", + "@esbuild/linux-arm64": "0.20.2", + "@esbuild/linux-ia32": "0.20.2", + "@esbuild/linux-loong64": "0.20.2", + "@esbuild/linux-mips64el": "0.20.2", + "@esbuild/linux-ppc64": "0.20.2", + "@esbuild/linux-riscv64": "0.20.2", + "@esbuild/linux-s390x": "0.20.2", + "@esbuild/linux-x64": "0.20.2", + "@esbuild/netbsd-x64": "0.20.2", + "@esbuild/openbsd-x64": "0.20.2", + "@esbuild/sunos-x64": "0.20.2", + "@esbuild/win32-arm64": "0.20.2", + "@esbuild/win32-ia32": "0.20.2", + "@esbuild/win32-x64": "0.20.2" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==" + }, + "node_modules/focus-trap": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-7.5.4.tgz", + "integrity": "sha512-N7kHdlgsO/v+iD/dMoJKtsSqs5Dz/dXZVebRgJw23LDk+jMi/974zyiOYDziY2JPp8xivq9BmUGwIJMiuSBi7w==", + "dependencies": { + "tabbable": "^6.2.0" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/hookable": { + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz", + "integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==" + }, + "node_modules/magic-string": { + "version": "0.30.10", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.10.tgz", + "integrity": "sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ==", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.15" + } + }, + "node_modules/mark.js": { + "version": "8.11.1", + "resolved": "https://registry.npmjs.org/mark.js/-/mark.js-8.11.1.tgz", + "integrity": "sha512-1I+1qpDt4idfgLQG+BNWmrqku+7/2bi5nLf4YwF8y8zXvmfiTBY3PV3ZibfrjBueCByROpuBjLLFCajqkgYoLQ==" + }, + "node_modules/minisearch": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/minisearch/-/minisearch-6.3.0.tgz", + "integrity": "sha512-ihFnidEeU8iXzcVHy74dhkxh/dn8Dc08ERl0xwoMMGqp4+LvRSCgicb+zGqWthVokQKvCSxITlh3P08OzdTYCQ==" + }, + "node_modules/mitt": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==" + }, + "node_modules/nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/perfect-debounce": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz", + "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==" + }, + "node_modules/picocolors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", + "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==" + }, + "node_modules/postcss": { + "version": "8.4.38", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", + "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.0.0", + "source-map-js": "^1.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/preact": { + "version": "10.22.0", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.22.0.tgz", + "integrity": "sha512-RRurnSjJPj4rp5K6XoP45Ui33ncb7e4H7WiOHVpjbkvqvA3U+N8Z6Qbo0AE6leGYBV66n8EhEaFixvIu3SkxFw==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/preact" + } + }, + "node_modules/rfdc": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.1.tgz", + "integrity": "sha512-r5a3l5HzYlIC68TpmYKlxWjmOP6wiPJ1vWv2HeLhNsRZMrCkxeqxiHlQ21oXmQ4F3SiryXBHhAD7JZqvOJjFmg==" + }, + "node_modules/rollup": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.18.0.tgz", + "integrity": "sha512-QmJz14PX3rzbJCN1SG4Xe/bAAX2a6NpCP8ab2vfu2GiUr8AQcr2nCV/oEO3yneFarB67zk8ShlIyWb2LGTb3Sg==", + "dependencies": { + "@types/estree": "1.0.5" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.18.0", + "@rollup/rollup-android-arm64": "4.18.0", + "@rollup/rollup-darwin-arm64": "4.18.0", + "@rollup/rollup-darwin-x64": "4.18.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.18.0", + "@rollup/rollup-linux-arm-musleabihf": "4.18.0", + "@rollup/rollup-linux-arm64-gnu": "4.18.0", + "@rollup/rollup-linux-arm64-musl": "4.18.0", + "@rollup/rollup-linux-powerpc64le-gnu": "4.18.0", + "@rollup/rollup-linux-riscv64-gnu": "4.18.0", + "@rollup/rollup-linux-s390x-gnu": "4.18.0", + "@rollup/rollup-linux-x64-gnu": "4.18.0", + "@rollup/rollup-linux-x64-musl": "4.18.0", + "@rollup/rollup-win32-arm64-msvc": "4.18.0", + "@rollup/rollup-win32-ia32-msvc": "4.18.0", + "@rollup/rollup-win32-x64-msvc": "4.18.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/search-insights": { + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/search-insights/-/search-insights-2.14.0.tgz", + "integrity": "sha512-OLN6MsPMCghDOqlCtsIsYgtsC0pnwVTyT9Mu6A3ewOj1DxvzZF6COrn2g86E/c05xbktB0XN04m/t1Z+n+fTGw==", + "peer": true + }, + "node_modules/shiki": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-1.6.0.tgz", + "integrity": "sha512-P31ROeXcVgW/k3Z+vUUErcxoTah7ZRaimctOpzGuqAntqnnSmx1HOsvnbAB8Z2qfXPRhw61yptAzCsuKOhTHwQ==", + "dependencies": { + "@shikijs/core": "1.6.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", + "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/speakingurl": { + "version": "14.0.1", + "resolved": "https://registry.npmjs.org/speakingurl/-/speakingurl-14.0.1.tgz", + "integrity": "sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tabbable": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz", + "integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==" + }, + "node_modules/vite": { + "version": "5.2.11", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.2.11.tgz", + "integrity": "sha512-HndV31LWW05i1BLPMUCE1B9E9GFbOu1MbenhS58FuK6owSO5qHm7GiCotrNY1YE5rMeQSFBGmT5ZaLEjFizgiQ==", + "dependencies": { + "esbuild": "^0.20.1", + "postcss": "^8.4.38", + "rollup": "^4.13.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vitepress": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/vitepress/-/vitepress-1.2.2.tgz", + "integrity": "sha512-uZ3nXR5NY4nYj3RJWCo5jev9qlNZAQo5SUXu1U0QSUx84cUm/o7hCTDVjZ4njVSVui+PsV1oAbdQOg8ygbaf4w==", + "dependencies": { + "@docsearch/css": "^3.6.0", + "@docsearch/js": "^3.6.0", + "@shikijs/core": "^1.5.2", + "@shikijs/transformers": "^1.5.2", + "@types/markdown-it": "^14.1.1", + "@vitejs/plugin-vue": "^5.0.4", + "@vue/devtools-api": "^7.2.0", + "@vue/shared": "^3.4.27", + "@vueuse/core": "^10.9.0", + "@vueuse/integrations": "^10.9.0", + "focus-trap": "^7.5.4", + "mark.js": "8.11.1", + "minisearch": "^6.3.0", + "shiki": "^1.5.2", + "vite": "^5.2.11", + "vue": "^3.4.27" + }, + "bin": { + "vitepress": "bin/vitepress.js" + }, + "peerDependencies": { + "markdown-it-mathjax3": "^4", + "postcss": "^8" + }, + "peerDependenciesMeta": { + "markdown-it-mathjax3": { + "optional": true + }, + "postcss": { + "optional": true + } + } + }, + "node_modules/vue": { + "version": "3.4.27", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.4.27.tgz", + "integrity": "sha512-8s/56uK6r01r1icG/aEOHqyMVxd1bkYcSe9j8HcKtr/xTOFWvnzIVTehNW+5Yt89f+DLBe4A569pnZLS5HzAMA==", + "dependencies": { + "@vue/compiler-dom": "3.4.27", + "@vue/compiler-sfc": "3.4.27", + "@vue/runtime-dom": "3.4.27", + "@vue/server-renderer": "3.4.27", + "@vue/shared": "3.4.27" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 000000000..32f82dffc --- /dev/null +++ b/package.json @@ -0,0 +1,10 @@ +{ + "scripts": { + "docs:dev": "vitepress dev docs", + "docs:build": "vitepress build docs", + "docs:preview": "vitepress preview docs" + }, + "dependencies": { + "vitepress": "^1.2.2" + } +} diff --git a/spec/cancan/ability_spec.rb b/spec/cancan/ability_spec.rb index 486cbc425..3951f3b27 100644 --- a/spec/cancan/ability_spec.rb +++ b/spec/cancan/ability_spec.rb @@ -5,6 +5,21 @@ describe CanCan::Ability do before(:each) do (@ability = double).extend(CanCan::Ability) + + connect_db + ActiveRecord::Migration.verbose = false + ActiveRecord::Schema.define do + create_table(:named_users) do |t| + t.string :first_name + t.string :last_name + end + end + + unless defined?(NamedUser) + class NamedUser < ActiveRecord::Base + attribute :role, :string # Virtual only + end + end end it 'is able to :read anything' do @@ -651,13 +666,10 @@ def active? end it 'returns an array of permitted attributes for a given action and subject' do - user_class = Class.new(ActiveRecord::Base) - allow(user_class).to receive(:column_names).and_return(%w[first_name last_name]) - allow(user_class).to receive(:primary_key).and_return('id') - @ability.can :read, user_class + @ability.can :read, NamedUser @ability.can :read, Array, :special @ability.can :action, :subject, :attribute - expect(@ability.permitted_attributes(:read, user_class)).to eq(%i[first_name last_name]) + expect(@ability.permitted_attributes(:read, NamedUser)).to eq(%i[id first_name last_name role]) expect(@ability.permitted_attributes(:read, Array)).to eq([:special]) expect(@ability.permitted_attributes(:action, :subject)).to eq([:attribute]) end diff --git a/spec/cancan/model_adapters/active_record_adapter_spec.rb b/spec/cancan/model_adapters/active_record_adapter_spec.rb index efb03c629..810af5928 100644 --- a/spec/cancan/model_adapters/active_record_adapter_spec.rb +++ b/spec/cancan/model_adapters/active_record_adapter_spec.rb @@ -60,6 +60,12 @@ t.timestamps null: false end + create_table(:attachments) do |t| + t.integer :attachable_id + t.string :attachable_type + t.timestamps null: false + end + create_table(:users) do |t| t.string :name t.timestamps null: false @@ -86,6 +92,7 @@ class Article < ActiveRecord::Base has_many :mentioned_users, through: :mentions, source: :user belongs_to :user belongs_to :project + has_many :attachments, as: :attachable scope :unpopular, lambda { joins('LEFT OUTER JOIN comments ON (comments.post_id = posts.id)') @@ -103,6 +110,7 @@ class Mention < ActiveRecord::Base class Comment < ActiveRecord::Base belongs_to :article belongs_to :project + has_many :attachments, as: :attachable end class LegacyComment < ActiveRecord::Base @@ -110,10 +118,15 @@ class LegacyComment < ActiveRecord::Base belongs_to :project end + class Attachment < ActiveRecord::Base + belongs_to :attachable, polymorphic: true + end + class User < ActiveRecord::Base has_many :articles has_many :mentions has_many :mentioned_articles, through: :mentions, source: :article + has_many :comments, through: :articles end (@ability = double).extend(CanCan::Ability) @@ -511,6 +524,10 @@ class User < ActiveRecord::Base @comment2 = Comment.create!(article: @article2) @legacy_comment1 = LegacyComment.create!(article: @article1) @legacy_comment2 = LegacyComment.create!(article: @article2) + @attachment1 = Attachment.create!(attachable: @article1) + @comment_attachment1 = Attachment.create!(attachable: @comment1) + @attachment2 = Attachment.create!(attachable: @article2) + @comment_attachment2 = Attachment.create!(attachable: @comment2) end context 'when conditions are defined using the parent model' do @@ -520,6 +537,8 @@ class User < ActiveRecord::Base ability.can :manage, Article, user: user1 ability.can :manage, Comment, article: user1.articles ability.can :manage, LegacyComment, article: user1.articles + ability.can :manage, Attachment, attachable: user1.articles + ability.can :manage, Attachment, attachable: user1.comments end end @@ -533,6 +552,20 @@ class User < ActiveRecord::Base expect(ability.can?(:manage, { @article2 => LegacyComment })).to eq(false) expect(ability.can?(:manage, { @article2 => @legacy_comment2 })).to eq(false) end + + context 'when the association is polymorphic' do + it 'verifies parent equality correctly' do + expect(ability.can?(:manage, { @article1 => Attachment })).to eq(true) + expect(ability.can?(:manage, { @article1 => @attachment1 })).to eq(true) + expect(ability.can?(:manage, { @comment1 => Attachment })).to eq(true) + expect(ability.can?(:manage, { @comment1 => @comment_attachment1 })).to eq(true) + + expect(ability.can?(:manage, { @article2 => Attachment })).to eq(false) + expect(ability.can?(:manage, { @article2 => @attachment2 })).to eq(false) + expect(ability.can?(:manage, { @comment2 => Attachment })).to eq(false) + expect(ability.can?(:manage, { @comment2 => @comment_attachment2 })).to eq(false) + end + end end context 'when conditions are defined using the parent id' do @@ -542,6 +575,7 @@ class User < ActiveRecord::Base ability.can :manage, Article, user_id: user1.id ability.can :manage, Comment, article_id: user1.article_ids ability.can :manage, LegacyComment, post_id: user1.article_ids + ability.can :manage, Attachment, attachable_id: user1.article_ids end end @@ -555,6 +589,20 @@ class User < ActiveRecord::Base expect(ability.can?(:manage, { @article2 => LegacyComment })).to eq(false) expect(ability.can?(:manage, { @article2 => @legacy_comment2 })).to eq(false) end + + context 'when the association is polymorphic' do + it 'verifies parent equality correctly' do + expect(ability.can?(:manage, { @article1 => Attachment })).to eq(true) + expect(ability.can?(:manage, { @article1 => @attachment1 })).to eq(true) + expect(ability.can?(:manage, { @comment1 => Attachment })).to eq(true) + expect(ability.can?(:manage, { @comment1 => @comment_attachment1 })).to eq(true) + + expect(ability.can?(:manage, { @article2 => Attachment })).to eq(false) + expect(ability.can?(:manage, { @article2 => @attachment2 })).to eq(false) + expect(ability.can?(:manage, { @comment2 => Attachment })).to eq(false) + expect(ability.can?(:manage, { @comment2 => @comment_attachment2 })).to eq(false) + end + end end end diff --git a/spec/cancan/rule_compressor_spec.rb b/spec/cancan/rule_compressor_spec.rb index 3f643ef24..a2e1d2953 100644 --- a/spec/cancan/rule_compressor_spec.rb +++ b/spec/cancan/rule_compressor_spec.rb @@ -87,8 +87,7 @@ def cannot(action, subject, args = nil) end end - # TODO: not supported yet - xcontext 'duplicate rules' do + context 'duplicate rules' do let(:rules) do [can(:read, Blog, id: 4), can(:read, Blog, id: 1), @@ -99,7 +98,42 @@ def cannot(action, subject, args = nil) end it 'minimizes the rules, by removing duplicates' do - expect(described_class.new(rules).rules_collapsed).to eq [rules[0], rules[1], rules[2], rules[4]] + expect(described_class.new(rules).rules_collapsed).to eq [rules[0], rules[1], rules[4], rules[5]] + end + end + + context 'duplicates rules with cannot' do + let(:rules) do + [can(:read, Blog, id: 1), + cannot(:read, Blog, id: 1)] + end + + it 'minimizes the rules, by removing useless previous rules' do + expect(described_class.new(rules).rules_collapsed).to eq [rules[1]] + end + end + + context 'duplicates rules with cannot and can again' do + let(:rules) do + [can(:read, Blog, id: [1, 2]), + cannot(:read, Blog, id: 1), + can(:read, Blog, id: 1)] + end + + it 'minimizes the rules, by removing useless previous rules' do + expect(described_class.new(rules).rules_collapsed).to eq [rules[0], rules[2]] + end + end + + context 'duplicates rules with 2 cannot' do + let(:rules) do + [can(:read, Blog), + cannot(:read, Blog, id: 1), + cannot(:read, Blog, id: 1)] + end + + it 'minimizes the rules, by removing useless previous rules' do + expect(described_class.new(rules).rules_collapsed).to eq [rules[0], rules[2]] end end diff --git a/spec/cancan/rule_spec.rb b/spec/cancan/rule_spec.rb index 7de14c1d6..3f789fd1f 100644 --- a/spec/cancan/rule_spec.rb +++ b/spec/cancan/rule_spec.rb @@ -61,7 +61,7 @@ def count_queries(&block) end before do - ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: ':memory:') + connect_db ActiveRecord::Migration.verbose = false ActiveRecord::Schema.define do create_table(:watermelons) do |t|