Compare commits

..
Author SHA1 Message Date
ahtlon 2c0a4f49a6 Change to release packaging
Package build / build (16.x) (push) Failing after 6m40s
2025-10-11 15:07:29 +02:00
ahtlon 0b07f6a5a4 Action doesnt work :(
Package build / build (16.x) (push) Successful in 6m29s
2025-10-09 13:14:07 +02:00
ahtlon bac32ace61 Patch cards to be draggable
Package build / build (16.x) (push) Failing after 4m23s
2025-10-09 12:22:08 +02:00
ahtlon afd1749ab7 update actions 2025-10-09 12:20:29 +02:00
Nextcloud bot b84025d59b fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-10-09 00:49:32 +00:00
Julius KnorrandGitHub b866db3559 Merge pull request #7287 from holger-dev/holger-dev-patch-readme-iosApp
Update README.md
2025-10-08 22:50:44 +02:00
Julius KnorrandGitHub 9e3fbae6c3 Merge pull request #7138 from nextcloud/automated/noid/main-fix-npm-audit 2025-10-08 18:33:30 +02:00
Julius KnorrandGitHub a20f2444b9 Merge pull request #7182 from nextcloud/feat/package-node-npm-engines-update
build: update node and npm engines versions
2025-10-08 12:29:57 +02:00
Julius KnorrandGitHub 682778b6b6 Merge pull request #7277 from nextcloud/dependabot/npm_and_yarn/tar-fs-2.1.4
Chore(deps-dev): Bump tar-fs from 2.1.3 to 2.1.4
2025-10-07 09:14:57 +02:00
Nextcloud bot 39693e842b fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-10-07 00:31:51 +00:00
grnd-altandGitHub 4c09c65e9e Merge pull request #7293 from nextcloud/chore/bump-min-php-version
Chore/bump min php version
2025-10-06 18:17:49 +02:00
grnd-alt 637b8666b1 chore: bump min php to 8.2
Signed-off-by: grnd-alt <github@belakkaf.net>
2025-10-06 16:57:36 +02:00
grnd-alt 9e098f17c7 fix: comments with mentions cant be submitted
Signed-off-by: grnd-alt <github@belakkaf.net>
2025-10-06 16:33:11 +02:00
nextcloud-commandandGitHub 7c74e28735 fix(deps): Fix npm audit
Signed-off-by: GitHub <noreply@github.com>
2025-10-05 03:26:30 +00:00
Holger HeidkampandGitHub 56b2a63723 Update README.md
Replace the outdated (non-existing) iOS app reference with the newly released and fully working app Next Deck.
The new app looks great and works reliably.

Signed-off-by: Holger Heidkamp <holger@heidkamp.dev>
2025-10-04 07:06:49 +02:00
Nextcloud bot 90f51f931f fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-10-04 00:31:15 +00:00
Nextcloud bot 0115fee075 fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-10-03 00:33:29 +00:00
Nextcloud bot 42cf69a05c fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-10-02 00:32:21 +00:00
Nextcloud bot 20b5716e33 fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-10-01 00:32:01 +00:00
Nextcloud bot 60d02ff837 fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-09-30 00:32:00 +00:00
Luka TrovicandGitHub 604018008c Merge pull request #7258 from nextcloud/carl/psalm
refactor: Fix psalm issues
2025-09-29 14:39:56 +02:00
dependabot[bot]andAndy Scherzinger e2d83ce90c Chore(deps-dev): Bump tar-fs from 2.1.3 to 2.1.4
Bumps [tar-fs](https://github.com/mafintosh/tar-fs) from 2.1.3 to 2.1.4.
- [Commits](https://github.com/mafintosh/tar-fs/compare/v2.1.3...v2.1.4)

---
updated-dependencies:
- dependency-name: tar-fs
  dependency-version: 2.1.4
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-28 23:43:38 +02:00
Carl Schwan 5cf486150a refactor: Fix psalm issues
- Add typing for most of the services, controllers and mappers
- Add api doc for mappers
- Use vendor-bin for psalm
- Use attributes for controllers
- Fix upload of attachments

Signed-off-by: Carl Schwan <carl.schwan@nextcloud.com>
2025-09-28 11:49:06 +02:00
github-actions[bot]andGitHub 64741e455d Merge pull request #7278 from nextcloud/automated/noid/main-update-nextcloud-ocp
[main] Update nextcloud/ocp dependency
2025-09-28 03:04:59 +00:00
nextcloud-commandandGitHub 9a7eb9b8de chore(dev-deps): Bump nextcloud/ocp package
Signed-off-by: GitHub <noreply@github.com>
2025-09-28 02:40:40 +00:00
Nextcloud bot 8ad3c9ac5c fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-09-28 00:31:35 +00:00
Nextcloud bot 445d842481 fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-09-27 00:32:12 +00:00
Luka TrovicandGitHub 6a7c02d8d8 Merge pull request #7154 from vdiezel/fix-reorder-card-rest-endpoint
fix: parse arguments to CardService.reorder correctly to int
2025-09-26 15:26:44 +02:00
Luka TrovicandGitHub f347f96474 Merge pull request #7261 from nextcloud/fix/correct-pointer-for-title
fix: use text cursor for card title on dashboard
2025-09-26 07:42:06 +02:00
Nextcloud bot 5828f90163 fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-09-26 00:32:16 +00:00
grnd-altandLuka Trovic 6ccb4b7c3f fix: use text cursor for card title on dashboard
Signed-off-by: grnd-alt <github@belakkaf.net>
2025-09-25 13:38:21 +02:00
Luka TrovicandGitHub 094efdaa06 Merge pull request #7255 from nextcloud/fix-missing-push-notifications
fix: missing push notifications
2025-09-25 11:16:21 +02:00
Luka TrovicandLuka Trovic 161003faf9 chore: update base query count
Signed-off-by: Luka Trovic <luka@nextcloud.com>
2025-09-25 11:08:54 +02:00
Luka TrovicandLuka Trovic fa31256b8a fix: missing push notifications
Signed-off-by: Luka Trovic <luka@nextcloud.com>
2025-09-25 11:08:54 +02:00
Nextcloud bot c590c25f68 fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-09-25 00:31:40 +00:00
Luka TrovicandGitHub 05832f69e9 Merge pull request #7266 from nextcloud/redirect-if-RewriteBase-enabled
fix: redirect to cleaner URL if RewriteBase is enabled
2025-09-24 20:26:18 +02:00
Luka Trovic c9bacabd4c fix: redirect to cleaner URL if RewriteBase is enabled
Signed-off-by: Luka Trovic <luka@nextcloud.com>
2025-09-24 19:15:49 +02:00
Nextcloud bot 4abf895d22 fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-09-24 00:31:53 +00:00
Nextcloud bot d216e56dac fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-09-23 00:31:50 +00:00
Luka TrovicandGitHub 15514162f3 Merge pull request #7256 from nextcloud/bump-nextcloud-vue-8.31.0
Chore(deps): Bump @nextcloud/vue from 8.27.0 to 8.31.0
2025-09-22 21:34:25 +02:00
Luka Trovic b3bb29157c Chore(deps): Bump @nextcloud/vue from 8.27.0 to 8.31.0
Signed-off-by: Luka Trovic <luka@nextcloud.com>
2025-09-22 20:45:52 +02:00
Nextcloud bot 12b656dd8c fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-09-22 00:31:30 +00:00
github-actions[bot]andGitHub 661eea3018 Merge pull request #7250 from nextcloud/automated/noid/main-update-nextcloud-ocp
[main] Update nextcloud/ocp dependency
2025-09-21 03:31:25 +00:00
nextcloud-commandandGitHub ecd3cb42de chore(dev-deps): Bump nextcloud/ocp package
Signed-off-by: GitHub <noreply@github.com>
2025-09-21 02:40:50 +00:00
github-actions[bot]andGitHub f49c8f6ee4 Merge pull request #7231 from nextcloud/automated/noid/main-update-nextcloud-ocp
[main] Update nextcloud/ocp dependency
2025-09-19 17:09:19 +00:00
nextcloud-commandandLuka Trovic 96b56a2447 chore(dev-deps): Bump nextcloud/ocp package
Signed-off-by: GitHub <noreply@github.com>
2025-09-19 19:03:21 +02:00
Nextcloud bot 26badb58dd fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-09-19 00:32:01 +00:00
Nextcloud bot 04d9433bdd fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-09-18 00:31:43 +00:00
Nextcloud bot a69eb654fd fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-09-17 00:31:43 +00:00
Elizabeth DanzbergerandGitHub ab5ccb7bc1 Merge pull request #7238 from nextcloud/bugfix/noid/activity-icons
fix(darkmode): Fix activity icon colors
2025-09-16 14:57:18 -04:00
Luka TrovicandGitHub f054cc2fbd Merge pull request #7225 from ABartelt/fix/mysql-error-1093-user-deletion
fix: resolve MySQL Error 1093 when deleting users from boards
2025-09-16 20:43:16 +02:00
Luka TrovicandGitHub 1afc5cdbcc Merge pull request #7237 from nextcloud/bugfix/7207/fix-colors
fix: Fix colors from due dates and done
2025-09-16 20:16:18 +02:00
Joas Schilling 1a6e5929b2 fix(darkmode): Fix activity icon colors
Signed-off-by: Joas Schilling <coding@schilljs.com>
2025-09-16 15:17:54 +02:00
Joas Schilling 74a3ab5008 fix: Fix colors from due dates and done
Signed-off-by: Joas Schilling <coding@schilljs.com>
2025-09-16 14:42:54 +02:00
Luka TrovicandGitHub 0ebd05e649 Merge pull request #7210 from nextcloud/style/noid/deleteMaterialSymbolOutline
Migrate delete icon to Material Symbol outline variant
2025-09-16 13:43:08 +02:00
Luka TrovicandGitHub 3611c8f8ac Merge pull request #7159 from Somebodyisnobody/patch-1
Docs: Updating docs for API endpoints for uploading attachments
2025-09-16 10:31:31 +02:00
Luka TrovicandGitHub da3b857ab0 Merge pull request #7165 from nextcloud/get-cards-at-once
perf(cards): fetch all cards at once
2025-09-16 10:31:00 +02:00
Nextcloud bot 199b698cb7 fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-09-16 00:31:49 +00:00
Nextcloud bot fb1132652a fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-09-15 00:31:00 +00:00
Nextcloud bot f78c3d42df fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-09-14 00:30:54 +00:00
Nextcloud bot 4a879ab1fb fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-09-13 00:31:23 +00:00
Nextcloud bot b4e9dd39cc fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-09-12 00:31:48 +00:00
Nextcloud bot 9035a23af8 fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-09-11 00:31:52 +00:00
Arne Bartelt bffa4d0925 fix: resolve MySQL Error 1093 when deleting users from boards
Fixes #7125 and #7069 by implementing a two-step deletion process
that avoids MySQL's restriction on deleting from a table while
selecting from it in a subquery.

The fix separates the SELECT and DELETE operations:
1. First query: Get card IDs for assignments to delete
2. Second query: Delete assignments using the collected IDs

This approach works on all supported database systems (MySQL 5.7+,
MySQL 8.0+, MariaDB 10.x+) and follows MySQL's official best practices
for handling Error 1093: 'You can't specify target table for update in FROM clause'.

The issue occurred because the original deleteByParticipantOnBoard method
used a subquery that referenced the same table being deleted from,
which MySQL prohibits but MariaDB allows (explaining why it worked
in development but failed in production).

Signed-off-by: Arne Bartelt <arne.bartelt@gmail.com>
Signed-off-by: Arne Bartelt <Arne.Bartelt@gmail.com>
2025-09-10 16:39:51 +02:00
Luka TrovicandGitHub 516294ee23 Merge pull request #6640 from ludij/feature/3331-dynamic-column-width
Feature/3331 dynamic column width
2025-09-10 09:50:07 +02:00
Luutzen DijkstraandLuka Trovic 5a39996357 feature(3331): aligned stack children spacing and fixed linting issues
Signed-off-by: Luutzen Dijkstra <luutzen.dijkstra@gmail.com>
2025-09-10 09:44:36 +02:00
Luutzen DijkstraandLuka Trovic 569a0961fd feature(3331): card image refactor
Signed-off-by: Luutzen Dijkstra <luutzen.dijkstra@gmail.com>
2025-09-10 09:44:36 +02:00
Luutzen DijkstraandLuka Trovic 376f46b7ba feature(3331): scroll column instead of page for board and overview
Signed-off-by: Luutzen Dijkstra <luutzen.dijkstra@gmail.com>
2025-09-10 09:44:36 +02:00
Luutzen DijkstraandLuka Trovic 447dc60f37 feature(3331): removed duplicate flex-grow property from GlobalSearchResults
Signed-off-by: Luutzen Dijkstra <luutzen.dijkstra@gmail.com>
2025-09-10 09:44:36 +02:00
Luutzen DijkstraandLuka Trovic 480af39d56 feature(3331): specify phrasing for search results depending on view
Signed-off-by: Luutzen Dijkstra <luutzen.dijkstra@gmail.com>
2025-09-10 09:44:36 +02:00
Luutzen DijkstraandLuka Trovic 7d18f3a48b feature(3331): improve accessibility attributes for overview column titles
Signed-off-by: Luutzen Dijkstra <luutzen.dijkstra@gmail.com>
2025-09-10 09:44:36 +02:00
Luutzen DijkstraandLuka Trovic 51f5b8f023 fix(3331): bind title attribute on h3 in stack
Signed-off-by: Luutzen Dijkstra <luutzen.dijkstra@gmail.com>
2025-09-10 09:44:36 +02:00
Luutzen DijkstraandLuka Trovic 1e68e1ff03 feature(3331): use existingIndex to define existingCard
Signed-off-by: Luutzen Dijkstra <luutzen.dijkstra@gmail.com>
2025-09-10 09:44:36 +02:00
Luutzen DijkstraandLuka Trovic a13619690e fix(3331): fixed border around add list form in boards
Signed-off-by: Luutzen Dijkstra <luutzen.dijkstra@gmail.com>
2025-09-10 09:44:36 +02:00
Luutzen DijkstraandLuka Trovic 3115363c28 feature(3331): made board, overview, stack and search result cards width behave more dynamic
Signed-off-by: Luutzen Dijkstra <luutzen.dijkstra@gmail.com>
2025-09-10 09:44:36 +02:00
rakeknivenandGitHub 6ace1867e1 Merge pull request #7222 from nextcloud/fix/4466/refproviders-translator-comments 2025-09-09 14:02:53 +02:00
Julien Veyssier af4a610a25 feat(reference): add translators comments in reference providers
Signed-off-by: Julien Veyssier <julien-nc@posteo.net>
2025-09-09 12:57:14 +02:00
Nextcloud bot d40661a985 fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-09-09 00:33:39 +00:00
Nextcloud bot 0a14a0dfd8 fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-09-08 00:32:13 +00:00
github-actions[bot]andGitHub 72d3cfa771 Merge pull request #7215 from nextcloud/automated/noid/main-update-nextcloud-ocp
[main] Update nextcloud/ocp dependency
2025-09-07 03:02:50 +00:00
nextcloud-commandandGitHub 411c0d9d46 chore(dev-deps): Bump nextcloud/ocp package
Signed-off-by: GitHub <noreply@github.com>
2025-09-07 02:39:28 +00:00
Nextcloud bot ec33c26fe4 fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-09-07 00:33:30 +00:00
Nextcloud bot 8fcb4a47ae fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-09-06 00:31:26 +00:00
Andy Scherzinger 44d244f9aa style(icon): Migrate delete icon to Material Symbol outline variant
Signed-off-by: Andy Scherzinger <info@andy-scherzinger.de>
2025-09-05 18:26:28 +02:00
Nextcloud bot 3ed1fbcc40 fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-09-05 00:32:02 +00:00
Luka TrovicandGitHub 7394cb2d48 Merge pull request #7204 from nextcloud/update-main-version
feat(deps): Add Nextcloud 33 support on main
2025-09-04 21:24:36 +02:00
Joas Schilling 4f39328d59 feat(deps): Add Nextcloud 33 support on main
Signed-off-by: Joas Schilling <coding@schilljs.com>
2025-09-04 12:22:14 +02:00
Nextcloud bot b877c58343 fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-09-04 00:30:31 +00:00
Nextcloud bot 6334b36666 fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-09-03 00:29:39 +00:00
Luka TrovicandGitHub f08eeec3a8 Merge pull request #7199 from nextcloud/release/1.16.0-beta.1
Release/1.16.0-beta.1
2025-09-02 19:16:43 +02:00
Nextcloud bot eec2ccd9ed fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-09-02 00:30:24 +00:00
Luka Trovic 3c8728c9ee release 1.16.0-beta.1
Signed-off-by: Luka Trovic <luka@nextcloud.com>
2025-09-01 15:59:41 +02:00
dependabot[bot]andGitHub 684cf519ce Merge pull request #7065 from nextcloud/dependabot/npm_and_yarn/main/nextcloud/dialogs-6.3.1 2025-09-01 10:00:34 +00:00
Luka TrovicandGitHub 215a4637b0 Merge pull request #7146 from nextcloud/dependabot/npm_and_yarn/linkifyjs-4.3.2
Chore(deps): Bump linkifyjs from 4.1.3 to 4.3.2
2025-09-01 11:55:25 +02:00
Luka TrovicandGitHub cab45170e5 Merge pull request #7128 from nextcloud/fix/comments-with-mention-editable
fix: make comments with mention editable
2025-09-01 11:54:16 +02:00
dependabot[bot]andLuka Trovic cd50becc49 Chore(deps): Bump @nextcloud/dialogs from 6.1.1 to 6.3.1
Bumps [@nextcloud/dialogs](https://github.com/nextcloud-libraries/nextcloud-dialogs) from 6.1.1 to 6.3.1.
- [Release notes](https://github.com/nextcloud-libraries/nextcloud-dialogs/releases)
- [Changelog](https://github.com/nextcloud-libraries/nextcloud-dialogs/blob/v6.3.1/CHANGELOG.md)
- [Commits](https://github.com/nextcloud-libraries/nextcloud-dialogs/compare/v6.1.1...v6.3.1)

---
updated-dependencies:
- dependency-name: "@nextcloud/dialogs"
  dependency-version: 6.3.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-01 11:53:42 +02:00
github-actions[bot]andGitHub d778a48afb Merge pull request #7195 from nextcloud/automated/noid/main-update-nextcloud-ocp
[main] Update nextcloud/ocp dependency
2025-08-31 03:02:41 +00:00
nextcloud-commandandGitHub 0a1ed8e0c2 chore(dev-deps): Bump nextcloud/ocp package
Signed-off-by: GitHub <noreply@github.com>
2025-08-31 02:41:12 +00:00
Nextcloud bot a200afa168 fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-08-30 00:29:22 +00:00
Nextcloud bot 3277de5cb4 fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-08-29 00:29:34 +00:00
Nextcloud bot 34effdc00e fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-08-28 00:29:33 +00:00
Nextcloud bot 014372a21a fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-08-27 00:29:28 +00:00
Nextcloud bot 7674a85f68 fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-08-26 00:30:38 +00:00
Nextcloud botandnextcloud-command b3d7e1b128 build: update node and npm engines versions
Signed-off-by: nextcloud-command <nextcloud-command@users.noreply.github.com>
2025-08-25 12:49:07 +00:00
github-actions[bot]andGitHub 357c6c1c5a Merge pull request #7178 from nextcloud/automated/noid/main-update-nextcloud-ocp
[main] Update nextcloud/ocp dependency
2025-08-24 03:08:30 +00:00
nextcloud-commandandGitHub 23be704099 chore(dev-deps): Bump nextcloud/ocp package
Signed-off-by: GitHub <noreply@github.com>
2025-08-24 02:44:14 +00:00
Nextcloud bot 2571d48290 fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-08-23 00:29:01 +00:00
Luka TrovicandGitHub c513b1d5b5 Merge pull request #7177 from nextcloud/clear-deleted-attachment-sharing-records
Clean attachment sharing records after permanent deleted
2025-08-22 20:47:38 +02:00
Luka Trovic b80e0d9297 fix: clean attachment sharing records after permanent deleted
Signed-off-by: Luka Trovic <luka@nextcloud.com>
2025-08-22 11:09:53 +02:00
Nextcloud bot 382c05ed8d fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-08-22 00:28:52 +00:00
Nextcloud bot 1283b8d78a fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-08-20 00:28:46 +00:00
Nextcloud bot 465f24f2f1 fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-08-19 00:31:12 +00:00
github-actions[bot]andGitHub ba0d211720 Merge pull request #7171 from nextcloud/automated/noid/main-update-nextcloud-ocp
[main] Update nextcloud/ocp dependency
2025-08-17 03:49:14 +00:00
nextcloud-commandandGitHub 23f56be988 chore(dev-deps): Bump nextcloud/ocp package
Signed-off-by: GitHub <noreply@github.com>
2025-08-17 03:37:30 +00:00
Luka TrovicandGitHub 87f2d65a96 Merge pull request #7162 from nextcloud/fix/6528-loosing-focus-when-editing-the-description-in-a-card
fix: do not change focus when card id stays the same
2025-08-14 20:52:50 +02:00
Nextcloud bot c95b3b746d fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-08-14 00:29:47 +00:00
Carl Schwan a3fa72341d perf(cards): fetch all cards at once
Instead of one by one

Signed-off-by: Carl Schwan <carl.schwan@nextclound.com>
2025-08-13 16:01:52 +02:00
grnd-alt da850b18ae fix: do not change focus when card id stays the same
Signed-off-by: grnd-alt <github@belakkaf.net>
2025-08-13 12:55:22 +02:00
Nextcloud bot 771cf93cf6 fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-08-13 00:30:21 +00:00
Nextcloud bot 0ed8b21b3c fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-08-12 00:29:18 +00:00
github-actions[bot]andGitHub 6f4f59a976 Merge pull request #7161 from nextcloud/automated/noid/main-update-nextcloud-ocp
[main] Update nextcloud/ocp dependency
2025-08-10 04:05:05 +00:00
nextcloud-commandandGitHub f80d7a5e79 chore(dev-deps): Bump nextcloud/ocp package
Signed-off-by: GitHub <noreply@github.com>
2025-08-10 03:47:48 +00:00
Nextcloud bot e3843f9808 fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-08-10 00:28:20 +00:00
Nextcloud bot 1ec7f1a971 fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-08-09 00:28:47 +00:00
Viktor Diezel eee35ac6ad fix: parse arguments to CardService.reorder correctly to int
Signed-off-by: Viktor Diezel <viktor.diezel@posteo.de>
2025-08-07 11:54:55 +02:00
SomebodyisnobodyandGitHub 152181ff67 docs: Update API docs
Clarifying documentation for the attachment upload endpoints.

Signed-off-by: Somebodyisnobody <35230554+Somebodyisnobody@users.noreply.github.com>
2025-08-07 11:43:42 +02:00
Nextcloud bot 18eea3584e fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-08-04 00:31:06 +00:00
github-actions[bot]andGitHub 844d492c8d Merge pull request #7153 from nextcloud/automated/noid/main-update-nextcloud-ocp
[main] Update nextcloud/ocp dependency
2025-08-03 04:40:26 +00:00
nextcloud-commandandGitHub 840e9f309f chore(dev-deps): Bump nextcloud/ocp package
Signed-off-by: GitHub <noreply@github.com>
2025-08-03 03:56:02 +00:00
Nextcloud bot fe53440ee4 fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-08-03 00:28:38 +00:00
Nextcloud bot 162dbf25dd fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-08-02 00:29:05 +00:00
Nextcloud bot 17de153ec8 fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-08-01 00:28:49 +00:00
grnd-altandGitHub 58027c8294 Merge pull request #7124 from stpronk/patch-1
[main] Add `$changes->getBefore()` to event in reorder
2025-07-31 10:40:35 +02:00
StPronk 79eba77b49 Updated all functionalities within cardService to include a before state where possible
Signed-off-by: StPronk <stpronk@gmail.com>
2025-07-31 08:14:00 +02:00
Nextcloud bot 0efdfab232 fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-07-30 00:28:59 +00:00
dependabot[bot]andGitHub ba9d2a4514 Chore(deps): Bump linkifyjs from 4.1.3 to 4.3.2
Bumps [linkifyjs](https://github.com/nfrasser/linkifyjs/tree/HEAD/packages/linkifyjs) from 4.1.3 to 4.3.2.
- [Release notes](https://github.com/nfrasser/linkifyjs/releases)
- [Changelog](https://github.com/nfrasser/linkifyjs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/nfrasser/linkifyjs/commits/v4.3.2/packages/linkifyjs)

---
updated-dependencies:
- dependency-name: linkifyjs
  dependency-version: 4.3.2
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-29 19:10:40 +00:00
grnd-altandGitHub 5744944957 Merge pull request #7139 from nextcloud/fix/6739
fix:  Use getId() method for card ID retrieval
2025-07-29 10:58:11 +02:00
grnd-alt 1c1e3e944e fix: make labels in dialog deletable
Signed-off-by: grnd-alt <github@belakkaf.net>
2025-07-29 10:39:39 +02:00
grnd-alt 85bb603103 fix: dont add labels without id
Signed-off-by: grnd-alt <github@belakkaf.net>
2025-07-29 10:37:19 +02:00
Nextcloud bot 90f10190ac fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-07-29 00:28:40 +00:00
Enjeck C. ab3b2aa23c fix: Use getId() method for card ID retrieval
Signed-off-by: Enjeck C. <patrathewhiz@gmail.com>
2025-07-28 09:03:43 +01:00
Nextcloud bot b9c0d454d5 fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-07-28 00:28:48 +00:00
github-actions[bot]andGitHub 438825530c Merge pull request #7134 from nextcloud/automated/noid/main-update-nextcloud-ocp
[main] Update nextcloud/ocp dependency
2025-07-27 04:09:50 +00:00
nextcloud-commandandGitHub c346c3cdf8 chore(dev-deps): Bump nextcloud/ocp package
Signed-off-by: GitHub <noreply@github.com>
2025-07-27 03:50:55 +00:00
Luka TrovicandGitHub f1da8b30a4 Merge pull request #7131 from nextcloud/fix-board-acl-check
fix: acl check when delete, update board acl
2025-07-25 17:51:00 +02:00
Luka Trovic 8229d40981 fix: acl check when delete, update board acl
Signed-off-by: Luka Trovic <luka@nextcloud.com>
2025-07-25 16:21:10 +02:00
Luka TrovicandGitHub 244d61c783 Merge pull request #7127 from nextcloud/fix/allow-foreign-label-deletion
fix:allow foreign label deletion
2025-07-25 12:26:54 +02:00
Nextcloud bot 879e59c003 fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-07-25 00:29:30 +00:00
grnd-alt 10dc1fe9c1 fix: make comments with mention editable
Signed-off-by: grnd-alt <github@belakkaf.net>
2025-07-24 15:45:15 +02:00
grnd-alt 4741817594 fix:allow foreign label deletion
Signed-off-by: grnd-alt <github@belakkaf.net>
2025-07-24 12:02:30 +02:00
grnd-altandGitHub af99211d6b Merge pull request #7114 from nextcloud/use-outline-icons
feat: use outline icons
2025-07-24 10:50:40 +02:00
Nextcloud bot b120ce868d fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-07-24 00:30:25 +00:00
Luka TrovicandGitHub f0ea3f3ce5 Merge pull request #7126 from nextcloud/update-workflows
chore: update workflows to skip stable29
2025-07-23 20:41:36 +02:00
dependabot[bot]andGitHub 2f0caac403 Merge pull request #7118 from nextcloud/dependabot/composer/tests/integration/behat/behat-approx-3.23.0 2025-07-23 15:53:24 +00:00
dependabot[bot]andLuka Trovic cc93386da6 Chore(deps-dev): Update behat/behat requirement in /tests/integration
Updates the requirements on [behat/behat](https://github.com/Behat/Behat) to permit the latest version.
- [Release notes](https://github.com/Behat/Behat/releases)
- [Changelog](https://github.com/Behat/Behat/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Behat/Behat/compare/v3.22.0...v3.23.0)

---
updated-dependencies:
- dependency-name: behat/behat
  dependency-version: 3.23.0
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-23 17:47:59 +02:00
Luka Trovic 5573a8bb1a chore: update workflows to skip stable29
Signed-off-by: Luka Trovic <luka@nextcloud.com>
2025-07-23 17:46:16 +02:00
Nextcloud bot c2e2e73b88 fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-07-23 00:33:41 +00:00
Nextcloud bot e509ac77d4 fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-07-22 00:29:05 +00:00
Luka TrovicandGitHub 909af7e1bb Merge pull request #7080 from Fledermaus-20/main
Add OCC commands for global calendar feature opt-in and opt-out in Deck
2025-07-21 13:58:09 +02:00
github-actions[bot]andGitHub 6828144815 Merge pull request #7120 from nextcloud/automated/noid/main-update-nextcloud-ocp
[main] Update nextcloud/ocp dependency
2025-07-20 04:29:44 +00:00
nextcloud-commandandGitHub 1d9382429e chore(dev-deps): Bump nextcloud/ocp package
Signed-off-by: GitHub <noreply@github.com>
2025-07-20 03:49:47 +00:00
Fledermaus-20andGitHub f68f7b54d6 Merge branch 'main' into main 2025-07-18 19:40:01 +02:00
Luka TrovicandGitHub dd4da2dd34 Merge pull request #7110 from nextcloud/dependabot/github_actions/svenstaro/upload-release-action-2.11.2
Chore(deps): Bump svenstaro/upload-release-action from 2.9.0 to 2.11.2
2025-07-16 09:30:11 +02:00
Luka TrovicandLuka Trovic 327bfff315 feat: use outline icons
Signed-off-by: Luka Trovic <luka@nextcloud.com>
2025-07-15 20:57:36 +02:00
dependabot[bot]andGitHub 0b6c492c75 Chore(deps): Bump svenstaro/upload-release-action from 2.9.0 to 2.11.2
Bumps [svenstaro/upload-release-action](https://github.com/svenstaro/upload-release-action) from 2.9.0 to 2.11.2.
- [Release notes](https://github.com/svenstaro/upload-release-action/releases)
- [Changelog](https://github.com/svenstaro/upload-release-action/blob/master/CHANGELOG.md)
- [Commits](https://github.com/svenstaro/upload-release-action/compare/04733e069f2d7f7f0b4aebc4fbdbce8613b03ccd...81c65b7cd4de9b2570615ce3aad67a41de5b1a13)

---
updated-dependencies:
- dependency-name: svenstaro/upload-release-action
  dependency-version: 2.11.2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-15 18:57:33 +00:00
Luka TrovicandGitHub bbe1b37dfe Merge pull request #7084 from nextcloud/dependabot/github_actions/svenstaro/upload-release-action-2.10.0
Chore(deps): Bump svenstaro/upload-release-action from 2.9.0 to 2.10.0
2025-07-15 20:56:20 +02:00
Luka TrovicandGitHub 3b5bf56049 Merge pull request #7053 from nextcloud/dependabot/composer/justinrainbow/json-schema-6.4.2
Chore(deps): Bump justinrainbow/json-schema from 6.4.1 to 6.4.2
2025-07-15 20:56:12 +02:00
dependabot[bot]andLuka Trovic 97ab42ad5c Chore(deps): Bump svenstaro/upload-release-action from 2.9.0 to 2.10.0
Bumps [svenstaro/upload-release-action](https://github.com/svenstaro/upload-release-action) from 2.9.0 to 2.10.0.
- [Release notes](https://github.com/svenstaro/upload-release-action/releases)
- [Changelog](https://github.com/svenstaro/upload-release-action/blob/master/CHANGELOG.md)
- [Commits](https://github.com/svenstaro/upload-release-action/compare/04733e069f2d7f7f0b4aebc4fbdbce8613b03ccd...ebd922b779f285dafcac6410a0710daee9c12b82)

---
updated-dependencies:
- dependency-name: svenstaro/upload-release-action
  dependency-version: 2.10.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-15 20:51:00 +02:00
dependabot[bot]andLuka Trovic 42d3e54841 Chore(deps): Bump justinrainbow/json-schema from 6.4.1 to 6.4.2
Bumps [justinrainbow/json-schema](https://github.com/jsonrainbow/json-schema) from 6.4.1 to 6.4.2.
- [Release notes](https://github.com/jsonrainbow/json-schema/releases)
- [Changelog](https://github.com/jsonrainbow/json-schema/blob/master/CHANGELOG.md)
- [Commits](https://github.com/jsonrainbow/json-schema/compare/6.4.1...6.4.2)

---
updated-dependencies:
- dependency-name: justinrainbow/json-schema
  dependency-version: 6.4.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-15 20:50:25 +02:00
dependabot[bot]andGitHub 8dd62dd8d9 Merge pull request #7047 from nextcloud/dependabot/npm_and_yarn/webpack-dev-server-5.2.2 2025-07-15 18:49:03 +00:00
dependabot[bot]andLuka Trovic 160900f2bb Chore(deps-dev): Bump webpack-dev-server from 5.1.0 to 5.2.2
Bumps [webpack-dev-server](https://github.com/webpack/webpack-dev-server) from 5.1.0 to 5.2.2.
- [Release notes](https://github.com/webpack/webpack-dev-server/releases)
- [Changelog](https://github.com/webpack/webpack-dev-server/blob/master/CHANGELOG.md)
- [Commits](https://github.com/webpack/webpack-dev-server/compare/v5.1.0...v5.2.2)

---
updated-dependencies:
- dependency-name: webpack-dev-server
  dependency-version: 5.2.2
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-15 20:42:21 +02:00
Luka TrovicandGitHub 020107d3f4 Merge pull request #7036 from nextcloud/dependabot/npm_and_yarn/main/nextcloud/vue-8.27.0
Chore(deps): Bump @nextcloud/vue from 8.26.0 to 8.27.0
2025-07-15 20:42:10 +02:00
dependabot[bot]andGitHub 68fb689df9 Merge pull request #6989 from nextcloud/dependabot/composer/tests/integration/behat/behat-approx-3.22.0 2025-07-15 18:38:53 +00:00
dependabot[bot]andLuka Trovic 97d8018cd1 Chore(deps): Bump @nextcloud/vue from 8.26.0 to 8.27.0
Bumps [@nextcloud/vue](https://github.com/nextcloud-libraries/nextcloud-vue) from 8.26.0 to 8.27.0.
- [Release notes](https://github.com/nextcloud-libraries/nextcloud-vue/releases)
- [Changelog](https://github.com/nextcloud-libraries/nextcloud-vue/blob/v8.27.0/CHANGELOG.md)
- [Commits](https://github.com/nextcloud-libraries/nextcloud-vue/compare/v8.26.0...v8.27.0)

---
updated-dependencies:
- dependency-name: "@nextcloud/vue"
  dependency-version: 8.27.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-15 20:36:31 +02:00
dependabot[bot]andLuka Trovic d666cba097 Chore(deps-dev): Update behat/behat requirement in /tests/integration
Updates the requirements on [behat/behat](https://github.com/Behat/Behat) to permit the latest version.
- [Release notes](https://github.com/Behat/Behat/releases)
- [Changelog](https://github.com/Behat/Behat/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Behat/Behat/compare/v3.21.1...v3.22.0)

---
updated-dependencies:
- dependency-name: behat/behat
  dependency-version: 3.22.0
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-15 20:33:37 +02:00
dependabot[bot]andGitHub a8c337eb07 Merge pull request #6984 from nextcloud/dependabot/npm_and_yarn/main/nextcloud/moment-1.3.4 2025-07-15 18:23:20 +00:00
dependabot[bot]andLuka Trovic a3a46012a4 Chore(deps): Bump @nextcloud/moment from 1.3.2 to 1.3.4
Bumps [@nextcloud/moment](https://github.com/nextcloud-libraries/nextcloud-moment) from 1.3.2 to 1.3.4.
- [Release notes](https://github.com/nextcloud-libraries/nextcloud-moment/releases)
- [Changelog](https://github.com/nextcloud-libraries/nextcloud-moment/blob/main/CHANGELOG.md)
- [Commits](https://github.com/nextcloud-libraries/nextcloud-moment/compare/v1.3.2...v1.3.4)

---
updated-dependencies:
- dependency-name: "@nextcloud/moment"
  dependency-version: 1.3.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-15 20:18:00 +02:00
dependabot[bot]andGitHub 1aa7105ceb Merge pull request #6983 from nextcloud/dependabot/npm_and_yarn/main/nextcloud/cypress-1.0.0-beta.15 2025-07-15 17:34:35 +00:00
dependabot[bot]andLuka Trovic a042a4b076 Chore(deps-dev): Bump @nextcloud/cypress
Bumps [@nextcloud/cypress](https://github.com/nextcloud/nextcloud-cypress) from 1.0.0-beta.13 to 1.0.0-beta.15.
- [Release notes](https://github.com/nextcloud/nextcloud-cypress/releases)
- [Commits](https://github.com/nextcloud/nextcloud-cypress/compare/v1.0.0-beta.13...v1.0.0-beta.15)

---
updated-dependencies:
- dependency-name: "@nextcloud/cypress"
  dependency-version: 1.0.0-beta.15
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-15 19:28:33 +02:00
dependabot[bot]andGitHub d0f992ecbb Merge pull request #6973 from nextcloud/dependabot/npm_and_yarn/main/babel/runtime-7.27.1 2025-07-15 17:25:51 +00:00
dependabot[bot]andLuka Trovic fc245759a3 Chore(deps): Bump @babel/runtime from 7.27.0 to 7.27.1
Bumps [@babel/runtime](https://github.com/babel/babel/tree/HEAD/packages/babel-runtime) from 7.27.0 to 7.27.1.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.27.1/packages/babel-runtime)

---
updated-dependencies:
- dependency-name: "@babel/runtime"
  dependency-version: 7.27.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-15 19:19:41 +02:00
Luka TrovicandGitHub 60bba3332b Merge pull request #7104 from nextcloud/fix-unstable-cypress
fix: unstable cypress
2025-07-15 16:47:08 +02:00
Luka TrovicandGitHub c93e07e0bd Merge pull request #7074 from nextcloud/automated/noid/main-update-nextcloud-ocp
[main] Update nextcloud/ocp dependency
2025-07-15 11:19:49 +02:00
Luka TrovicandLuka Trovic 9e6975b22b chore: update query count
Signed-off-by: Luka Trovic <luka@nextcloud.com>
2025-07-15 11:11:37 +02:00
nextcloud-commandandLuka Trovic 979f9b2c53 chore(dev-deps): Bump nextcloud/ocp package
Signed-off-by: GitHub <noreply@github.com>
2025-07-15 11:11:37 +02:00
Luka TrovicandGitHub 307bdf5e68 Merge pull request #7078 from nextcloud/dependabot/npm_and_yarn/pbkdf2-3.1.3
Chore(deps-dev): Bump pbkdf2 from 3.1.2 to 3.1.3
2025-07-15 10:52:09 +02:00
Luka TrovicandGitHub 4380533a66 Merge pull request #7107 from nextcloud/fix-new-stack-input-field
fix: styling for new stack input field
2025-07-14 15:13:32 +02:00
Nextcloud bot 3a1fbb90c3 fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-07-14 00:27:50 +00:00
Luka Trovic dd64e0c73d fix: styling for new stack input field
Signed-off-by: Luka Trovic <luka@nextcloud.com>
2025-07-11 18:59:52 +02:00
Fledermaus-20andGitHub 8a9751ac6b Merge branch 'main' into main 2025-07-11 12:54:25 +02:00
Luka Trovic c16f26d8d2 fix: unstable cypress
Signed-off-by: Luka Trovic <luka@nextcloud.com>
2025-07-10 16:53:03 +02:00
Fledermaus-20 c7edec8f6e updated composer.json
Signed-off-by: Fledermaus-20 <benno.traub@icloud.com>
2025-07-10 11:37:57 +02:00
Nextcloud bot a6de59fe9c fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-07-10 00:28:44 +00:00
Nextcloud bot 81e2c4768b fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-07-09 00:29:04 +00:00
Luka TrovicandGitHub 3c917f3b2d Merge pull request #7083 from nextcloud/feat/workflow-auto-update-reuse.yml
ci: update reuse.yml workflow from template
2025-07-08 10:51:19 +02:00
Nextcloud bot 6fd24ffa45 fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-07-08 00:29:03 +00:00
Fledermaus-20andGitHub 8c891c7f57 Merge branch 'main' into main
Signed-off-by: Fledermaus-20 <139645999+Fledermaus-20@users.noreply.github.com>
2025-07-06 12:55:53 +02:00
Fledermaus-20 b73c2becda Add toggle for calendar feature with OCC
Signed-off-by: Fledermaus-20 <benno.traub@icloud.com>
2025-07-06 12:52:58 +02:00
Nextcloud bot a9235e00c7 fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-07-06 00:28:13 +00:00
Nextcloud bot f8ad05b55b fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-07-05 00:28:23 +00:00
Nextcloud bot be497a741c fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-07-04 00:28:38 +00:00
Luka TrovicandGitHub 62a9ce3131 fix: add retry and show warning on description saving error (#7070)
Signed-off-by: Luka Trovic <luka@nextcloud.com>
2025-07-03 11:47:28 +02:00
grnd-altandGitHub 7799854df4 fix: ensure correct type when filtering events (#7071)
Signed-off-by: grnd-alt <github@belakkaf.net>
2025-07-03 10:45:42 +02:00
Nextcloud bot bf961c3de4 fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-07-03 00:28:51 +00:00
Nextcloud bot 5f73d97e4e fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-07-02 00:29:11 +00:00
Nextcloud bot 32f0852a23 fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-07-01 00:28:28 +00:00
Nextcloud bot d80c9b3eec fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-06-30 00:27:42 +00:00
Nextcloud bot 002bac60b1 fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-06-29 00:27:40 +00:00
Nextcloud bot 08756b6c5d fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-06-28 00:29:06 +00:00
Nextcloud bot 71bb838964 fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-06-28 00:28:03 +00:00
Nextcloud bot 6c55e3cd70 ci: update reuse.yml workflow from template
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-06-27 10:14:06 +00:00
Nextcloud bot 8391d843d5 fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-06-27 00:43:49 +00:00
Luka TrovicandGitHub 7cb9f8b966 Merge pull request #7066 from nextcloud/dependabot/github_actions/shivammathur/setup-php-2.34.1
Chore(deps): Bump shivammathur/setup-php from 2.33.0 to 2.34.1
2025-06-26 11:13:34 +02:00
Nextcloud bot 1dfbec4278 fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-06-26 00:42:57 +00:00
dependabot[bot]andAndy Scherzinger 26861c890d Chore(deps-dev): Bump pbkdf2 from 3.1.2 to 3.1.3
Bumps [pbkdf2](https://github.com/crypto-browserify/pbkdf2) from 3.1.2 to 3.1.3.
- [Changelog](https://github.com/browserify/pbkdf2/blob/master/CHANGELOG.md)
- [Commits](https://github.com/crypto-browserify/pbkdf2/compare/v3.1.2...v3.1.3)

---
updated-dependencies:
- dependency-name: pbkdf2
  dependency-version: 3.1.3
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-25 21:26:44 +02:00
Nextcloud bot 6841d4c068 fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-06-25 00:43:14 +00:00
Nextcloud bot 4e70286629 fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-06-24 00:43:11 +00:00
Nextcloud bot 54a5a1ecb2 fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-06-20 00:42:28 +00:00
Nextcloud bot 4139f8b94a fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-06-19 00:42:34 +00:00
Julius KnorrandGitHub 5c21cd2cf5 Merge pull request #7000 from nextcloud/automated/noid/main-update-nextcloud-ocp 2025-06-18 08:34:43 +02:00
Nextcloud bot af253270aa fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-06-17 00:40:26 +00:00
Nextcloud bot 6a64b4fc75 fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-06-16 00:40:38 +00:00
nextcloud-commandandGitHub 4b260a047c chore(dev-deps): Bump nextcloud/ocp package
Signed-off-by: GitHub <noreply@github.com>
2025-06-15 03:34:29 +00:00
dependabot[bot]andGitHub 8d41f9730f Chore(deps): Bump shivammathur/setup-php from 2.33.0 to 2.34.1
Bumps [shivammathur/setup-php](https://github.com/shivammathur/setup-php) from 2.33.0 to 2.34.1.
- [Release notes](https://github.com/shivammathur/setup-php/releases)
- [Commits](https://github.com/shivammathur/setup-php/compare/2.33.0...2.34.1)

---
updated-dependencies:
- dependency-name: shivammathur/setup-php
  dependency-version: 2.34.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-14 02:25:16 +00:00
Nextcloud bot 2adacaa39e fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-06-14 00:40:02 +00:00
Luka TrovicandGitHub 69fcd9bfdf Merge pull request #7030 from nextcloud/fix-cypress
fix: unstable cypress test
2025-06-12 18:14:49 +02:00
Luka Trovic c0b3775232 fix: unstable cypress test
Signed-off-by: Luka Trovic <luka@nextcloud.com>
2025-06-12 16:17:02 +02:00
Nextcloud bot df0e909be4 fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-06-11 00:44:33 +00:00
Julius KnorrandGitHub 96905c891c Merge pull request #7059 from nextcloud/fix-file-sharing-picker 2025-06-10 17:11:51 +02:00
Nextcloud bot 58c8dd5221 fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-06-10 00:40:10 +00:00
Luka Trovic e21c82b38e fix: not show Share with a Deck card for unauthorized users
Signed-off-by: Luka Trovic <luka@nextcloud.com>
2025-06-09 12:25:51 +02:00
Julius KnorrandGitHub 02afc218ff Merge pull request #7043 from nextcloud/dependabot/npm_and_yarn/tar-fs-2.1.3
Chore(deps-dev): Bump tar-fs from 2.1.2 to 2.1.3
2025-06-03 14:41:17 +02:00
dependabot[bot]andGitHub 50d199dd08 Chore(deps-dev): Bump tar-fs from 2.1.2 to 2.1.3
Bumps [tar-fs](https://github.com/mafintosh/tar-fs) from 2.1.2 to 2.1.3.
- [Commits](https://github.com/mafintosh/tar-fs/commits)

---
updated-dependencies:
- dependency-name: tar-fs
  dependency-version: 2.1.3
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-03 09:16:36 +00:00
Nextcloud bot ff74bfbf13 fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-06-01 00:39:59 +00:00
Nextcloud bot a64bec0ecd fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-05-31 00:40:55 +00:00
Luka TrovicandGitHub d66e6408b1 Merge pull request #7027 from nextcloud/purge-deleted-lists
fix: update DeleteCron to remove deleted lists
2025-05-28 17:58:59 +02:00
Luka Trovic 324eb05598 fix: update DeleteCron to remove deleted lists
Signed-off-by: Luka Trovic <luka@nextcloud.com>
2025-05-28 12:03:06 +02:00
Nextcloud bot 9938a6d57f fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-05-28 00:41:37 +00:00
Nextcloud bot c2e4165bf7 fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-05-26 00:41:08 +00:00
Nextcloud bot 7de0d6f1c4 fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-05-23 00:40:37 +00:00
Nextcloud bot 5631741b6b fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-05-22 00:41:20 +00:00
Luka TrovicandGitHub 724ad74c47 Merge pull request #7012 from nextcloud/update-dependabot
chore: update dependabot.yml
2025-05-21 20:22:13 +02:00
Nextcloud bot c8897477a7 fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-05-21 00:45:40 +00:00
Luka Trovic 7ac93d311c chore: update dependabot.yml
Signed-off-by: Luka Trovic <luka@nextcloud.com>
2025-05-19 15:48:12 +02:00
Nextcloud bot be8806102a fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-05-18 00:41:52 +00:00
Nextcloud bot f6a5877b85 fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-05-17 00:44:01 +00:00
Nextcloud bot 9360a49d50 fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-05-15 00:44:21 +00:00
Nextcloud bot 3aee848221 fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-05-14 00:41:37 +00:00
Nextcloud bot 2f53f8a1d6 fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-05-13 00:42:47 +00:00
Nextcloud bot d63764fa78 fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-05-12 00:41:52 +00:00
Nextcloud bot d06ec0ad98 fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-05-11 00:41:44 +00:00
Nextcloud bot 4bf086b55e fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-05-10 00:43:58 +00:00
dependabot[bot]andGitHub 99e2912d7c Merge pull request #6972 from nextcloud/dependabot/npm_and_yarn/main/relative-ci/agent-4.3.1 2025-05-08 14:45:00 +00:00
dependabot[bot]andLuka Trovic 10aed3660c Chore(deps-dev): Bump @relative-ci/agent from 4.2.14 to 4.3.1
Bumps [@relative-ci/agent](https://github.com/relative-ci/agent) from 4.2.14 to 4.3.1.
- [Release notes](https://github.com/relative-ci/agent/releases)
- [Commits](https://github.com/relative-ci/agent/compare/v4.2.14...v4.3.1)

---
updated-dependencies:
- dependency-name: "@relative-ci/agent"
  dependency-version: 4.3.1
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-08 16:39:31 +02:00
Nextcloud bot 6628c42eca fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-05-08 00:43:26 +00:00
Nextcloud bot dbe1ced6b0 fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-05-07 00:43:32 +00:00
Nextcloud bot 04b4da39c9 fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-05-06 00:43:38 +00:00
Nextcloud bot 530a695e6f fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-05-05 01:11:34 +00:00
github-actions[bot]andGitHub 02d7dd469e Merge pull request #6979 from nextcloud/automated/noid/main-update-nextcloud-ocp
[main] Update nextcloud/ocp dependency
2025-05-04 04:36:55 +00:00
nextcloud-commandandGitHub 71340b1217 chore(dev-deps): Bump nextcloud/ocp package
Signed-off-by: GitHub <noreply@github.com>
2025-05-04 03:20:39 +00:00
Nextcloud bot 153a9d2b7d fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-05-04 00:41:43 +00:00
Nextcloud bot e18f73a333 fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-05-03 00:42:55 +00:00
dependabot[bot]andGitHub 127f1b2d40 Merge pull request #6861 from nextcloud/dependabot/npm_and_yarn/main/babel/runtime-7.27.0 2025-05-02 16:21:14 +00:00
dependabot[bot]andLuka Trovic fe4c8df97e Chore(deps): Bump @babel/runtime from 7.26.10 to 7.27.0
Bumps [@babel/runtime](https://github.com/babel/babel/tree/HEAD/packages/babel-runtime) from 7.26.10 to 7.27.0.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.27.0/packages/babel-runtime)

---
updated-dependencies:
- dependency-name: "@babel/runtime"
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-02 18:16:03 +02:00
dependabot[bot]andGitHub d5582535d6 Merge pull request #6926 from nextcloud/dependabot/github_actions/shivammathur/setup-php-2.33.0 2025-05-02 15:59:19 +00:00
dependabot[bot]andLuka Trovic 7ec112ae1d Chore(deps): Bump shivammathur/setup-php from 2.32.0 to 2.33.0
Bumps [shivammathur/setup-php](https://github.com/shivammathur/setup-php) from 2.32.0 to 2.33.0.
- [Release notes](https://github.com/shivammathur/setup-php/releases)
- [Commits](https://github.com/shivammathur/setup-php/compare/2.32.0...2.33.0)

---
updated-dependencies:
- dependency-name: shivammathur/setup-php
  dependency-version: 2.33.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-02 17:54:08 +02:00
dependabot[bot]andGitHub aa747d7d62 Merge pull request #6938 from nextcloud/dependabot/composer/tests/integration/behat/behat-approx-3.21.1 2025-05-02 15:08:12 +00:00
dependabot[bot]andLuka Trovic d97f6c1a5f Chore(deps-dev): Update behat/behat requirement in /tests/integration
Updates the requirements on [behat/behat](https://github.com/Behat/Behat) to permit the latest version.
- [Release notes](https://github.com/Behat/Behat/releases)
- [Changelog](https://github.com/Behat/Behat/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Behat/Behat/compare/v3.18.1...v3.21.1)

---
updated-dependencies:
- dependency-name: behat/behat
  dependency-version: 3.21.1
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-02 17:00:07 +02:00
Nextcloud bot 8e0b585662 fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-05-02 00:43:00 +00:00
Nextcloud bot c0f7e16463 fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-05-01 00:42:07 +00:00
Luka TrovicandGitHub 71b4700a07 Merge pull request #6872 from nextcloud/board-import-export
feat: add board import and export
2025-04-30 22:41:09 +02:00
Luka TrovicandGitHub 688649f5a8 Merge pull request #6916 from nextcloud/perf/dont-enrich-calendar-entries
perf: don't enrich cards when finding calendar entries
2025-04-29 13:39:08 +02:00
Luka TrovicandLuka Trovic 03cdc47540 feat: add board import and export
Signed-off-by: Luka Trovic <luka@nextcloud.com>
2025-04-29 09:53:49 +02:00
Nextcloud bot 9f06a43d4b fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-04-29 00:42:23 +00:00
grnd-altandGitHub d31e31735d Merge pull request #6933 from nextcloud/fix/label-card-limit
fix: Ensure consistent label mappings
2025-04-28 10:43:20 +02:00
Julius KnorrandGitHub 4a05762c0a Merge pull request #6950 from nextcloud/automated/noid/main-fix-npm-audit
[main] Fix npm audit
2025-04-28 08:52:07 +02:00
Julius KnorrandGitHub a72f48f9ce Merge pull request #6898 from nextcloud/fix/log-spam
fix: Use strings as rich object ids
2025-04-28 08:51:15 +02:00
Nextcloud bot 7e6f23fa82 fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-04-28 00:42:35 +00:00
github-actions[bot]andGitHub a72f4e3aa3 Merge pull request #6947 from nextcloud/automated/noid/main-update-nextcloud-ocp
[main] Update nextcloud/ocp dependency
2025-04-27 04:29:11 +00:00
nextcloud-commandandGitHub ec8945bb54 fix(deps): Fix npm audit
Signed-off-by: GitHub <noreply@github.com>
2025-04-27 03:53:25 +00:00
nextcloud-commandandGitHub 760f771e11 chore(dev-deps): Bump nextcloud/ocp package
Signed-off-by: GitHub <noreply@github.com>
2025-04-27 03:15:52 +00:00
Nextcloud bot f0a443cbb6 fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-04-27 00:42:04 +00:00
Nextcloud bot 69b9c09a14 fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-04-25 00:41:58 +00:00
Nextcloud bot 7c93108172 fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-04-24 00:42:52 +00:00
grnd-altandGitHub 899d8a6531 fix: only delete assignments on unshared board (#6932)
Signed-off-by: grnd-alt <github@belakkaf.net>
2025-04-23 11:37:17 +02:00
Julius Knorrandgrnd-alt 60b34d190a tests: Fix unit test mocking around label checks
Signed-off-by: Julius Knorr <jus@bitgrid.net>
2025-04-23 09:48:02 +02:00
Julius Knorrandgrnd-alt 438a149304 chore: Add migration step for wrong label mapping
Signed-off-by: Julius Knorr <jus@bitgrid.net>
2025-04-23 09:47:37 +02:00
Nextcloud bot 9a2fcf6925 fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-04-23 00:42:06 +00:00
dependabot[bot]andGitHub e3c802c0f3 Merge pull request #6925 from nextcloud/dependabot/github_actions/actions/setup-node-4.4.0 2025-04-22 17:22:42 +00:00
Julius Knorr ea84743864 fix: Use strings as rich object ids
Signed-off-by: Julius Knorr <jus@bitgrid.net>
2025-04-22 17:01:56 +02:00
Julius KnorrandGitHub abd38e0c15 Merge pull request #6928 from nextcloud/dependabot/npm_and_yarn/main/nextcloud/vue-8.25.1
Chore(deps): Bump @nextcloud/vue from 8.24.0 to 8.25.1
2025-04-22 16:43:56 +02:00
Julius KnorrandGitHub 0ec1f7151c Merge pull request #6843 from nextcloud/automated/noid/main-update-nextcloud-ocp
[main] Update nextcloud/ocp dependency
2025-04-22 16:43:20 +02:00
Julius KnorrandGitHub a27cbf8bad Merge pull request #6773 from nextcloud/automated/noid/main-fix-npm-audit
[main] Fix npm audit
2025-04-22 16:42:51 +02:00
Luka TrovicandGitHub f4a1424e90 Merge pull request #6930 from nextcloud/dependabot/npm_and_yarn/main/eslint-webpack-plugin-5.0.1
Chore(deps-dev): Bump eslint-webpack-plugin from 4.2.0 to 5.0.1
2025-04-22 15:57:04 +02:00
Julius Härtl a92dc282a8 fix: Limit label actions to labels of the cards board
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2025-04-22 15:41:39 +02:00
nextcloud-commandandGitHub e3ffbf73c3 fix(deps): Fix npm audit
Signed-off-by: GitHub <noreply@github.com>
2025-04-20 03:32:19 +00:00
nextcloud-commandandGitHub c6e075af92 chore(dev-deps): Bump nextcloud/ocp package
Signed-off-by: GitHub <noreply@github.com>
2025-04-20 03:12:21 +00:00
dependabot[bot]andGitHub 144f293231 Chore(deps-dev): Bump eslint-webpack-plugin from 4.2.0 to 5.0.1
Bumps [eslint-webpack-plugin](https://github.com/webpack-contrib/eslint-webpack-plugin) from 4.2.0 to 5.0.1.
- [Release notes](https://github.com/webpack-contrib/eslint-webpack-plugin/releases)
- [Changelog](https://github.com/webpack-contrib/eslint-webpack-plugin/blob/master/CHANGELOG.md)
- [Commits](https://github.com/webpack-contrib/eslint-webpack-plugin/compare/v4.2.0...v5.0.1)

---
updated-dependencies:
- dependency-name: eslint-webpack-plugin
  dependency-version: 5.0.1
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-19 01:50:17 +00:00
dependabot[bot]andGitHub 6d78bc70f3 Chore(deps): Bump @nextcloud/vue from 8.24.0 to 8.25.1
Bumps [@nextcloud/vue](https://github.com/nextcloud-libraries/nextcloud-vue) from 8.24.0 to 8.25.1.
- [Release notes](https://github.com/nextcloud-libraries/nextcloud-vue/releases)
- [Changelog](https://github.com/nextcloud-libraries/nextcloud-vue/blob/v8.25.1/CHANGELOG.md)
- [Commits](https://github.com/nextcloud-libraries/nextcloud-vue/compare/v8.24.0...v8.25.1)

---
updated-dependencies:
- dependency-name: "@nextcloud/vue"
  dependency-version: 8.25.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-19 01:49:37 +00:00
dependabot[bot]andGitHub bf15865957 Chore(deps): Bump actions/setup-node from 4.3.0 to 4.4.0
Bumps [actions/setup-node](https://github.com/actions/setup-node) from 4.3.0 to 4.4.0.
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](https://github.com/actions/setup-node/compare/v4.3.0...v4.4.0)

---
updated-dependencies:
- dependency-name: actions/setup-node
  dependency-version: 4.4.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-19 01:35:47 +00:00
Nextcloud bot d2e5c0c515 fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-04-18 00:43:11 +00:00
Nextcloud bot 933f2515db fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-04-17 00:42:38 +00:00
dependabot[bot]andGitHub 17bf361767 Merge pull request #6917 from nextcloud/dependabot/npm_and_yarn/http-proxy-middleware-2.0.9 2025-04-16 18:31:25 +00:00
dependabot[bot]andGitHub 1df731396f Merge pull request #6874 from nextcloud/dependabot/npm_and_yarn/main/nextcloud/vue-8.24.0 2025-04-16 18:27:55 +00:00
dependabot[bot]andGitHub 968ea30fd4 Chore(deps): Bump @nextcloud/vue from 8.22.0 to 8.24.0
Bumps [@nextcloud/vue](https://github.com/nextcloud-libraries/nextcloud-vue) from 8.22.0 to 8.24.0.
- [Release notes](https://github.com/nextcloud-libraries/nextcloud-vue/releases)
- [Changelog](https://github.com/nextcloud-libraries/nextcloud-vue/blob/main/CHANGELOG.md)
- [Commits](https://github.com/nextcloud-libraries/nextcloud-vue/compare/v8.22.0...v8.24.0)

---
updated-dependencies:
- dependency-name: "@nextcloud/vue"
  dependency-version: 8.24.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-16 18:22:48 +00:00
dependabot[bot]andGitHub 8f75c7f92d Chore(deps-dev): Bump http-proxy-middleware from 2.0.7 to 2.0.9
Bumps [http-proxy-middleware](https://github.com/chimurai/http-proxy-middleware) from 2.0.7 to 2.0.9.
- [Release notes](https://github.com/chimurai/http-proxy-middleware/releases)
- [Changelog](https://github.com/chimurai/http-proxy-middleware/blob/v2.0.9/CHANGELOG.md)
- [Commits](https://github.com/chimurai/http-proxy-middleware/compare/v2.0.7...v2.0.9)

---
updated-dependencies:
- dependency-name: http-proxy-middleware
  dependency-version: 2.0.9
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-16 18:22:26 +00:00
Luka TrovicandGitHub 234be011cc Merge pull request #6879 from nextcloud/dependabot/npm_and_yarn/multi-b29f0fd473
Chore(deps): Bump tar-fs and dockerode
2025-04-16 20:21:06 +02:00
Richard Steinmetz fa2f5bcab8 perf: don't enrich cards when finding calendar entries
Signed-off-by: Richard Steinmetz <richard@steinmetz.cloud>
2025-04-16 13:49:58 +02:00
Nextcloud bot 685014ec48 fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-04-16 00:43:52 +00:00
Luka TrovicandGitHub 1cd6c55d30 Merge pull request #6902 from nextcloud/update-card-move-dialog
Clear selected stack when selected board changed
2025-04-15 18:33:07 +02:00
Luka Trovic 6dbcf5d17b fix: clear selected stack when selected board changed
Signed-off-by: Luka Trovic <luka@nextcloud.com>
2025-04-14 20:28:52 +02:00
dependabot[bot]andGitHub f84d7c372b Merge pull request #6885 from nextcloud/dependabot/composer/justinrainbow/json-schema-6.4.1 2025-04-14 14:50:36 +00:00
dependabot[bot]andGitHub 8be8647f6d Merge pull request #6910 from nextcloud/dependabot/github_actions/nextcloud-libraries/npm-audit-action-0.2.0 2025-04-12 02:12:00 +00:00
dependabot[bot]andGitHub eb878055ae Merge pull request #6908 from nextcloud/dependabot/npm_and_yarn/main/nextcloud/webpack-vue-config-6.3.0 2025-04-12 02:04:52 +00:00
dependabot[bot]andGitHub 00b334d3d9 Chore(deps): Bump nextcloud-libraries/npm-audit-action
Bumps [nextcloud-libraries/npm-audit-action](https://github.com/nextcloud-libraries/npm-audit-action) from 0.1.0 to 0.2.0.
- [Release notes](https://github.com/nextcloud-libraries/npm-audit-action/releases)
- [Commits](https://github.com/nextcloud-libraries/npm-audit-action/compare/2a60bd2e79cc77f2cc4d9a3fe40f1a69896f3a87...1b1728b2b4a7a78d69de65608efcf4db0e3e42d0)

---
updated-dependencies:
- dependency-name: nextcloud-libraries/npm-audit-action
  dependency-version: 0.2.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-12 01:54:09 +00:00
dependabot[bot]andGitHub 5905154fe0 Chore(deps-dev): Bump @nextcloud/webpack-vue-config from 6.2.0 to 6.3.0
Bumps [@nextcloud/webpack-vue-config](https://github.com/nextcloud-libraries/webpack-vue-config) from 6.2.0 to 6.3.0.
- [Release notes](https://github.com/nextcloud-libraries/webpack-vue-config/releases)
- [Changelog](https://github.com/nextcloud-libraries/webpack-vue-config/blob/main/CHANGELOG.md)
- [Commits](https://github.com/nextcloud-libraries/webpack-vue-config/compare/v6.2.0...v6.3.0)

---
updated-dependencies:
- dependency-name: "@nextcloud/webpack-vue-config"
  dependency-version: 6.3.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-12 01:39:42 +00:00
Nextcloud bot 8280f0796f Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-04-12 00:44:01 +00:00
Nextcloud bot 18ea2f9a7e Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-04-11 00:43:38 +00:00
Nextcloud bot 3cbddae6bc Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-04-10 00:42:51 +00:00
Julius KnorrandGitHub aac11e96b5 Merge pull request #6893 from nextcloud/perf/deck-share-deleted-cards 2025-04-08 22:59:39 +02:00
Julius Knorr 2e62a9c82a perf: Skip doing a query just to check if a board is deleted
Signed-off-by: Julius Knorr <jus@bitgrid.net>
2025-04-08 22:13:11 +02:00
Nextcloud bot ec18ec758b Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-04-06 00:44:05 +00:00
dependabot[bot]andGitHub 8f3e2ae1d9 Chore(deps): Bump justinrainbow/json-schema from 6.0.0 to 6.4.1
Bumps [justinrainbow/json-schema](https://github.com/jsonrainbow/json-schema) from 6.0.0 to 6.4.1.
- [Release notes](https://github.com/jsonrainbow/json-schema/releases)
- [Changelog](https://github.com/jsonrainbow/json-schema/blob/master/CHANGELOG.md)
- [Commits](https://github.com/jsonrainbow/json-schema/compare/6.0.0...6.4.1)

---
updated-dependencies:
- dependency-name: justinrainbow/json-schema
  dependency-version: 6.4.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-05 01:46:07 +00:00
dependabot[bot]andGitHub 7f00601007 Chore(deps): Bump tar-fs and dockerode
Bumps [tar-fs](https://github.com/mafintosh/tar-fs) and [dockerode](https://github.com/apocas/dockerode). These dependencies needed to be updated together.

Updates `tar-fs` from 2.0.1 to 2.1.2
- [Commits](https://github.com/mafintosh/tar-fs/compare/v2.0.1...v2.1.2)

Updates `dockerode` from 4.0.2 to 4.0.5
- [Release notes](https://github.com/apocas/dockerode/releases)
- [Commits](https://github.com/apocas/dockerode/compare/v4.0.2...v4.0.5)

---
updated-dependencies:
- dependency-name: tar-fs
  dependency-version: 2.1.2
  dependency-type: indirect
- dependency-name: dockerode
  dependency-version: 4.0.5
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-05 01:17:26 +00:00
dependabot[bot]andGitHub 175c10c146 Merge pull request #6873 from nextcloud/dependabot/npm_and_yarn/main/dompurify-3.2.5 2025-04-05 01:16:25 +00:00
dependabot[bot]andGitHub 8dc0933c47 Chore(deps): Bump dompurify from 3.2.4 to 3.2.5
Bumps [dompurify](https://github.com/cure53/DOMPurify) from 3.2.4 to 3.2.5.
- [Release notes](https://github.com/cure53/DOMPurify/releases)
- [Commits](https://github.com/cure53/DOMPurify/compare/3.2.4...3.2.5)

---
updated-dependencies:
- dependency-name: dompurify
  dependency-version: 3.2.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-05 01:11:29 +00:00
Nextcloud bot 8c52b9bad9 Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-04-04 00:42:52 +00:00
Nextcloud bot a1c9396946 Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-04-02 00:43:19 +00:00
Luka TrovicandGitHub b0fbb386b8 Merge pull request #6800 from gidan80/main
CSV export fixes
2025-03-31 13:56:15 +02:00
Ronni Dal Lago e77a2fcd79 Merge branch 'main' of https://github.com/gidan80/deck 2025-03-31 12:19:51 +02:00
Ronni Dal Lago 89152325be Increased query count
Signed-off-by: Ronni Dal Lago <blackjacksuper@hotmail.it>
2025-03-31 12:19:36 +02:00
Ronni Dal LagoandLuka Trovic 03cb9e2bc5 CSV export fixes
Signed-off-by: Ronni Dal Lago <blackjacksuper@hotmail.it>
2025-03-31 09:53:36 +02:00
Nextcloud bot 3c6f8ec419 Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-03-30 00:44:10 +00:00
dependabot[bot]andGitHub 3cfb1f6bfe Merge pull request #6860 from nextcloud/dependabot/github_actions/actions/setup-node-4.3.0 2025-03-29 02:15:46 +00:00
dependabot[bot]andGitHub 96ad191ce2 Chore(deps): Bump actions/setup-node from 4.1.0 to 4.3.0
Bumps [actions/setup-node](https://github.com/actions/setup-node) from 4.1.0 to 4.3.0.
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](https://github.com/actions/setup-node/compare/v4.1.0...v4.3.0)

---
updated-dependencies:
- dependency-name: actions/setup-node
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-29 02:06:40 +00:00
Nextcloud bot 52d4039dc8 Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-03-29 00:43:37 +00:00
Nextcloud bot 73b3517840 Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-03-28 00:44:51 +00:00
Nextcloud bot 3d78802446 Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-03-27 00:44:45 +00:00
Julius KnorrandGitHub 8a99e6d539 Merge pull request #6853 from nextcloud/ci/update-workflows
chore: update workflows from templates
2025-03-25 15:17:08 +01:00
grnd-alt 0e08ad16e1 chore: set minimum phpVersion for psalm
Signed-off-by: grnd-alt <github@belakkaf.net>
2025-03-25 14:05:17 +01:00
Nextcloud bot d04ab25315 Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-03-25 00:43:41 +00:00
grnd-alt 19d35c65a0 chore: update workflows from templates
Signed-off-by: grnd-alt <github@belakkaf.net>
2025-03-24 15:41:10 +01:00
Nextcloud bot 1783914d3a Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-03-24 00:43:02 +00:00
Nextcloud bot c837dd4db6 Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-03-23 00:43:32 +00:00
dependabot[bot]andGitHub db22052729 Merge pull request #6850 from nextcloud/dependabot/github_actions/actions/setup-node-4.3.0 2025-03-22 02:22:09 +00:00
dependabot[bot]andGitHub 116babcaaf Chore(deps): Bump actions/setup-node from 4.2.0 to 4.3.0
Bumps [actions/setup-node](https://github.com/actions/setup-node) from 4.2.0 to 4.3.0.
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](https://github.com/actions/setup-node/compare/v4.2.0...v4.3.0)

---
updated-dependencies:
- dependency-name: actions/setup-node
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-22 02:16:47 +00:00
Ronni Dal Lago 94da732bfb CSV export fixes
Signed-off-by: Ronni Dal Lago <blackjacksuper@hotmail.it>
2025-03-21 18:20:37 +01:00
Nextcloud bot 86d42b7060 Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-03-20 00:45:34 +00:00
Nextcloud bot 122387a195 Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-03-18 00:45:10 +00:00
Nextcloud bot 028f26a969 Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-03-16 00:43:43 +00:00
dependabot[bot]andGitHub 13c13b8dde Merge pull request #6831 from nextcloud/dependabot/npm_and_yarn/main/babel/runtime-7.26.10 2025-03-15 02:14:25 +00:00
dependabot[bot]andGitHub 5df4a49ff5 Chore(deps): Bump @babel/runtime from 7.26.7 to 7.26.10
Bumps [@babel/runtime](https://github.com/babel/babel/tree/HEAD/packages/babel-runtime) from 7.26.7 to 7.26.10.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.26.10/packages/babel-runtime)

---
updated-dependencies:
- dependency-name: "@babel/runtime"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-15 02:05:59 +00:00
Nextcloud bot 97902d2f6a Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-03-15 00:45:23 +00:00
Nextcloud bot db6221d1c9 Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-03-12 00:44:24 +00:00
github-actions[bot]andGitHub 0223dd9a3a Merge pull request #6825 from nextcloud/automated/noid/main-update-nextcloud-ocp
[main] Update nextcloud/ocp dependency
2025-03-09 03:34:50 +00:00
nextcloud-commandandGitHub 974a4c979a chore(dev-deps): Bump nextcloud/ocp package
Signed-off-by: GitHub <noreply@github.com>
2025-03-09 02:36:44 +00:00
dependabot[bot]andGitHub 51ba51fe33 Merge pull request #6822 from nextcloud/dependabot/npm_and_yarn/axios-1.8.2 2025-03-08 04:06:48 +00:00
dependabot[bot]andGitHub 4d5c69f250 Chore(deps): Bump axios from 1.7.7 to 1.8.2
Bumps [axios](https://github.com/axios/axios) from 1.7.7 to 1.8.2.
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/v1.7.7...v1.8.2)

---
updated-dependencies:
- dependency-name: axios
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-08 04:01:11 +00:00
dependabot[bot]andGitHub 9bddab4928 Merge pull request #6821 from nextcloud/dependabot/github_actions/peter-evans/create-pull-request-7.0.8 2025-03-08 03:59:58 +00:00
dependabot[bot]andGitHub 6dcb1d4b8b bump peter-evans/create-pull-request from 7.0.7 to 7.0.8
---
updated-dependencies:
- dependency-name: peter-evans/create-pull-request
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-08 03:54:25 +00:00
Nextcloud bot 9ed384fa20 Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-03-03 00:42:20 +00:00
github-actions[bot]andGitHub 7cdc79a9ef Merge pull request #6812 from nextcloud/automated/noid/main-update-nextcloud-ocp
[main] Update nextcloud/ocp dependency
2025-03-02 03:46:38 +00:00
nextcloud-commandandGitHub 852770c574 chore(dev-deps): Bump nextcloud/ocp package
Signed-off-by: GitHub <noreply@github.com>
2025-03-02 03:01:01 +00:00
dependabot[bot]andGitHub 74afeb85d0 Merge pull request #6811 from nextcloud/dependabot/github_actions/peter-evans/create-pull-request-7.0.7 2025-03-01 03:44:08 +00:00
dependabot[bot]andGitHub 12a494720b chore(deps): bump peter-evans/create-pull-request from 7.0.6 to 7.0.7
Bumps [peter-evans/create-pull-request](https://github.com/peter-evans/create-pull-request) from 7.0.6 to 7.0.7.
- [Release notes](https://github.com/peter-evans/create-pull-request/releases)
- [Commits](https://github.com/peter-evans/create-pull-request/compare/67ccf781d68cd99b580ae25a5c18a1cc84ffff1f...dd2324fc52d5d43c699a5636bcf19fceaa70c284)

---
updated-dependencies:
- dependency-name: peter-evans/create-pull-request
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-01 03:02:53 +00:00
dependabot[bot]andGitHub b3261e0b56 Merge pull request #6805 from nextcloud/dependabot/npm_and_yarn/main/nextcloud/event-bus-3.3.2 2025-03-01 02:22:07 +00:00
dependabot[bot]andGitHub 88dbb010d0 chore(deps): bump @nextcloud/event-bus from 3.3.1 to 3.3.2
Bumps [@nextcloud/event-bus](https://github.com/nextcloud/nextcloud-event-bus) from 3.3.1 to 3.3.2.
- [Release notes](https://github.com/nextcloud/nextcloud-event-bus/releases)
- [Changelog](https://github.com/nextcloud-libraries/nextcloud-event-bus/blob/main/CHANGELOG.md)
- [Commits](https://github.com/nextcloud/nextcloud-event-bus/compare/v3.3.1...v3.3.2)

---
updated-dependencies:
- dependency-name: "@nextcloud/event-bus"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-01 02:05:52 +00:00
Nextcloud bot d8adaf5fe6 Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-03-01 01:05:09 +00:00
Nextcloud bot 2a36b45072 Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-02-27 00:44:24 +00:00
Nextcloud bot 4e6474fa99 Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-02-25 00:45:11 +00:00
github-actions[bot]andGitHub dbe7536339 Merge pull request #6770 from nextcloud/automated/noid/main-update-nextcloud-ocp
[main] Update nextcloud/ocp dependency
2025-02-24 14:51:38 +00:00
nextcloud-commandandLuka Trovic b5e08110ab chore(dev-deps): Bump nextcloud/ocp package
Signed-off-by: GitHub <noreply@github.com>
2025-02-24 15:46:05 +01:00
dependabot[bot]andGitHub a7f2558aab Merge pull request #6787 from nextcloud/dependabot/npm_and_yarn/main/nextcloud/eslint-config-8.4.2 2025-02-24 14:37:30 +00:00
dependabot[bot]andLuka Trovic 3c3e6fa7d2 chore(deps-dev): bump @nextcloud/eslint-config from 8.4.1 to 8.4.2
Bumps [@nextcloud/eslint-config](https://github.com/nextcloud-libraries/eslint-config) from 8.4.1 to 8.4.2.
- [Release notes](https://github.com/nextcloud-libraries/eslint-config/releases)
- [Changelog](https://github.com/nextcloud-libraries/eslint-config/blob/v8.4.2/CHANGELOG.md)
- [Commits](https://github.com/nextcloud-libraries/eslint-config/compare/v8.4.1...v8.4.2)

---
updated-dependencies:
- dependency-name: "@nextcloud/eslint-config"
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-24 15:26:51 +01:00
Nextcloud bot 6a2b092ad6 Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-02-24 00:41:53 +00:00
Nextcloud bot 5f2c300b21 Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-02-22 00:41:52 +00:00
Luka TrovicandGitHub 439e44128a Merge pull request #6778 from nextcloud/fix-cypress-main
fix: cypress issue
2025-02-21 10:58:05 +01:00
Nextcloud bot 55056e27f1 Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-02-21 00:41:20 +00:00
Luka Trovic 22c36a0d77 fix: cypress issue
Signed-off-by: Luka Trovic <luka@nextcloud.com>
2025-02-20 16:24:45 +01:00
Nextcloud bot 1ebaa0c41f Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-02-20 00:43:26 +00:00
Luka TrovicandGitHub a2dbb94179 Merge pull request #6724 from nextcloud/feat/workflow-auto-update-pr-feedback.yml
chore(CI): Updating pr-feedback.yml workflow from template
2025-02-19 13:43:07 +01:00
Nextcloud bot 6f6f54965c Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-02-19 00:42:11 +00:00
Nextcloud bot 5a833ec367 Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-02-18 00:42:04 +00:00
Nextcloud bot f1161be49f Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-02-17 00:41:29 +00:00
Nextcloud bot c0731ce142 Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-02-16 01:06:39 +00:00
Nextcloud bot 4534ad5a92 Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-02-15 00:41:52 +00:00
Luka TrovicandGitHub 6788210c27 Merge pull request #6740 from nextcloud/update-default-content
feat: update default content
2025-02-14 13:32:26 +01:00
Luka TrovicandGitHub 1051dea281 Merge pull request #6745 from nextcloud/dependabot/npm_and_yarn/elliptic-6.6.1
chore(deps-dev): bump elliptic from 6.6.0 to 6.6.1
2025-02-14 08:42:28 +01:00
Nextcloud bot eb9fce2e5f Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-02-14 00:42:23 +00:00
dependabot[bot]andGitHub 955a16f1c5 chore(deps-dev): bump elliptic from 6.6.0 to 6.6.1
Bumps [elliptic](https://github.com/indutny/elliptic) from 6.6.0 to 6.6.1.
- [Commits](https://github.com/indutny/elliptic/compare/v6.6.0...v6.6.1)

---
updated-dependencies:
- dependency-name: elliptic
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-13 17:43:42 +00:00
Luka TrovicandGitHub f38773473b Merge pull request #6736 from nextcloud/automated/noid/main-update-nextcloud-ocp
[main] Update nextcloud/ocp dependency
2025-02-13 18:41:01 +01:00
Luka Trovic b15254f1ed feat: update default content
Signed-off-by: Luka Trovic <luka@nextcloud.com>
2025-02-13 14:18:47 +01:00
Nextcloud bot 6dd623f796 Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-02-13 00:42:08 +00:00
Julius KnorrandGitHub 41e97d7281 Merge pull request #6649 from nextcloud/fix/redirect-no-rewrite
fix: Adapt URLs generated in the backend to new routes
2025-02-12 22:32:04 +01:00
Julius Knorr 1b2a352df1 ci: Update query count
Signed-off-by: Julius Knorr <jus@bitgrid.net>
2025-02-12 21:56:34 +01:00
Julius Knorr 7fcad2425d fix: Use dynamic base URL for vue router to make routing work in all cases
Signed-off-by: Julius Knorr <jus@bitgrid.net>
2025-02-12 21:56:06 +01:00
Julius Knorr 5fb43086b6 tests: Fix url generation mocks and cleanup some phpunit code
Signed-off-by: Julius Knorr <jus@bitgrid.net>
2025-02-12 21:56:06 +01:00
Julius Knorr 7b4586a43a fix: Adapt URLs generated in the backend to new routes
Signed-off-by: Julius Knorr <jus@bitgrid.net>
2025-02-12 21:56:06 +01:00
Nextcloud bot aaa76f7bd0 Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-02-12 00:43:09 +00:00
Nextcloud bot 8260296d64 Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-02-11 00:41:47 +00:00
nextcloud-commandandGitHub b02bc530f1 chore(dev-deps): Bump nextcloud/ocp package
Signed-off-by: GitHub <noreply@github.com>
2025-02-09 02:54:36 +00:00
Nextcloud bot 1b4e859431 Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-02-09 00:41:43 +00:00
dependabot[bot]andGitHub acc61ad584 Merge pull request #6729 from nextcloud/dependabot/npm_and_yarn/main/nextcloud/cypress-1.0.0-beta.13 2025-02-08 02:26:34 +00:00
dependabot[bot]andGitHub cb1d5c49e0 chore(deps-dev): bump @nextcloud/cypress
Bumps [@nextcloud/cypress](https://github.com/nextcloud/nextcloud-cypress) from 1.0.0-beta.12 to 1.0.0-beta.13.
- [Release notes](https://github.com/nextcloud/nextcloud-cypress/releases)
- [Commits](https://github.com/nextcloud/nextcloud-cypress/compare/v1.0.0-beta.12...v1.0.0-beta.13)

---
updated-dependencies:
- dependency-name: "@nextcloud/cypress"
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-08 02:10:29 +00:00
Luka TrovicandGitHub 0c7b1c4731 Merge pull request #6707 from nextcloud/feat/workflow-auto-update-npm-audit-fix.yml
chore(CI): Updating npm-audit-fix.yml workflow from template
2025-02-07 16:36:10 +01:00
Nextcloud botandLuka Trovic 629ef85d64 chore(CI): Updating npm-audit-fix.yml workflow from template
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-02-07 16:22:02 +01:00
Luka TrovicandGitHub 914f1d76ac Merge pull request #6720 from nextcloud/automated/noid/main-fix-npm-audit
[main] Fix npm audit
2025-02-07 10:18:15 +01:00
Nextcloud bot 0923070c45 Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-02-07 00:41:26 +00:00
Nextcloud bot bbf51952e0 Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-02-05 00:43:20 +00:00
Nextcloud bot 1d1c2df8f3 chore(CI): Updating pr-feedback.yml workflow from template
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-02-04 18:42:46 +00:00
Elizabeth DanzbergerandGitHub 0780b10170 Merge pull request #6692 from nextcloud/fix/6446
fix: skip exporting a deleted card
2025-02-04 13:27:54 -05:00
Luka TrovicandGitHub fd6ef9b6eb Merge pull request #6712 from nextcloud/dependabot/npm_and_yarn/main/dompurify-3.2.4
chore(deps): bump dompurify from 3.2.3 to 3.2.4
2025-02-04 13:09:57 +01:00
Luka TrovicandGitHub da4a54900c Merge pull request #6711 from nextcloud/dependabot/github_actions/actions/setup-node-4.2.0
chore(deps): bump actions/setup-node from 4.1.0 to 4.2.0
2025-02-04 13:09:35 +01:00
Luka TrovicandGitHub 53bd32d6b6 Merge pull request #6695 from nextcloud/dependabot/npm_and_yarn/main/babel/runtime-7.26.7
chore(deps): bump @babel/runtime from 7.26.0 to 7.26.7
2025-02-04 13:05:56 +01:00
Elizabeth Danzbergerandgrnd-alt f7fa419105 fix: skip exporting a deleted card
Signed-off-by: Elizabeth Danzberger <lizzy7128@tutanota.de>
2025-02-04 09:49:39 +01:00
dependabot[bot]andGitHub 023ab01e93 Merge pull request #6668 from nextcloud/dependabot/composer/tests/integration/behat/behat-approx-3.18.1 2025-02-04 07:24:15 +00:00
dependabot[bot]andLuka Trovic 615d5ef871 chore(deps-dev): update behat/behat requirement in /tests/integration
Updates the requirements on [behat/behat](https://github.com/Behat/Behat) to permit the latest version.
- [Release notes](https://github.com/Behat/Behat/releases)
- [Changelog](https://github.com/Behat/Behat/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Behat/Behat/compare/v3.15.0...v3.18.1)

---
updated-dependencies:
- dependency-name: behat/behat
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-04 08:18:49 +01:00
Luka TrovicandGitHub a52b7b9742 Merge pull request #6665 from nextcloud/dependabot/github_actions/shivammathur/setup-php-2.32.0
chore(deps): bump shivammathur/setup-php from 2.31.1 to 2.32.0
2025-02-04 08:18:00 +01:00
Luka TrovicandGitHub dcbc6bc604 Merge pull request #6666 from nextcloud/dependabot/github_actions/peter-evans/create-pull-request-7.0.6
chore(deps): bump peter-evans/create-pull-request from 7.0.5 to 7.0.6
2025-02-04 08:12:07 +01:00
Nextcloud bot ce13d89e07 Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-02-03 01:02:33 +00:00
nextcloud-commandandGitHub 7114b10871 fix(deps): Fix npm audit
Signed-off-by: GitHub <noreply@github.com>
2025-02-02 03:25:51 +00:00
Nextcloud bot 100404d25f Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-02-02 00:42:30 +00:00
dependabot[bot]andLuka Trovic 60bfc192d3 chore(deps): bump actions/setup-node from 4.1.0 to 4.2.0
Bumps [actions/setup-node](https://github.com/actions/setup-node) from 4.1.0 to 4.2.0.
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](https://github.com/actions/setup-node/compare/v4.1.0...v4.2.0)

---
updated-dependencies:
- dependency-name: actions/setup-node
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-01 19:38:35 +01:00
dependabot[bot]andLuka Trovic fd090c7749 chore(deps): bump peter-evans/create-pull-request from 7.0.5 to 7.0.6
Bumps [peter-evans/create-pull-request](https://github.com/peter-evans/create-pull-request) from 7.0.5 to 7.0.6.
- [Release notes](https://github.com/peter-evans/create-pull-request/releases)
- [Commits](https://github.com/peter-evans/create-pull-request/compare/5e914681df9dc83aa4e4905692ca88beb2f9e91f...67ccf781d68cd99b580ae25a5c18a1cc84ffff1f)

---
updated-dependencies:
- dependency-name: peter-evans/create-pull-request
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-01 19:14:56 +01:00
dependabot[bot]andLuka Trovic d735308bd3 chore(deps): bump shivammathur/setup-php from 2.31.1 to 2.32.0
Bumps [shivammathur/setup-php](https://github.com/shivammathur/setup-php) from 2.31.1 to 2.32.0.
- [Release notes](https://github.com/shivammathur/setup-php/releases)
- [Commits](https://github.com/shivammathur/setup-php/compare/2.31.1...2.32.0)

---
updated-dependencies:
- dependency-name: shivammathur/setup-php
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-01 19:13:02 +01:00
github-actions[bot]andGitHub 4ec068ca62 Merge pull request #6670 from nextcloud/automated/noid/main-update-nextcloud-ocp
[main] Update nextcloud/ocp dependency
2025-02-01 16:53:40 +00:00
nextcloud-commandandLuka Trovic f5e3e5e0f8 chore(dev-deps): Bump nextcloud/ocp package
Signed-off-by: GitHub <noreply@github.com>
2025-02-01 17:48:09 +01:00
dependabot[bot]andGitHub 94aee3e07a chore(deps): bump dompurify from 3.2.3 to 3.2.4
Bumps [dompurify](https://github.com/cure53/DOMPurify) from 3.2.3 to 3.2.4.
- [Release notes](https://github.com/cure53/DOMPurify/releases)
- [Commits](https://github.com/cure53/DOMPurify/compare/3.2.3...3.2.4)

---
updated-dependencies:
- dependency-name: dompurify
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-01 02:33:07 +00:00
Nextcloud bot b1f7c623a1 Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-01-28 00:41:14 +00:00
Julius KnorrandGitHub afa95d3c50 Merge pull request #6671 from nextcloud/fix/attachment-extension
fix: Properly show attachment extension
2025-01-27 08:43:31 +01:00
Nextcloud bot ba8e77abfe Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-01-27 00:40:20 +00:00
dependabot[bot]andGitHub 8c09047203 chore(deps): bump @babel/runtime from 7.26.0 to 7.26.7
Bumps [@babel/runtime](https://github.com/babel/babel/tree/HEAD/packages/babel-runtime) from 7.26.0 to 7.26.7.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.26.7/packages/babel-runtime)

---
updated-dependencies:
- dependency-name: "@babel/runtime"
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-25 02:29:31 +00:00
Nextcloud bot baa85e8947 Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-01-25 01:01:22 +00:00
Nextcloud bot f1e5acc52e Fix(l10n): Update translations from Transifex
Signed-off-by: Nextcloud bot <bot@nextcloud.com>
2025-01-24 00:41:33 +00:00
Elizabeth DanzbergerandGitHub d76b7a4667 Merge pull request #6682 from nextcloud/update-main-version
feat(deps): Add Nextcloud 32 support on main
2025-01-23 14:08:15 -05:00
Joas Schilling df32a3ee34 feat(deps): Add Nextcloud 32 support on main
Signed-off-by: Joas Schilling <coding@schilljs.com>
2025-01-23 10:57:25 +01:00
Julius Knorr 2fa74662db ci: Test for separate extension
Signed-off-by: Julius Knorr <jus@bitgrid.net>
2025-01-23 09:28:09 +01:00
Julius Härtl d5fd80d116 fix: Properly show attachment extension
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2025-01-23 09:28:09 +01:00
161 changed files with 6076 additions and 4315 deletions
@@ -2,10 +2,11 @@ name: Package build
on: on:
push: push:
branches: tags:
- main - 'mod*'
- master
- stable* permissions:
contents: write
jobs: jobs:
build: build:
@@ -18,26 +19,35 @@ jobs:
steps: steps:
- uses: actions/checkout@v4.2.2 - uses: actions/checkout@v4.2.2
- name: Use Node.js ${{ matrix.node-version }} - name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4.1.0 uses: actions/setup-node@v4.4.0
with: with:
node-version: ${{ matrix.node-version }} node-version: ${{ matrix.node-version }}
- name: Set up npm7 - name: Set up npm7
run: npm i -g npm@7 run: npm i -g npm@7
- name: Setup PHP - name: Setup PHP
uses: shivammathur/setup-php@2.31.1 uses: shivammathur/setup-php@2.34.1
with: with:
php-version: '7.4' php-version: '7.4'
tools: composer tools: composer
- name: install dependencies - name: install dependencies
run: | run: |
wget https://github.com/ChristophWurst/krankerl/releases/download/v0.14.0/krankerl_0.14.0_amd64.deb wget https://github.com/ChristophWurst/krankerl/releases/download/v0.13.3/krankerl_0.13.3_amd64.deb
sudo dpkg -i krankerl_0.14.0_amd64.deb sudo dpkg -i krankerl_0.13.3_amd64.deb
- name: package - name: package
run: | run: |
uname -a uname -a
RUST_BACKTRACE=1 krankerl --version RUST_BACKTRACE=1 krankerl --version
RUST_BACKTRACE=1 krankerl package RUST_BACKTRACE=1 krankerl package
- uses: actions/upload-artifact@v4 - uses: https://data.forgejo.org/forgejo/upload-artifact@v4
with: with:
name: Deck app tarball name: Deck app tarball
path: build/artifacts/deck.tar.gz path: build/artifacts/deck.tar.gz
- name: Attach tarball to release
uses: svenstaro/upload-release-action@81c65b7cd4de9b2570615ce3aad67a41de5b1a13 # v2
id: attach_to_release
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
file: build/artifacts/deck.tar.gz
asset_name: deck.tar.gz
tag: ${{ github.ref }}
overwrite: true
-2
View File
@@ -1,2 +0,0 @@
# App maintainers
* @luka-nextcloud @grnd-alt @elzody
-120
View File
@@ -1,120 +0,0 @@
---
name: Bug report
about: Create a report to help us improve
---
<!--
Thanks for reporting issues back!
Guidelines for submitting issues:
* Please search the existing issues first, it's likely that your issue was already reported or even fixed.
* SECURITY: Report any potential security bug to us via our HackerOne page (https://hackerone.com/nextcloud) following our security policy (https://nextcloud.com/security/) instead of filing an issue in our bug tracker.
* The issues in other components should be reported in their respective repositories: You will find them in our GitHub Organization (https://github.com/nextcloud/)
* You can also use the Issue Template app to prefill most of the required information: https://apps.nextcloud.com/apps/issuetemplate
-->
<!--- Please keep this note for other contributors -->
### How to use GitHub
* Please use the 👍 [reaction](https://blog.github.com/2016-03-10-add-reactions-to-pull-requests-issues-and-comments/) to show that you are affected by the same issue.
* Please don't comment if you have no relevant information to add. It's just extra noise for everyone subscribed to this issue.
* Subscribe to receive notifications on status change and new comments.
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Client details:**
- OS: [e.g. iOS]
- Browser [e.g. chrome, safari]
- Version [e.g. 22]
- Device: [e.g. iPhone6, desktop]
<details>
<summary>Server details</summary>
<!--
You can use the Issue Template application to prefill most of the required information: https://apps.nextcloud.com/apps/issuetemplate
-->
**Operating system**:
**Web server:**
**Database:**
**PHP version:**
**Nextcloud version:** (see Nextcloud admin page)
**Where did you install Nextcloud from:**
**Signing status:**
```
Login as admin user into your Nextcloud and access
http://example.com/index.php/settings/integrity/failed
paste the results here.
```
**List of activated apps:**
```
If you have access to your command line run e.g.:
sudo -u www-data php occ app:list
from within your Nextcloud installation folder
```
**Nextcloud configuration:**
```
If you have access to your command line run e.g.:
sudo -u www-data php occ config:list system
from within your Nextcloud installation folder
or
Insert your config.php content here
Make sure to remove all sensitive content such as passwords. (e.g. database password, passwordsalt, secret, smtp password, …)
```
**Are you using an external user-backend, if yes which one:** LDAP/ActiveDirectory/Webdav/...
</details>
<details>
<summary>Logs</summary>
#### Nextcloud log (data/nextcloud.log)
```
Insert your Nextcloud log here
```
#### Browser log
```
Insert your browser log here, this could for example include:
a) The javascript console log
b) The network log
c) ...
```
</details>
-39
View File
@@ -1,39 +0,0 @@
---
name: Feature request
about: Suggest an idea for this project
---
<!--
Thanks for reporting issues back!
Guidelines for submitting issues:
* Please search the existing issues first, it's likely that your issue was already reported or even fixed.
* SECURITY: Report any potential security bug to us via our HackerOne page (https://hackerone.com/nextcloud) following our security policy (https://nextcloud.com/security/) instead of filing an issue in our bug tracker.
* The issues in other components should be reported in their respective repositories: You will find them in our GitHub Organization (https://github.com/nextcloud/)
* You can also use the Issue Template app to prefill most of the required information: https://apps.nextcloud.com/apps/issuetemplate
-->
<!--- Please keep this note for other contributors -->
### How to use GitHub
* Please use the 👍 [reaction](https://blog.github.com/2016-03-10-add-reactions-to-pull-requests-issues-and-comments/) to show that you are affected by the same issue.
* Please don't comment if you have no relevant information to add. It's just extra noise for everyone subscribed to this issue.
* Subscribe to receive notifications on status change and new comments.
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.
-101
View File
@@ -1,101 +0,0 @@
version: 2
updates:
- package-ecosystem: npm
directory: "/"
target-branch: "main"
schedule:
interval: weekly
day: saturday
time: "03:00"
timezone: Europe/Paris
open-pull-requests-limit: 10
reviewers:
- juliushaertl
- luka-nextcloud
- package-ecosystem: npm
target-branch: stable30
versioning-strategy: lockfile-only
directory: "/"
schedule:
interval: weekly
day: saturday
time: "03:00"
timezone: Europe/Paris
ignore:
- dependency-name: "*"
update-types: ["version-update:semver-major"]
open-pull-requests-limit: 30
labels:
- 3. to review
- dependencies
- package-ecosystem: npm
target-branch: stable29
versioning-strategy: lockfile-only
directory: "/"
schedule:
interval: weekly
day: saturday
time: "03:00"
timezone: Europe/Paris
ignore:
- dependency-name: "*"
update-types: ["version-update:semver-major"]
open-pull-requests-limit: 30
labels:
- 3. to review
- dependencies
- package-ecosystem: npm
target-branch: stable28
versioning-strategy: lockfile-only
directory: "/"
schedule:
interval: weekly
day: saturday
time: "03:00"
timezone: Europe/Paris
ignore:
- dependency-name: "*"
update-types: ["version-update:semver-major"]
open-pull-requests-limit: 30
labels:
- 3. to review
- dependencies
- package-ecosystem: composer
directory: "/"
schedule:
interval: weekly
day: saturday
time: "03:00"
timezone: Europe/Paris
open-pull-requests-limit: 10
reviewers:
- juliushaertl
- luka-nextcloud
- package-ecosystem: composer
directory: "/tests/integration"
schedule:
interval: weekly
day: saturday
time: "03:00"
timezone: Europe/Paris
open-pull-requests-limit: 10
reviewers:
- juliushaertl
- luka-nextcloud
- package-ecosystem: github-actions
directory: "/"
schedule:
interval: weekly
day: saturday
time: "03:00"
timezone: Europe/Paris
open-pull-requests-limit: 10
reviewers:
- juliushaertl
- luka-nextcloud
-17
View File
@@ -1,17 +0,0 @@
* Resolves: # <!-- related github issue -->
* Target version: main
### Summary
### TODO
- [ ] ...
### Checklist
- [ ] Code is properly formatted
- [ ] Sign-off message is added to all commits
- [ ] Tests (unit, integration, api and/or acceptance) are included
- [ ] Documentation (manuals or wiki) has been updated or is not required
@@ -1,191 +0,0 @@
# This workflow is provided via the organization template repository
#
# https://github.com/nextcloud/.github
# https://docs.github.com/en/actions/learn-github-actions/sharing-workflows-with-your-organization
#
# SPDX-FileCopyrightText: 2021-2024 Nextcloud GmbH and Nextcloud contributors
# SPDX-License-Identifier: MIT
name: Build and publish app release
on:
release:
types: [published]
permissions:
contents: write
jobs:
build_and_publish:
runs-on: ubuntu-latest
# Only allowed to be run on nextcloud-releases repositories
if: ${{ github.repository_owner == 'nextcloud-releases' }}
steps:
- name: Check actor permission
uses: skjnldsv/check-actor-permission@69e92a3c4711150929bca9fcf34448c5bf5526e7 # v3.0
with:
require: write
- name: Set app env
run: |
# Split and keep last
echo "APP_NAME=${GITHUB_REPOSITORY##*/}" >> $GITHUB_ENV
echo "APP_VERSION=${GITHUB_REF##*/}" >> $GITHUB_ENV
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
path: ${{ env.APP_NAME }}
- name: Get app version number
id: app-version
uses: skjnldsv/xpath-action@f5b036e9d973f42c86324833fd00be90665fbf77 # master
with:
filename: ${{ env.APP_NAME }}/appinfo/info.xml
expression: "//info//version/text()"
- name: Validate app version against tag
run: |
[ "${{ env.APP_VERSION }}" = "v${{ fromJSON(steps.app-version.outputs.result).version }}" ]
- name: Get appinfo data
id: appinfo
uses: skjnldsv/xpath-action@f5b036e9d973f42c86324833fd00be90665fbf77 # master
with:
filename: ${{ env.APP_NAME }}/appinfo/info.xml
expression: "//info//dependencies//nextcloud/@min-version"
- name: Read package.json node and npm engines version
uses: skjnldsv/read-package-engines-version-actions@06d6baf7d8f41934ab630e97d9e6c0bc9c9ac5e4 # v3
id: versions
# Continue if no package.json
continue-on-error: true
with:
path: ${{ env.APP_NAME }}
fallbackNode: '^20'
fallbackNpm: '^10'
- name: Set up node ${{ steps.versions.outputs.nodeVersion }}
# Skip if no package.json
if: ${{ steps.versions.outputs.nodeVersion }}
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
with:
node-version: ${{ steps.versions.outputs.nodeVersion }}
- name: Set up npm ${{ steps.versions.outputs.npmVersion }}
# Skip if no package.json
if: ${{ steps.versions.outputs.npmVersion }}
run: npm i -g 'npm@${{ steps.versions.outputs.npmVersion }}'
- name: Get php version
id: php-versions
uses: icewind1991/nextcloud-version-matrix@58becf3b4bb6dc6cef677b15e2fd8e7d48c0908f # v1.3.1
with:
filename: ${{ env.APP_NAME }}/appinfo/info.xml
- name: Set up php ${{ steps.php-versions.outputs.php-min }}
uses: shivammathur/setup-php@c541c155eee45413f5b09a52248675b1a2575231 # v2.31.1
with:
php-version: ${{ steps.php-versions.outputs.php-min }}
coverage: none
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Check composer.json
id: check_composer
uses: andstor/file-existence-action@076e0072799f4942c8bc574a82233e1e4d13e9d6 # v3.0.0
with:
files: "${{ env.APP_NAME }}/composer.json"
- name: Install composer dependencies
if: steps.check_composer.outputs.files_exists == 'true'
run: |
cd ${{ env.APP_NAME }}
composer install --no-dev
- name: Build ${{ env.APP_NAME }}
# Skip if no package.json
if: ${{ steps.versions.outputs.nodeVersion }}
env:
CYPRESS_INSTALL_BINARY: 0
run: |
cd ${{ env.APP_NAME }}
npm ci
npm run build --if-present
- name: Check Krankerl config
id: krankerl
uses: andstor/file-existence-action@076e0072799f4942c8bc574a82233e1e4d13e9d6 # v3.0.0
with:
files: ${{ env.APP_NAME }}/krankerl.toml
- name: Install Krankerl
if: steps.krankerl.outputs.files_exists == 'true'
run: |
wget https://github.com/ChristophWurst/krankerl/releases/download/v0.14.0/krankerl_0.14.0_amd64.deb
sudo dpkg -i krankerl_0.14.0_amd64.deb
- name: Package ${{ env.APP_NAME }} ${{ env.APP_VERSION }} with krankerl
if: steps.krankerl.outputs.files_exists == 'true'
run: |
cd ${{ env.APP_NAME }}
krankerl package
- name: Package ${{ env.APP_NAME }} ${{ env.APP_VERSION }} with makefile
if: steps.krankerl.outputs.files_exists != 'true'
run: |
cd ${{ env.APP_NAME }}
make appstore
- name: Checkout server ${{ fromJSON(steps.appinfo.outputs.result).nextcloud.min-version }}
continue-on-error: true
id: server-checkout
run: |
NCVERSION='${{ fromJSON(steps.appinfo.outputs.result).nextcloud.min-version }}'
wget --quiet https://download.nextcloud.com/server/releases/latest-$NCVERSION.zip
unzip latest-$NCVERSION.zip
- name: Checkout server master fallback
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
if: ${{ steps.server-checkout.outcome != 'success' }}
with:
persist-credentials: false
submodules: true
repository: nextcloud/server
path: nextcloud
- name: Sign app
run: |
# Extracting release
cd ${{ env.APP_NAME }}/build/artifacts
tar -xvf ${{ env.APP_NAME }}.tar.gz
cd ../../../
# Setting up keys
echo '${{ secrets.APP_PRIVATE_KEY }}' > ${{ env.APP_NAME }}.key
wget --quiet "https://github.com/nextcloud/app-certificate-requests/raw/master/${{ env.APP_NAME }}/${{ env.APP_NAME }}.crt"
# Signing
php nextcloud/occ integrity:sign-app --privateKey=../${{ env.APP_NAME }}.key --certificate=../${{ env.APP_NAME }}.crt --path=../${{ env.APP_NAME }}/build/artifacts/${{ env.APP_NAME }}
# Rebuilding archive
cd ${{ env.APP_NAME }}/build/artifacts
tar -zcvf ${{ env.APP_NAME }}.tar.gz ${{ env.APP_NAME }}
- name: Attach tarball to github release
uses: svenstaro/upload-release-action@04733e069f2d7f7f0b4aebc4fbdbce8613b03ccd # v2
id: attach_to_release
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
file: ${{ env.APP_NAME }}/build/artifacts/${{ env.APP_NAME }}.tar.gz
asset_name: ${{ env.APP_NAME }}-${{ env.APP_VERSION }}.tar.gz
tag: ${{ github.ref }}
overwrite: true
- name: Upload app to Nextcloud appstore
uses: nextcloud-releases/nextcloud-appstore-push-action@a011fe619bcf6e77ddebc96f9908e1af4071b9c1 # v1
with:
app_name: ${{ env.APP_NAME }}
appstore_token: ${{ secrets.APPSTORE_TOKEN }}
download_url: ${{ steps.attach_to_release.outputs.browser_download_url }}
app_private_key: ${{ secrets.APP_PRIVATE_KEY }}
-153
View File
@@ -1,153 +0,0 @@
name: Cypress
on:
pull_request:
push:
branches:
- main
- stable*
env:
APP_NAME: deck
CYPRESS_baseUrl: http://localhost:8081/index.php
jobs:
cypress:
runs-on: 'ubuntu-latest'
strategy:
fail-fast: false
matrix:
node-version: [20.x]
# containers: [1, 2, 3]
php-versions: [ '8.2' ]
server-versions: [ 'stable31' ]
env:
extensions: mbstring, iconv, fileinfo, intl, sqlite, pdo_sqlite, zip, gd, apcu
key: cache-v1
services:
postgres:
image: ghcr.io/nextcloud/continuous-integration-postgres-14:latest
ports:
- 4444:5432/tcp
env:
POSTGRES_USER: root
POSTGRES_PASSWORD: rootpassword
POSTGRES_DB: nextcloud
options: --health-cmd pg_isready --health-interval 5s --health-timeout 2s --health-retries 5
steps:
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4.1.0
with:
node-version: ${{ matrix.node-version }}
- name: Register text Git reference
run: |
text_app_ref="$(if [ "${{ matrix.server-versions }}" = "master" ]; then echo -n "main"; else echo -n "${{ matrix.server-versions }}"; fi)"
echo "text_app_ref=$text_app_ref" >> $GITHUB_ENV
- name: Checkout server
uses: actions/checkout@v4.2.2
with:
repository: nextcloud/server
ref: ${{ matrix.server-versions }}
- name: Checkout submodules
shell: bash
run: |
auth_header="$(git config --local --get http.https://github.com/.extraheader)"
git submodule sync --recursive
git -c "http.extraheader=$auth_header" -c protocol.version=2 submodule update --init --force --recursive --depth=1
- name: Checkout ${{ env.APP_NAME }}
uses: actions/checkout@v4.2.2
with:
path: apps/${{ env.APP_NAME }}
- name: Checkout text
uses: actions/checkout@v4.2.2
with:
repository: nextcloud/text
ref: ${{ env.text_app_ref }}
path: apps/text
- name: Setup cache environment
id: extcache
uses: shivammathur/cache-extensions@v1
with:
php-version: ${{ matrix.php-versions }}
extensions: ${{ env.extensions }}
key: ${{ env.key }}
- name: Cache extensions
uses: actions/cache@v4
with:
path: ${{ steps.extcache.outputs.dir }}
key: ${{ steps.extcache.outputs.key }}
restore-keys: ${{ steps.extcache.outputs.key }}
- name: Set up php ${{ matrix.php-versions }}
uses: shivammathur/setup-php@2.31.1
with:
php-version: ${{ matrix.php-versions }}
extensions: ${{ env.extensions }}
ini-values:
apc.enable_cli=on
coverage: none
- name: Install composer dependencies
working-directory: apps/${{ env.APP_NAME }}
run: |
composer install --no-dev
- name: Set up Nextcloud
env:
DB_PORT: 4444
PHP_CLI_SERVER_WORKERS: 20
run: |
mkdir data
echo '<?php $CONFIG=["memcache.local"=>"\OC\Memcache\APCu","hashing_default_password"=>true];' > config/config.php
php occ maintenance:install --verbose --database=pgsql --database-name=nextcloud --database-host=127.0.0.1 --database-port=$DB_PORT --database-user=root --database-pass=rootpassword --admin-user admin --admin-pass admin
php occ background:cron
php -f index.php
php -S 0.0.0.0:8081 &
export OC_PASS=1234561
php occ user:add --password-from-env user1
php occ user:add --password-from-env user2
php occ app:enable deck
php occ app:list
curl -v http://localhost:8081/index.php/login
- name: Cypress run
uses: cypress-io/github-action@v6
with:
build: npm run dev
record: false
parallel: false
browser: chrome
wait-on: '${{ env.CYPRESS_baseUrl }}'
working-directory: 'apps/${{ env.APP_NAME }}'
config: defaultCommandTimeout=10000,video=false
env:
CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
npm_package_name: ${{ env.APP_NAME }}
- name: Upload test failure screenshots
uses: actions/upload-artifact@v4
if: failure()
with:
name: Upload screenshots
path: apps/${{ env.APP_NAME }}/cypress/screenshots/
retention-days: 5
- name: Upload nextcloud logs
uses: actions/upload-artifact@v4
if: failure()
with:
name: Upload nextcloud log
path: data/nextcloud.log
retention-days: 5
@@ -1,49 +0,0 @@
# This workflow is provided via the organization template repository
#
# https://github.com/nextcloud/.github
# https://docs.github.com/en/actions/learn-github-actions/sharing-workflows-with-your-organization
#
# SPDX-FileCopyrightText: 2021-2024 Nextcloud GmbH and Nextcloud contributors
# SPDX-License-Identifier: MIT
name: Dependabot
on:
pull_request_target: # zizmor: ignore[dangerous-triggers]
branches:
- main
- master
- stable*
permissions:
contents: read
concurrency:
group: dependabot-approve-merge-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
jobs:
auto-approve-merge:
if: github.event.pull_request.user.login == 'dependabot[bot]' || github.event.pull_request.user.login == 'renovate[bot]'
runs-on: ubuntu-latest-low
permissions:
# for hmarr/auto-approve-action to approve PRs
pull-requests: write
steps:
- name: Disabled on forks
if: ${{ github.event.pull_request.head.repo.full_name != github.repository }}
run: |
echo 'Can not approve PRs from forks'
exit 1
# GitHub actions bot approve
- uses: hmarr/auto-approve-action@b40d6c9ed2fa10c9a2749eca7eb004418a705501 # v2
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
# Nextcloud bot approve and merge request
- uses: ahmadnassri/action-dependabot-auto-merge@45fc124d949b19b6b8bf6645b6c9d55f4f9ac61a # v2
with:
target: minor
github-token: ${{ secrets.DEPENDABOT_AUTOMERGE_TOKEN }}
-36
View File
@@ -1,36 +0,0 @@
# This workflow is provided via the organization template repository
#
# https://github.com/nextcloud/.github
# https://docs.github.com/en/actions/learn-github-actions/sharing-workflows-with-your-organization
#
# SPDX-FileCopyrightText: 2021-2024 Nextcloud GmbH and Nextcloud contributors
# SPDX-License-Identifier: MIT
name: Block fixup and squash commits
on:
pull_request:
types: [opened, ready_for_review, reopened, synchronize]
permissions:
contents: read
concurrency:
group: fixup-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
jobs:
commit-message-check:
if: github.event.pull_request.draft == false
permissions:
pull-requests: write
name: Block fixup and squash commits
runs-on: ubuntu-latest-low
steps:
- name: Run check
uses: skjnldsv/block-fixup-merge-action@c138ea99e45e186567b64cf065ce90f7158c236a # v2
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
-165
View File
@@ -1,165 +0,0 @@
name: Integration tests
on:
pull_request:
paths:
- '.github/workflows/integration.yml'
- 'appinfo/**'
- 'lib/**'
- 'templates/**'
- 'tests/**'
- 'composer.json'
- 'composer.lock'
push:
branches:
- main
- master
- stable*
env:
APP_NAME: deck
jobs:
integration:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
php-versions: ['8.1']
databases: ['sqlite', 'mysql', 'pgsql']
server-versions: ['master']
name: php${{ matrix.php-versions }}-${{ matrix.databases }}-${{ matrix.server-versions }}
services:
postgres:
image: postgres:14
ports:
- 4445:5432/tcp
env:
POSTGRES_USER: root
POSTGRES_PASSWORD: rootpassword
POSTGRES_DB: nextcloud
options: --health-cmd pg_isready --health-interval 5s --health-timeout 2s --health-retries 5
mysql:
image: mariadb:10.5
ports:
- 4444:3306/tcp
env:
MYSQL_ROOT_PASSWORD: rootpassword
options: --health-cmd="mysqladmin ping" --health-interval 5s --health-timeout 2s --health-retries 5
steps:
- name: Checkout server
uses: actions/checkout@v4.2.2
with:
repository: nextcloud/server
ref: ${{ matrix.server-versions }}
- name: Checkout submodules
shell: bash
run: |
auth_header="$(git config --local --get http.https://github.com/.extraheader)"
git submodule sync --recursive
git -c "http.extraheader=$auth_header" -c protocol.version=2 submodule update --init --force --recursive --depth=1
cd build/integration && composer require --dev phpunit/phpunit:~9
- name: Checkout app
uses: actions/checkout@v4.2.2
with:
path: apps/${{ env.APP_NAME }}
- name: Checkout activity
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
repository: nextcloud/activity
ref: ${{ matrix.server-versions }}
path: apps/activity
- name: Set up php ${{ matrix.php-versions }}
uses: shivammathur/setup-php@2.31.1
with:
php-version: ${{ matrix.php-versions }}
extensions: mbstring, iconv, fileinfo, intl, sqlite, pdo_sqlite, mysql, pdo_mysql, pgsql, pdo_pgsql, apcu, gd
ini-values:
apc.enable_cli=on
coverage: none
- name: Set up dependencies
working-directory: apps/${{ env.APP_NAME }}
run: composer i --no-dev
- name: Set up Nextcloud
run: |
if [ "${{ matrix.databases }}" = "mysql" ]; then
export DB_PORT=4444
elif [ "${{ matrix.databases }}" = "pgsql" ]; then
export DB_PORT=4445
fi
mkdir data
./occ maintenance:install --verbose --database=${{ matrix.databases }} --database-name=nextcloud --database-host=127.0.0.1 --database-port=$DB_PORT --database-user=root --database-pass=rootpassword --admin-user admin --admin-pass admin
./occ config:system:set hashing_default_password --value=true --type=boolean
./occ config:system:set memcache.local --value="\\OC\\Memcache\\APCu"
./occ config:system:set memcache.distributed --value="\\OC\\Memcache\\APCu"
cat config/config.php
./occ user:list
./occ app:enable --force ${{ env.APP_NAME }}
./occ config:system:set query_log_file --value "$PWD/query.log"
php -S localhost:8080 &
- name: Run behat
working-directory: apps/${{ env.APP_NAME }}/tests/integration
run: ./run.sh
- name: Print log
if: always()
run: cat data/nextcloud.log
- name: Query count
if: ${{ matrix.databases == 'mysql' }}
uses: actions/github-script@v7
with:
github-token: ${{secrets.GITHUB_TOKEN}}
script: |
let myOutput = ''
let myError = ''
const options = {}
options.listeners = {
stdout: (data) => {
myOutput += data.toString()
},
stderr: (data) => {
myError += data.toString()
}
}
await exec.exec(`/bin/bash -c "cat query.log | wc -l"`, [], options)
msg = myOutput
const queryCount = parseInt(myOutput, 10)
myOutput = ''
await exec.exec('cat', ['apps/${{ env.APP_NAME }}/tests/integration/base-query-count.txt'], options)
const baseCount = parseInt(myOutput, 10)
const absoluteIncrease = queryCount - baseCount
const relativeIncrease = baseCount <= 0 ? 100 : (parseInt((absoluteIncrease / baseCount * 10000), 10) / 100)
if (absoluteIncrease >= 100 || relativeIncrease > 5) {
const comment = `🐢 Performance warning.\nIt looks like the query count of the integration tests increased with this PR.\nDatabase query count is now ` + queryCount + ' was ' + baseCount + ' (+' + relativeIncrease + '%)\nPlease check your code again. If you added a new test this can be expected and the base value in tests/integration/base-query-count.txt can be increased.'
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: comment
})
}
if (queryCount < 100) {
const comment = `🐈 Performance messuring seems broken. Failed to get query count.`
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: comment
})
}
-100
View File
@@ -1,100 +0,0 @@
# This workflow is provided via the organization template repository
#
# https://github.com/nextcloud/.github
# https://docs.github.com/en/actions/learn-github-actions/sharing-workflows-with-your-organization
#
# SPDX-FileCopyrightText: 2021-2024 Nextcloud GmbH and Nextcloud contributors
# SPDX-License-Identifier: MIT
name: Lint eslint
on: pull_request
permissions:
contents: read
concurrency:
group: lint-eslint-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
jobs:
changes:
runs-on: ubuntu-latest-low
permissions:
contents: read
pull-requests: read
outputs:
src: ${{ steps.changes.outputs.src}}
steps:
- uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2
id: changes
continue-on-error: true
with:
filters: |
src:
- '.github/workflows/**'
- 'src/**'
- 'appinfo/info.xml'
- 'package.json'
- 'package-lock.json'
- 'tsconfig.json'
- '.eslintrc.*'
- '.eslintignore'
- '**.js'
- '**.ts'
- '**.vue'
lint:
runs-on: ubuntu-latest
needs: changes
if: needs.changes.outputs.src != 'false'
name: NPM lint
steps:
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
- name: Read package.json node and npm engines version
uses: skjnldsv/read-package-engines-version-actions@06d6baf7d8f41934ab630e97d9e6c0bc9c9ac5e4 # v3
id: versions
with:
fallbackNode: '^20'
fallbackNpm: '^10'
- name: Set up node ${{ steps.versions.outputs.nodeVersion }}
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
with:
node-version: ${{ steps.versions.outputs.nodeVersion }}
- name: Set up npm ${{ steps.versions.outputs.npmVersion }}
run: npm i -g 'npm@${{ steps.versions.outputs.npmVersion }}'
- name: Install dependencies
env:
CYPRESS_INSTALL_BINARY: 0
PUPPETEER_SKIP_DOWNLOAD: true
run: npm ci
- name: Lint
run: npm run lint
summary:
permissions:
contents: none
runs-on: ubuntu-latest-low
needs: [changes, lint]
if: always()
# This is the summary, we just avoid to rename it so that branch protection rules still match
name: eslint
steps:
- name: Summary status
run: if ${{ needs.changes.outputs.src != 'false' && needs.lint.result != 'success' }}; then exit 1; fi
-52
View File
@@ -1,52 +0,0 @@
# This workflow is provided via the organization template repository
#
# https://github.com/nextcloud/.github
# https://docs.github.com/en/actions/learn-github-actions/sharing-workflows-with-your-organization
#
# SPDX-FileCopyrightText: 2021-2024 Nextcloud GmbH and Nextcloud contributors
# SPDX-License-Identifier: MIT
name: Lint php-cs
on: pull_request
permissions:
contents: read
concurrency:
group: lint-php-cs-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
jobs:
lint:
runs-on: ubuntu-latest
name: php-cs
steps:
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
- name: Get php version
id: versions
uses: icewind1991/nextcloud-version-matrix@58becf3b4bb6dc6cef677b15e2fd8e7d48c0908f # v1.3.1
- name: Set up php${{ steps.versions.outputs.php-available }}
uses: shivammathur/setup-php@c541c155eee45413f5b09a52248675b1a2575231 # v2.31.1
with:
php-version: ${{ steps.versions.outputs.php-min }}
extensions: bz2, ctype, curl, dom, fileinfo, gd, iconv, intl, json, libxml, mbstring, openssl, pcntl, posix, session, simplexml, xmlreader, xmlwriter, zip, zlib, sqlite, pdo_sqlite
coverage: none
ini-file: development
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Install dependencies
run: |
composer remove nextcloud/ocp --dev
composer i
- name: Lint
run: composer run cs:check || ( echo 'Please run `composer run cs:fix` to format your code' && exit 1 )
-75
View File
@@ -1,75 +0,0 @@
# This workflow is provided via the organization template repository
#
# https://github.com/nextcloud/.github
# https://docs.github.com/en/actions/learn-github-actions/sharing-workflows-with-your-organization
#
# SPDX-FileCopyrightText: 2021-2024 Nextcloud GmbH and Nextcloud contributors
# SPDX-License-Identifier: MIT
name: Lint php
on: pull_request
permissions:
contents: read
concurrency:
group: lint-php-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
jobs:
matrix:
runs-on: ubuntu-latest-low
outputs:
php-versions: ${{ steps.versions.outputs.php-versions }}
steps:
- name: Checkout app
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
- name: Get version matrix
id: versions
uses: icewind1991/nextcloud-version-matrix@58becf3b4bb6dc6cef677b15e2fd8e7d48c0908f # v1.0.0
php-lint:
runs-on: ubuntu-latest
needs: matrix
strategy:
matrix:
php-versions: ${{fromJson(needs.matrix.outputs.php-versions)}}
name: php-lint
steps:
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
- name: Set up php ${{ matrix.php-versions }}
uses: shivammathur/setup-php@c541c155eee45413f5b09a52248675b1a2575231 # v2.31.1
with:
php-version: ${{ matrix.php-versions }}
extensions: bz2, ctype, curl, dom, fileinfo, gd, iconv, intl, json, libxml, mbstring, openssl, pcntl, posix, session, simplexml, xmlreader, xmlwriter, zip, zlib, sqlite, pdo_sqlite
coverage: none
ini-file: development
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Lint
run: composer run lint
summary:
permissions:
contents: none
runs-on: ubuntu-latest-low
needs: php-lint
if: always()
name: php-lint-summary
steps:
- name: Summary status
run: if ${{ needs.php-lint.result != 'success' && needs.php-lint.result != 'skipped' }}; then exit 1; fi
-53
View File
@@ -1,53 +0,0 @@
# This workflow is provided via the organization template repository
#
# https://github.com/nextcloud/.github
# https://docs.github.com/en/actions/learn-github-actions/sharing-workflows-with-your-organization
#
# SPDX-FileCopyrightText: 2021-2024 Nextcloud GmbH and Nextcloud contributors
# SPDX-License-Identifier: MIT
name: Lint stylelint
on: pull_request
permissions:
contents: read
concurrency:
group: lint-stylelint-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
jobs:
lint:
runs-on: ubuntu-latest
name: stylelint
steps:
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
- name: Read package.json node and npm engines version
uses: skjnldsv/read-package-engines-version-actions@06d6baf7d8f41934ab630e97d9e6c0bc9c9ac5e4 # v3
id: versions
with:
fallbackNode: '^20'
fallbackNpm: '^10'
- name: Set up node ${{ steps.versions.outputs.nodeVersion }}
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
with:
node-version: ${{ steps.versions.outputs.nodeVersion }}
- name: Set up npm ${{ steps.versions.outputs.npmVersion }}
run: npm i -g 'npm@${{ steps.versions.outputs.npmVersion }}'
- name: Install dependencies
env:
CYPRESS_INSTALL_BINARY: 0
run: npm ci
- name: Lint
run: npm run stylelint
-34
View File
@@ -1,34 +0,0 @@
name: Node CI
on: [push]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [14.x]
steps:
- uses: actions/checkout@v4.2.2
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4.1.0
with:
node-version: ${{ matrix.node-version }}
- name: Set up npm7
run: npm i -g npm@7
- name: install dependencies
run: |
npm ci
- name: build
env:
RELATIVE_CI_KEY: ${{ secrets.RELATIVE_CI_KEY }}
RELATIVE_CI_SLUG: nextcloud/deck
run: |
mkdir -p js
npm run build --if-present -- --profile --json | tail -n +6 > js/webpack-stats.json
npx relative-ci-agent
-75
View File
@@ -1,75 +0,0 @@
# This workflow is provided via the organization template repository
#
# https://github.com/nextcloud/.github
# https://docs.github.com/en/actions/learn-github-actions/sharing-workflows-with-your-organization
#
# SPDX-FileCopyrightText: 2023-2024 Nextcloud GmbH and Nextcloud contributors
# SPDX-License-Identifier: MIT
name: Npm audit fix and compile
on:
workflow_dispatch:
schedule:
# At 2:30 on Sundays
- cron: '30 2 * * 0'
jobs:
build:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
branches: ['main', 'master', 'stable30', 'stable29', 'stable28']
name: npm-audit-fix-${{ matrix.branches }}
steps:
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
ref: ${{ matrix.branches }}
- name: Read package.json node and npm engines version
uses: skjnldsv/read-package-engines-version-actions@06d6baf7d8f41934ab630e97d9e6c0bc9c9ac5e4 # v3
id: versions
with:
fallbackNode: '^20'
fallbackNpm: '^10'
- name: Set up node ${{ steps.versions.outputs.nodeVersion }}
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
with:
node-version: ${{ steps.versions.outputs.nodeVersion }}
- name: Set up npm ${{ steps.versions.outputs.npmVersion }}
run: npm i -g 'npm@${{ steps.versions.outputs.npmVersion }}'
- name: Fix npm audit
id: npm-audit
uses: nextcloud-libraries/npm-audit-action@2a60bd2e79cc77f2cc4d9a3fe40f1a69896f3a87 # v0.1.0
- name: Run npm ci and npm run build
if: always()
env:
CYPRESS_INSTALL_BINARY: 0
run: |
npm ci
npm run build --if-present
- name: Create Pull Request
if: always()
uses: peter-evans/create-pull-request@5e914681df9dc83aa4e4905692ca88beb2f9e91f # v7.0.5
with:
token: ${{ secrets.COMMAND_BOT_PAT }}
commit-message: 'fix(deps): Fix npm audit'
committer: GitHub <noreply@github.com>
author: nextcloud-command <nextcloud-command@users.noreply.github.com>
signoff: true
branch: automated/noid/${{ matrix.branches }}-fix-npm-audit
title: '[${{ matrix.branches }}] Fix npm audit'
body: ${{ steps.npm-audit.outputs.markdown }}
labels: |
dependencies
3. to review
-200
View File
@@ -1,200 +0,0 @@
# This workflow is provided via the organization template repository
#
# https://github.com/nextcloud/.github
# https://docs.github.com/en/actions/learn-github-actions/sharing-workflows-with-your-organization
#
# SPDX-FileCopyrightText: 2022-2024 Nextcloud GmbH and Nextcloud contributors
# SPDX-License-Identifier: MIT
name: PHPUnit MySQL
on: pull_request
permissions:
contents: read
concurrency:
group: phpunit-mysql-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
jobs:
matrix:
runs-on: ubuntu-latest-low
outputs:
matrix: ${{ steps.versions.outputs.sparse-matrix }}
steps:
- name: Checkout app
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
- name: Get version matrix
id: versions
uses: icewind1991/nextcloud-version-matrix@58becf3b4bb6dc6cef677b15e2fd8e7d48c0908f # v1.3.1
with:
matrix: '{"mysql-versions": ["8.4"]}'
changes:
runs-on: ubuntu-latest-low
permissions:
contents: read
pull-requests: read
outputs:
src: ${{ steps.changes.outputs.src}}
steps:
- uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2
id: changes
continue-on-error: true
with:
filters: |
src:
- '.github/workflows/**'
- 'appinfo/**'
- 'lib/**'
- 'templates/**'
- 'tests/**'
- 'vendor/**'
- 'vendor-bin/**'
- '.php-cs-fixer.dist.php'
- 'composer.json'
- 'composer.lock'
phpunit-mysql:
runs-on: ubuntu-latest
needs: [changes, matrix]
if: needs.changes.outputs.src != 'false'
strategy:
matrix: ${{ fromJson(needs.matrix.outputs.matrix) }}
name: MySQL ${{ matrix.mysql-versions }} PHP ${{ matrix.php-versions }} Nextcloud ${{ matrix.server-versions }}
services:
mysql:
image: ghcr.io/nextcloud/continuous-integration-mysql-${{ matrix.mysql-versions }}:latest
ports:
- 4444:3306/tcp
env:
MYSQL_ROOT_PASSWORD: rootpassword
options: --health-cmd="mysqladmin ping" --health-interval 5s --health-timeout 2s --health-retries 10
steps:
- name: Set app env
if: ${{ env.APP_NAME == '' }}
run: |
# Split and keep last
echo "APP_NAME=${GITHUB_REPOSITORY##*/}" >> $GITHUB_ENV
- name: Checkout server
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
submodules: true
repository: nextcloud/server
ref: ${{ matrix.server-versions }}
- name: Checkout app
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
path: apps/${{ env.APP_NAME }}
- name: Set up php ${{ matrix.php-versions }}
uses: shivammathur/setup-php@c541c155eee45413f5b09a52248675b1a2575231 # v2.31.1
with:
php-version: ${{ matrix.php-versions }}
# https://docs.nextcloud.com/server/stable/admin_manual/installation/source_installation.html#prerequisites-for-manual-installation
extensions: bz2, ctype, curl, dom, fileinfo, gd, iconv, intl, json, libxml, mbstring, openssl, pcntl, posix, session, simplexml, xmlreader, xmlwriter, zip, zlib, mysql, pdo_mysql
coverage: none
ini-file: development
# Temporary workaround for missing pcntl_* in PHP 8.3
ini-values: disable_functions=
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Enable ONLY_FULL_GROUP_BY MySQL option
run: |
echo "SET GLOBAL sql_mode=(SELECT CONCAT(@@sql_mode,',ONLY_FULL_GROUP_BY'));" | mysql -h 127.0.0.1 -P 4444 -u root -prootpassword
echo 'SELECT @@sql_mode;' | mysql -h 127.0.0.1 -P 4444 -u root -prootpassword
- name: Check composer file existence
id: check_composer
uses: andstor/file-existence-action@076e0072799f4942c8bc574a82233e1e4d13e9d6 # v3.0.0
with:
files: apps/${{ env.APP_NAME }}/composer.json
- name: Set up dependencies
# Only run if phpunit config file exists
if: steps.check_composer.outputs.files_exists == 'true'
working-directory: apps/${{ env.APP_NAME }}
run: |
composer remove nextcloud/ocp --dev
composer i
- name: Set up Nextcloud
env:
DB_PORT: 4444
run: |
mkdir data
./occ maintenance:install --verbose --database=mysql --database-name=nextcloud --database-host=127.0.0.1 --database-port=$DB_PORT --database-user=root --database-pass=rootpassword --admin-user admin --admin-pass admin
./occ app:enable --force ${{ env.APP_NAME }}
- name: Check PHPUnit script is defined
id: check_phpunit
continue-on-error: true
working-directory: apps/${{ env.APP_NAME }}
run: |
composer run --list | grep '^ test:unit ' | wc -l | grep 1
- name: PHPUnit
# Only run if phpunit config file exists
if: steps.check_phpunit.outcome == 'success'
working-directory: apps/${{ env.APP_NAME }}
run: composer run test:unit
- name: Check PHPUnit integration script is defined
id: check_integration
continue-on-error: true
working-directory: apps/${{ env.APP_NAME }}
run: |
composer run --list | grep '^ test:integration ' | wc -l | grep 1
- name: Run Nextcloud
# Only run if phpunit integration config file exists
if: steps.check_integration.outcome == 'success'
run: php -S localhost:8080 &
- name: PHPUnit integration
# Only run if phpunit integration config file exists
if: steps.check_integration.outcome == 'success'
working-directory: apps/${{ env.APP_NAME }}
run: composer run test:integration
- name: Print logs
if: always()
run: |
cat data/nextcloud.log
- name: Skipped
# Fail the action when neither unit nor integration tests ran
if: steps.check_phpunit.outcome == 'failure' && steps.check_integration.outcome == 'failure'
run: |
echo 'Neither PHPUnit nor PHPUnit integration tests are specified in composer.json scripts'
exit 1
summary:
permissions:
contents: none
runs-on: ubuntu-latest-low
needs: [changes, phpunit-mysql]
if: always()
name: phpunit-mysql-summary
steps:
- name: Summary status
run: if ${{ needs.changes.outputs.src != 'false' && needs.phpunit-mysql.result != 'success' }}; then exit 1; fi
-198
View File
@@ -1,198 +0,0 @@
# This workflow is provided via the organization template repository
#
# https://github.com/nextcloud/.github
# https://docs.github.com/en/actions/learn-github-actions/sharing-workflows-with-your-organization
#
# SPDX-FileCopyrightText: 2022-2024 Nextcloud GmbH and Nextcloud contributors
# SPDX-License-Identifier: MIT
name: PHPUnit PostgreSQL
on: pull_request
permissions:
contents: read
concurrency:
group: phpunit-pgsql-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
jobs:
matrix:
runs-on: ubuntu-latest-low
outputs:
php-version: ${{ steps.versions.outputs.php-available-list }}
server-max: ${{ steps.versions.outputs.branches-max-list }}
steps:
- name: Checkout app
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
- name: Get version matrix
id: versions
uses: icewind1991/nextcloud-version-matrix@58becf3b4bb6dc6cef677b15e2fd8e7d48c0908f # v1.3.1
changes:
runs-on: ubuntu-latest-low
permissions:
contents: read
pull-requests: read
outputs:
src: ${{ steps.changes.outputs.src }}
steps:
- uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2
id: changes
continue-on-error: true
with:
filters: |
src:
- '.github/workflows/**'
- 'appinfo/**'
- 'lib/**'
- 'templates/**'
- 'tests/**'
- 'vendor/**'
- 'vendor-bin/**'
- '.php-cs-fixer.dist.php'
- 'composer.json'
- 'composer.lock'
phpunit-pgsql:
runs-on: ubuntu-latest
needs: [changes, matrix]
if: needs.changes.outputs.src != 'false'
strategy:
matrix:
php-versions: ${{ fromJson(needs.matrix.outputs.php-version) }}
server-versions: ${{ fromJson(needs.matrix.outputs.server-max) }}
name: PostgreSQL PHP ${{ matrix.php-versions }} Nextcloud ${{ matrix.server-versions }}
services:
postgres:
image: ghcr.io/nextcloud/continuous-integration-postgres-14:latest
ports:
- 4444:5432/tcp
env:
POSTGRES_USER: root
POSTGRES_PASSWORD: rootpassword
POSTGRES_DB: nextcloud
options: --health-cmd pg_isready --health-interval 5s --health-timeout 2s --health-retries 5
steps:
- name: Set app env
if: ${{ env.APP_NAME == '' }}
run: |
# Split and keep last
echo "APP_NAME=${GITHUB_REPOSITORY##*/}" >> $GITHUB_ENV
- name: Checkout server
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
submodules: true
repository: nextcloud/server
ref: ${{ matrix.server-versions }}
- name: Checkout app
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
path: apps/${{ env.APP_NAME }}
- name: Set up php ${{ matrix.php-versions }}
uses: shivammathur/setup-php@c541c155eee45413f5b09a52248675b1a2575231 # v2.31.1
with:
php-version: ${{ matrix.php-versions }}
# https://docs.nextcloud.com/server/stable/admin_manual/installation/source_installation.html#prerequisites-for-manual-installation
extensions: bz2, ctype, curl, dom, fileinfo, gd, iconv, intl, json, libxml, mbstring, openssl, pcntl, posix, session, simplexml, xmlreader, xmlwriter, zip, zlib, pgsql, pdo_pgsql
coverage: none
ini-file: development
# Temporary workaround for missing pcntl_* in PHP 8.3
ini-values: disable_functions=
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Check composer file existence
id: check_composer
uses: andstor/file-existence-action@076e0072799f4942c8bc574a82233e1e4d13e9d6 # v3.0.0
with:
files: apps/${{ env.APP_NAME }}/composer.json
- name: Set up dependencies
# Only run if phpunit config file exists
if: steps.check_composer.outputs.files_exists == 'true'
working-directory: apps/${{ env.APP_NAME }}
run: |
composer remove nextcloud/ocp --dev
composer i
- name: Set up Nextcloud
env:
DB_PORT: 4444
run: |
mkdir data
./occ maintenance:install --verbose --database=pgsql --database-name=nextcloud --database-host=127.0.0.1 --database-port=$DB_PORT --database-user=root --database-pass=rootpassword --admin-user admin --admin-pass admin
./occ app:enable --force ${{ env.APP_NAME }}
- name: Check PHPUnit script is defined
id: check_phpunit
continue-on-error: true
working-directory: apps/${{ env.APP_NAME }}
run: |
composer run --list | grep '^ test:unit ' | wc -l | grep 1
- name: PHPUnit
# Only run if phpunit config file exists
if: steps.check_phpunit.outcome == 'success'
working-directory: apps/${{ env.APP_NAME }}
run: composer run test:unit
- name: Check PHPUnit integration script is defined
id: check_integration
continue-on-error: true
working-directory: apps/${{ env.APP_NAME }}
run: |
composer run --list | grep '^ test:integration ' | wc -l | grep 1
- name: Run Nextcloud
# Only run if phpunit integration config file exists
if: steps.check_integration.outcome == 'success'
run: php -S localhost:8080 &
- name: PHPUnit integration
# Only run if phpunit integration config file exists
if: steps.check_integration.outcome == 'success'
working-directory: apps/${{ env.APP_NAME }}
run: composer run test:integration
- name: Print logs
if: always()
run: |
cat data/nextcloud.log
- name: Skipped
# Fail the action when neither unit nor integration tests ran
if: steps.check_phpunit.outcome == 'failure' && steps.check_integration.outcome == 'failure'
run: |
echo 'Neither PHPUnit nor PHPUnit integration tests are specified in composer.json scripts'
exit 1
summary:
permissions:
contents: none
runs-on: ubuntu-latest-low
needs: [changes, phpunit-pgsql]
if: always()
name: phpunit-pgsql-summary
steps:
- name: Summary status
run: if ${{ needs.changes.outputs.src != 'false' && needs.phpunit-pgsql.result != 'success' }}; then exit 1; fi
-187
View File
@@ -1,187 +0,0 @@
# This workflow is provided via the organization template repository
#
# https://github.com/nextcloud/.github
# https://docs.github.com/en/actions/learn-github-actions/sharing-workflows-with-your-organization
#
# SPDX-FileCopyrightText: 2022-2024 Nextcloud GmbH and Nextcloud contributors
# SPDX-License-Identifier: MIT
name: PHPUnit SQLite
on: pull_request
permissions:
contents: read
concurrency:
group: phpunit-sqlite-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
jobs:
matrix:
runs-on: ubuntu-latest-low
outputs:
php-version: ${{ steps.versions.outputs.php-available-list }}
server-max: ${{ steps.versions.outputs.branches-max-list }}
steps:
- name: Checkout app
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
- name: Get version matrix
id: versions
uses: icewind1991/nextcloud-version-matrix@58becf3b4bb6dc6cef677b15e2fd8e7d48c0908f # v1.3.1
changes:
runs-on: ubuntu-latest-low
permissions:
contents: read
pull-requests: read
outputs:
src: ${{ steps.changes.outputs.src}}
steps:
- uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2
id: changes
continue-on-error: true
with:
filters: |
src:
- '.github/workflows/**'
- 'appinfo/**'
- 'lib/**'
- 'templates/**'
- 'tests/**'
- 'vendor/**'
- 'vendor-bin/**'
- '.php-cs-fixer.dist.php'
- 'composer.json'
- 'composer.lock'
phpunit-sqlite:
runs-on: ubuntu-latest
needs: [changes, matrix]
if: needs.changes.outputs.src != 'false'
strategy:
matrix:
php-versions: ${{ fromJson(needs.matrix.outputs.php-version) }}
server-versions: ${{ fromJson(needs.matrix.outputs.server-max) }}
name: SQLite PHP ${{ matrix.php-versions }} Nextcloud ${{ matrix.server-versions }}
steps:
- name: Set app env
if: ${{ env.APP_NAME == '' }}
run: |
# Split and keep last
echo "APP_NAME=${GITHUB_REPOSITORY##*/}" >> $GITHUB_ENV
- name: Checkout server
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
submodules: true
repository: nextcloud/server
ref: ${{ matrix.server-versions }}
- name: Checkout app
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
path: apps/${{ env.APP_NAME }}
- name: Set up php ${{ matrix.php-versions }}
uses: shivammathur/setup-php@c541c155eee45413f5b09a52248675b1a2575231 # v2.31.1
with:
php-version: ${{ matrix.php-versions }}
# https://docs.nextcloud.com/server/stable/admin_manual/installation/source_installation.html#prerequisites-for-manual-installation
extensions: bz2, ctype, curl, dom, fileinfo, gd, iconv, intl, json, libxml, mbstring, openssl, pcntl, posix, session, simplexml, xmlreader, xmlwriter, zip, zlib, sqlite, pdo_sqlite
coverage: none
ini-file: development
# Temporary workaround for missing pcntl_* in PHP 8.3
ini-values: disable_functions=
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Check composer file existence
id: check_composer
uses: andstor/file-existence-action@076e0072799f4942c8bc574a82233e1e4d13e9d6 # v3.0.0
with:
files: apps/${{ env.APP_NAME }}/composer.json
- name: Set up dependencies
# Only run if phpunit config file exists
if: steps.check_composer.outputs.files_exists == 'true'
working-directory: apps/${{ env.APP_NAME }}
run: |
composer remove nextcloud/ocp --dev
composer i
- name: Set up Nextcloud
env:
DB_PORT: 4444
run: |
mkdir data
./occ maintenance:install --verbose --database=sqlite --database-name=nextcloud --database-host=127.0.0.1 --database-port=$DB_PORT --database-user=root --database-pass=rootpassword --admin-user admin --admin-pass admin
./occ app:enable --force ${{ env.APP_NAME }}
- name: Check PHPUnit script is defined
id: check_phpunit
continue-on-error: true
working-directory: apps/${{ env.APP_NAME }}
run: |
composer run --list | grep '^ test:unit ' | wc -l | grep 1
- name: PHPUnit
# Only run if phpunit config file exists
if: steps.check_phpunit.outcome == 'success'
working-directory: apps/${{ env.APP_NAME }}
run: composer run test:unit
- name: Check PHPUnit integration script is defined
id: check_integration
continue-on-error: true
working-directory: apps/${{ env.APP_NAME }}
run: |
composer run --list | grep '^ test:integration ' | wc -l | grep 1
- name: Run Nextcloud
# Only run if phpunit integration config file exists
if: steps.check_integration.outcome == 'success'
run: php -S localhost:8080 &
- name: PHPUnit integration
# Only run if phpunit integration config file exists
if: steps.check_integration.outcome == 'success'
working-directory: apps/${{ env.APP_NAME }}
run: composer run test:integration
- name: Print logs
if: always()
run: |
cat data/nextcloud.log
- name: Skipped
# Fail the action when neither unit nor integration tests ran
if: steps.check_phpunit.outcome == 'failure' && steps.check_integration.outcome == 'failure'
run: |
echo 'Neither PHPUnit nor PHPUnit integration tests are specified in composer.json scripts'
exit 1
summary:
permissions:
contents: none
runs-on: ubuntu-latest-low
needs: [changes, phpunit-sqlite]
if: always()
name: phpunit-sqlite-summary
steps:
- name: Summary status
run: if ${{ needs.changes.outputs.src != 'false' && needs.phpunit-sqlite.result != 'success' }}; then exit 1; fi
-50
View File
@@ -1,50 +0,0 @@
# This workflow is provided via the organization template repository
#
# https://github.com/nextcloud/.github
# https://docs.github.com/en/actions/learn-github-actions/sharing-workflows-with-your-organization
# SPDX-FileCopyrightText: 2023-2024 Nextcloud GmbH and Nextcloud contributors
# SPDX-FileCopyrightText: 2023 Marcel Klehr <mklehr@gmx.net>
# SPDX-FileCopyrightText: 2023 Joas Schilling <213943+nickvergessen@users.noreply.github.com>
# SPDX-FileCopyrightText: 2023 Daniel Kesselberg <mail@danielkesselberg.de>
# SPDX-FileCopyrightText: 2023 Florian Steffens <florian.steffens@nextcloud.com>
# SPDX-License-Identifier: MIT
name: 'Ask for feedback on PRs'
on:
schedule:
- cron: '30 1 * * *'
jobs:
pr-feedback:
runs-on: ubuntu-latest
steps:
- name: The get-github-handles-from-website action
uses: marcelklehr/get-github-handles-from-website-action@06b2239db0a48fe1484ba0bfd966a3ab81a08308 # v1.0.1
id: scrape
with:
website: 'https://nextcloud.com/team/'
- name: Get blocklist
id: blocklist
run: |
blocklist=$(curl https://raw.githubusercontent.com/nextcloud/.github/master/non-community-usernames.txt | paste -s -d, -)
echo "blocklist=$blocklist" >> "$GITHUB_OUTPUT"
- uses: nextcloud/pr-feedback-action@1883b38a033fb16f576875e0cf45f98b857655c4 # main
with:
feedback-message: |
Hello there,
Thank you so much for taking the time and effort to create a pull request to our Nextcloud project.
We hope that the review process is going smooth and is helpful for you. We want to ensure your pull request is reviewed to your satisfaction. If you have a moment, our community management team would very much appreciate your feedback on your experience with this PR review process.
Your feedback is valuable to us as we continuously strive to improve our community developer experience. Please take a moment to complete our short survey by clicking on the following link: https://cloud.nextcloud.com/apps/forms/s/i9Ago4EQRZ7TWxjfmeEpPkf6
Thank you for contributing to Nextcloud and we hope to hear from you soon!
(If you believe you should not receive this message, you can add yourself to the [blocklist](https://github.com/nextcloud/.github/blob/master/non-community-usernames.txt).)
days-before-feedback: 14
start-date: '2024-04-30'
exempt-authors: '${{ steps.blocklist.outputs.blocklist }},${{ steps.scrape.outputs.users }}'
exempt-bots: true
-59
View File
@@ -1,59 +0,0 @@
# This workflow is provided via the organization template repository
#
# https://github.com/nextcloud/.github
# https://docs.github.com/en/actions/learn-github-actions/sharing-workflows-with-your-organization
#
# SPDX-FileCopyrightText: 2022-2024 Nextcloud GmbH and Nextcloud contributors
# SPDX-License-Identifier: MIT
name: Static analysis
on: pull_request
concurrency:
group: psalm-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
permissions:
contents: read
jobs:
static-analysis:
runs-on: ubuntu-latest
name: static-psalm-analysis
steps:
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
- name: Get php version
id: versions
uses: icewind1991/nextcloud-version-matrix@58becf3b4bb6dc6cef677b15e2fd8e7d48c0908f # v1.3.1
- name: Check enforcement of minimum PHP version ${{ steps.versions.outputs.php-min }} in psalm.xml
run: grep 'phpVersion="${{ steps.versions.outputs.php-min }}' psalm.xml
- name: Set up php${{ steps.versions.outputs.php-available }}
uses: shivammathur/setup-php@c541c155eee45413f5b09a52248675b1a2575231 # v2.31.1
with:
php-version: ${{ steps.versions.outputs.php-min }}
extensions: bz2, ctype, curl, dom, fileinfo, gd, iconv, intl, json, libxml, mbstring, openssl, pcntl, posix, session, simplexml, xmlreader, xmlwriter, zip, zlib, sqlite, pdo_sqlite
coverage: none
ini-file: development
# Temporary workaround for missing pcntl_* in PHP 8.3
ini-values: disable_functions=
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Install dependencies
run: |
composer remove nextcloud/ocp --dev
composer i
- name: Install nextcloud/ocp
run: composer require --dev nextcloud/ocp:dev-${{ steps.versions.outputs.branches-max }} --ignore-platform-reqs --with-dependencies
- name: Run coding standards check
run: composer run psalm -- --threads=1 --monochrome --no-progress --output-format=github
-27
View File
@@ -1,27 +0,0 @@
# This workflow is provided via the organization template repository
#
# https://github.com/nextcloud/.github
# https://docs.github.com/en/actions/learn-github-actions/sharing-workflows-with-your-organization
# SPDX-FileCopyrightText: 2022 Free Software Foundation Europe e.V. <https://fsfe.org>
#
# SPDX-License-Identifier: CC0-1.0
name: REUSE Compliance Check
on: [pull_request]
permissions:
contents: read
jobs:
reuse-compliance-check:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
- name: REUSE Compliance Check
uses: fsfe/reuse-action@bb774aa972c2a89ff34781233d275075cbddf542 # v5.0.0
@@ -1,58 +0,0 @@
# This workflow is provided via the organization template repository
#
# https://github.com/nextcloud/.github
# https://docs.github.com/en/actions/learn-github-actions/sharing-workflows-with-your-organization
#
# SPDX-FileCopyrightText: 2023-2024 Nextcloud GmbH and Nextcloud contributors
# SPDX-License-Identifier: MIT
name: Auto approve nextcloud/ocp
on:
pull_request_target: # zizmor: ignore[dangerous-triggers]
branches:
- main
- master
- stable*
permissions:
contents: read
concurrency:
group: update-nextcloud-ocp-approve-merge-${{ github.head_ref || github.run_id }}
cancel-in-progress: true
jobs:
auto-approve-merge:
if: github.actor == 'nextcloud-command'
runs-on: ubuntu-latest-low
permissions:
# for hmarr/auto-approve-action to approve PRs
pull-requests: write
# for alexwilson/enable-github-automerge-action to approve PRs
contents: write
steps:
- name: Disabled on forks
if: ${{ github.event.pull_request.head.repo.full_name != github.repository }}
run: |
echo 'Can not approve PRs from forks'
exit 1
- uses: mdecoleman/pr-branch-name@55795d86b4566d300d237883103f052125cc7508 # v3.0.0
id: branchname
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
# GitHub actions bot approve
- uses: hmarr/auto-approve-action@b40d6c9ed2fa10c9a2749eca7eb004418a705501 # v2
if: startsWith(steps.branchname.outputs.branch, 'automated/noid/') && endsWith(steps.branchname.outputs.branch, 'update-nextcloud-ocp')
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
# Enable GitHub auto merge
- name: Auto merge
uses: alexwilson/enable-github-automerge-action@56e3117d1ae1540309dc8f7a9f2825bc3c5f06ff # v2.0.0
if: startsWith(steps.branchname.outputs.branch, 'automated/noid/') && endsWith(steps.branchname.outputs.branch, 'update-nextcloud-ocp')
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
-123
View File
@@ -1,123 +0,0 @@
# This workflow is provided via the organization template repository
#
# https://github.com/nextcloud/.github
# https://docs.github.com/en/actions/learn-github-actions/sharing-workflows-with-your-organization
#
# SPDX-FileCopyrightText: 2022-2024 Nextcloud GmbH and Nextcloud contributors
# SPDX-License-Identifier: MIT
name: Update nextcloud/ocp
on:
workflow_dispatch:
schedule:
- cron: "5 2 * * 0"
permissions:
contents: read
jobs:
update-nextcloud-ocp:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
branches: ['main', 'master', 'stable30', 'stable29', 'stable28']
name: update-nextcloud-ocp-${{ matrix.branches }}
steps:
- id: checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
ref: ${{ matrix.branches }}
submodules: true
continue-on-error: true
- name: Set up php8.2
if: steps.checkout.outcome == 'success'
uses: shivammathur/setup-php@c541c155eee45413f5b09a52248675b1a2575231 # v2.31.1
with:
php-version: 8.2
# https://docs.nextcloud.com/server/stable/admin_manual/installation/source_installation.html#prerequisites-for-manual-installation
extensions: bz2, ctype, curl, dom, fileinfo, gd, iconv, intl, json, libxml, mbstring, openssl, pcntl, posix, session, simplexml, xmlreader, xmlwriter, zip, zlib, sqlite, pdo_sqlite
coverage: none
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Read codeowners
if: steps.checkout.outcome == 'success'
id: codeowners
run: |
grep '/appinfo/info.xml' .github/CODEOWNERS | cut -f 2- -d ' ' | xargs | awk '{ print "codeowners="$0 }' >> $GITHUB_OUTPUT
continue-on-error: true
- name: Composer install
if: steps.checkout.outcome == 'success'
run: composer install
- name: Composer update nextcloud/ocp
id: update_branch
if: ${{ steps.checkout.outcome == 'success' && matrix.branches != 'main' }}
run: composer require --dev 'nextcloud/ocp:dev-${{ matrix.branches }}'
- name: Raise on issue on failure
uses: dacbd/create-issue-action@cdb57ab6ff8862aa09fee2be6ba77a59581921c2 # v2.0.0
if: ${{ steps.checkout.outcome == 'success' && failure() && steps.update_branch.conclusion == 'failure' }}
with:
token: ${{ secrets.GITHUB_TOKEN }}
title: 'Failed to update nextcloud/ocp package on branch ${{ matrix.branches }}'
body: 'Please check the output of the GitHub action and manually resolve the issues<br>${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}<br>${{ steps.codeowners.outputs.codeowners }}'
- name: Composer update nextcloud/ocp
id: update_main
if: ${{ steps.checkout.outcome == 'success' && matrix.branches == 'main' }}
run: composer require --dev nextcloud/ocp:dev-master
- name: Raise on issue on failure
uses: dacbd/create-issue-action@cdb57ab6ff8862aa09fee2be6ba77a59581921c2 # v2.0.0
if: ${{ steps.checkout.outcome == 'success' && failure() && steps.update_main.conclusion == 'failure' }}
with:
token: ${{ secrets.GITHUB_TOKEN }}
title: 'Failed to update nextcloud/ocp package on branch ${{ matrix.branches }}'
body: 'Please check the output of the GitHub action and manually resolve the issues<br>${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}<br>${{ steps.codeowners.outputs.codeowners }}'
- name: Reset checkout 3rdparty
if: steps.checkout.outcome == 'success'
run: |
git clean -f 3rdparty
git checkout 3rdparty
continue-on-error: true
- name: Reset checkout vendor
if: steps.checkout.outcome == 'success'
run: |
git clean -f vendor
git checkout vendor
continue-on-error: true
- name: Reset checkout vendor-bin
if: steps.checkout.outcome == 'success'
run: |
git clean -f vendor-bin
git checkout vendor-bin
continue-on-error: true
- name: Create Pull Request
if: steps.checkout.outcome == 'success'
uses: peter-evans/create-pull-request@5e914681df9dc83aa4e4905692ca88beb2f9e91f # v7.0.5
with:
token: ${{ secrets.COMMAND_BOT_PAT }}
commit-message: 'chore(dev-deps): Bump nextcloud/ocp package'
committer: GitHub <noreply@github.com>
author: nextcloud-command <nextcloud-command@users.noreply.github.com>
signoff: true
branch: 'automated/noid/${{ matrix.branches }}-update-nextcloud-ocp'
title: '[${{ matrix.branches }}] Update nextcloud/ocp dependency'
body: |
Auto-generated update of [nextcloud/ocp](https://github.com/nextcloud-deps/ocp/) dependency
labels: |
dependencies
3. to review
+1
View File
@@ -6,6 +6,7 @@
- Adrian Missy <adrian.missy@onewavestudios.com> - Adrian Missy <adrian.missy@onewavestudios.com>
- Alexandru Puiu <alexpuiu20@yahoo.com> - Alexandru Puiu <alexpuiu20@yahoo.com>
- Arne Bartelt <arne.bartelt@gmail.com>
- Chandi Langecker <git@chandi.it> - Chandi Langecker <git@chandi.it>
- Christoph Wurst <christoph@winzerhof-wurst.at> - Christoph Wurst <christoph@winzerhof-wurst.at>
- Gary Kim <gary@garykim.dev> - Gary Kim <gary@garykim.dev>
+7 -55
View File
@@ -5,64 +5,16 @@
# Changelog # Changelog
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
# 1.15.3 ## 1.16.0-beta.1
### Fixed
* [stable31] fix: Use getId() method for card ID retrieval by @backportbot[bot] in https://github.com/nextcloud/deck/pull/7144
* [stable31]fix: ensure correct type when filtering events by @backportbot[bot] in https://github.com/nextcloud/deck/pull/7148
* [stable31] fix: do not change focus when card id stays the same by @backportbot[bot] in https://github.com/nextcloud/deck/pull/7173
* [stable31] fix(darkmode): Fix activity icon colors by @backportbot[bot] in https://github.com/nextcloud/deck/pull/7240
* [stable31] fix: make comments with mention editable @backportbot[bot] in https://github.com/nextcloud/deck/pull/7198
* [stable31] fix: redirect to cleaner URL if RewriteBase is enabled by @backportbot[bot] in https://github.com/nextcloud/deck/pull/7267
* [stable31] fix: missing push notifications by @backportbot[bot] in https://github.com/nextcloud/deck/pull/7269
* [stable31] fix: use text cursor for card title on dashboard by @backportbot[bot] in https://github.com/nextcloud/deck/pull/7274
* [stable31] fix: parse arguments to CardService.reorder correctly to int by @backportbot[bot] in https://github.com/nextcloud/deck/pull/7275
### Improvements
* [stable31] Clean attachment sharing records after permanent deleted by @backportbot[bot] in https://github.com/nextcloud/deck/pull/7180
* Chore(deps): Bump @nextcloud/vue from 8.22.0 to 8.31.0 by @luka-nextcloud in https://github.com/nextcloud/deck/pull/7272
# 1.15.2
### Fixed
* [stable31] fix: update DeleteCron to remove deleted lists by @backportbot[bot] in https://github.com/nextcloud/deck/pull/7029
* [stable31] fix: not show Share with a Deck card for unauthorized users by @backportbot[bot] in https://github.com/nextcloud/deck/pull/7061
* [stable31] fix: unstable cypress test by @backportbot[bot] in https://github.com/nextcloud/deck/pull/7063
* [stable31] fix: add retry and show warning on description saving error by @backportbot[bot] in https://github.com/nextcloud/deck/pull/7091
* [stable31] fix: styling for new stack input field by @backportbot[bot] in https://github.com/nextcloud/deck/pull/7113
* [stable31] fix:allow foreign label deletion by @backportbot[bot] in https://github.com/nextcloud/deck/pull/7130
* [stable31] fix: acl check when delete, update board acl by @backportbot[bot] in https://github.com/nextcloud/deck/pull/7132
### Improvements
* [stable31] perf: don't enrich cards when finding calendar entries by @backportbot[bot] in https://github.com/nextcloud/deck/pull/6959
## 1.15.1
### Fixed
* [stable31] fix: Properly show attachment extension by @backportbot in https://github.com/nextcloud/deck/pull/6701
* [stable31] Clear selected stack when selected board changed by @backportbot in https://github.com/nextcloud/deck/pull/6915
* [stable31] fix: Use strings as rich object ids by @backportbot in https://github.com/nextcloud/deck/pull/6951
* [stable31] fix: Limit label actions to labels of the cards board by @backportbot in https://github.com/nextcloud/deck/pull/6954
* [stable31] fix: only delete assignments on unshared board by @grnd-alt in https://github.com/nextcloud/deck/pull/6934
### Improvements
* [stable31] perf: Skip doing a query just to check if a board is deleted by @backportbot in https://github.com/nextcloud/deck/pull/6894
## 1.15.0
### Fixed
- Fix: Adapt URLs generated in the backend to new routes #6743
- Fix npm audit #6719
- Fix: skip exporting a deleted card #6723
## 1.15.0-beta.2
### Added ### Added
- feat: update default content @luka-nextcloud [#6740](https://github.com/nextcloud/deck/pull/6740)
- feat: add board import and export @luka-nextcloud [#6872](https://github.com/nextcloud/deck/pull/6872)
- feat: use outline icons @luka-nextcloud [#7114](https://github.com/nextcloud/deck/pull/7114)
- Add OCC commands for global calendar feature opt-in and opt-out in Deck @Fledermaus-20 [#7080](https://github.com/nextcloud/deck/pull/7080)
- feat: Implement reference resolving for cards that have a link in the title @juliusknorr [#6286](https://github.com/nextcloud/deck/pull/6286) ### Fixed
- CSV export fixes @gidan80 [#6800](https://github.com/nextcloud/deck/pull/6800)
### Other
- Remove old project from README @edent [#6658](https://github.com/nextcloud/deck/pull/6658)
- devcontainer(image): Fix package path @niclasheinz [#6653](https://github.com/nextcloud/deck/pull/6653)
- remove deprecated nextcloud-vue-collections @grnd-alt [#6664](https://github.com/nextcloud/deck/pull/6664)
- fix: set cypress ci server version to stable31 @grnd-alt [#6705](https://github.com/nextcloud/deck/pull/6705)
## 1.15.0-beta.1 ## 1.15.0-beta.1
### Fixed ### Fixed
+1 -1
View File
@@ -24,7 +24,7 @@ Deck is a kanban style organization tool aimed at personal planning and project
### Mobile apps ### Mobile apps
- [Nextcloud Deck app for Android](https://github.com/stefan-niedermann/nextcloud-deck) - It is available in [F-Droid](https://f-droid.org/de/packages/it.niedermann.nextcloud.deck/) and the [Google Play Store](https://play.google.com/store/apps/details?id=it.niedermann.nextcloud.deck.play) - [Nextcloud Deck app for Android](https://github.com/stefan-niedermann/nextcloud-deck) - It is available in [F-Droid](https://f-droid.org/de/packages/it.niedermann.nextcloud.deck/) and the [Google Play Store](https://play.google.com/store/apps/details?id=it.niedermann.nextcloud.deck.play)
- [Nextcloud Deck app for iOS](https://github.com/StCyr/deck-react-native) - It is available in [Apple App store](https://apps.apple.com/ml/app/nextcloud-deck/id1570892788) - Nextcloud Deck app for iOS - It is available in [Apple App store](https://apps.apple.com/de/app/next-deck/id6752478755)
### 3rd-Party Integrations ### 3rd-Party Integrations
+1 -1
View File
@@ -12,7 +12,7 @@ SPDX-FileCopyrightText = "none"
SPDX-License-Identifier = "CC0-1.0" SPDX-License-Identifier = "CC0-1.0"
[[annotations]] [[annotations]]
path = ["l10n/**.js", "l10n/**.json", "js/**.js.map", "js/**.js", "js/**.mjs", "js/**.mjs.map", "js/templates/**.handlebars", "lib/Service/Importer/fixtures/config-deckJson-schema.json", "lib/Service/Importer/fixtures/config-trelloApi-schema.json", "lib/Service/Importer/fixtures/config-trelloJson-schema.json", "screenshots/screenshot1.png", "src/assets/file-placeholder.svg", "img/favicon.ico", "img/favicon.png", "img/favicon.svg", "img/activity.svg", "img/activity-dark.svg", "img/deck.svg", "img/deck-current.svg", "img/deck-dark.svg", "img/details-white.svg", "img/card.svg"] path = ["l10n/**.js", "l10n/**.json", "js/**.js.map", "js/**.js", "js/**.mjs", "js/**.mjs.map", "js/templates/**.handlebars", "lib/Service/Importer/fixtures/config-deckJson-schema.json", "lib/Service/Importer/fixtures/config-trelloApi-schema.json", "lib/Service/Importer/fixtures/config-trelloJson-schema.json", "lib/Service/fixtures/default-board.json", "screenshots/screenshot1.png", "src/assets/file-placeholder.svg", "img/favicon.ico", "img/favicon.png", "img/favicon.svg", "img/activity.svg", "img/activity-dark.svg", "img/deck.svg", "img/deck-current.svg", "img/deck-dark.svg", "img/details-white.svg", "img/card.svg", "img/sample-image.jpg"]
precedence = "aggregate" precedence = "aggregate"
SPDX-FileCopyrightText = "2019 Nextcloud GmbH and Nextcloud contributors" SPDX-FileCopyrightText = "2019 Nextcloud GmbH and Nextcloud contributors"
SPDX-License-Identifier = "AGPL-3.0-or-later" SPDX-License-Identifier = "AGPL-3.0-or-later"
+6 -2
View File
@@ -20,7 +20,7 @@
- 🚀 Get your project organized - 🚀 Get your project organized
</description> </description>
<version>1.15.3</version> <version>2.0.0-dev.0</version>
<licence>agpl</licence> <licence>agpl</licence>
<author>Julius Härtl</author> <author>Julius Härtl</author>
<namespace>Deck</namespace> <namespace>Deck</namespace>
@@ -42,7 +42,7 @@
<database min-version="9.4">pgsql</database> <database min-version="9.4">pgsql</database>
<database>sqlite</database> <database>sqlite</database>
<database min-version="8.0">mysql</database> <database min-version="8.0">mysql</database>
<nextcloud min-version="31" max-version="31"/> <nextcloud min-version="33" max-version="33"/>
</dependencies> </dependencies>
<background-jobs> <background-jobs>
<job>OCA\Deck\Cron\DeleteCron</job> <job>OCA\Deck\Cron\DeleteCron</job>
@@ -54,11 +54,15 @@
<live-migration> <live-migration>
<step>OCA\Deck\Migration\DeletedCircleCleanup</step> <step>OCA\Deck\Migration\DeletedCircleCleanup</step>
</live-migration> </live-migration>
<post-migration>
<step>OCA\Deck\Migration\LabelMismatchCleanup</step>
</post-migration>
</repair-steps> </repair-steps>
<commands> <commands>
<command>OCA\Deck\Command\UserExport</command> <command>OCA\Deck\Command\UserExport</command>
<command>OCA\Deck\Command\BoardImport</command> <command>OCA\Deck\Command\BoardImport</command>
<command>OCA\Deck\Command\TransferOwnership</command> <command>OCA\Deck\Command\TransferOwnership</command>
<command>OCA\Deck\Command\CalendarToggle</command>
</commands> </commands>
<activity> <activity>
<settings> <settings>
+1
View File
@@ -29,6 +29,7 @@ return [
['name' => 'board#clone', 'url' => '/boards/{boardId}/clone', 'verb' => 'POST'], ['name' => 'board#clone', 'url' => '/boards/{boardId}/clone', 'verb' => 'POST'],
['name' => 'board#transferOwner', 'url' => '/boards/{boardId}/transferOwner', 'verb' => 'PUT'], ['name' => 'board#transferOwner', 'url' => '/boards/{boardId}/transferOwner', 'verb' => 'PUT'],
['name' => 'board#export', 'url' => '/boards/{boardId}/export', 'verb' => 'GET'], ['name' => 'board#export', 'url' => '/boards/{boardId}/export', 'verb' => 'GET'],
['name' => 'board#import', 'url' => '/boards/import', 'verb' => 'POST'],
// stacks // stacks
['name' => 'stack#index', 'url' => '/stacks/{boardId}', 'verb' => 'GET'], ['name' => 'stack#index', 'url' => '/stacks/{boardId}', 'verb' => 'GET'],
+11 -7
View File
@@ -9,31 +9,35 @@
} }
], ],
"require": { "require": {
"justinrainbow/json-schema": "^6.0" "justinrainbow/json-schema": "^6.0",
"bamarni/composer-bin-plugin": "^1.8"
}, },
"require-dev": { "require-dev": {
"roave/security-advisories": "dev-master", "roave/security-advisories": "dev-master",
"phpunit/phpunit": "^9", "phpunit/phpunit": "^9",
"nextcloud/coding-standard": "^1.1", "nextcloud/coding-standard": "^1.1",
"nextcloud/ocp": "dev-stable31", "nextcloud/ocp": "dev-master"
"psalm/phar": "^5.13"
}, },
"config": { "config": {
"optimize-autoloader": true, "optimize-autoloader": true,
"allow-plugins": { "allow-plugins": {
"composer/package-versions-deprecated": true "composer/package-versions-deprecated": true,
"bamarni/composer-bin-plugin": true
}, },
"platform": { "platform": {
"php": "8.1" "php": "8.1"
} }
}, },
"scripts": { "scripts": {
"post-install-cmd": [
"@composer bin all install --ansi"
],
"lint": "find . -name \\*.php -not -path './vendor/*' -print0 | xargs -0 -n1 php -l", "lint": "find . -name \\*.php -not -path './vendor/*' -print0 | xargs -0 -n1 php -l",
"cs:check": "php-cs-fixer fix --dry-run --diff", "cs:check": "php-cs-fixer fix --dry-run --diff",
"cs:fix": "php-cs-fixer fix", "cs:fix": "php-cs-fixer fix",
"psalm": "psalm.phar", "psalm": "psalm",
"psalm:update-baseline": "psalm.phar --update-baseline", "psalm:update-baseline": "psalm --threads=$(nproc) --no-cache --update-baseline",
"psalm:fix": "psalm.phar --alter --issues=InvalidReturnType,InvalidNullableReturnType,MismatchingDocblockParamType,MismatchingDocblockReturnType,MissingParamType,InvalidFalsableReturnType", "psalm:fix": "psalm --alter --issues=InvalidReturnType,InvalidNullableReturnType,MismatchingDocblockParamType,MismatchingDocblockReturnType,MissingParamType,InvalidFalsableReturnType",
"test": [ "test": [
"@test:unit", "@test:unit",
"@test:integration" "@test:integration"
Generated
+54 -143
View File
@@ -4,146 +4,91 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "c89537a172cee5c19093b4ea0cb5365c", "content-hash": "263f9ff9e6a13d50ab09bc9f4e06b749",
"packages": [ "packages": [
{ {
"name": "icecave/parity", "name": "bamarni/composer-bin-plugin",
"version": "1.0.0", "version": "1.8.2",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/icecave/parity.git", "url": "https://github.com/bamarni/composer-bin-plugin.git",
"reference": "0109fef58b3230d23b20b2ac52ecdf477218d300" "reference": "92fd7b1e6e9cdae19b0d57369d8ad31a37b6a880"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/icecave/parity/zipball/0109fef58b3230d23b20b2ac52ecdf477218d300", "url": "https://api.github.com/repos/bamarni/composer-bin-plugin/zipball/92fd7b1e6e9cdae19b0d57369d8ad31a37b6a880",
"reference": "0109fef58b3230d23b20b2ac52ecdf477218d300", "reference": "92fd7b1e6e9cdae19b0d57369d8ad31a37b6a880",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"icecave/repr": "~1", "composer-plugin-api": "^2.0",
"php": ">=5.3" "php": "^7.2.5 || ^8.0"
}, },
"require-dev": { "require-dev": {
"eloquent/liberator": "~1", "composer/composer": "^2.0",
"icecave/archer": "~1" "ext-json": "*",
"phpstan/extension-installer": "^1.1",
"phpstan/phpstan": "^1.8",
"phpstan/phpstan-phpunit": "^1.1",
"phpunit/phpunit": "^8.5 || ^9.5",
"symfony/console": "^2.8.52 || ^3.4.35 || ^4.4 || ^5.0 || ^6.0",
"symfony/finder": "^2.8.52 || ^3.4.35 || ^4.4 || ^5.0 || ^6.0",
"symfony/process": "^2.8.52 || ^3.4.35 || ^4.4 || ^5.0 || ^6.0"
}, },
"suggest": { "type": "composer-plugin",
"eloquent/asplode": "Drop-in exception-based error handling." "extra": {
"class": "Bamarni\\Composer\\Bin\\BamarniBinPlugin"
}, },
"type": "library",
"autoload": {
"psr-0": {
"Icecave\\Parity": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "James Harris",
"email": "james.harris@icecave.com.au",
"homepage": "https://github.com/jmalloc"
}
],
"description": "A customizable deep comparison library.",
"homepage": "https://github.com/IcecaveStudios/parity",
"keywords": [
"compare",
"comparison",
"equal",
"equality",
"greater",
"less",
"sort",
"sorting"
],
"support": {
"issues": "https://github.com/icecave/parity/issues",
"source": "https://github.com/icecave/parity/tree/1.0.0"
},
"time": "2014-01-17T05:56:27+00:00"
},
{
"name": "icecave/repr",
"version": "1.0.1",
"source": {
"type": "git",
"url": "https://github.com/icecave/repr.git",
"reference": "8a3d2953adf5f464a06e3e2587aeacc97e2bed07"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/icecave/repr/zipball/8a3d2953adf5f464a06e3e2587aeacc97e2bed07",
"reference": "8a3d2953adf5f464a06e3e2587aeacc97e2bed07",
"shasum": ""
},
"require": {
"php": ">=5.3"
},
"require-dev": {
"icecave/archer": "~1"
},
"suggest": {
"eloquent/asplode": "Drop-in exception-based error handling."
},
"type": "library",
"autoload": { "autoload": {
"psr-4": { "psr-4": {
"Icecave\\Repr\\": "src" "Bamarni\\Composer\\Bin\\": "src"
} }
}, },
"notification-url": "https://packagist.org/downloads/", "notification-url": "https://packagist.org/downloads/",
"license": [ "license": [
"MIT" "MIT"
], ],
"authors": [ "description": "No conflicts for your bin dependencies",
{
"name": "James Harris",
"email": "james.harris@icecave.com.au",
"homepage": "https://github.com/jmalloc"
}
],
"description": "A library for generating string representations of any value, inspired by Python's reprlib library.",
"homepage": "https://github.com/IcecaveStudios/repr",
"keywords": [ "keywords": [
"human", "composer",
"readable", "conflict",
"repr", "dependency",
"representation", "executable",
"string" "isolation",
"tool"
], ],
"support": { "support": {
"issues": "https://github.com/icecave/repr/issues", "issues": "https://github.com/bamarni/composer-bin-plugin/issues",
"source": "https://github.com/icecave/repr/tree/1.0.1" "source": "https://github.com/bamarni/composer-bin-plugin/tree/1.8.2"
}, },
"time": "2014-07-25T05:44:41+00:00" "time": "2022-10-31T08:38:03+00:00"
}, },
{ {
"name": "justinrainbow/json-schema", "name": "justinrainbow/json-schema",
"version": "6.0.0", "version": "6.4.2",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/jsonrainbow/json-schema.git", "url": "https://github.com/jsonrainbow/json-schema.git",
"reference": "a38c6198d53b09c0702f440585a4f4a5d9137bd9" "reference": "ce1fd2d47799bb60668643bc6220f6278a4c1d02"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/jsonrainbow/json-schema/zipball/a38c6198d53b09c0702f440585a4f4a5d9137bd9", "url": "https://api.github.com/repos/jsonrainbow/json-schema/zipball/ce1fd2d47799bb60668643bc6220f6278a4c1d02",
"reference": "a38c6198d53b09c0702f440585a4f4a5d9137bd9", "reference": "ce1fd2d47799bb60668643bc6220f6278a4c1d02",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"icecave/parity": "1.0.0", "ext-json": "*",
"marc-mabe/php-enum": "^2.0 || ^3.0 || ^4.0", "marc-mabe/php-enum": "^4.0",
"php": ">=5.3.3" "php": "^7.2 || ^8.0"
}, },
"require-dev": { "require-dev": {
"friendsofphp/php-cs-fixer": "~2.2.20 || ~2.19.0", "friendsofphp/php-cs-fixer": "3.3.0",
"json-schema/json-schema-test-suite": "1.2.0", "json-schema/json-schema-test-suite": "1.2.0",
"phpunit/phpunit": "^4.8.35" "marc-mabe/php-enum-phpstan": "^2.0",
"phpspec/prophecy": "^1.19",
"phpstan/phpstan": "^1.12",
"phpunit/phpunit": "^8.5"
}, },
"bin": [ "bin": [
"bin/validate-json" "bin/validate-json"
@@ -189,9 +134,9 @@
], ],
"support": { "support": {
"issues": "https://github.com/jsonrainbow/json-schema/issues", "issues": "https://github.com/jsonrainbow/json-schema/issues",
"source": "https://github.com/jsonrainbow/json-schema/tree/6.0.0" "source": "https://github.com/jsonrainbow/json-schema/tree/6.4.2"
}, },
"time": "2024-07-30T17:49:21+00:00" "time": "2025-06-03T18:27:04+00:00"
}, },
{ {
"name": "marc-mabe/php-enum", "name": "marc-mabe/php-enum",
@@ -488,16 +433,16 @@
}, },
{ {
"name": "nextcloud/ocp", "name": "nextcloud/ocp",
"version": "dev-stable31", "version": "dev-master",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/nextcloud-deps/ocp.git", "url": "https://github.com/nextcloud-deps/ocp.git",
"reference": "abd32429d794ede1d92b7b0a88a1070371c907b5" "reference": "9a2e6c0bf6f2d87e1db8d18063a5bedf85040bb2"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/nextcloud-deps/ocp/zipball/abd32429d794ede1d92b7b0a88a1070371c907b5", "url": "https://api.github.com/repos/nextcloud-deps/ocp/zipball/9a2e6c0bf6f2d87e1db8d18063a5bedf85040bb2",
"reference": "abd32429d794ede1d92b7b0a88a1070371c907b5", "reference": "9a2e6c0bf6f2d87e1db8d18063a5bedf85040bb2",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@@ -507,10 +452,11 @@
"psr/event-dispatcher": "^1.0", "psr/event-dispatcher": "^1.0",
"psr/log": "^3.0.2" "psr/log": "^3.0.2"
}, },
"default-branch": true,
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-stable31": "31.0.0-dev" "dev-master": "33.0.0-dev"
} }
}, },
"notification-url": "https://packagist.org/downloads/", "notification-url": "https://packagist.org/downloads/",
@@ -530,9 +476,9 @@
"description": "Composer package containing Nextcloud's public OCP API and the unstable NCU API", "description": "Composer package containing Nextcloud's public OCP API and the unstable NCU API",
"support": { "support": {
"issues": "https://github.com/nextcloud-deps/ocp/issues", "issues": "https://github.com/nextcloud-deps/ocp/issues",
"source": "https://github.com/nextcloud-deps/ocp/tree/stable31" "source": "https://github.com/nextcloud-deps/ocp/tree/master"
}, },
"time": "2025-07-31T00:57:37+00:00" "time": "2025-09-27T00:45:05+00:00"
}, },
{ {
"name": "nikic/php-parser", "name": "nikic/php-parser",
@@ -1184,41 +1130,6 @@
], ],
"time": "2024-12-05T13:48:26+00:00" "time": "2024-12-05T13:48:26+00:00"
}, },
{
"name": "psalm/phar",
"version": "5.26.1",
"source": {
"type": "git",
"url": "https://github.com/psalm/phar.git",
"reference": "8a38e7ad04499a0ccd2c506fd1da6fc01fff4547"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/psalm/phar/zipball/8a38e7ad04499a0ccd2c506fd1da6fc01fff4547",
"reference": "8a38e7ad04499a0ccd2c506fd1da6fc01fff4547",
"shasum": ""
},
"require": {
"php": "^7.1 || ^8.0"
},
"conflict": {
"vimeo/psalm": "*"
},
"bin": [
"psalm.phar"
],
"type": "library",
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"description": "Composer-based Psalm Phar",
"support": {
"issues": "https://github.com/psalm/phar/issues",
"source": "https://github.com/psalm/phar/tree/5.26.1"
},
"time": "2024-09-09T16:22:43+00:00"
},
{ {
"name": "psr/clock", "name": "psr/clock",
"version": "1.0.0", "version": "1.0.0",
+1
View File
@@ -14,6 +14,7 @@
input[type=submit].icon-confirm { input[type=submit].icon-confirm {
border-color: var(--color-border-maxcontrast) !important; border-color: var(--color-border-maxcontrast) !important;
border-style: solid;
border-left: none; border-left: none;
} }
+78
View File
@@ -129,3 +129,81 @@ describe('Board cloning', function() {
}) })
}) })
}) })
describe('Board export', function() {
before(function() {
cy.createUser(user)
})
it('Exports a board as JSON', function() {
const boardName = 'Export JSON board'
const board = sampleBoard(boardName)
cy.createExampleBoard({ user, board }).then((board) => {
const boardId = board.id
cy.visit(`/apps/deck/board/${boardId}`)
cy.get('.app-navigation__list .app-navigation-entry:contains("' + boardName + '")')
.parent()
.find('button[aria-label="Actions"]')
.click()
cy.get('button:contains("Export board")')
.click()
cy.get('.modal-container .checkbox-radio-switch__text:contains("Export as JSON")')
.click()
cy.get('.modal-container button:contains("Export")')
.click()
const downloadsFolder = Cypress.config('downloadsFolder')
cy.readFile(`${downloadsFolder}/${boardName}.json`)
})
})
it('Exports a board as CSV', function() {
const boardName = 'Export CSV board'
const board = sampleBoard(boardName)
cy.createExampleBoard({ user, board }).then((board) => {
const boardId = board.id
cy.visit(`/apps/deck/board/${boardId}`)
cy.get('.app-navigation__list .app-navigation-entry:contains("' + boardName + '")')
.parent()
.find('button[aria-label="Actions"]')
.click()
cy.get('button:contains("Export board")')
.click()
cy.get('.modal-container .checkbox-radio-switch__text:contains("Export as CSV")')
.click()
cy.get('.modal-container button:contains("Export")')
.click()
const downloadsFolder = Cypress.config('downloadsFolder')
cy.readFile(`${downloadsFolder}/${boardName}.csv`)
})
})
})
describe('Board import', function() {
before(function () {
cy.createUser(user)
})
beforeEach(function() {
cy.login(user)
cy.visit('/apps/deck')
})
it('Imports a board from JSON', function() {
cy.get('#app-navigation-vue .app-navigation__list .app-navigation-entry:contains("Import board")')
.should('be.visible')
.click()
// Upload a JSON file
cy.get('input[type="file"]')
.selectFile([
{
contents: 'cypress/fixtures/import-board.json',
fileName: 'import-board.json',
},
], { force: true })
cy.get('.app-navigation__list .app-navigation-entry:contains("Imported board")')
.should('be.visible')
})
})
+3
View File
@@ -302,6 +302,9 @@ describe('Card', function () {
.first().click() .first().click()
cy.get(`.card:contains("${newCardTitle}")`).should('be.visible').click() cy.get(`.card:contains("${newCardTitle}")`).should('be.visible').click()
// Add delay to ensure the events are bound
cy.wait(1000)
cy.get('#app-sidebar-vue [data-test="tag-selector"] .vs__dropdown-toggle').should('be.visible').click() cy.get('#app-sidebar-vue [data-test="tag-selector"] .vs__dropdown-toggle').should('be.visible').click()
cy.get('.vs__dropdown-menu .tag:contains("Action needed")').should('be.visible').click() cy.get('.vs__dropdown-menu .tag:contains("Action needed")').should('be.visible').click()
cy.get('.vs__dropdown-menu .tag:contains("Later")').should('be.visible').click() cy.get('.vs__dropdown-menu .tag:contains("Later")').should('be.visible').click()
+2 -2
View File
@@ -22,10 +22,10 @@ describe('Deck dashboard', function() {
.should($el => expect($el.text().trim()).to.equal('Upcoming cards')) .should($el => expect($el.text().trim()).to.equal('Upcoming cards'))
}) })
it('Can see the default "Personal Board" created for user by default', function() { it('Can see the default "Welcome Board" created for user by default', function() {
cy.visit('/apps/deck') cy.visit('/apps/deck')
const defaultBoard = 'Personal' const defaultBoard = 'Welcome to Nextcloud Deck!'
cy.get('.app-navigation-entry-wrapper[icon=icon-deck]') cy.get('.app-navigation-entry-wrapper[icon=icon-deck]')
.find('ul.app-navigation-entry__children .app-navigation-entry:contains(' + defaultBoard + ')') .find('ul.app-navigation-entry__children .app-navigation-entry:contains(' + defaultBoard + ')')
+102
View File
@@ -0,0 +1,102 @@
{
"boards": [
{
"id": 70,
"title": "Imported board",
"owner": "unvjrmwuag",
"color": "00ff00",
"archived": false,
"labels": [
{
"id": 293,
"title": "Finished",
"color": "31CC7C",
"boardId": 70,
"cardId": null,
"lastModified": 0,
"ETag": "cfcd208495d565ef66e7dff9f98764da"
},
{
"id": 294,
"title": "To review",
"color": "317CCC",
"boardId": 70,
"cardId": null,
"lastModified": 0,
"ETag": "cfcd208495d565ef66e7dff9f98764da"
},
{
"id": 295,
"title": "Action needed",
"color": "FF7A66",
"boardId": 70,
"cardId": null,
"lastModified": 0,
"ETag": "cfcd208495d565ef66e7dff9f98764da"
},
{
"id": 296,
"title": "Later",
"color": "F1DB50",
"boardId": 70,
"cardId": null,
"lastModified": 0,
"ETag": "cfcd208495d565ef66e7dff9f98764da"
}
],
"acl": [],
"permissions": [],
"users": [],
"stacks": {
"114": {
"id": 114,
"title": "TestList",
"boardId": 70,
"deletedAt": 0,
"lastModified": 1743495533,
"cards": [
{
"id": 124,
"title": "Hello world",
"description": "# Hello world",
"descriptionPrev": null,
"stackId": 114,
"type": "plain",
"lastModified": 1743495533,
"lastEditor": null,
"createdAt": 1743495533,
"labels": [],
"assignedUsers": null,
"attachments": null,
"attachmentCount": null,
"owner": {
"primaryKey": "unvjrmwuag",
"uid": "unvjrmwuag",
"displayname": "unvjrmwuag",
"type": 0
},
"order": 999,
"archived": false,
"done": null,
"duedate": null,
"notified": false,
"deletedAt": 0,
"commentsUnread": 0,
"commentsCount": 0,
"relatedStack": null,
"relatedBoard": null,
"ETag": "aa85bb973089e7fbc0bbf122e926c23f"
}
],
"order": 0,
"ETag": "aa85bb973089e7fbc0bbf122e926c23f"
}
},
"activeSessions": [],
"deletedAt": 0,
"lastModified": 1743495533,
"settings": [],
"ETag": "aa85bb973089e7fbc0bbf122e926c23f"
}
]
}
+5 -1
View File
@@ -102,7 +102,11 @@ Cypress.Commands.add('shareBoardWithUi', (query, userId=query) => {
cy.intercept({ method: 'GET', url: `**/ocs/v2.php/apps/files_sharing/api/v1/sharees?search=${query}*` }).as('fetchRecipients') cy.intercept({ method: 'GET', url: `**/ocs/v2.php/apps/files_sharing/api/v1/sharees?search=${query}*` }).as('fetchRecipients')
cy.get('[aria-label="Open details"]').click() cy.get('[aria-label="Open details"]').click()
cy.get('.app-sidebar').should('be.visible') cy.get('.app-sidebar').should('be.visible')
cy.get('.select input').type(`${query}`)
// Add delay to ensure the events are bound
cy.wait(1000)
cy.get('.select input').click().type(`${query}`)
cy.wait('@fetchRecipients', { timeout: 7000 }) cy.wait('@fetchRecipients', { timeout: 7000 })
cy.get('.vs__dropdown-menu .option').first().contains(query) cy.get('.vs__dropdown-menu .option').first().contains(query)
+14 -10
View File
@@ -6,7 +6,7 @@ The REST API provides access for authenticated users to their data inside the De
# Prerequisites # Prerequisites
- All requests require a `OCS-APIRequest` HTTP header to be set to `true` and a `Content-Type` of `application/json`. - All requests require a `OCS-APIRequest` HTTP header to be set to `true` and a `Content-Type` of `application/json`. This does not apply to the endpoint for uploading attachments, which consumes `multipart/form-data`.
- The API is located at https://nextcloud.local/index.php/apps/deck/api/v1.0 - The API is located at https://nextcloud.local/index.php/apps/deck/api/v1.0
- All request parameters are required, unless otherwise specified - All request parameters are required, unless otherwise specified
@@ -733,6 +733,7 @@ The board list endpoint supports setting an `If-Modified-Since` header to limit
| Parameter | Type | Description | | Parameter | Type | Description |
| --------- | ------- | --------------------------------------- | | --------- | ------- | --------------------------------------- |
| labelId | Integer | The label id to assign to the card | | labelId | Integer | The label id to assign to the card |
#### Response #### Response
##### 200 Success ##### 200 Success
@@ -997,10 +998,12 @@ The request can fail with a bad request response for the following reasons:
#### Request data #### Request data
| Parameter | Type | Description | The request is performed as `multipart/form-data`.
| --------- | ------- | --------------------------------------------- |
| type | String | The type of the attachement | | Parameter | Type | Description |
| file | Binary | File data to add as an attachment | | --------- | ------- | ----------------------------------------------------------------------------------------------- |
| type | String | The type of the attachement. Use `file` or `deck_file`. |
| file | Binary | File data to add as an attachment together with the `filename` parameter according to RFC 7578. |
- Prior to Deck version v1.3.0 (API v1.0), attachments were stored within deck. For this type of attachments `deck_file` was used as the default type of attachments - Prior to Deck version v1.3.0 (API v1.0), attachments were stored within deck. For this type of attachments `deck_file` was used as the default type of attachments
- Starting with Deck version 1.3.0 (API v1.1) files are stored within the users regular Nextcloud files and the type `file` has been introduced for that - Starting with Deck version 1.3.0 (API v1.1) files are stored within the users regular Nextcloud files and the type `file` has been introduced for that
@@ -1022,12 +1025,13 @@ The request can fail with a bad request response for the following reasons:
#### Request data #### Request data
| Parameter | Type | Description | The request is performed as `multipart/form-data`.
| --------- | ------- | --------------------------------------------- |
| type | String | The type of the attachement | | Parameter | Type | Description |
| file | Binary | File data to add as an attachment | | --------- | ------- | ----------------------------------------------------------------------------------------------- |
| type | String | The type of the attachement. For now only `deck_file` is supported as an attachment type. |
| file | Binary | File data to add as an attachment together with the `filename` parameter according to RFC 7578. |
For now only `deck_file` is supported as an attachment type.
#### Response #### Response
Binary file not shown.

After

Width:  |  Height:  |  Size: 188 KiB

+3
View File
@@ -84,6 +84,9 @@ OC.L10N.register(
"Failed to upload {name}" : "Не ўдалося запампаваць {name}", "Failed to upload {name}" : "Не ўдалося запампаваць {name}",
"Share" : "Абагуліць", "Share" : "Абагуліць",
"Personal" : "Асабістыя", "Personal" : "Асабістыя",
"Example Task 3" : "Прыклад задання 3",
"Example Task 2" : "Прыклад задання 2",
"Example Task 1" : "Прыклад задання 1",
"Today" : "Сёння", "Today" : "Сёння",
"Tomorrow" : "Заўтра" "Tomorrow" : "Заўтра"
}, },
+3
View File
@@ -82,6 +82,9 @@
"Failed to upload {name}" : "Не ўдалося запампаваць {name}", "Failed to upload {name}" : "Не ўдалося запампаваць {name}",
"Share" : "Абагуліць", "Share" : "Абагуліць",
"Personal" : "Асабістыя", "Personal" : "Асабістыя",
"Example Task 3" : "Прыклад задання 3",
"Example Task 2" : "Прыклад задання 2",
"Example Task 1" : "Прыклад задання 1",
"Today" : "Сёння", "Today" : "Сёння",
"Tomorrow" : "Заўтра" "Tomorrow" : "Заўтра"
},"pluralForm" :"nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n%100>=11 && n%100<=14)? 2 : 3);" },"pluralForm" :"nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n%100>=11 && n%100<=14)? 2 : 3);"
+1 -1
View File
@@ -288,7 +288,7 @@ OC.L10N.register(
"Create a new tag:" : "Направи нова ознака:", "Create a new tag:" : "Направи нова ознака:",
"(group)" : "(group)", "(group)" : "(group)",
"{count} comments, {unread} unread" : "{count} коментари, {unread} непрочитани", "{count} comments, {unread} unread" : "{count} коментари, {unread} непрочитани",
"Todo items" : "Работи за правење", "Todo items" : "Задачи",
"Edit card title" : "Измени наслов на картица", "Edit card title" : "Измени наслов на картица",
"Open link" : "Отвори линк", "Open link" : "Отвори линк",
"Card deleted" : "Картицата е избришана", "Card deleted" : "Картицата е избришана",
+1 -1
View File
@@ -286,7 +286,7 @@
"Create a new tag:" : "Направи нова ознака:", "Create a new tag:" : "Направи нова ознака:",
"(group)" : "(group)", "(group)" : "(group)",
"{count} comments, {unread} unread" : "{count} коментари, {unread} непрочитани", "{count} comments, {unread} unread" : "{count} коментари, {unread} непрочитани",
"Todo items" : "Работи за правење", "Todo items" : "Задачи",
"Edit card title" : "Измени наслов на картица", "Edit card title" : "Измени наслов на картица",
"Open link" : "Отвори линк", "Open link" : "Отвори линк",
"Card deleted" : "Картицата е избришана", "Card deleted" : "Картицата е избришана",
+1 -1
View File
@@ -516,7 +516,7 @@ class ActivityManager {
]; ];
} }
private function findDetailsForCard($cardId, $subject = null) { private function findDetailsForCard(int $cardId, ?string $subject = null): array {
$card = $this->cardMapper->find($cardId); $card = $this->cardMapper->find($cardId);
$stack = $this->stackMapper->find($card->getStackId()); $stack = $this->stackMapper->find($card->getStackId());
$board = $this->boardMapper->find($stack->getBoardId()); $board = $this->boardMapper->find($stack->getBoardId());
+3
View File
@@ -7,6 +7,9 @@
namespace OCA\Deck\Activity; namespace OCA\Deck\Activity;
/**
* @psalm-api SettingComment
*/
class SettingComment extends SettingBase { class SettingComment extends SettingBase {
/** /**
+67
View File
@@ -0,0 +1,67 @@
<?php
/**
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Deck\Command;
use OCP\IConfig;
use OCP\IUserManager;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
class CalendarToggle extends Command {
private IUserManager $userManager;
private IConfig $config;
public function __construct(IUserManager $userManager, IConfig $config) {
parent::__construct();
$this->userManager = $userManager;
$this->config = $config;
}
protected function configure() {
$this
->setName('deck:calendar-toggle')
->setDescription('Enable or disable Deck calendar/tasks integration for all existing users. Users can still change their own setting afterwards. Only affects users that already exist at the time of execution.')
->addOption(
'on',
null,
InputOption::VALUE_NONE,
'Enable calendar/tasks integration for all existing users (users can opt-out later)'
)
->addOption(
'off',
null,
InputOption::VALUE_NONE,
'Disable calendar/tasks integration for all existing users (users can opt-in later)'
);
}
protected function execute(InputInterface $input, OutputInterface $output): int {
$enable = $input->getOption('on');
$disable = $input->getOption('off');
if ($enable && $disable) {
$output->writeln('<error>Cannot use --on and --off together.</error>');
return 1;
}
if (!$enable && !$disable) {
$output->writeln('<error>Please specify either --on or --off.</error>');
return 1;
}
$value = $enable ? 'yes' : '';
$users = $this->userManager->search('');
$count = 0;
foreach ($users as $user) {
$uid = $user->getUID();
$this->config->setUserValue($uid, 'deck', 'calendar', $value);
$output->writeln("Set calendar integration to '" . ($enable ? 'on' : 'off') . "' for user: $uid");
$count++;
}
$output->writeln("Done. Updated $count existing users.");
return 0;
}
}
+29 -45
View File
@@ -6,9 +6,13 @@
*/ */
namespace OCA\Deck\Controller; namespace OCA\Deck\Controller;
use OCA\Deck\Db\Attachment;
use OCA\Deck\Service\AttachmentService; use OCA\Deck\Service\AttachmentService;
use OCP\AppFramework\ApiController; use OCP\AppFramework\ApiController;
use OCP\AppFramework\Http; use OCP\AppFramework\Http;
use OCP\AppFramework\Http\Attribute\CORS;
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
use OCP\AppFramework\Http\DataResponse; use OCP\AppFramework\Http\DataResponse;
use OCP\IRequest; use OCP\IRequest;
@@ -21,72 +25,52 @@ class AttachmentApiController extends ApiController {
parent::__construct($appName, $request); parent::__construct($appName, $request);
} }
/** #[NoAdminRequired]
* @NoAdminRequired #[CORS]
* @CORS #[NoCSRFRequired]
* @NoCSRFRequired public function getAll(string $apiVersion): DataResponse {
*
*/
public function getAll($apiVersion) {
$attachment = $this->attachmentService->findAll($this->request->getParam('cardId'), true); $attachment = $this->attachmentService->findAll($this->request->getParam('cardId'), true);
if ($apiVersion === '1.0') { if ($apiVersion === '1.0') {
$attachment = array_filter($attachment, function ($attachment) { $attachment = array_filter($attachment, fn (Attachment $attachment): bool => $attachment->getType() === 'deck_file');
return $attachment->getType() === 'deck_file';
});
} }
return new DataResponse($attachment, HTTP::STATUS_OK); return new DataResponse($attachment, HTTP::STATUS_OK);
} }
/** #[NoAdminRequired]
* @NoAdminRequired #[CORS]
* @CORS #[NoCSRFRequired]
* @NoCSRFRequired public function display(int $cardId, int $attachmentId, string $type = 'deck_file') {
*
*/
public function display($cardId, $attachmentId, $type = 'deck_file') {
return $this->attachmentService->display($cardId, $attachmentId, $type); return $this->attachmentService->display($cardId, $attachmentId, $type);
} }
/** #[NoAdminRequired]
* @NoAdminRequired #[CORS]
* @CORS #[NoCSRFRequired]
* @NoCSRFRequired public function create(int $cardId, string $type, string $data): DataResponse {
*
*/
public function create($cardId, $type, $data) {
$attachment = $this->attachmentService->create($cardId, $type, $data); $attachment = $this->attachmentService->create($cardId, $type, $data);
return new DataResponse($attachment, HTTP::STATUS_OK); return new DataResponse($attachment, HTTP::STATUS_OK);
} }
/** #[NoAdminRequired]
* @NoAdminRequired #[CORS]
* @CORS #[NoCSRFRequired]
* @NoCSRFRequired public function update(int $cardId, int $attachmentId, string $data, string $type = 'deck_file'): DataResponse {
*
*/
public function update($cardId, $attachmentId, $data, $type = 'deck_file') {
$attachment = $this->attachmentService->update($cardId, $attachmentId, $data, $type); $attachment = $this->attachmentService->update($cardId, $attachmentId, $data, $type);
return new DataResponse($attachment, HTTP::STATUS_OK); return new DataResponse($attachment, HTTP::STATUS_OK);
} }
/** #[NoAdminRequired]
* @NoAdminRequired #[CORS]
* @CORS #[NoCSRFRequired]
* @NoCSRFRequired public function delete(int $cardId, int $attachmentId, string $type = 'deck_file'): DataResponse {
*
*/
public function delete($cardId, $attachmentId, $type = 'deck_file') {
$attachment = $this->attachmentService->delete($cardId, $attachmentId, $type); $attachment = $this->attachmentService->delete($cardId, $attachmentId, $type);
return new DataResponse($attachment, HTTP::STATUS_OK); return new DataResponse($attachment, HTTP::STATUS_OK);
} }
/** #[NoAdminRequired]
* @NoAdminRequired #[CORS]
* @CORS #[NoCSRFRequired]
* @NoCSRFRequired public function restore(int $cardId, int $attachmentId, string $type = 'deck_file'): DataResponse {
*
*/
public function restore($cardId, $attachmentId, $type = 'deck_file') {
$attachment = $this->attachmentService->restore($cardId, $attachmentId, $type); $attachment = $this->attachmentService->restore($cardId, $attachmentId, $type);
return new DataResponse($attachment, HTTP::STATUS_OK); return new DataResponse($attachment, HTTP::STATUS_OK);
} }
+40 -43
View File
@@ -7,8 +7,13 @@
namespace OCA\Deck\Controller; namespace OCA\Deck\Controller;
use OCA\Deck\BadRequestException;
use OCA\Deck\Db\Attachment;
use OCA\Deck\Service\AttachmentService; use OCA\Deck\Service\AttachmentService;
use OCP\AppFramework\Controller; use OCP\AppFramework\Controller;
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
use OCP\AppFramework\Http\Response;
use OCP\IRequest; use OCP\IRequest;
class AttachmentController extends Controller { class AttachmentController extends Controller {
@@ -20,74 +25,66 @@ class AttachmentController extends Controller {
parent::__construct($appName, $request); parent::__construct($appName, $request);
} }
/** #[NoAdminRequired]
* @NoAdminRequired public function getAll(int $cardId): array {
*/
public function getAll($cardId) {
return $this->attachmentService->findAll($cardId, true); return $this->attachmentService->findAll($cardId, true);
} }
/** /**
* @param $cardId
* @param $attachmentId
* @NoCSRFRequired
* @NoAdminRequired
* @return \OCP\AppFramework\Http\Response
* @throws \OCA\Deck\NotFoundException * @throws \OCA\Deck\NotFoundException
*/ */
public function display($cardId, $attachmentId) { #[NoAdminRequired]
if (!str_contains($attachmentId, ':')) { #[NoCSRFRequired]
$type = 'deck_file'; public function display(int $cardId, string $attachmentId): Response {
} else { ['type' => $type, 'attachmentId' => $attachmentId] = $this->extractTypeAndAttachmentId($attachmentId);
[$type, $attachmentId] = explode(':', $attachmentId);
}
return $this->attachmentService->display($cardId, $attachmentId, $type); return $this->attachmentService->display($cardId, $attachmentId, $type);
} }
/** #[NoAdminRequired]
* @NoAdminRequired public function create(int $cardId): Attachment {
*/
public function create($cardId) {
return $this->attachmentService->create( return $this->attachmentService->create(
$cardId, $cardId,
$this->request->getParam('type'), $this->request->getParam('type'),
$this->request->getParam('data') $this->request->getParam('data') ?? '',
); );
} }
/** #[NoAdminRequired]
* @NoAdminRequired public function update(int $cardId, string $attachmentId): Attachment {
*/ ['type' => $type, 'attachmentId' => $attachmentId] = $this->extractTypeAndAttachmentId($attachmentId);
public function update($cardId, $attachmentId) { return $this->attachmentService->update($cardId, $attachmentId, $this->request->getParam('data') ?? '', $type);
if (!str_contains($attachmentId, ':')) {
$type = 'deck_file';
} else {
[$type, $attachmentId] = explode(':', $attachmentId);
}
return $this->attachmentService->update($cardId, $attachmentId, $this->request->getParam('data'), $type);
} }
/** #[NoAdminRequired]
* @NoAdminRequired public function delete(int $cardId, string $attachmentId): Attachment {
*/ ['type' => $type, 'attachmentId' => $attachmentId] = $this->extractTypeAndAttachmentId($attachmentId);
public function delete($cardId, $attachmentId) {
if (!str_contains($attachmentId, ':')) {
$type = 'deck_file';
} else {
[$type, $attachmentId] = explode(':', $attachmentId);
}
return $this->attachmentService->delete($cardId, $attachmentId, $type); return $this->attachmentService->delete($cardId, $attachmentId, $type);
} }
#[NoAdminRequired]
public function restore(int $cardId, string $attachmentId): Attachment {
['type' => $type, 'attachmentId' => $attachmentId] = $this->extractTypeAndAttachmentId($attachmentId);
return $this->attachmentService->restore($cardId, $attachmentId, $type);
}
/** /**
* @NoAdminRequired * @return array{type: string, attachmentId: int}
* @throws BadRequestException
*/ */
public function restore($cardId, $attachmentId) { private function extractTypeAndAttachmentId(string $attachmentId): array {
if (!str_contains($attachmentId, ':')) { if (!str_contains($attachmentId, ':')) {
$type = 'deck_file'; $type = 'deck_file';
} else { } else {
[$type, $attachmentId] = explode(':', $attachmentId); [$type, $attachmentId] = [...explode(':', $attachmentId), '', ''];
} }
return $this->attachmentService->restore($cardId, $attachmentId, $type);
if ($type === '' || !is_numeric($attachmentId)) {
throw new BadRequestException('Invalid attachment id');
}
return [
'type' => $type,
'attachmentId' => (int)$attachmentId,
];
} }
} }
+36 -53
View File
@@ -12,10 +12,13 @@ use OCA\Deck\Service\BoardService;
use OCA\Deck\StatusException; use OCA\Deck\StatusException;
use OCP\AppFramework\ApiController; use OCP\AppFramework\ApiController;
use OCP\AppFramework\Http; use OCP\AppFramework\Http;
use OCP\AppFramework\Http\Attribute\CORS;
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
use OCP\AppFramework\Http\DataResponse; use OCP\AppFramework\Http\DataResponse;
use OCP\IRequest; use OCP\IRequest;
use Sabre\HTTP\Util;
use function Sabre\HTTP\parseDate;
/** /**
* Class BoardApiController * Class BoardApiController
@@ -36,21 +39,18 @@ class BoardApiController extends ApiController {
} }
/** /**
* @NoAdminRequired * Return all the boards that the current user has access to.
* @CORS
* @NoCSRFRequired
*
* Return all of the boards that the current user has access to.
*
* @param bool $details
* @throws StatusException * @throws StatusException
*/ */
public function index(bool $details = false) { #[NoAdminRequired]
#[NoCSRFRequired]
#[CORS]
public function index(bool $details = false): DataResponse {
$modified = $this->request->getHeader('If-Modified-Since'); $modified = $this->request->getHeader('If-Modified-Since');
if ($modified === null || $modified === '') { if ($modified === '') {
$boards = $this->boardService->findAll(0, $details === true); $boards = $this->boardService->findAll(0, $details === true);
} else { } else {
$date = Util::parseHTTPDate($modified); $date = parseDate($modified);
if (!$date) { if (!$date) {
throw new StatusException('Invalid If-Modified-Since header provided.'); throw new StatusException('Invalid If-Modified-Since header provided.');
} }
@@ -64,14 +64,12 @@ class BoardApiController extends ApiController {
} }
/** /**
* @NoAdminRequired
* @CORS
* @NoCSRFRequired
*
*
* Return the board specified by $this->request->getParam('boardId'). * Return the board specified by $this->request->getParam('boardId').
*/ */
public function get() { #[NoAdminRequired]
#[NoCSRFRequired]
#[CORS]
public function get(): DataResponse {
$board = $this->boardService->find($this->request->getParam('boardId')); $board = $this->boardService->find($this->request->getParam('boardId'));
$response = new DataResponse($board, HTTP::STATUS_OK); $response = new DataResponse($board, HTTP::STATUS_OK);
$response->setETag($board->getEtag()); $response->setETag($board->getEtag());
@@ -79,68 +77,53 @@ class BoardApiController extends ApiController {
} }
/** /**
* @NoAdminRequired
* @CORS
* @NoCSRFRequired
*
* @params $title
* @params $color
*
* Create a board with the specified title and color. * Create a board with the specified title and color.
*/ */
public function create($title, $color) { #[NoAdminRequired]
#[NoCSRFRequired]
#[CORS]
public function create(string $title, string $color): DataResponse {
$board = $this->boardService->create($title, $this->userId, $color); $board = $this->boardService->create($title, $this->userId, $color);
return new DataResponse($board, HTTP::STATUS_OK); return new DataResponse($board, HTTP::STATUS_OK);
} }
/** /**
* @NoAdminRequired
* @CORS
* @NoCSRFRequired
*
* @params $title
* @params $color
* @params $archived
*
* Update a board with the specified boardId, title and color, and archived state. * Update a board with the specified boardId, title and color, and archived state.
*/ */
public function update($title, $color, $archived = false) { #[NoAdminRequired]
#[NoCSRFRequired]
#[CORS]
public function update(string $title, string $color, bool $archived = false): DataResponse {
$board = $this->boardService->update($this->request->getParam('boardId'), $title, $color, $archived); $board = $this->boardService->update($this->request->getParam('boardId'), $title, $color, $archived);
return new DataResponse($board, HTTP::STATUS_OK); return new DataResponse($board, HTTP::STATUS_OK);
} }
/** /**
* @NoAdminRequired
* @CORS
* @NoCSRFRequired
*
*
* Delete the board specified by $boardId. Return the board that was deleted. * Delete the board specified by $boardId. Return the board that was deleted.
*/ */
public function delete() { #[NoAdminRequired]
#[NoCSRFRequired]
#[CORS]
public function delete(): DataResponse {
$board = $this->boardService->delete($this->request->getParam('boardId')); $board = $this->boardService->delete($this->request->getParam('boardId'));
return new DataResponse($board, HTTP::STATUS_OK); return new DataResponse($board, HTTP::STATUS_OK);
} }
/** /**
* @NoAdminRequired
* @CORS
* @NoCSRFRequired
*
*
* Undo the deletion of the board specified by $boardId. * Undo the deletion of the board specified by $boardId.
*/ */
public function undoDelete() { #[NoAdminRequired]
#[NoCSRFRequired]
#[CORS]
public function undoDelete(): DataResponse {
$board = $this->boardService->deleteUndo($this->request->getParam('boardId')); $board = $this->boardService->deleteUndo($this->request->getParam('boardId'));
return new DataResponse($board, HTTP::STATUS_OK); return new DataResponse($board, HTTP::STATUS_OK);
} }
/** #[NoAdminRequired]
* @NoAdminRequired #[NoCSRFRequired]
* @CORS #[CORS]
* @NoCSRFRequired public function addAcl(int $boardId, $type, $participant, $permissionEdit, $permissionShare, $permissionManage) {
*/
public function addAcl($boardId, $type, $participant, $permissionEdit, $permissionShare, $permissionManage) {
$acl = $this->boardService->addAcl($boardId, $type, $participant, $permissionEdit, $permissionShare, $permissionManage); $acl = $this->boardService->addAcl($boardId, $type, $participant, $permissionEdit, $permissionShare, $permissionManage);
return new DataResponse($acl, HTTP::STATUS_OK); return new DataResponse($acl, HTTP::STATUS_OK);
} }
+79 -52
View File
@@ -10,10 +10,13 @@ namespace OCA\Deck\Controller;
use OCA\Deck\Db\Acl; use OCA\Deck\Db\Acl;
use OCA\Deck\Db\Board; use OCA\Deck\Db\Board;
use OCA\Deck\Service\BoardService; use OCA\Deck\Service\BoardService;
use OCA\Deck\Service\Importer\BoardImportService;
use OCA\Deck\Service\PermissionService; use OCA\Deck\Service\PermissionService;
use OCP\AppFramework\ApiController; use OCP\AppFramework\ApiController;
use OCP\AppFramework\Http; use OCP\AppFramework\Http;
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
use OCP\AppFramework\Http\DataResponse; use OCP\AppFramework\Http\DataResponse;
use OCP\IL10N;
use OCP\IRequest; use OCP\IRequest;
class BoardController extends ApiController { class BoardController extends ApiController {
@@ -22,73 +25,45 @@ class BoardController extends ApiController {
IRequest $request, IRequest $request,
private BoardService $boardService, private BoardService $boardService,
private PermissionService $permissionService, private PermissionService $permissionService,
private BoardImportService $boardImportService,
private IL10N $l10n,
private $userId, private $userId,
) { ) {
parent::__construct($appName, $request); parent::__construct($appName, $request);
} }
/** #[NoAdminRequired]
* @NoAdminRequired
*/
public function index() { public function index() {
return $this->boardService->findAll(); return $this->boardService->findAll();
} }
/** #[NoAdminRequired]
* @NoAdminRequired public function read(int $boardId): Board {
* @param $boardId
* @return \OCP\AppFramework\Db\Entity
*/
public function read(int $boardId) {
return $this->boardService->find($boardId); return $this->boardService->find($boardId);
} }
/** #[NoAdminRequired]
* @NoAdminRequired public function create(string $title, string $color): Board {
* @param $title
* @param $color
* @return \OCP\AppFramework\Db\Entity
*/
public function create($title, $color) {
return $this->boardService->create($title, $this->userId, $color); return $this->boardService->create($title, $this->userId, $color);
} }
/** #[NoAdminRequired]
* @NoAdminRequired public function update(int $id, string $title, string $color, bool $archived): Board {
* @param $id
* @param $title
* @param $color
* @param $archived
* @return \OCP\AppFramework\Db\Entity
*/
public function update($id, $title, $color, $archived) {
return $this->boardService->update($id, $title, $color, $archived); return $this->boardService->update($id, $title, $color, $archived);
} }
/** #[NoAdminRequired]
* @NoAdminRequired public function delete(int $boardId): Board {
* @param $boardId
* @return \OCP\AppFramework\Db\Entity
*/
public function delete($boardId) {
return $this->boardService->delete($boardId); return $this->boardService->delete($boardId);
} }
/**
* @NoAdminRequired #[NoAdminRequired]
* @param $boardId public function deleteUndo(int $boardId): Board {
* @return \OCP\AppFramework\Db\Entity
*/
public function deleteUndo($boardId) {
return $this->boardService->deleteUndo($boardId); return $this->boardService->deleteUndo($boardId);
} }
/** #[NoAdminRequired]
* @NoAdminRequired public function getUserPermissions(int $boardId): array {
* @param $boardId
* @return array|bool
* @internal param $userId
*/
public function getUserPermissions($boardId) {
$permissions = $this->permissionService->getPermissions($boardId); $permissions = $this->permissionService->getPermissions($boardId);
return [ return [
'PERMISSION_READ' => $permissions[Acl::PERMISSION_READ], 'PERMISSION_READ' => $permissions[Acl::PERMISSION_READ],
@@ -99,16 +74,10 @@ class BoardController extends ApiController {
} }
/** /**
* @NoAdminRequired
* @param $boardId
* @param $type
* @param $participant * @param $participant
* @param $permissionEdit
* @param $permissionShare
* @param $permissionManage
* @return \OCP\AppFramework\Db\Entity
*/ */
public function addAcl($boardId, $type, $participant, $permissionEdit, $permissionShare, $permissionManage) { #[NoAdminRequired]
public function addAcl(int $boardId, int $type, $participant, bool $permissionEdit, bool $permissionShare, bool $permissionManage): Acl {
return $this->boardService->addAcl($boardId, $type, $participant, $permissionEdit, $permissionShare, $permissionManage); return $this->boardService->addAcl($boardId, $type, $participant, $permissionEdit, $permissionShare, $permissionManage);
} }
@@ -163,4 +132,62 @@ class BoardController extends ApiController {
public function export($boardId) { public function export($boardId) {
return $this->boardService->export($boardId); return $this->boardService->export($boardId);
} }
/**
* @NoAdminRequired
*/
public function import(): DataResponse {
$file = $this->request->getUploadedFile('file');
$error = null;
$phpFileUploadErrors = [
UPLOAD_ERR_OK => $this->l10n->t('The file was uploaded'),
UPLOAD_ERR_INI_SIZE => $this->l10n->t('The uploaded file exceeds the upload_max_filesize directive in php.ini'),
UPLOAD_ERR_FORM_SIZE => $this->l10n->t('The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form'),
UPLOAD_ERR_PARTIAL => $this->l10n->t('The file was only partially uploaded'),
UPLOAD_ERR_NO_FILE => $this->l10n->t('No file was uploaded'),
UPLOAD_ERR_NO_TMP_DIR => $this->l10n->t('Missing a temporary folder'),
UPLOAD_ERR_CANT_WRITE => $this->l10n->t('Could not write file to disk'),
UPLOAD_ERR_EXTENSION => $this->l10n->t('A PHP extension stopped the file upload'),
];
if (empty($file)) {
$error = $this->l10n->t('No file uploaded or file size exceeds maximum of %s', [\OCP\Util::humanFileSize(\OCP\Util::uploadLimit())]);
}
if (!empty($file) && array_key_exists('error', $file) && $file['error'] !== UPLOAD_ERR_OK) {
$error = $phpFileUploadErrors[$file['error']];
}
if (!empty($file) && $file['error'] === UPLOAD_ERR_OK && !in_array($file['type'], ['application/json', 'text/plain'])) {
$error = $this->l10n->t('Invalid file type. Only JSON files are allowed.');
}
if ($error !== null) {
return new DataResponse([
'status' => 'error',
'message' => $error,
], Http::STATUS_BAD_REQUEST);
}
try {
$fileContent = file_get_contents($file['tmp_name']);
$this->boardImportService->setSystem('DeckJson');
$config = new \stdClass();
$config->owner = $this->userId;
$this->boardImportService->setConfigInstance($config);
$this->boardImportService->setData(json_decode($fileContent));
$this->boardImportService->import();
$importedBoard = $this->boardImportService->getBoard();
$board = $this->boardService->find($importedBoard->getId());
return new DataResponse($board, Http::STATUS_OK);
} catch (\TypeError $e) {
return new DataResponse([
'status' => 'error',
'message' => $this->l10n->t('Invalid JSON data'),
], Http::STATUS_BAD_REQUEST);
} catch (\Exception $e) {
return new DataResponse([
'status' => 'error',
'message' => $this->l10n->t('Failed to import board'),
], Http::STATUS_BAD_REQUEST);
}
}
} }
+12 -15
View File
@@ -9,6 +9,9 @@ namespace OCA\Deck\Controller;
use OCA\Deck\Service\Importer\BoardImportService; use OCA\Deck\Service\Importer\BoardImportService;
use OCP\AppFramework\Http; use OCP\AppFramework\Http;
use OCP\AppFramework\Http\Attribute\CORS;
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
use OCP\AppFramework\Http\DataResponse; use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\OCSController; use OCP\AppFramework\OCSController;
use OCP\IRequest; use OCP\IRequest;
@@ -23,11 +26,9 @@ class BoardImportApiController extends OCSController {
parent::__construct($appName, $request); parent::__construct($appName, $request);
} }
/** #[NoAdminRequired]
* @NoAdminRequired #[CORS]
* @CORS #[NoCSRFRequired]
* @NoCSRFRequired
*/
public function import(string $system, array $config, array $data): DataResponse { public function import(string $system, array $config, array $data): DataResponse {
$this->boardImportService->setSystem($system); $this->boardImportService->setSystem($system);
$config = json_decode(json_encode($config)); $config = json_decode(json_encode($config));
@@ -38,21 +39,17 @@ class BoardImportApiController extends OCSController {
return new DataResponse($this->boardImportService->getBoard(), Http::STATUS_OK); return new DataResponse($this->boardImportService->getBoard(), Http::STATUS_OK);
} }
/** #[NoAdminRequired]
* @NoAdminRequired #[CORS]
* @CORS #[NoCSRFRequired]
* @NoCSRFRequired
*/
public function getAllowedSystems(): DataResponse { public function getAllowedSystems(): DataResponse {
$allowedSystems = $this->boardImportService->getAllowedImportSystems(); $allowedSystems = $this->boardImportService->getAllowedImportSystems();
return new DataResponse($allowedSystems, Http::STATUS_OK); return new DataResponse($allowedSystems, Http::STATUS_OK);
} }
/** #[NoAdminRequired]
* @NoAdminRequired #[CORS]
* @CORS #[NoCSRFRequired]
* @NoCSRFRequired
*/
public function getConfigSchema(string $name): DataResponse { public function getConfigSchema(string $name): DataResponse {
$this->boardImportService->setSystem($name); $this->boardImportService->setSystem($name);
$this->boardImportService->validateSystem(); $this->boardImportService->validateSystem();
+41 -48
View File
@@ -12,6 +12,9 @@ use OCA\Deck\Service\AssignmentService;
use OCA\Deck\Service\CardService; use OCA\Deck\Service\CardService;
use OCP\AppFramework\ApiController; use OCP\AppFramework\ApiController;
use OCP\AppFramework\Http; use OCP\AppFramework\Http;
use OCP\AppFramework\Http\Attribute\CORS;
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
use OCP\AppFramework\Http\DataResponse; use OCP\AppFramework\Http\DataResponse;
use OCP\IRequest; use OCP\IRequest;
@@ -27,7 +30,7 @@ class CardApiController extends ApiController {
* @param IRequest $request * @param IRequest $request
* @param CardService $cardService * @param CardService $cardService
* @param AssignmentService $assignmentService * @param AssignmentService $assignmentService
* @param $userId * @param string $userId
*/ */
public function __construct( public function __construct(
string $appName, string $appName,
@@ -80,112 +83,102 @@ class CardApiController extends ApiController {
} }
/** /**
* @NoAdminRequired
* @CORS
* @NoCSRFRequired
*
*
* Update a card * Update a card
*/ */
public function update($title, $type, $owner, $description = '', $order = 0, $duedate = null, $archived = null) { #[NoAdminRequired]
#[CORS]
#[NoCSRFRequired]
public function update(string $title, $type, string $owner, string $description = '', int $order = 0, $duedate = null, $archived = null): DataResponse {
$done = array_key_exists('done', $this->request->getParams()) ? new OptionalNullableValue($this->request->getParam('done', null)) : null; $done = array_key_exists('done', $this->request->getParams()) ? new OptionalNullableValue($this->request->getParam('done', null)) : null;
$card = $this->cardService->update($this->request->getParam('cardId'), $title, $this->request->getParam('stackId'), $type, $owner, $description, $order, $duedate, 0, $archived, $done); $card = $this->cardService->update($this->request->getParam('cardId'), $title, $this->request->getParam('stackId'), $type, $owner, $description, $order, $duedate, 0, $archived, $done);
return new DataResponse($card, HTTP::STATUS_OK); return new DataResponse($card, HTTP::STATUS_OK);
} }
/** /**
* @NoAdminRequired
* @CORS
* @NoCSRFRequired
*
* Delete a specific card. * Delete a specific card.
*/ */
public function delete() { #[NoAdminRequired]
#[CORS]
#[NoCSRFRequired]
public function delete(): DataResponse {
$card = $this->cardService->delete($this->request->getParam('cardId')); $card = $this->cardService->delete($this->request->getParam('cardId'));
return new DataResponse($card, HTTP::STATUS_OK); return new DataResponse($card, HTTP::STATUS_OK);
} }
/** /**
* @NoAdminRequired
* @CORS
* @NoCSRFRequired
*
* Assign a label to a card. * Assign a label to a card.
*/ */
public function assignLabel($labelId) { #[NoAdminRequired]
#[CORS]
#[NoCSRFRequired]
public function assignLabel(int $labelId): DataResponse {
$card = $this->cardService->assignLabel($this->request->getParam('cardId'), $labelId); $card = $this->cardService->assignLabel($this->request->getParam('cardId'), $labelId);
return new DataResponse($card, HTTP::STATUS_OK); return new DataResponse($card, HTTP::STATUS_OK);
} }
/** /**
* @NoAdminRequired
* @CORS
* @NoCSRFRequired
*
* Assign a label to a card. * Assign a label to a card.
*/ */
public function removeLabel($labelId) { #[NoAdminRequired]
#[CORS]
#[NoCSRFRequired]
public function removeLabel(int $labelId): DataResponse {
$card = $this->cardService->removeLabel($this->request->getParam('cardId'), $labelId); $card = $this->cardService->removeLabel($this->request->getParam('cardId'), $labelId);
return new DataResponse($card, HTTP::STATUS_OK); return new DataResponse($card, HTTP::STATUS_OK);
} }
/** /**
* @NoAdminRequired
* @CORS
* @NoCSRFRequired
*
* Assign a user to a card * Assign a user to a card
*/ */
public function assignUser($cardId, $userId, $type = 0) { #[NoAdminRequired]
#[CORS]
#[NoCSRFRequired]
public function assignUser(int $cardId, string $userId, int $type = 0): DataResponse {
$card = $this->assignmentService->assignUser($cardId, $userId, $type); $card = $this->assignmentService->assignUser($cardId, $userId, $type);
return new DataResponse($card, HTTP::STATUS_OK); return new DataResponse($card, HTTP::STATUS_OK);
} }
/** /**
* @NoAdminRequired
* @CORS
* @NoCSRFRequired
*
* Unassign a user from a card * Unassign a user from a card
*/ */
public function unassignUser($cardId, $userId, $type = 0) { #[NoAdminRequired]
#[CORS]
#[NoCSRFRequired]
public function unassignUser(int $cardId, string $userId, int $type = 0): DataResponse {
$card = $this->assignmentService->unassignUser($cardId, $userId, $type); $card = $this->assignmentService->unassignUser($cardId, $userId, $type);
return new DataResponse($card, HTTP::STATUS_OK); return new DataResponse($card, HTTP::STATUS_OK);
} }
/** /**
* @NoAdminRequired
* @CORS
* @NoCSRFRequired
*
* Archive card * Archive card
*/ */
public function archive($cardId) { #[NoAdminRequired]
#[CORS]
#[NoCSRFRequired]
public function archive(int $cardId): DataResponse {
$card = $this->cardService->archive($cardId); $card = $this->cardService->archive($cardId);
return new DataResponse($card, HTTP::STATUS_OK); return new DataResponse($card, HTTP::STATUS_OK);
} }
/** /**
* @NoAdminRequired
* @CORS
* @NoCSRFRequired
*
* Unarchive card * Unarchive card
*/ */
public function unarchive($cardId) { #[NoAdminRequired]
#[CORS]
#[NoCSRFRequired]
public function unarchive(int $cardId): DataResponse {
$card = $this->cardService->unarchive($cardId); $card = $this->cardService->unarchive($cardId);
return new DataResponse($card, HTTP::STATUS_OK); return new DataResponse($card, HTTP::STATUS_OK);
} }
/** /**
* @NoAdminRequired
* @CORS
* @NoCSRFRequired
*
* Reorder cards * Reorder cards
*/ */
public function reorder($stackId, $order) { #[NoAdminRequired]
$card = $this->cardService->reorder((int)$this->request->getParam('cardId'), (int)$stackId, (int)$order); #[CORS]
#[NoCSRFRequired]
public function reorder(int $stackId, int $order): DataResponse {
$card = $this->cardService->reorder((int)$this->request->getParam('cardId'), $stackId, $order);
return new DataResponse($card, HTTP::STATUS_OK); return new DataResponse($card, HTTP::STATUS_OK);
} }
} }
+38 -99
View File
@@ -7,9 +7,12 @@
namespace OCA\Deck\Controller; namespace OCA\Deck\Controller;
use OCA\Deck\Db\Assignment;
use OCA\Deck\Db\Card;
use OCA\Deck\Service\AssignmentService; use OCA\Deck\Service\AssignmentService;
use OCA\Deck\Service\CardService; use OCA\Deck\Service\CardService;
use OCP\AppFramework\Controller; use OCP\AppFramework\Controller;
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
use OCP\IRequest; use OCP\IRequest;
class CardController extends Controller { class CardController extends Controller {
@@ -23,45 +26,26 @@ class CardController extends Controller {
parent::__construct($appName, $request); parent::__construct($appName, $request);
} }
/** #[NoAdminRequired]
* @NoAdminRequired public function read(int $cardId): Card {
* @param $cardId
* @return \OCP\AppFramework\Db\Entity
*/
public function read($cardId) {
return $this->cardService->find($cardId); return $this->cardService->find($cardId);
} }
/** /**
* @NoAdminRequired * @return Card[]
* @param $cardId
* @param $stackId
* @param $order
* @return array
*/ */
public function reorder($cardId, $stackId, $order) { #[NoAdminRequired]
return $this->cardService->reorder((int)$cardId, (int)$stackId, (int)$order); public function reorder(int $cardId, int $stackId, int $order): array {
return $this->cardService->reorder($cardId, $stackId, $order);
} }
/** #[NoAdminRequired]
* @NoAdminRequired public function rename(int $cardId, string $title): Card {
* @param $cardId
* @param $title
* @return \OCP\AppFramework\Db\Entity
*/
public function rename($cardId, $title) {
return $this->cardService->rename($cardId, $title); return $this->cardService->rename($cardId, $title);
} }
/** #[NoAdminRequired]
* @NoAdminRequired public function create(string $title, int $stackId, string $type = 'plain', int $order = 999, string $description = '', $duedate = null, array $labels = [], array $users = []): Card {
* @param $title
* @param $stackId
* @param $type
* @param int $order
* @return \OCP\AppFramework\Db\Entity
*/
public function create($title, $stackId, $type = 'plain', $order = 999, string $description = '', $duedate = null, $labels = [], $users = []) {
$card = $this->cardService->create($title, $stackId, $type, $order, $this->userId, $description, $duedate); $card = $this->cardService->create($title, $stackId, $type, $order, $this->userId, $description, $duedate);
foreach ($labels as $label) { foreach ($labels as $label) {
@@ -76,113 +60,68 @@ class CardController extends Controller {
} }
/** /**
* @NoAdminRequired
* @param $id
* @param $title
* @param $stackId
* @param $type
* @param $order
* @param $description
* @param $duedate * @param $duedate
* @param $deletedAt
* @return \OCP\AppFramework\Db\Entity
*/ */
public function update($id, $title, $stackId, $type, $order, $description, $duedate, $deletedAt) { #[NoAdminRequired]
public function update(int $id, string $title, int $stackId, string $type, int $order, string $description, $duedate, $deletedAt): Card {
return $this->cardService->update($id, $title, $stackId, $type, $this->userId, $description, $order, $duedate, $deletedAt); return $this->cardService->update($id, $title, $stackId, $type, $this->userId, $description, $order, $duedate, $deletedAt);
} }
/**
* @NoAdminRequired #[NoAdminRequired]
* @param $cardId public function clone(int $cardId, ?int $targetStackId = null): Card {
* @param $targetStackId
* @return \OCP\AppFramework\Db\Entity
*/
public function clone(int $cardId, ?int $targetStackId = null) {
return $this->cardService->cloneCard($cardId, $targetStackId); return $this->cardService->cloneCard($cardId, $targetStackId);
} }
/** #[NoAdminRequired]
* @NoAdminRequired public function delete(int $cardId): Card {
* @param $cardId
* @return \OCP\AppFramework\Db\Entity
*/
public function delete($cardId) {
return $this->cardService->delete($cardId); return $this->cardService->delete($cardId);
} }
/** /**
* @NoAdminRequired * @return Card[]
* @param $boardId
* @return \OCP\AppFramework\Db\Entity
*/ */
public function deleted($boardId) { #[NoAdminRequired]
public function deleted(int $boardId): array {
return $this->cardService->fetchDeleted($boardId); return $this->cardService->fetchDeleted($boardId);
} }
/** #[NoAdminRequired]
* @NoAdminRequired
* @param $cardId
* @return \OCP\AppFramework\Db\Entity
*/
public function archive($cardId) { public function archive($cardId) {
return $this->cardService->archive($cardId); return $this->cardService->archive($cardId);
} }
/** #[NoAdminRequired]
* @NoAdminRequired public function unarchive(int $cardId): Card {
* @param $cardId
* @return \OCP\AppFramework\Db\Entity
*/
public function unarchive($cardId) {
return $this->cardService->unarchive($cardId); return $this->cardService->unarchive($cardId);
} }
/** #[NoAdminRequired]
* @NoAdminRequired public function done(int $cardId): Card {
* @param $cardId
* @return \OCP\AppFramework\Db\Entity
*/
public function done(int $cardId) {
return $this->cardService->done($cardId); return $this->cardService->done($cardId);
} }
/** #[NoAdminRequired]
* @NoAdminRequired public function undone(int $cardId): Card {
* @param $cardId
* @return \OCP\AppFramework\Db\Entity
*/
public function undone(int $cardId) {
return $this->cardService->undone($cardId); return $this->cardService->undone($cardId);
} }
/** #[NoAdminRequired]
* @NoAdminRequired public function assignLabel(int $cardId, int $labelId): void {
* @param $cardId
* @param $labelId
*/
public function assignLabel($cardId, $labelId) {
$this->cardService->assignLabel($cardId, $labelId); $this->cardService->assignLabel($cardId, $labelId);
} }
/** #[NoAdminRequired]
* @NoAdminRequired public function removeLabel(int $cardId, int $labelId): void {
* @param $cardId
* @param $labelId
*/
public function removeLabel($cardId, $labelId) {
$this->cardService->removeLabel($cardId, $labelId); $this->cardService->removeLabel($cardId, $labelId);
} }
/** #[NoAdminRequired]
* @NoAdminRequired public function assignUser(int $cardId, string $userId, int $type = 0): Assignment {
*/
public function assignUser($cardId, $userId, $type = 0) {
return $this->assignmentService->assignUser($cardId, $userId, $type); return $this->assignmentService->assignUser($cardId, $userId, $type);
} }
/** #[NoAdminRequired]
* @NoAdminRequired public function unassignUser(int $cardId, string $userId, int $type = 0): Assignment {
*/
public function unassignUser($cardId, $userId, $type = 0) {
return $this->assignmentService->unassignUser($cardId, $userId, $type); return $this->assignmentService->unassignUser($cardId, $userId, $type);
} }
} }
+9 -5
View File
@@ -9,11 +9,15 @@ namespace OCA\Deck\Controller;
use OCA\Deck\Service\CommentService; use OCA\Deck\Service\CommentService;
use OCA\Deck\StatusException; use OCA\Deck\StatusException;
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
use OCP\AppFramework\Http\DataResponse; use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\OCSController; use OCP\AppFramework\OCSController;
use OCP\IRequest; use OCP\IRequest;
/**
* @psalm-api
*/
class CommentsApiController extends OCSController { class CommentsApiController extends OCSController {
public function __construct( public function __construct(
string $appName, string $appName,
@@ -27,33 +31,33 @@ class CommentsApiController extends OCSController {
} }
/** /**
* @NoAdminRequired
* @throws StatusException * @throws StatusException
*/ */
public function list(string $cardId, int $limit = 20, int $offset = 0): DataResponse { #[NoAdminRequired]
public function list(int $cardId, int $limit = 20, int $offset = 0): DataResponse {
return $this->commentService->list($cardId, $limit, $offset); return $this->commentService->list($cardId, $limit, $offset);
} }
/** /**
* @NoAdminRequired
* @throws StatusException * @throws StatusException
*/ */
#[NoAdminRequired]
public function create(int $cardId, string $message, int $parentId = 0): DataResponse { public function create(int $cardId, string $message, int $parentId = 0): DataResponse {
return $this->commentService->create($cardId, $message, $parentId); return $this->commentService->create($cardId, $message, $parentId);
} }
/** /**
* @NoAdminRequired
* @throws StatusException * @throws StatusException
*/ */
#[NoAdminRequired]
public function update(int $cardId, int $commentId, string $message): DataResponse { public function update(int $cardId, int $commentId, string $message): DataResponse {
return $this->commentService->update($cardId, $commentId, $message); return $this->commentService->update($cardId, $commentId, $message);
} }
/** /**
* @NoAdminRequired
* @throws StatusException * @throws StatusException
*/ */
#[NoAdminRequired]
public function delete(int $cardId, int $commentId): DataResponse { public function delete(int $cardId, int $commentId): DataResponse {
return $this->commentService->delete($cardId, $commentId); return $this->commentService->delete($cardId, $commentId);
} }
+7 -9
View File
@@ -8,6 +8,8 @@
namespace OCA\Deck\Controller; namespace OCA\Deck\Controller;
use OCA\Deck\Service\ConfigService; use OCA\Deck\Service\ConfigService;
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
use OCP\AppFramework\Http\DataResponse; use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\Http\NotFoundResponse; use OCP\AppFramework\Http\NotFoundResponse;
use OCP\AppFramework\OCSController; use OCP\AppFramework\OCSController;
@@ -22,19 +24,15 @@ class ConfigController extends OCSController {
parent::__construct($AppName, $request); parent::__construct($AppName, $request);
} }
/** #[NoAdminRequired]
* @NoCSRFRequired #[NoCSRFRequired]
* @NoAdminRequired
*/
public function get(): DataResponse { public function get(): DataResponse {
return new DataResponse($this->configService->getAll()); return new DataResponse($this->configService->getAll());
} }
/** #[NoAdminRequired]
* @NoCSRFRequired #[NoCSRFRequired]
* @NoAdminRequired public function setValue(string $key, mixed $value): DataResponse|NotFoundResponse {
*/
public function setValue(string $key, $value) {
$result = $this->configService->set($key, $value); $result = $this->configService->set($key, $value);
if ($result === null) { if ($result === null) {
return new NotFoundResponse(); return new NotFoundResponse();
+19 -25
View File
@@ -10,6 +10,9 @@ namespace OCA\Deck\Controller;
use OCA\Deck\Service\LabelService; use OCA\Deck\Service\LabelService;
use OCP\AppFramework\ApiController; use OCP\AppFramework\ApiController;
use OCP\AppFramework\Http; use OCP\AppFramework\Http;
use OCP\AppFramework\Http\Attribute\CORS;
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
use OCP\AppFramework\Http\DataResponse; use OCP\AppFramework\Http\DataResponse;
use OCP\IRequest; use OCP\IRequest;
@@ -26,59 +29,50 @@ class LabelApiController extends ApiController {
$appName, $appName,
IRequest $request, IRequest $request,
private LabelService $labelService, private LabelService $labelService,
private $userId,
) { ) {
parent::__construct($appName, $request); parent::__construct($appName, $request);
} }
/** /**
* @NoAdminRequired
* @CORS
* @NoCSRFRequired
*
* Get a specific label. * Get a specific label.
*/ */
public function get() { #[NoAdminRequired]
#[NoCSRFRequired]
#[CORS]
public function get(): DataResponse {
$label = $this->labelService->find($this->request->getParam('labelId')); $label = $this->labelService->find($this->request->getParam('labelId'));
return new DataResponse($label, HTTP::STATUS_OK); return new DataResponse($label, HTTP::STATUS_OK);
} }
/** /**
* @NoAdminRequired
* @CORS
* @NoCSRFRequired
*
* @params $title
* @params $color
* Create a new label * Create a new label
*/ */
public function create($title, $color) { #[NoAdminRequired]
#[NoCSRFRequired]
#[CORS]
public function create(string $title, string $color): DataResponse {
$label = $this->labelService->create($title, $color, $this->request->getParam('boardId')); $label = $this->labelService->create($title, $color, $this->request->getParam('boardId'));
return new DataResponse($label, HTTP::STATUS_OK); return new DataResponse($label, HTTP::STATUS_OK);
} }
/** /**
* @NoAdminRequired
* @CORS
* @NoCSRFRequired
*
* @params $title
* @params $color
* Update a specific label * Update a specific label
*/ */
public function update($title, $color) { #[NoAdminRequired]
#[NoCSRFRequired]
#[CORS]
public function update(string $title, string $color): DataResponse {
$label = $this->labelService->update($this->request->getParam('labelId'), $title, $color); $label = $this->labelService->update($this->request->getParam('labelId'), $title, $color);
return new DataResponse($label, HTTP::STATUS_OK); return new DataResponse($label, HTTP::STATUS_OK);
} }
/** /**
* @NoAdminRequired
* @CORS
* @NoCSRFRequired
*
* Delete a specific label * Delete a specific label
*/ */
public function delete() { #[NoAdminRequired]
#[NoCSRFRequired]
#[CORS]
public function delete(): DataResponse {
$label = $this->labelService->delete($this->request->getParam('labelId')); $label = $this->labelService->delete($this->request->getParam('labelId'));
return new DataResponse($label, HTTP::STATUS_OK); return new DataResponse($label, HTTP::STATUS_OK);
} }
+8 -22
View File
@@ -7,8 +7,10 @@
namespace OCA\Deck\Controller; namespace OCA\Deck\Controller;
use OCA\Deck\Db\Label;
use OCA\Deck\Service\LabelService; use OCA\Deck\Service\LabelService;
use OCP\AppFramework\Controller; use OCP\AppFramework\Controller;
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
use OCP\IRequest; use OCP\IRequest;
class LabelController extends Controller { class LabelController extends Controller {
@@ -20,34 +22,18 @@ class LabelController extends Controller {
parent::__construct($appName, $request); parent::__construct($appName, $request);
} }
/** #[NoAdminRequired]
* @NoAdminRequired public function create(string $title, string $color, int $boardId): Label {
* @param $title
* @param $color
* @param $boardId
* @return \OCP\AppFramework\Db\Entity
*/
public function create($title, $color, $boardId) {
return $this->labelService->create($title, $color, $boardId); return $this->labelService->create($title, $color, $boardId);
} }
/** #[NoAdminRequired]
* @NoAdminRequired public function update(int $id, string $title, string $color): Label {
* @param $id
* @param $title
* @param $color
* @return \OCP\AppFramework\Db\Entity
*/
public function update($id, $title, $color) {
return $this->labelService->update($id, $title, $color); return $this->labelService->update($id, $title, $color);
} }
/** #[NoAdminRequired]
* @NoAdminRequired public function delete(int $labelId): Label {
* @param $labelId
* @return \OCP\AppFramework\Db\Entity
*/
public function delete($labelId) {
return $this->labelService->delete($labelId); return $this->labelService->delete($labelId);
} }
} }
+2 -3
View File
@@ -10,6 +10,7 @@ declare(strict_types=1);
namespace OCA\Deck\Controller; namespace OCA\Deck\Controller;
use OCA\Deck\Service\OverviewService; use OCA\Deck\Service\OverviewService;
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
use OCP\AppFramework\Http\DataResponse; use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\OCSController; use OCP\AppFramework\OCSController;
use OCP\IRequest; use OCP\IRequest;
@@ -24,9 +25,7 @@ class OverviewApiController extends OCSController {
parent::__construct($appName, $request); parent::__construct($appName, $request);
} }
/** #[NoAdminRequired]
* @NoAdminRequired
*/
public function upcomingCards(): DataResponse { public function upcomingCards(): DataResponse {
return new DataResponse($this->dashboardService->findUpcomingCards($this->userId)); return new DataResponse($this->dashboardService->findUpcomingCards($this->userId));
} }
+2 -3
View File
@@ -13,6 +13,7 @@ namespace OCA\Deck\Controller;
use OCA\Deck\Db\Card; use OCA\Deck\Db\Card;
use OCA\Deck\Model\CardDetails; use OCA\Deck\Model\CardDetails;
use OCA\Deck\Service\SearchService; use OCA\Deck\Service\SearchService;
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
use OCP\AppFramework\Http\DataResponse; use OCP\AppFramework\Http\DataResponse;
use OCP\AppFramework\OCSController; use OCP\AppFramework\OCSController;
use OCP\IRequest; use OCP\IRequest;
@@ -26,9 +27,7 @@ class SearchController extends OCSController {
parent::__construct($appName, $request); parent::__construct($appName, $request);
} }
/** #[NoAdminRequired]
* @NoAdminRequired
*/
public function search(string $term, ?int $limit = null, ?int $cursor = null): DataResponse { public function search(string $term, ?int $limit = null, ?int $cursor = null): DataResponse {
$cards = $this->searchService->searchCards($term, $limit, $cursor); $cards = $this->searchService->searchCards($term, $limit, $cursor);
return new DataResponse(array_map(function (Card $card) { return new DataResponse(array_map(function (Card $card) {
+33 -44
View File
@@ -7,14 +7,16 @@
namespace OCA\Deck\Controller; namespace OCA\Deck\Controller;
use OCA\Deck\Service\BoardService;
use OCA\Deck\Service\StackService; use OCA\Deck\Service\StackService;
use OCA\Deck\StatusException; use OCA\Deck\StatusException;
use OCP\AppFramework\ApiController; use OCP\AppFramework\ApiController;
use OCP\AppFramework\Http; use OCP\AppFramework\Http;
use OCP\AppFramework\Http\Attribute\CORS;
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
use OCP\AppFramework\Http\Attribute\NoCSRFRequired;
use OCP\AppFramework\Http\DataResponse; use OCP\AppFramework\Http\DataResponse;
use OCP\IRequest; use OCP\IRequest;
use Sabre\HTTP\Util; use function Sabre\HTTP\parseDate;
/** /**
* Class StackApiController * Class StackApiController
@@ -29,23 +31,21 @@ class StackApiController extends ApiController {
$appName, $appName,
IRequest $request, IRequest $request,
private StackService $stackService, private StackService $stackService,
private BoardService $boardService,
) { ) {
parent::__construct($appName, $request); parent::__construct($appName, $request);
} }
/** /**
* @NoAdminRequired * Return all the stacks in the specified board.
* @CORS
* @NoCSRFRequired
*
* Return all of the stacks in the specified board.
*/ */
public function index() { #[NoAdminRequired]
#[CORS]
#[NoCSRFRequired]
public function index(): DataResponse {
$since = 0; $since = 0;
$modified = $this->request->getHeader('If-Modified-Since'); $modified = $this->request->getHeader('If-Modified-Since');
if ($modified !== null && $modified !== '') { if ($modified !== '') {
$date = Util::parseHTTPDate($modified); $date = parseDate($modified);
if (!$date) { if (!$date) {
throw new StatusException('Invalid If-Modified-Since header provided.'); throw new StatusException('Invalid If-Modified-Since header provided.');
} }
@@ -56,13 +56,12 @@ class StackApiController extends ApiController {
} }
/** /**
* @NoAdminRequired * Return all the stacks in the specified board.
* @CORS
* @NoCSRFRequired
*
* Return all of the stacks in the specified board.
*/ */
public function get() { #[NoAdminRequired]
#[CORS]
#[NoCSRFRequired]
public function get(): DataResponse {
$stack = $this->stackService->find($this->request->getParam('stackId')); $stack = $this->stackService->find($this->request->getParam('stackId'));
$response = new DataResponse($stack, HTTP::STATUS_OK); $response = new DataResponse($stack, HTTP::STATUS_OK);
$response->setETag($stack->getETag()); $response->setETag($stack->getETag());
@@ -70,55 +69,45 @@ class StackApiController extends ApiController {
} }
/** /**
* @NoAdminRequired
* @CORS
* @NoCSRFRequired
*
* @params $title
* @params $order
*
* Create a stack with the specified title and order. * Create a stack with the specified title and order.
*/ */
public function create($title, $order) { #[NoAdminRequired]
#[CORS]
#[NoCSRFRequired]
public function create(string $title, int $order): DataResponse {
$stack = $this->stackService->create($title, $this->request->getParam('boardId'), $order); $stack = $this->stackService->create($title, $this->request->getParam('boardId'), $order);
return new DataResponse($stack, HTTP::STATUS_OK); return new DataResponse($stack, HTTP::STATUS_OK);
} }
/** /**
* @NoAdminRequired
* @CORS
* @NoCSRFRequired
*
* @params $title
* @params $order
*
* Update a stack by the specified stackId and boardId with the values that were put. * Update a stack by the specified stackId and boardId with the values that were put.
*/ */
public function update($title, $order) { #[NoAdminRequired]
#[CORS]
#[NoCSRFRequired]
public function update(string $title, int $order) {
$stack = $this->stackService->update($this->request->getParam('stackId'), $title, $this->request->getParam('boardId'), $order, 0); $stack = $this->stackService->update($this->request->getParam('stackId'), $title, $this->request->getParam('boardId'), $order, 0);
return new DataResponse($stack, HTTP::STATUS_OK); return new DataResponse($stack, HTTP::STATUS_OK);
} }
/** /**
* @NoAdminRequired
* @CORS
* @NoCSRFRequired
*
* Delete the stack specified by $this->request->getParam('stackId'). * Delete the stack specified by $this->request->getParam('stackId').
*/ */
public function delete() { #[NoAdminRequired]
#[CORS]
#[NoCSRFRequired]
public function delete(): DataResponse {
$stack = $this->stackService->delete($this->request->getParam('stackId')); $stack = $this->stackService->delete($this->request->getParam('stackId'));
return new DataResponse($stack, HTTP::STATUS_OK); return new DataResponse($stack, HTTP::STATUS_OK);
} }
/** /**
* @NoAdminRequired * Get the stacks that have been archived.
* @CORS
* @NoCSRFRequired
*
* get the stacks that have been archived.
*/ */
public function getArchived() { #[NoAdminRequired]
#[CORS]
#[NoCSRFRequired]
public function getArchived(): DataResponse {
$stacks = $this->stackService->findAllArchived($this->request->getParam('boardId')); $stacks = $this->stackService->findAllArchived($this->request->getParam('boardId'));
return new DataResponse($stacks, HTTP::STATUS_OK); return new DataResponse($stacks, HTTP::STATUS_OK);
} }
+21 -43
View File
@@ -7,10 +7,12 @@
namespace OCA\Deck\Controller; namespace OCA\Deck\Controller;
use OCA\Deck\Db\Stack;
use OCA\Deck\Service\StackService; use OCA\Deck\Service\StackService;
use OCP\AppFramework\Controller; use OCP\AppFramework\Controller;
use OCP\AppFramework\Http\Attribute\NoAdminRequired;
use OCP\IRequest; use OCP\IRequest;
class StackController extends Controller { class StackController extends Controller {
@@ -18,78 +20,54 @@ class StackController extends Controller {
string $appName, string $appName,
IRequest $request, IRequest $request,
private StackService $stackService, private StackService $stackService,
private $userId,
) { ) {
parent::__construct($appName, $request); parent::__construct($appName, $request);
} }
/** /**
* @NoAdminRequired * @return Stack[]
* @param $boardId
* @return array
*/ */
public function index($boardId) { #[NoAdminRequired]
public function index(int $boardId): array {
return $this->stackService->findAll($boardId); return $this->stackService->findAll($boardId);
} }
/** /**
* @NoAdminRequired * @return Stack[]
* @param $boardId
* @return array
*/ */
public function archived($boardId) { #[NoAdminRequired]
public function archived(int $boardId): array {
return $this->stackService->findAllArchived($boardId); return $this->stackService->findAllArchived($boardId);
} }
/** #[NoAdminRequired]
* @NoAdminRequired public function create(string $title, int $boardId, int $order = 999): Stack {
* @param $title
* @param $boardId
* @param int $order
* @return \OCP\AppFramework\Db\Entity
*/
public function create($title, $boardId, $order = 999) {
return $this->stackService->create($title, $boardId, $order); return $this->stackService->create($title, $boardId, $order);
} }
/** #[NoAdminRequired]
* @NoAdminRequired public function update(int $id, string $title, int $boardId, int $order, ?int $deletedAt = null): Stack {
* @param $id
* @param $title
* @param $boardId
* @param $order
* @param $deletedAt
* @return \OCP\AppFramework\Db\Entity
*/
public function update($id, $title, $boardId, $order, $deletedAt) {
return $this->stackService->update($id, $title, $boardId, $order, $deletedAt); return $this->stackService->update($id, $title, $boardId, $order, $deletedAt);
} }
/** /**
* @NoAdminRequired * @return array<int, Stack>
* @param $stackId
* @param $order
* @return array
*/ */
public function reorder($stackId, $order) { #[NoAdminRequired]
return $this->stackService->reorder((int)$stackId, (int)$order); public function reorder(int $stackId, int $order): array {
return $this->stackService->reorder($stackId, $order);
} }
/** #[NoAdminRequired]
* @NoAdminRequired public function delete(int $stackId): Stack {
* @param $stackId
* @return \OCP\AppFramework\Db\Entity
*/
public function delete($stackId) {
return $this->stackService->delete($stackId); return $this->stackService->delete($stackId);
} }
/** /**
* @NoAdminRequired * @return Stack[]
* @param $boardId
* @return \OCP\AppFramework\Db\Entity
*/ */
public function deleted($boardId) { #[NoAdminRequired]
public function deleted(int $boardId): array {
return $this->stackService->fetchDeleted($boardId); return $this->stackService->fetchDeleted($boardId);
} }
} }
+22 -12
View File
@@ -7,6 +7,20 @@
namespace OCA\Deck\Db; namespace OCA\Deck\Db;
/**
* @method int getBoardId()
* @method bool isPermissionEdit()
* @method void setPermissionEdit(bool $permissionEdit)
* @method bool isPermissionShare()
* @method void setPermissionShare(bool $permissionShare)
* @method bool isPermissionManage()
* @method void setPermissionManage(bool $permissionManage)
* @method int getType()
* @method void setType(int $type)
* @method bool isOwner()
* @method void setOwner(int $owner)
*
*/
class Acl extends RelationalEntity { class Acl extends RelationalEntity {
public const PERMISSION_READ = 0; public const PERMISSION_READ = 0;
public const PERMISSION_EDIT = 1; public const PERMISSION_EDIT = 1;
@@ -37,17 +51,13 @@ class Acl extends RelationalEntity {
$this->addResolvable('participant'); $this->addResolvable('participant');
} }
public function getPermission($permission) { public function getPermission(int $permission): bool {
switch ($permission) { return match ($permission) {
case self::PERMISSION_READ: self::PERMISSION_READ => true,
return true; self::PERMISSION_EDIT => $this->getPermissionEdit(),
case self::PERMISSION_EDIT: self::PERMISSION_SHARE => $this->getPermissionShare(),
return $this->getPermissionEdit(); self::PERMISSION_MANAGE => $this->getPermissionManage(),
case self::PERMISSION_SHARE: default => false,
return $this->getPermissionShare(); };
case self::PERMISSION_MANAGE:
return $this->getPermissionManage();
}
return false;
} }
} }
+4 -14
View File
@@ -19,13 +19,10 @@ class AclMapper extends DeckMapper implements IPermissionMapper {
} }
/** /**
* @param numeric $boardId
* @param int|null $limit
* @param int|null $offset
* @return Acl[] * @return Acl[]
* @throws \OCP\DB\Exception * @throws \OCP\DB\Exception
*/ */
public function findAll($boardId, $limit = null, $offset = null) { public function findAll(int $boardId, ?int $limit = null, ?int $offset = null) {
$qb = $this->db->getQueryBuilder(); $qb = $this->db->getQueryBuilder();
$qb->select('id', 'board_id', 'type', 'participant', 'permission_edit', 'permission_share', 'permission_manage') $qb->select('id', 'board_id', 'type', 'participant', 'permission_edit', 'permission_share', 'permission_manage')
->from('deck_board_acl') ->from('deck_board_acl')
@@ -51,12 +48,9 @@ class AclMapper extends DeckMapper implements IPermissionMapper {
} }
/** /**
* @param numeric $userId
* @param numeric $id
* @return bool
* @throws \OCP\DB\Exception * @throws \OCP\DB\Exception
*/ */
public function isOwner($userId, $id): bool { public function isOwner(string $userId, int $id): bool {
$aclId = $id; $aclId = $id;
$qb = $this->db->getQueryBuilder(); $qb = $this->db->getQueryBuilder();
$qb->select('acl.id') $qb->select('acl.id')
@@ -68,11 +62,7 @@ class AclMapper extends DeckMapper implements IPermissionMapper {
return count($qb->executeQuery()->fetchAll()) > 0; return count($qb->executeQuery()->fetchAll()) > 0;
} }
/** public function findBoardId(int $id): ?int {
* @param numeric $id
* @return int|null
*/
public function findBoardId($id): ?int {
try { try {
$entity = $this->find($id); $entity = $this->find($id);
return $entity->getBoardId(); return $entity->getBoardId();
@@ -87,7 +77,7 @@ class AclMapper extends DeckMapper implements IPermissionMapper {
* @return Acl[] * @return Acl[]
* @throws \OCP\DB\Exception * @throws \OCP\DB\Exception
*/ */
public function findByParticipant($type, $participant): array { public function findByParticipant(int $type, string $participant): array {
$qb = $this->db->getQueryBuilder(); $qb = $this->db->getQueryBuilder();
$qb->select('*') $qb->select('*')
+28 -10
View File
@@ -77,26 +77,41 @@ class AssignmentMapper extends DeckMapper implements IPermissionMapper {
} }
public function deleteByParticipantOnBoard(string $participant, int $boardId, $type = Assignment::TYPE_USER) { public function deleteByParticipantOnBoard(string $participant, int $boardId, $type = Assignment::TYPE_USER) {
$qb = $this->db->getQueryBuilder(); // Step 1: Get all card IDs for the board that have assignments for this participant
// This avoids MySQL Error 1093 by separating the SELECT from the DELETE operation
$cardIdQuery = $this->db->getQueryBuilder(); $cardIdQuery = $this->db->getQueryBuilder();
$cardIdQuery->select('a.card_id') $cardIdQuery->select('a.card_id')
->from('deck_assigned_users', 'a') ->from('deck_assigned_users', 'a')
->innerJoin('a', 'deck_cards', 'c', 'c.id = a.card_id') ->innerJoin('a', 'deck_cards', 'c', 'c.id = a.card_id')
->innerJoin('c', 'deck_stacks', 's', 's.id = c.stack_id') ->innerJoin('c', 'deck_stacks', 's', 's.id = c.stack_id')
->where($cardIdQuery->expr()->eq('a.participant', $qb->createNamedParameter($participant, IQueryBuilder::PARAM_STR))) ->where($cardIdQuery->expr()->eq('a.participant', $cardIdQuery->createNamedParameter($participant, IQueryBuilder::PARAM_STR)))
->andWhere($cardIdQuery->expr()->eq('s.board_id', $qb->createNamedParameter($boardId, IQueryBuilder::PARAM_INT))) ->andWhere($cardIdQuery->expr()->eq('s.board_id', $cardIdQuery->createNamedParameter($boardId, IQueryBuilder::PARAM_INT)))
->andWhere($cardIdQuery->expr()->eq('a.type', $qb->createNamedParameter($type, IQueryBuilder::PARAM_INT))); ->andWhere($cardIdQuery->expr()->eq('a.type', $cardIdQuery->createNamedParameter($type, IQueryBuilder::PARAM_INT)));
$qb->delete('deck_assigned_users')
->where($qb->expr()->in('card_id', $qb->createFunction($cardIdQuery->getSQL()), IQueryBuilder::PARAM_INT_ARRAY)); $result = $cardIdQuery->executeQuery();
$qb->executeStatement(); $cardIds = [];
while ($row = $result->fetch()) {
$cardIds[] = $row['card_id'];
}
$result->closeCursor();
// Step 2: If we have card IDs, delete the assignments
if (!empty($cardIds)) {
$deleteQuery = $this->db->getQueryBuilder();
$deleteQuery->delete('deck_assigned_users')
->where($deleteQuery->expr()->eq('participant', $deleteQuery->createNamedParameter($participant, IQueryBuilder::PARAM_STR)))
->andWhere($deleteQuery->expr()->eq('type', $deleteQuery->createNamedParameter($type, IQueryBuilder::PARAM_INT)))
->andWhere($deleteQuery->expr()->in('card_id', $deleteQuery->createNamedParameter($cardIds, IQueryBuilder::PARAM_INT_ARRAY)));
$deleteQuery->executeStatement();
}
} }
public function isOwner($userId, $id): bool { public function isOwner(string $userId, int $id): bool {
return $this->cardMapper->isOwner($userId, $id); return $this->cardMapper->isOwner($userId, $id);
} }
public function findBoardId($id): ?int { public function findBoardId(int $id): ?int {
return $this->cardMapper->findBoardId($id); return $this->cardMapper->findBoardId($id);
} }
@@ -108,6 +123,9 @@ class AssignmentMapper extends DeckMapper implements IPermissionMapper {
* @throws NotFoundException * @throws NotFoundException
*/ */
public function insert(Entity $entity): Entity { public function insert(Entity $entity): Entity {
if (!($entity instanceof Assignment)) {
throw new \LogicException('Trying to insert a ' . get_class($entity) . ' in the assignment mapper');
}
$origin = $this->getOrigin($entity); $origin = $this->getOrigin($entity);
if ($origin === null) { if ($origin === null) {
throw new NotFoundException('No origin found for assignment'); throw new NotFoundException('No origin found for assignment');
@@ -126,7 +144,7 @@ class AssignmentMapper extends DeckMapper implements IPermissionMapper {
}); });
} }
public function isUserAssigned($cardId, $userId): bool { public function isUserAssigned(int $cardId, string $userId): bool {
$assignments = $this->findAll($cardId); $assignments = $this->findAll($cardId);
foreach ($assignments as $assignment) { foreach ($assignments as $assignment) {
$origin = $this->getOrigin($assignment); $origin = $this->getOrigin($assignment);
+8 -20
View File
@@ -36,13 +36,11 @@ class AttachmentMapper extends DeckMapper implements IPermissionMapper {
} }
/** /**
* @param int $id
* @return Attachment
* @throws DoesNotExistException * @throws DoesNotExistException
* @throws MultipleObjectsReturnedException * @throws MultipleObjectsReturnedException
* @throws \OCP\DB\Exception * @throws \OCP\DB\Exception
*/ */
public function find($id) { public function find(int $id): Attachment {
$qb = $this->db->getQueryBuilder(); $qb = $this->db->getQueryBuilder();
$qb->select('*') $qb->select('*')
->from($this->getTableName()) ->from($this->getTableName())
@@ -52,14 +50,11 @@ class AttachmentMapper extends DeckMapper implements IPermissionMapper {
} }
/** /**
* @param int $cardId
* @param string $data
* @return Attachment
* @throws DoesNotExistException * @throws DoesNotExistException
* @throws MultipleObjectsReturnedException * @throws MultipleObjectsReturnedException
* @throws \OCP\DB\Exception * @throws \OCP\DB\Exception
*/ */
public function findByData($cardId, $data) { public function findByData(int $cardId, string $data): Attachment {
$qb = $this->db->getQueryBuilder(); $qb = $this->db->getQueryBuilder();
$qb->select('*') $qb->select('*')
->from($this->getTableName()) ->from($this->getTableName())
@@ -70,11 +65,10 @@ class AttachmentMapper extends DeckMapper implements IPermissionMapper {
} }
/** /**
* @param $cardId
* @return Entity[] * @return Entity[]
* @throws \OCP\DB\Exception * @throws \OCP\DB\Exception
*/ */
public function findAll($cardId) { public function findAll(int $cardId): array {
$qb = $this->db->getQueryBuilder(); $qb = $this->db->getQueryBuilder();
$qb->select('*') $qb->select('*')
->from($this->getTableName()) ->from($this->getTableName())
@@ -86,11 +80,9 @@ class AttachmentMapper extends DeckMapper implements IPermissionMapper {
} }
/** /**
* @param null $cardId * @return Attachment[]
* @param bool $withOffset
* @return array
*/ */
public function findToDelete($cardId = null, $withOffset = true) { public function findToDelete(?int $cardId = null, bool $withOffset = true): array {
// add buffer of 5 min // add buffer of 5 min
$timeLimit = time() - (60 * 5); $timeLimit = time() - (60 * 5);
$qb = $this->db->getQueryBuilder(); $qb = $this->db->getQueryBuilder();
@@ -112,12 +104,8 @@ class AttachmentMapper extends DeckMapper implements IPermissionMapper {
/** /**
* Check if $userId is owner of Entity with $id * Check if $userId is owner of Entity with $id
*
* @param $userId string userId
* @param $id int|string unique entity identifier
* @return boolean
*/ */
public function isOwner($userId, $id): bool { public function isOwner(string $userId, int $id): bool {
try { try {
$attachment = $this->find($id); $attachment = $this->find($id);
return $this->cardMapper->isOwner($userId, $attachment->getCardId()); return $this->cardMapper->isOwner($userId, $attachment->getCardId());
@@ -130,10 +118,10 @@ class AttachmentMapper extends DeckMapper implements IPermissionMapper {
/** /**
* Query boardId for Entity of given $id * Query boardId for Entity of given $id
* *
* @param $id int|string unique entity identifier * @param $id int unique entity identifier
* @return int|null id of Board * @return int|null id of Board
*/ */
public function findBoardId($id): ?int { public function findBoardId(int $id): ?int {
try { try {
$attachment = $this->find($id); $attachment = $this->find($id);
} catch (\Exception $e) { } catch (\Exception $e) {
+10
View File
@@ -10,10 +10,20 @@ namespace OCA\Deck\Db;
/** /**
* @method int getId() * @method int getId()
* @method string getTitle() * @method string getTitle()
* @method void setTitle(string $title)
* @method int getShared() * @method int getShared()
* @method void setShared(int $shared)
* @method bool isArchived()
* @method bool getArchived() * @method bool getArchived()
* @method void setArchived(bool $archived)
* @method int getDeletedAt() * @method int getDeletedAt()
* @method void setDeletedAt(int $deletedAt)
* @method int getLastModified() * @method int getLastModified()
* @method void setLastModified(int $lastModified)
* @method string getOwner()
* @method void setOwner(string $owner)
* @method string getColor()
* @method void setColor(string $color)
*/ */
class Board extends RelationalEntity { class Board extends RelationalEntity {
protected $title; protected $title;
+3 -3
View File
@@ -469,16 +469,16 @@ class BoardMapper extends QBMapper implements IPermissionMapper {
return parent::delete($entity); return parent::delete($entity);
} }
public function isOwner($userId, $id): bool { public function isOwner(string $userId, int $id): bool {
$board = $this->find($id); $board = $this->find($id);
return ($board->getOwner() === $userId); return ($board->getOwner() === $userId);
} }
public function findBoardId($id): ?int { public function findBoardId(int $id): ?int {
return $id; return $id;
} }
public function mapAcl(Acl &$acl) { public function mapAcl(Acl &$acl): void {
$acl->resolveRelation('participant', function ($participant) use (&$acl) { $acl->resolveRelation('participant', function ($participant) use (&$acl) {
if ($acl->getType() === Acl::PERMISSION_TYPE_USER) { if ($acl->getType() === Acl::PERMISSION_TYPE_USER) {
if ($this->userManager->userExists($acl->getParticipant())) { if ($this->userManager->userExists($acl->getParticipant())) {
+7 -2
View File
@@ -15,13 +15,18 @@ use Sabre\VObject\Component\VCalendar;
/** /**
* @method string getTitle() * @method string getTitle()
* @method void setTitle(string $title)
* @method string getDescription() * @method string getDescription()
* @method string getDescriptionPrev() * @method string getDescriptionPrev()
* @method int getStackId() * @method int getStackId()
* @method void setStackId(int $stackId)
* @method int getOrder() * @method int getOrder()
* @method void setOrder(int $order)
* @method int getLastModified() * @method int getLastModified()
* @method int getCreatedAt() * @method int getCreatedAt()
* @method bool getArchived() * @method bool getArchived()
* @method string getType()
* @method void setType(string $type)
* @method int getDeletedAt() * @method int getDeletedAt()
* @method void setDeletedAt(int $deletedAt) * @method void setDeletedAt(int $deletedAt)
* @method bool getNotified() * @method bool getNotified()
@@ -68,8 +73,8 @@ class Card extends RelationalEntity {
protected $createdAt; protected $createdAt;
protected $labels; protected $labels;
protected $assignedUsers; protected $assignedUsers;
protected $attachments; protected array $attachments = [];
protected $attachmentCount; protected int $attachmentCount = 0;
protected $owner; protected $owner;
protected $order; protected $order;
protected $archived = false; protected $archived = false;
+81 -28
View File
@@ -86,16 +86,15 @@ class CardMapper extends QBMapper implements IPermissionMapper {
$updatedFields = $entity->getUpdatedFields(); $updatedFields = $entity->getUpdatedFields();
if (isset($updatedFields['duedate']) && $updatedFields['duedate']) { if (isset($updatedFields['duedate']) && $updatedFields['duedate']) {
try { try {
/** @var Card $existing */
$existing = $this->find($entity->getId()); $existing = $this->find($entity->getId());
if ($existing && $entity->getDuedate() !== $existing->getDuedate()) { if ($entity->getDueDate() !== $existing->getDueDate()) {
$entity->setNotified(false); $entity->setNotified(false);
} }
// remove pending notifications // remove pending notifications
$notification = $this->notificationManager->createNotification(); $notification = $this->notificationManager->createNotification();
$notification $notification
->setApp('deck') ->setApp('deck')
->setObject('card', $entity->getId()); ->setObject('card', (string)$entity->getId());
$this->notificationManager->markProcessed($notification); $this->notificationManager->markProcessed($notification);
} catch (Exception $e) { } catch (Exception $e) {
} }
@@ -131,7 +130,11 @@ class CardMapper extends QBMapper implements IPermissionMapper {
return $card; return $card;
} }
public function findAll($stackId, $limit = null, $offset = null, $since = -1) { /**
* @return Card[]
* @throws \OCP\DB\Exception
*/
public function findAll($stackId, ?int $limit = null, int $offset = 0, int $since = -1) {
$qb = $this->db->getQueryBuilder(); $qb = $this->db->getQueryBuilder();
$qb->select('*') $qb->select('*')
->from('deck_cards') ->from('deck_cards')
@@ -146,6 +149,32 @@ class CardMapper extends QBMapper implements IPermissionMapper {
return $this->findEntities($qb); return $this->findEntities($qb);
} }
/**
* @param int[] $stackIds
* @return array<int, null|Card[]>
* @throws \OCP\DB\Exception
*/
public function findAllForStacks(array $stackIds, ?int $limit = null, int $offset = 0, int $since = -1): array {
$qb = $this->db->getQueryBuilder();
$qb->select('*')
->from('deck_cards')
->where($qb->expr()->in('stack_id', $qb->createNamedParameter($stackIds, IQueryBuilder::PARAM_INT_ARRAY)))
->andWhere($qb->expr()->eq('archived', $qb->createNamedParameter(false, IQueryBuilder::PARAM_BOOL)))
->andWhere($qb->expr()->eq('deleted_at', $qb->createNamedParameter(0, IQueryBuilder::PARAM_INT)))
->andWhere($qb->expr()->gt('last_modified', $qb->createNamedParameter($since, IQueryBuilder::PARAM_INT)))
->setMaxResults($limit)
->setFirstResult($offset)
->orderBy('order')
->addOrderBy('id');
$rawCards = $this->findEntities($qb);
$cards = array_fill_keys($stackIds, null);
foreach ($rawCards as $card) {
$cards[$card->getStackId()][] = $card;
}
return $cards;
}
public function queryCardsByBoard(int $boardId): IQueryBuilder { public function queryCardsByBoard(int $boardId): IQueryBuilder {
$qb = $this->db->getQueryBuilder(); $qb = $this->db->getQueryBuilder();
$qb->select('c.*') $qb->select('c.*')
@@ -164,7 +193,10 @@ class CardMapper extends QBMapper implements IPermissionMapper {
return $qb; return $qb;
} }
public function findToDelete($timeLimit, $limit = null) { /**
* @return Card[]
*/
public function findToDelete(int $timeLimit, ?int $limit = null): array {
$qb = $this->db->getQueryBuilder(); $qb = $this->db->getQueryBuilder();
$qb->select('id', 'title', 'owner', 'archived', 'deleted_at', 'last_modified') $qb->select('id', 'title', 'owner', 'archived', 'deleted_at', 'last_modified')
->from('deck_cards') ->from('deck_cards')
@@ -175,7 +207,10 @@ class CardMapper extends QBMapper implements IPermissionMapper {
return $this->findEntities($qb); return $this->findEntities($qb);
} }
public function findDeleted($boardId, $limit = null, $offset = null) { /**
* @return Card[]
*/
public function findDeleted(int $boardId, ?int $limit = null, int $offset = 0): array {
$qb = $this->queryCardsByBoard($boardId); $qb = $this->queryCardsByBoard($boardId);
$qb->andWhere($qb->expr()->neq('c.deleted_at', $qb->createNamedParameter(0, IQueryBuilder::PARAM_INT))) $qb->andWhere($qb->expr()->neq('c.deleted_at', $qb->createNamedParameter(0, IQueryBuilder::PARAM_INT)))
->setMaxResults($limit) ->setMaxResults($limit)
@@ -185,7 +220,10 @@ class CardMapper extends QBMapper implements IPermissionMapper {
return $this->findEntities($qb); return $this->findEntities($qb);
} }
public function findCalendarEntries($boardId, $limit = null, $offset = null) { /**
* @return Card[]
*/
public function findCalendarEntries(int $boardId, ?int $limit = null, $offset = 0): array {
$qb = $this->db->getQueryBuilder(); $qb = $this->db->getQueryBuilder();
$qb->select('c.*') $qb->select('c.*')
->from('deck_cards', 'c') ->from('deck_cards', 'c')
@@ -240,7 +278,11 @@ class CardMapper extends QBMapper implements IPermissionMapper {
return $this->findEntities($qb); return $this->findEntities($qb);
} }
public function findAllWithDue(array $boardIds) { /**
* @param int[] $boardIds
* @return Card[]
*/
public function findAllWithDue(array $boardIds): array {
$qb = $this->db->getQueryBuilder(); $qb = $this->db->getQueryBuilder();
$qb->select('c.*') $qb->select('c.*')
->from('deck_cards', 'c') ->from('deck_cards', 'c')
@@ -257,7 +299,11 @@ class CardMapper extends QBMapper implements IPermissionMapper {
return $this->findEntities($qb); return $this->findEntities($qb);
} }
public function findToMeOrNotAssignedCards(array $boardIds, string $username) { /**
* @param int[] $boardIds
* @return Card[]
*/
public function findToMeOrNotAssignedCards(array $boardIds, string $username): array {
$qb = $this->db->getQueryBuilder(); $qb = $this->db->getQueryBuilder();
$qb->select('c.*') $qb->select('c.*')
->from('deck_cards', 'c') ->from('deck_cards', 'c')
@@ -279,7 +325,10 @@ class CardMapper extends QBMapper implements IPermissionMapper {
return $this->findEntities($qb); return $this->findEntities($qb);
} }
public function findOverdue() { /**
* @return Card[]
*/
public function findOverdue(): array {
$qb = $this->db->getQueryBuilder(); $qb = $this->db->getQueryBuilder();
$qb->select('id', 'title', 'duedate', 'notified') $qb->select('id', 'title', 'duedate', 'notified')
->from('deck_cards') ->from('deck_cards')
@@ -291,6 +340,9 @@ class CardMapper extends QBMapper implements IPermissionMapper {
return $this->findEntities($qb); return $this->findEntities($qb);
} }
/**
* @return Card[]
*/
public function findUnexposedDescriptionChances() { public function findUnexposedDescriptionChances() {
$qb = $this->db->getQueryBuilder(); $qb = $this->db->getQueryBuilder();
$qb->select('id', 'title', 'duedate', 'notified', 'description_prev', 'last_editor', 'description') $qb->select('id', 'title', 'duedate', 'notified', 'description_prev', 'last_editor', 'description')
@@ -299,6 +351,9 @@ class CardMapper extends QBMapper implements IPermissionMapper {
return $this->findEntities($qb); return $this->findEntities($qb);
} }
/**
* @return Card[]
*/
public function search(array $boardIds, SearchQuery $query, ?int $limit = null, ?int $offset = null): array { public function search(array $boardIds, SearchQuery $query, ?int $limit = null, ?int $offset = null): array {
$qb = $this->queryCardsByBoards($boardIds); $qb = $this->queryCardsByBoards($boardIds);
$this->extendQueryByFilter($qb, $query); $this->extendQueryByFilter($qb, $query);
@@ -333,7 +388,7 @@ class CardMapper extends QBMapper implements IPermissionMapper {
$qb->andWhere($qb->expr()->lt('c.last_modified', $qb->createNamedParameter($offset, IQueryBuilder::PARAM_INT))); $qb->andWhere($qb->expr()->lt('c.last_modified', $qb->createNamedParameter($offset, IQueryBuilder::PARAM_INT)));
} }
$result = $qb->execute(); $result = $qb->executeQuery();
$entities = []; $entities = [];
while ($row = $result->fetch()) { while ($row = $result->fetch()) {
$entities[] = Card::fromRow($row); $entities[] = Card::fromRow($row);
@@ -376,7 +431,7 @@ class CardMapper extends QBMapper implements IPermissionMapper {
$qb->andWhere($qb->expr()->lt('comments.id', $qb->createNamedParameter($offset, IQueryBuilder::PARAM_INT))); $qb->andWhere($qb->expr()->lt('comments.id', $qb->createNamedParameter($offset, IQueryBuilder::PARAM_INT)));
} }
$result = $qb->execute(); $result = $qb->executeQuery();
$entities = $result->fetchAll(); $entities = $result->fetchAll();
$result->closeCursor(); $result->closeCursor();
return $entities; return $entities;
@@ -472,7 +527,7 @@ class CardMapper extends QBMapper implements IPermissionMapper {
}); });
$groups = $this->groupManager->search($assignment->getValue()); $groups = $this->groupManager->search($assignment->getValue());
foreach ($searchUsers as $user) { foreach ($searchUsers as $user) {
$groups = array_merge($groups, $this->groupManager->getUserIdGroups($user->getUID())); $groups = array_merge($groups, $this->groupManager->getUserGroups($user));
} }
$assignmentSearches = []; $assignmentSearches = [];
@@ -525,7 +580,7 @@ class CardMapper extends QBMapper implements IPermissionMapper {
if ($offset !== null) { if ($offset !== null) {
$qb->setFirstResult($offset); $qb->setFirstResult($offset);
} }
$result = $qb->execute(); $result = $qb->executeQuery();
$all = $result->fetchAll(); $all = $result->fetchAll();
$result->closeCursor(); $result->closeCursor();
return $all; return $all;
@@ -537,32 +592,32 @@ class CardMapper extends QBMapper implements IPermissionMapper {
return parent::delete($entity); return parent::delete($entity);
} }
public function deleteByStack($stackId) { public function deleteByStack($stackId): void {
$cards = $this->findAllByStack($stackId); $cards = $this->findAllByStack($stackId);
foreach ($cards as $card) { foreach ($cards as $card) {
$this->delete($card); $this->delete($card);
} }
} }
public function assignLabel($card, $label) { public function assignLabel(int $card, int $label): void {
$qb = $this->db->getQueryBuilder(); $qb = $this->db->getQueryBuilder();
$qb->insert('deck_assigned_labels') $qb->insert('deck_assigned_labels')
->values([ ->values([
'label_id' => $qb->createNamedParameter($label, IQueryBuilder::PARAM_INT), 'label_id' => $qb->createNamedParameter($label, IQueryBuilder::PARAM_INT),
'card_id' => $qb->createNamedParameter($card, IQueryBuilder::PARAM_INT), 'card_id' => $qb->createNamedParameter($card, IQueryBuilder::PARAM_INT),
]); ]);
$qb->execute(); $qb->executeStatement();
} }
public function removeLabel($card, $label) { public function removeLabel(int $card, int $label): void {
$qb = $this->db->getQueryBuilder(); $qb = $this->db->getQueryBuilder();
$qb->delete('deck_assigned_labels') $qb->delete('deck_assigned_labels')
->where($qb->expr()->eq('card_id', $qb->createNamedParameter($card, IQueryBuilder::PARAM_INT))) ->where($qb->expr()->eq('card_id', $qb->createNamedParameter($card, IQueryBuilder::PARAM_INT)))
->andWhere($qb->expr()->eq('label_id', $qb->createNamedParameter($label, IQueryBuilder::PARAM_INT))); ->andWhere($qb->expr()->eq('label_id', $qb->createNamedParameter($label, IQueryBuilder::PARAM_INT)));
$qb->execute(); $qb->executeStatement();
} }
public function isOwner($userId, $id): bool { public function isOwner(string $userId, int $id): bool {
$qb = $this->db->getQueryBuilder(); $qb = $this->db->getQueryBuilder();
$qb->select('c.id') $qb->select('c.id')
->from($this->getTableName(), 'c') ->from($this->getTableName(), 'c')
@@ -574,7 +629,7 @@ class CardMapper extends QBMapper implements IPermissionMapper {
return count($qb->executeQuery()->fetchAll()) > 0; return count($qb->executeQuery()->fetchAll()) > 0;
} }
public function findBoardId($id): ?int { public function findBoardId(int $id): ?int {
$result = $this->cache->get('findBoardId:' . $id); $result = $this->cache->get('findBoardId:' . $id);
if ($result === null) { if ($result === null) {
try { try {
@@ -604,13 +659,11 @@ class CardMapper extends QBMapper implements IPermissionMapper {
} }
public function transferOwnership(string $ownerId, string $newOwnerId, ?int $boardId = null): void { public function transferOwnership(string $ownerId, string $newOwnerId, ?int $boardId = null): void {
$params = [ $qb = $this->db->getQueryBuilder();
'owner' => $ownerId, $qb->update($this->getTableName())
'newOwner' => $newOwnerId ->set('owner', $qb->createNamedParameter($newOwnerId, IQueryBuilder::PARAM_STR))
]; ->where('owner', $qb->createNamedParameter($ownerId, IQueryBuilder::PARAM_STR))
$sql = "UPDATE `*PREFIX*{$this->tableName}` SET `owner` = :newOwner WHERE `owner` = :owner"; ->executeStatement();
$stmt = $this->db->executeQuery($sql, $params);
$stmt->closeCursor();
} }
public function remapCardOwner(int $boardId, string $userId, string $newUserId): void { public function remapCardOwner(int $boardId, string $userId, string $newUserId): void {
+2 -3
View File
@@ -19,12 +19,11 @@ use OCP\DB\QueryBuilder\IQueryBuilder;
abstract class DeckMapper extends QBMapper { abstract class DeckMapper extends QBMapper {
/** /**
* @param $id
* @return T * @return T
* @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException * @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException
* @throws \OCP\AppFramework\Db\DoesNotExistException * @throws \OCP\AppFramework\Db\DoesNotExistException
*/ */
public function find($id) { public function find(int $id): Entity {
$qb = $this->db->getQueryBuilder(); $qb = $this->db->getQueryBuilder();
$qb->select('*') $qb->select('*')
->from($this->getTableName()) ->from($this->getTableName())
@@ -37,7 +36,7 @@ abstract class DeckMapper extends QBMapper {
* Helper function to split passed array into chunks of 1000 elements and * Helper function to split passed array into chunks of 1000 elements and
* call a given callback for fetching query results * call a given callback for fetching query results
* *
* Can be useful to limit to 1000 results per query for oracle compatiblity * Can be useful to limit to 1000 results per query for oracle compatibility
* but still iterate over all results * but still iterate over all results
*/ */
public function chunkQuery(array $ids, callable $callback): Generator { public function chunkQuery(array $ids, callable $callback): Generator {
+8 -5
View File
@@ -8,22 +8,25 @@
namespace OCA\Deck\Db; namespace OCA\Deck\Db;
/**
*
*/
interface IPermissionMapper { interface IPermissionMapper {
/** /**
* Check if $userId is owner of Entity with $id * Check if $userId is owner of Entity with $id
* *
* @param $userId string userId * @param $userId string userId
* @param $id int|string unique entity identifier * @param $id int unique entity identifier
* @return boolean * @return boolean
*/ */
public function isOwner($userId, $id): bool; public function isOwner(string $userId, int $id): bool;
/** /**
* Query boardId for Entity of given $id * Query boardId for Entity of given $id
* *
* @param $id int|string unique entity identifier * @param $id int unique entity identifier
* @return int|null id of Board * @return ?int id of Board
*/ */
public function findBoardId($id): ?int; public function findBoardId(int $id): ?int;
} }
+11 -2
View File
@@ -8,7 +8,16 @@
namespace OCA\Deck\Db; namespace OCA\Deck\Db;
/** /**
* @method getTitle(): string * @method string getTitle()
* @method void setTitle(string $title)
* @method string getColor()
* @method void setColor(string $color)
* @method int getBoardId()
* @method void setBoardId(int $boardId)
* @method int getCardId()
* @method void setCardId(int $cardId)
* @method int getLastModified()
* @method void setLastModified(int $lastModified)
*/ */
class Label extends RelationalEntity { class Label extends RelationalEntity {
protected $title; protected $title;
@@ -24,7 +33,7 @@ class Label extends RelationalEntity {
$this->addType('lastModified', 'integer'); $this->addType('lastModified', 'integer');
} }
public function getETag() { public function getETag(): string {
return md5((string)$this->getLastModified()); return md5((string)$this->getLastModified());
} }
} }
+13 -35
View File
@@ -20,13 +20,10 @@ class LabelMapper extends DeckMapper implements IPermissionMapper {
} }
/** /**
* @param numeric $boardId
* @param int|null $limit
* @param int|null $offset
* @return Label[] * @return Label[]
* @throws \OCP\DB\Exception * @throws \OCP\DB\Exception
*/ */
public function findAll($boardId, $limit = null, $offset = null): array { public function findAll(int $boardId, ?int $limit = null, int $offset = 0): array {
$qb = $this->db->getQueryBuilder(); $qb = $this->db->getQueryBuilder();
$qb->select('*') $qb->select('*')
->from($this->getTableName()) ->from($this->getTableName())
@@ -44,13 +41,10 @@ class LabelMapper extends DeckMapper implements IPermissionMapper {
} }
/** /**
* @param numeric $cardId
* @param int|null $limit
* @param int|null $offset
* @return Label[] * @return Label[]
* @throws \OCP\DB\Exception * @throws \OCP\DB\Exception
*/ */
public function findAssignedLabelsForCard($cardId, $limit = null, $offset = null): array { public function findAssignedLabelsForCard(int $cardId, ?int $limit = null, int $offset = 0): array {
$qb = $this->db->getQueryBuilder(); $qb = $this->db->getQueryBuilder();
$qb->select('l.*', 'card_id') $qb->select('l.*', 'card_id')
->from($this->getTableName(), 'l') ->from($this->getTableName(), 'l')
@@ -63,7 +57,7 @@ class LabelMapper extends DeckMapper implements IPermissionMapper {
return $this->findEntities($qb); return $this->findEntities($qb);
} }
public function findAssignedLabelsForCards($cardIds, $limit = null, $offset = null): array { public function findAssignedLabelsForCards(array $cardIds, ?int $limit = null, int $offset = 0): array {
$qb = $this->db->getQueryBuilder(); $qb = $this->db->getQueryBuilder();
$qb->select('l.*', 'card_id') $qb->select('l.*', 'card_id')
->from($this->getTableName(), 'l') ->from($this->getTableName(), 'l')
@@ -77,13 +71,10 @@ class LabelMapper extends DeckMapper implements IPermissionMapper {
} }
/** /**
* @param numeric $boardId
* @param int|null $limit
* @param int|null $offset
* @return Label[] * @return Label[]
* @throws \OCP\DB\Exception * @throws \OCP\DB\Exception
*/ */
public function findAssignedLabelsForBoard($boardId, $limit = null, $offset = null): array { public function findAssignedLabelsForBoard(int $boardId, ?int $limit = null, int $offset = 0): array {
$qb = $this->db->getQueryBuilder(); $qb = $this->db->getQueryBuilder();
$qb->select('l.id as id', 'l.title as title', 'l.color as color') $qb->select('l.id as id', 'l.title as title', 'l.color as color')
->selectAlias('c.id', 'card_id') ->selectAlias('c.id', 'card_id')
@@ -113,11 +104,10 @@ class LabelMapper extends DeckMapper implements IPermissionMapper {
} }
/** /**
* @param numeric $boardId * @return array<int, list<Label>>
* @return array
* @throws \OCP\DB\Exception * @throws \OCP\DB\Exception
*/ */
public function getAssignedLabelsForBoard($boardId) { public function getAssignedLabelsForBoard(int $boardId): array {
$labels = $this->findAssignedLabelsForBoard($boardId); $labels = $this->findAssignedLabelsForBoard($boardId);
$result = []; $result = [];
foreach ($labels as $label) { foreach ($labels as $label) {
@@ -130,11 +120,9 @@ class LabelMapper extends DeckMapper implements IPermissionMapper {
} }
/** /**
* @param numeric $labelId
* @return void
* @throws \OCP\DB\Exception * @throws \OCP\DB\Exception
*/ */
public function deleteLabelAssignments($labelId) { public function deleteLabelAssignments(int $labelId): void {
$qb = $this->db->getQueryBuilder(); $qb = $this->db->getQueryBuilder();
$qb->delete('deck_assigned_labels') $qb->delete('deck_assigned_labels')
->where($qb->expr()->eq('label_id', $qb->createNamedParameter($labelId, IQueryBuilder::PARAM_INT))); ->where($qb->expr()->eq('label_id', $qb->createNamedParameter($labelId, IQueryBuilder::PARAM_INT)));
@@ -142,11 +130,9 @@ class LabelMapper extends DeckMapper implements IPermissionMapper {
} }
/** /**
* @param numeric $cardId
* @return void
* @throws \OCP\DB\Exception * @throws \OCP\DB\Exception
*/ */
public function deleteLabelAssignmentsForCard($cardId) { public function deleteLabelAssignmentsForCard(int $cardId): void {
$qb = $this->db->getQueryBuilder(); $qb = $this->db->getQueryBuilder();
$qb->delete('deck_assigned_labels') $qb->delete('deck_assigned_labels')
->where($qb->expr()->eq('card_id', $qb->createNamedParameter($cardId, IQueryBuilder::PARAM_INT))); ->where($qb->expr()->eq('card_id', $qb->createNamedParameter($cardId, IQueryBuilder::PARAM_INT)));
@@ -154,33 +140,25 @@ class LabelMapper extends DeckMapper implements IPermissionMapper {
} }
/** /**
* @param string $userId
* @param numeric $labelId
* @return bool
* @throws \OCP\DB\Exception * @throws \OCP\DB\Exception
*/ */
public function isOwner($userId, $labelId): bool { public function isOwner(string $userId, int $id): bool {
$qb = $this->db->getQueryBuilder(); $qb = $this->db->getQueryBuilder();
$qb->select('l.id') $qb->select('l.id')
->from($this->getTableName(), 'l') ->from($this->getTableName(), 'l')
->innerJoin('l', 'deck_boards', 'b', 'l.board_id = b.id') ->innerJoin('l', 'deck_boards', 'b', 'l.board_id = b.id')
->where($qb->expr()->eq('l.id', $qb->createNamedParameter($labelId, IQueryBuilder::PARAM_INT))) ->where($qb->expr()->eq('l.id', $qb->createNamedParameter($id, IQueryBuilder::PARAM_INT)))
->andWhere($qb->expr()->eq('b.owner', $qb->createNamedParameter($userId, IQueryBuilder::PARAM_STR))); ->andWhere($qb->expr()->eq('b.owner', $qb->createNamedParameter($userId, IQueryBuilder::PARAM_STR)));
return count($qb->executeQuery()->fetchAll()) > 0; return count($qb->executeQuery()->fetchAll()) > 0;
} }
/** public function findBoardId(int $id): ?int {
* @param numeric $id
* @return int|null
*/
public function findBoardId($id): ?int {
try { try {
$entity = $this->find($id); $entity = $this->find($id);
return $entity->getBoardId(); return $entity->getBoardId();
} catch (DoesNotExistException $e) { } catch (DoesNotExistException|MultipleObjectsReturnedException) {
} catch (MultipleObjectsReturnedException $e) { return null;
} }
return null;
} }
} }
+8 -1
View File
@@ -11,10 +11,17 @@ use Sabre\VObject\Component\VCalendar;
/** /**
* @method int getId() * @method int getId()
* @method string getTitle()
* @method void setTitle(string $title)
* @method int getBoardId() * @method int getBoardId()
* @method void setBoardId(int $boardId)
* @method int getDeletedAt() * @method int getDeletedAt()
* @method void setDeletedAt(int $deletedAt)
* @method int getLastModified() * @method int getLastModified()
* @method int getOrder() * @method void setLastModified(int $lastModified)
* @method \int getOrder()
* @method void setOrder(int $order)
* @method Card[] getCards()
*/ */
class Stack extends RelationalEntity { class Stack extends RelationalEntity {
protected $title; protected $title;
+10 -22
View File
@@ -35,13 +35,11 @@ class StackMapper extends DeckMapper implements IPermissionMapper {
/** /**
* @param numeric $id
* @return Stack
* @throws DoesNotExistException * @throws DoesNotExistException
* @throws MultipleObjectsReturnedException * @throws MultipleObjectsReturnedException
* @throws \OCP\DB\Exception * @throws \OCP\DB\Exception
*/ */
public function find($id): Stack { public function find(int $id): Stack {
if (isset($this->stackCache[(string)$id])) { if (isset($this->stackCache[(string)$id])) {
return $this->stackCache[(string)$id]; return $this->stackCache[(string)$id];
} }
@@ -56,11 +54,9 @@ class StackMapper extends DeckMapper implements IPermissionMapper {
} }
/** /**
* @param $cardId
* @return Stack|null
* @throws \OCP\DB\Exception * @throws \OCP\DB\Exception
*/ */
public function findStackFromCardId($cardId): ?Stack { public function findStackFromCardId(int $cardId): ?Stack {
$qb = $this->db->getQueryBuilder(); $qb = $this->db->getQueryBuilder();
$qb->select('s.*') $qb->select('s.*')
->from($this->getTableName(), 's') ->from($this->getTableName(), 's')
@@ -76,13 +72,10 @@ class StackMapper extends DeckMapper implements IPermissionMapper {
} }
/** /**
* @param numeric $boardId
* @param int|null $limit
* @param int|null $offset
* @return Stack[] * @return Stack[]
* @throws \OCP\DB\Exception * @throws \OCP\DB\Exception
*/ */
public function findAll($boardId, $limit = null, $offset = null): array { public function findAll(int $boardId, ?int $limit = null, int $offset = 0): array {
$qb = $this->db->getQueryBuilder(); $qb = $this->db->getQueryBuilder();
$qb->select('*') $qb->select('*')
->from($this->getTableName()) ->from($this->getTableName())
@@ -95,13 +88,9 @@ class StackMapper extends DeckMapper implements IPermissionMapper {
} }
/** /**
* @param numeric $boardId
* @param int|null $limit
* @param int|null $offset
* @return Stack[]
* @throws \OCP\DB\Exception * @throws \OCP\DB\Exception
*/ */
public function findDeleted($boardId, $limit = null, $offset = null) { public function findDeleted(int $boardId, ?int $limit = null, int $offset = 0): array {
$qb = $this->db->getQueryBuilder(); $qb = $this->db->getQueryBuilder();
$qb->select('*') $qb->select('*')
->from($this->getTableName()) ->from($this->getTableName())
@@ -127,12 +116,9 @@ class StackMapper extends DeckMapper implements IPermissionMapper {
} }
/** /**
* @param numeric $userId
* @param numeric $stackId
* @return bool
* @throws \OCP\DB\Exception * @throws \OCP\DB\Exception
*/ */
public function isOwner($userId, $id): bool { public function isOwner(string $userId, int $id): bool {
$qb = $this->db->getQueryBuilder(); $qb = $this->db->getQueryBuilder();
$qb->select('s.id') $qb->select('s.id')
->from($this->getTableName(), 's') ->from($this->getTableName(), 's')
@@ -144,11 +130,9 @@ class StackMapper extends DeckMapper implements IPermissionMapper {
} }
/** /**
* @param numeric $id
* @return int|null
* @throws \OCP\DB\Exception * @throws \OCP\DB\Exception
*/ */
public function findBoardId($id): ?int { public function findBoardId(int $id): ?int {
$result = $this->cache->get('findBoardId:' . $id); $result = $this->cache->get('findBoardId:' . $id);
if ($result !== null) { if ($result !== null) {
return $result !== false ? $result : null; return $result !== false ? $result : null;
@@ -165,6 +149,10 @@ class StackMapper extends DeckMapper implements IPermissionMapper {
return $result !== false ? $result : null; return $result !== false ? $result : null;
} }
/**
* @return array<Stack>
* @throws \OCP\DB\Exception
*/
public function findToDelete(): array { public function findToDelete(): array {
// add buffer of 5 min // add buffer of 5 min
$timeLimit = time() - (60 * 5); $timeLimit = time() - (60 * 5);
@@ -35,6 +35,10 @@ class BeforeTemplateRenderedListener implements IEventListener {
Util::addStyle('deck', 'deck'); Util::addStyle('deck', 'deck');
$pathInfo = $this->request->getPathInfo(); $pathInfo = $this->request->getPathInfo();
if (!$pathInfo) {
return;
}
if (str_starts_with($pathInfo, '/apps/calendar')) { if (str_starts_with($pathInfo, '/apps/calendar')) {
Util::addScript('deck', 'deck-calendar'); Util::addScript('deck', 'deck-calendar');
} }
+6 -10
View File
@@ -18,14 +18,10 @@ use OCP\EventDispatcher\IEventListener;
/** @template-implements IEventListener<Event|AclDeletedEvent|AclCreatedEvent> */ /** @template-implements IEventListener<Event|AclDeletedEvent|AclCreatedEvent> */
class ResourceListener implements IEventListener { class ResourceListener implements IEventListener {
/** @var IManager */ public function __construct(
private $resourceManager; private readonly IManager $resourceManager,
/** @var ResourceProviderCard */ private readonly ResourceProviderCard $resourceProviderCard,
private $resourceProviderCard; ) {
public function __construct(IManager $resourceManager, ResourceProviderCard $resourceProviderCard) {
$this->resourceManager = $resourceManager;
$this->resourceProviderCard = $resourceProviderCard;
} }
public function handle(Event $event): void { public function handle(Event $event): void {
@@ -38,10 +34,10 @@ class ResourceListener implements IEventListener {
$this->resourceManager->invalidateAccessCacheForProvider($this->resourceProviderCard); $this->resourceManager->invalidateAccessCacheForProvider($this->resourceProviderCard);
try { try {
$resource = $this->resourceManager->getResourceForUser(ResourceProvider::RESOURCE_TYPE, $boardId, null); $resource = $this->resourceManager->getResourceForUser(ResourceProvider::RESOURCE_TYPE, (string)$boardId, null);
$this->resourceManager->invalidateAccessCacheForResource($resource); $this->resourceManager->invalidateAccessCacheForResource($resource);
} catch (ResourceException $e) { } catch (ResourceException $e) {
// If there is no resource we don't need to invalidate anything, but this should not happen anyways // If there is no resource we don't need to invalidate anything, but this should not happen anyway
} }
} }
} }
+1 -1
View File
@@ -27,7 +27,7 @@ class DefaultBoardMiddleware extends Middleware {
public function beforeController($controller, $methodName) { public function beforeController($controller, $methodName) {
try { try {
if ($this->userId !== null && $this->defaultBoardService->checkFirstRun($this->userId) && $this->permissionService->canCreate()) { if ($this->userId !== null && $this->defaultBoardService->checkFirstRun($this->userId) && $this->permissionService->canCreate()) {
$this->defaultBoardService->createDefaultBoard($this->l10n->t('Personal'), $this->userId, '0087C5'); $this->defaultBoardService->createDefaultBoard($this->l10n->t('Welcome to Nextcloud Deck!'), $this->userId, 'bf678b');
} }
} catch (\Throwable $e) { } catch (\Throwable $e) {
$this->logger->error('Could not create default board', ['exception' => $e]); $this->logger->error('Could not create default board', ['exception' => $e]);
+78
View File
@@ -0,0 +1,78 @@
<?php
/**
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
namespace OCA\Deck\Migration;
use OCP\IDBConnection;
use OCP\Migration\IOutput;
use OCP\Migration\IRepairStep;
class LabelMismatchCleanup implements IRepairStep {
public function __construct(
private IDBConnection $db,
) {
}
public function getName() {
return 'Migrate labels with wrong board mapping';
}
public function run(IOutput $output) {
// Find assingments where a label of another (wrong) board is used
$qb = $this->db->getQueryBuilder();
$qb->select('al.id', 'al.label_id', 'al.card_id', 's.board_id as actual_board_id', 'l.board_id as wrong_id', 'l.color', 'l.title')
->from('deck_assigned_labels', 'al')
->innerJoin('al', 'deck_cards', 'c', 'c.id = al.card_id')
->innerJoin('c', 'deck_stacks', 's', 'c.stack_id = s.id')
->innerJoin('al', 'deck_labels', 'l', 'l.id = al.label_id')
->where($qb->expr()->neq('l.board_id', 's.board_id'));
$labels = $qb->executeQUery()->fetchAll();
if (count($labels) === 0) {
return;
}
$output->info('Found ' . count($labels) . ' labels with wrong board mapping');
foreach ($labels as $label) {
// Select existing label on the correct board
$qb = $this->db->getQueryBuilder();
$qb->select('id')
->from('deck_labels')
->where($qb->expr()->eq('title', $qb->createNamedParameter($label['title'])))
->andWhere($qb->expr()->eq('color', $qb->createNamedParameter($label['color'])))
->andWhere($qb->expr()->eq('board_id', $qb->createNamedParameter($label['actual_board_id'])));
$result = $qb->executeQuery();
$newLabel = $result->fetchOne();
$result->closeCursor();
if (!$newLabel) {
// Create a new label with the same title and color on the correct board
$qb = $this->db->getQueryBuilder();
$qb->insert('deck_labels')
->values([
'title' => $qb->createNamedParameter($label['title']),
'color' => $qb->createNamedParameter($label['color']),
'board_id' => $qb->createNamedParameter($label['actual_board_id']),
]);
$qb->executeStatement();
$newLabel = $qb->getLastInsertId();
$output->debug('Created new label ' . $label['title'] . ' on board ' . $label['actual_board_id']);
} else {
$output->debug('Found existing label ' . $label['title'] . ' on board ' . $label['actual_board_id']);
}
// Update the assignment to use the new label
$qb = $this->db->getQueryBuilder();
$qb->update('deck_assigned_labels')
->set('label_id', $qb->createNamedParameter($newLabel))
->where($qb->expr()->eq('id', $qb->createNamedParameter($label['id'])));
$qb->executeStatement();
$output->debug('Updated label assignment ' . $label['id'] . ' to use label ' . $newLabel);
}
}
}
+3 -2
View File
@@ -11,6 +11,7 @@ use OCA\Deck\Db\Board;
class BoardSummary extends Board { class BoardSummary extends Board {
private Board $board; private Board $board;
/** @psalm-suppress ConstructorSignatureMismatch */
public function __construct(Board $board) { public function __construct(Board $board) {
parent::__construct(); parent::__construct();
$this->board = $board; $this->board = $board;
@@ -27,7 +28,7 @@ class BoardSummary extends Board {
return $this->board->getter($name); return $this->board->getter($name);
} }
public function __call($name, $arguments) { public function __call($methodName, $args) {
return $this->board->__call($name, $arguments); return $this->board->__call($methodName, $args);
} }
} }
+1
View File
@@ -15,6 +15,7 @@ class CardDetails extends Card {
private ?Board $board; private ?Board $board;
private ?Reference $referenceData = null; private ?Reference $referenceData = null;
/** @psalm-suppress ConstructorSignatureMismatch */
public function __construct(Card $card, ?Board $board = null) { public function __construct(Card $card, ?Board $board = null) {
parent::__construct(); parent::__construct();
$this->card = $card; $this->card = $card;
+1 -1
View File
@@ -95,7 +95,7 @@ class NotificationHelper {
$shouldNotify = $notificationSetting === ConfigService::SETTING_BOARD_NOTIFICATION_DUE_ALL; $shouldNotify = $notificationSetting === ConfigService::SETTING_BOARD_NOTIFICATION_DUE_ALL;
if ($user->getUID() === $board->getOwner() && count($board->getAcl()) === 0) { if ($user->getUID() === $board->getOwner() && count($board->getAcl() ?? []) === 0) {
// Notify if all or assigned is configured for unshared boards // Notify if all or assigned is configured for unshared boards
$shouldNotify = true; $shouldNotify = true;
} elseif ($notificationSetting === ConfigService::SETTING_BOARD_NOTIFICATION_DUE_ASSIGNED && $this->assignmentMapper->isUserAssigned($card->getId(), $user->getUID())) { } elseif ($notificationSetting === ConfigService::SETTING_BOARD_NOTIFICATION_DUE_ASSIGNED && $this->assignmentMapper->isUserAssigned($card->getId(), $user->getUID())) {
+1
View File
@@ -64,6 +64,7 @@ class BoardReferenceProvider implements IReferenceProvider {
$reference = new Reference($referenceText); $reference = new Reference($referenceText);
$reference->setTitle($this->l10n->t('Deck board') . ': ' . $board['title']); $reference->setTitle($this->l10n->t('Deck board') . ': ' . $board['title']);
$ownerDisplayName = $board['owner']['displayname'] ?? $board['owner']['uid'] ?? '???'; $ownerDisplayName = $board['owner']['displayname'] ?? $board['owner']['uid'] ?? '???';
// TRANSLATORS Owned by {boardOwnerName}
$reference->setDescription($this->l10n->t('Owned by %1$s', [$ownerDisplayName])); $reference->setDescription($this->l10n->t('Owned by %1$s', [$ownerDisplayName]));
$imageUrl = $this->urlGenerator->getAbsoluteURL( $imageUrl = $this->urlGenerator->getAbsoluteURL(
$this->urlGenerator->imagePath(Application::APP_ID, 'deck-dark.svg') $this->urlGenerator->imagePath(Application::APP_ID, 'deck-dark.svg')
@@ -90,6 +90,7 @@ class CommentReferenceProvider implements IReferenceProvider {
$reference->setTitle($comment['message']); $reference->setTitle($comment['message']);
$boardOwnerDisplayName = $board['owner']['displayname'] ?? $board['owner']['uid'] ?? '???'; $boardOwnerDisplayName = $board['owner']['displayname'] ?? $board['owner']['uid'] ?? '???';
$reference->setDescription( $reference->setDescription(
// TRANSLATORS From {userName}, in {boardTitle}/{stackTitle}, owned by {boardOwnerName}
$this->l10n->t('From %1$s, in %2$s/%3$s, owned by %4$s', [ $this->l10n->t('From %1$s, in %2$s/%3$s, owned by %4$s', [
$comment['actorDisplayName'], $comment['actorDisplayName'],
$board['title'], $board['title'],
+2 -9
View File
@@ -21,7 +21,6 @@ use OCA\Deck\NotFoundException;
use OCA\Deck\Notification\NotificationHelper; use OCA\Deck\Notification\NotificationHelper;
use OCA\Deck\Validators\AssignmentServiceValidator; use OCA\Deck\Validators\AssignmentServiceValidator;
use OCP\AppFramework\Db\DoesNotExistException; use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Db\Entity;
use OCP\AppFramework\Db\MultipleObjectsReturnedException; use OCP\AppFramework\Db\MultipleObjectsReturnedException;
use OCP\EventDispatcher\IEventDispatcher; use OCP\EventDispatcher\IEventDispatcher;
@@ -92,15 +91,12 @@ class AssignmentService {
} }
/** /**
* @param $cardId
* @param $userId
* @return bool|null|Entity
* @throws BadRequestException * @throws BadRequestException
* @throws NoPermissionException * @throws NoPermissionException
* @throws MultipleObjectsReturnedException * @throws MultipleObjectsReturnedException
* @throws DoesNotExistException * @throws DoesNotExistException
*/ */
public function assignUser($cardId, $userId, int $type = Assignment::TYPE_USER) { public function assignUser(int $cardId, string $userId, int $type = Assignment::TYPE_USER): Assignment {
$this->assignmentServiceValidator->check(compact('cardId', 'userId')); $this->assignmentServiceValidator->check(compact('cardId', 'userId'));
if ($type !== Assignment::TYPE_USER && $type !== Assignment::TYPE_GROUP) { if ($type !== Assignment::TYPE_USER && $type !== Assignment::TYPE_GROUP) {
@@ -144,16 +140,13 @@ class AssignmentService {
} }
/** /**
* @param $cardId
* @param $userId
* @return Entity
* @throws BadRequestException * @throws BadRequestException
* @throws NotFoundException * @throws NotFoundException
* @throws NoPermissionException * @throws NoPermissionException
* @throws DoesNotExistException * @throws DoesNotExistException
* @throws MultipleObjectsReturnedException * @throws MultipleObjectsReturnedException
*/ */
public function unassignUser($cardId, $userId, $type = 0) { public function unassignUser(int $cardId, string $userId, int $type = 0): Assignment {
$this->assignmentServiceValidator->check(compact('cardId', 'userId')); $this->assignmentServiceValidator->check(compact('cardId', 'userId'));
$this->permissionService->checkPermission($this->cardMapper, $cardId, Acl::PERMISSION_EDIT); $this->permissionService->checkPermission($this->cardMapper, $cardId, Acl::PERMISSION_EDIT);
+16 -40
View File
@@ -25,6 +25,7 @@ use OCP\AppFramework\Db\IMapperException;
use OCP\AppFramework\Http\Response; use OCP\AppFramework\Http\Response;
use OCP\IL10N; use OCP\IL10N;
use OCP\IUserManager; use OCP\IUserManager;
use Psr\Container\ContainerExceptionInterface;
class AttachmentService { class AttachmentService {
private $attachmentMapper; private $attachmentMapper;
@@ -80,20 +81,16 @@ class AttachmentService {
} }
/** /**
* @param string $type * @throws ContainerExceptionInterface
* @param string $class
* @throws \OCP\AppFramework\QueryException
*/ */
public function registerAttachmentService($type, $class) { public function registerAttachmentService(string $type, string $class): void {
$this->services[$type] = $this->application->getContainer()->query($class); $this->services[$type] = $this->application->getContainer()->get($class);
} }
/** /**
* @param string $type
* @return IAttachmentService
* @throws InvalidAttachmentType * @throws InvalidAttachmentType
*/ */
public function getService($type) { public function getService(string $type): IAttachmentService {
if (isset($this->services[$type])) { if (isset($this->services[$type])) {
return $this->services[$type]; return $this->services[$type];
} }
@@ -101,16 +98,11 @@ class AttachmentService {
} }
/** /**
* @param $cardId * @return Attachment[]
* @return array
* @throws \OCA\Deck\NoPermissionException * @throws \OCA\Deck\NoPermissionException
* @throws BadRequestException * @throws BadRequestException
*/ */
public function findAll($cardId, $withDeleted = false) { public function findAll(int $cardId, bool $withDeleted = false): array {
if (is_numeric($cardId) === false) {
throw new BadRequestException('card id must be a number');
}
$this->permissionService->checkPermission($this->cardMapper, $cardId, Acl::PERMISSION_READ); $this->permissionService->checkPermission($this->cardMapper, $cardId, Acl::PERMISSION_READ);
$attachments = $this->attachmentMapper->findAll($cardId); $attachments = $this->attachmentMapper->findAll($cardId);
@@ -122,7 +114,7 @@ class AttachmentService {
/** @var IAttachmentService $service */ /** @var IAttachmentService $service */
$service = $this->getService($attachmentType); $service = $this->getService($attachmentType);
if ($service instanceof ICustomAttachmentService) { if ($service instanceof ICustomAttachmentService) {
$attachments = array_merge($attachments, $service->listAttachments((int)$cardId)); $attachments = array_merge($attachments, $service->listAttachments($cardId));
} }
} }
@@ -140,49 +132,40 @@ class AttachmentService {
} }
/** /**
* @param $cardId
* @return int|mixed
* @throws BadRequestException * @throws BadRequestException
* @throws InvalidAttachmentType * @throws InvalidAttachmentType
* @throws \OCP\DB\Exception * @throws \OCP\DB\Exception
*/ */
public function count($cardId) { public function count(int $cardId): int {
if (is_numeric($cardId) === false) { $count = $this->attachmentCacheHelper->getAttachmentCount($cardId);
throw new BadRequestException('card id must be a number');
}
$count = $this->attachmentCacheHelper->getAttachmentCount((int)$cardId);
if ($count === null) { if ($count === null) {
$count = count($this->attachmentMapper->findAll($cardId)); $count = count($this->attachmentMapper->findAll($cardId));
foreach (array_keys($this->services) as $attachmentType) { foreach (array_keys($this->services) as $attachmentType) {
$service = $this->getService($attachmentType); $service = $this->getService($attachmentType);
if ($service instanceof ICustomAttachmentService) { if ($service instanceof ICustomAttachmentService) {
$count += $service->getAttachmentCount((int)$cardId); $count += $service->getAttachmentCount($cardId);
} }
} }
$this->attachmentCacheHelper->setAttachmentCount((int)$cardId, $count); $this->attachmentCacheHelper->setAttachmentCount($cardId, $count);
} }
return $count; return $count;
} }
/** /**
* @param $cardId
* @param $type
* @param $data
* @return Attachment|\OCP\AppFramework\Db\Entity * @return Attachment|\OCP\AppFramework\Db\Entity
* @throws NoPermissionException * @throws NoPermissionException
* @throws StatusException * @throws StatusException
* @throws BadRequestException * @throws BadRequestException
*/ */
public function create($cardId, $type, $data) { public function create(int $cardId, string $type, string $data) {
$this->attachmentServiceValidator->check(compact('cardId', 'type')); $this->attachmentServiceValidator->check(compact('cardId', 'type'));
$this->permissionService->checkPermission($this->cardMapper, $cardId, Acl::PERMISSION_EDIT); $this->permissionService->checkPermission($this->cardMapper, $cardId, Acl::PERMISSION_EDIT);
$this->attachmentCacheHelper->clearAttachmentCount((int)$cardId); $this->attachmentCacheHelper->clearAttachmentCount($cardId);
$attachment = new Attachment(); $attachment = new Attachment();
$attachment->setCardId($cardId); $attachment->setCardId($cardId);
$attachment->setType($type); $attachment->setType($type);
@@ -218,12 +201,10 @@ class AttachmentService {
/** /**
* Display the attachment * Display the attachment
* *
* @param $attachmentId
* @return Response
* @throws NoPermissionException * @throws NoPermissionException
* @throws NotFoundException * @throws NotFoundException
*/ */
public function display($cardId, $attachmentId, $type = 'deck_file') { public function display(int $cardId, int $attachmentId, string $type = 'deck_file'): Response {
try { try {
$service = $this->getService($type); $service = $this->getService($type);
} catch (InvalidAttachmentType $e) { } catch (InvalidAttachmentType $e) {
@@ -257,13 +238,10 @@ class AttachmentService {
/** /**
* Update an attachment with custom data * Update an attachment with custom data
* *
* @param $attachmentId
* @param $data
* @return mixed
* @throws BadRequestException * @throws BadRequestException
* @throws NoPermissionException * @throws NoPermissionException
*/ */
public function update($cardId, $attachmentId, $data, $type = 'deck_file') { public function update(int $cardId, int $attachmentId, string $data, string $type = 'deck_file'): Attachment {
$this->attachmentServiceValidator->check(compact('cardId', 'type', 'data')); $this->attachmentServiceValidator->check(compact('cardId', 'type', 'data'));
try { try {
@@ -384,8 +362,6 @@ class AttachmentService {
} }
/** /**
* @param Attachment $attachment
* @return Attachment
* @throws \ReflectionException * @throws \ReflectionException
*/ */
private function addCreator(Attachment $attachment): Attachment { private function addCreator(Attachment $attachment): Attachment {
+40 -76
View File
@@ -75,8 +75,6 @@ class BoardService {
/** /**
* Set a different user than the current one, e.g. when no user is available in occ * Set a different user than the current one, e.g. when no user is available in occ
*
* @param string $userId
*/ */
public function setUserId(string $userId): void { public function setUserId(string $userId): void {
$this->userId = $userId; $this->userId = $userId;
@@ -117,22 +115,18 @@ class BoardService {
} }
$this->permissionService->checkPermission($this->boardMapper, $boardId, Acl::PERMISSION_READ); $this->permissionService->checkPermission($this->boardMapper, $boardId, Acl::PERMISSION_READ);
/** @var Board $board */
$board = $this->boardMapper->find($boardId, true, true, $allowDeleted); $board = $this->boardMapper->find($boardId, true, true, $allowDeleted);
[$board] = $this->enrichBoards([$board], $fullDetails); [$board] = $this->enrichBoards([$board], $fullDetails);
return $board; return $board;
} }
/** /**
* @param $mapper
* @param $id
* @return bool
* @throws DoesNotExistException * @throws DoesNotExistException
* @throws NoPermissionException * @throws NoPermissionException
* @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException * @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException
* @throws BadRequestException * @throws BadRequestException
*/ */
public function isArchived($mapper, $id) { public function isArchived($mapper, int $id): bool {
$this->boardServiceValidator->check(compact('id')); $this->boardServiceValidator->check(compact('id'));
try { try {
@@ -151,15 +145,12 @@ class BoardService {
} }
/** /**
* @param $mapper
* @param $id
* @return bool
* @throws DoesNotExistException * @throws DoesNotExistException
* @throws NoPermissionException * @throws NoPermissionException
* @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException * @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException
* @throws BadRequestException * @throws BadRequestException
*/ */
public function isDeleted($mapper, $id) { public function isDeleted($mapper, int $id): bool {
$this->boardServiceValidator->check(compact('mapper', 'id')); $this->boardServiceValidator->check(compact('mapper', 'id'));
try { try {
@@ -179,13 +170,9 @@ class BoardService {
/** /**
* @param $title
* @param $userId
* @param $color
* @return \OCP\AppFramework\Db\Entity
* @throws BadRequestException * @throws BadRequestException
*/ */
public function create($title, $userId, $color) { public function create(string $title, string $userId, string $color): Board {
$this->boardServiceValidator->check(compact('title', 'userId', 'color')); $this->boardServiceValidator->check(compact('title', 'userId', 'color'));
if (!$this->permissionService->canCreate()) { if (!$this->permissionService->canCreate()) {
@@ -196,7 +183,8 @@ class BoardService {
$board->setTitle($title); $board->setTitle($title);
$board->setOwner($userId); $board->setOwner($userId);
$board->setColor($color); $board->setColor($color);
$new_board = $this->boardMapper->insert($board); /** @var Board $board */
$board = $this->boardMapper->insert($board);
// create new labels // create new labels
$default_labels = [ $default_labels = [
@@ -210,33 +198,31 @@ class BoardService {
$label = new Label(); $label = new Label();
$label->setColor($labelColor); $label->setColor($labelColor);
$label->setTitle($labelTitle); $label->setTitle($labelTitle);
$label->setBoardId($new_board->getId()); $label->setBoardId($board->getId());
$labels[] = $this->labelMapper->insert($label); $labels[] = $this->labelMapper->insert($label);
} }
$new_board->setLabels($labels); $board->setLabels($labels);
$this->boardMapper->mapOwner($new_board); $this->boardMapper->mapOwner($board);
$permissions = $this->permissionService->matchPermissions($new_board); $permissions = $this->permissionService->matchPermissions($board);
$new_board->setPermissions([ $board->setPermissions([
'PERMISSION_READ' => $permissions[Acl::PERMISSION_READ] ?? false, 'PERMISSION_READ' => $permissions[Acl::PERMISSION_READ] ?? false,
'PERMISSION_EDIT' => $permissions[Acl::PERMISSION_EDIT] ?? false, 'PERMISSION_EDIT' => $permissions[Acl::PERMISSION_EDIT] ?? false,
'PERMISSION_MANAGE' => $permissions[Acl::PERMISSION_MANAGE] ?? false, 'PERMISSION_MANAGE' => $permissions[Acl::PERMISSION_MANAGE] ?? false,
'PERMISSION_SHARE' => $permissions[Acl::PERMISSION_SHARE] ?? false 'PERMISSION_SHARE' => $permissions[Acl::PERMISSION_SHARE] ?? false
]); ]);
$this->activityManager->triggerEvent(ActivityManager::DECK_OBJECT_BOARD, $new_board, ActivityManager::SUBJECT_BOARD_CREATE, [], $userId); $this->activityManager->triggerEvent(ActivityManager::DECK_OBJECT_BOARD, $board, ActivityManager::SUBJECT_BOARD_CREATE, [], $userId);
$this->changeHelper->boardChanged($new_board->getId()); $this->changeHelper->boardChanged($board->getId());
return $new_board; return $board;
} }
/** /**
* @param $id
* @return Board
* @throws DoesNotExistException * @throws DoesNotExistException
* @throws NoPermissionException * @throws NoPermissionException
* @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException * @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException
* @throws BadRequestException * @throws BadRequestException
*/ */
public function delete($id) { public function delete(int $id): Board {
$this->boardServiceValidator->check(compact('id')); $this->boardServiceValidator->check(compact('id'));
$this->permissionService->checkPermission($this->boardMapper, $id, Acl::PERMISSION_MANAGE); $this->permissionService->checkPermission($this->boardMapper, $id, Acl::PERMISSION_MANAGE);
@@ -253,13 +239,11 @@ class BoardService {
} }
/** /**
* @param $id
* @return \OCP\AppFramework\Db\Entity
* @throws DoesNotExistException * @throws DoesNotExistException
* @throws NoPermissionException * @throws NoPermissionException
* @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException * @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException
*/ */
public function deleteUndo($id) { public function deleteUndo(int $id): Board {
$this->boardServiceValidator->check(compact('id')); $this->boardServiceValidator->check(compact('id'));
$this->permissionService->checkPermission($this->boardMapper, $id, Acl::PERMISSION_MANAGE); $this->permissionService->checkPermission($this->boardMapper, $id, Acl::PERMISSION_MANAGE);
@@ -273,14 +257,12 @@ class BoardService {
} }
/** /**
* @param $id
* @return \OCP\AppFramework\Db\Entity
* @throws DoesNotExistException * @throws DoesNotExistException
* @throws NoPermissionException * @throws NoPermissionException
* @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException * @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException
* @throws BadRequestException * @throws BadRequestException
*/ */
public function deleteForce($id) { public function deleteForce(int $id): Board {
$this->boardServiceValidator->check(compact('id')); $this->boardServiceValidator->check(compact('id'));
$this->permissionService->checkPermission($this->boardMapper, $id, Acl::PERMISSION_MANAGE); $this->permissionService->checkPermission($this->boardMapper, $id, Acl::PERMISSION_MANAGE);
@@ -291,17 +273,12 @@ class BoardService {
} }
/** /**
* @param $id
* @param $title
* @param $color
* @param $archived
* @return \OCP\AppFramework\Db\Entity
* @throws DoesNotExistException * @throws DoesNotExistException
* @throws NoPermissionException * @throws NoPermissionException
* @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException * @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException
* @throws BadRequestException * @throws BadRequestException
*/ */
public function update($id, $title, $color, $archived) { public function update(int $id, string $title, string $color, bool $archived): Board {
$this->boardServiceValidator->check(compact('id', 'title', 'color', 'archived')); $this->boardServiceValidator->check(compact('id', 'title', 'color', 'archived'));
$this->permissionService->checkPermission($this->boardMapper, $id, Acl::PERMISSION_MANAGE); $this->permissionService->checkPermission($this->boardMapper, $id, Acl::PERMISSION_MANAGE);
@@ -320,7 +297,7 @@ class BoardService {
return $board; return $board;
} }
private function applyPermissions($boardId, $edit, $share, $manage, $oldAcl = null) { private function applyPermissions(int $boardId, bool $edit, bool $share, bool $manage, ?Acl $oldAcl = null): array {
try { try {
$this->permissionService->checkPermission($this->boardMapper, $boardId, Acl::PERMISSION_MANAGE); $this->permissionService->checkPermission($this->boardMapper, $boardId, Acl::PERMISSION_MANAGE);
} catch (NoPermissionException $e) { } catch (NoPermissionException $e) {
@@ -332,7 +309,7 @@ class BoardService {
return [$edit, $share, $manage]; return [$edit, $share, $manage];
} }
public function enrichWithBoardSettings(Board $board) { public function enrichWithBoardSettings(Board $board): void {
$globalCalendarConfig = (bool)$this->config->getUserValue($this->userId, Application::APP_ID, 'calendar', true); $globalCalendarConfig = (bool)$this->config->getUserValue($this->userId, Application::APP_ID, 'calendar', true);
$settings = [ $settings = [
'notify-due' => $this->config->getUserValue($this->userId, Application::APP_ID, 'board:' . $board->getId() . ':notify-due', ConfigService::SETTING_BOARD_NOTIFICATION_DUE_ASSIGNED), 'notify-due' => $this->config->getUserValue($this->userId, Application::APP_ID, 'board:' . $board->getId() . ':notify-due', ConfigService::SETTING_BOARD_NOTIFICATION_DUE_ASSIGNED),
@@ -341,7 +318,7 @@ class BoardService {
$board->setSettings($settings); $board->setSettings($settings);
} }
public function enrichWithActiveSessions(Board $board) { public function enrichWithActiveSessions(Board $board): void {
$sessions = $this->sessionMapper->findAllActive($board->getId()); $sessions = $this->sessionMapper->findAllActive($board->getId());
$board->setActiveSessions(array_values( $board->setActiveSessions(array_values(
@@ -354,17 +331,11 @@ class BoardService {
} }
/** /**
* @param $boardId * @param Acl::PERMISSION_TYPE_* $type
* @param $type
* @param $participant
* @param $edit
* @param $share
* @param $manage
* @return \OCP\AppFramework\Db\Entity
* @throws BadRequestException * @throws BadRequestException
* @throws NoPermissionException * @throws NoPermissionException
*/ */
public function addAcl($boardId, $type, $participant, $edit, $share, $manage) { public function addAcl(int $boardId, int $type, $participant, bool $edit, bool $share, bool $manage): Acl {
$this->boardServiceValidator->check(compact('boardId', 'type', 'participant', 'edit', 'share', 'manage')); $this->boardServiceValidator->check(compact('boardId', 'type', 'participant', 'edit', 'share', 'manage'));
$this->permissionService->checkPermission($this->boardMapper, $boardId, Acl::PERMISSION_SHARE); $this->permissionService->checkPermission($this->boardMapper, $boardId, Acl::PERMISSION_SHARE);
@@ -400,17 +371,12 @@ class BoardService {
} }
/** /**
* @param $id
* @param $edit
* @param $share
* @param $manage
* @return \OCP\AppFramework\Db\Entity
* @throws DoesNotExistException * @throws DoesNotExistException
* @throws NoPermissionException * @throws NoPermissionException
* @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException * @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException
* @throws BadRequestException * @throws BadRequestException
*/ */
public function updateAcl($id, $edit, $share, $manage) { public function updateAcl(int $id, bool $edit, bool $share, bool $manage): Acl {
$this->boardServiceValidator->check(compact('id', 'edit', 'share', 'manage')); $this->boardServiceValidator->check(compact('id', 'edit', 'share', 'manage'));
$this->permissionService->checkPermission($this->aclMapper, $id, Acl::PERMISSION_SHARE); $this->permissionService->checkPermission($this->aclMapper, $id, Acl::PERMISSION_SHARE);
@@ -422,12 +388,12 @@ class BoardService {
$acl->setPermissionShare($share); $acl->setPermissionShare($share);
$acl->setPermissionManage($manage); $acl->setPermissionManage($manage);
$this->boardMapper->mapAcl($acl); $this->boardMapper->mapAcl($acl);
$board = $this->aclMapper->update($acl); $acl = $this->aclMapper->update($acl);
$this->changeHelper->boardChanged($acl->getBoardId()); $this->changeHelper->boardChanged($acl->getBoardId());
$this->eventDispatcher->dispatchTyped(new AclUpdatedEvent($acl)); $this->eventDispatcher->dispatchTyped(new AclUpdatedEvent($acl));
return $board; return $acl;
} }
/** /**
@@ -579,27 +545,23 @@ class BoardService {
} }
/** /**
* @param $id
* @return Board
* @throws DoesNotExistException * @throws DoesNotExistException
* @throws NoPermissionException * @throws NoPermissionException
* @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException * @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException
* @throws BadRequestException
*/ */
public function export($id) : Board { public function export(int $id): Board {
if (is_numeric($id) === false) {
throw new BadRequestException('board id must be a number');
}
$this->permissionService->checkPermission($this->boardMapper, $id, Acl::PERMISSION_READ); $this->permissionService->checkPermission($this->boardMapper, $id, Acl::PERMISSION_READ);
$board = $this->boardMapper->find((int)$id); $board = $this->boardMapper->find($id);
$this->enrichWithCards($board); $this->enrichWithCards($board);
$this->enrichWithLabels($board); $this->enrichWithLabels($board);
return $board; return $board;
} }
/** @param Board[] $boards */ /**
* @param Board[] $boards
* @return Board[]
*/
private function enrichBoards(array $boards, bool $fullDetails = true): array { private function enrichBoards(array $boards, bool $fullDetails = true): array {
$result = []; $result = [];
foreach ($boards as $board) { foreach ($boards as $board) {
@@ -715,8 +677,8 @@ class BoardService {
} }
} }
private function enrichWithStacks($board, $since = -1) { private function enrichWithStacks(Board $board): void {
$stacks = $this->stackMapper->findAll($board->getId(), null, null, $since); $stacks = $this->stackMapper->findAll($board->getId());
if (\count($stacks) === 0) { if (\count($stacks) === 0) {
return; return;
@@ -725,8 +687,8 @@ class BoardService {
$board->setStacks($stacks); $board->setStacks($stacks);
} }
private function enrichWithLabels($board, $since = -1) { private function enrichWithLabels(Board $board): void {
$labels = $this->labelMapper->findAll($board->getId(), null, null, $since); $labels = $this->labelMapper->findAll($board->getId());
if (\count($labels) === 0) { if (\count($labels) === 0) {
return; return;
@@ -735,7 +697,7 @@ class BoardService {
$board->setLabels($labels); $board->setLabels($labels);
} }
private function enrichWithUsers($board, $since = -1) { private function enrichWithUsers(Board $board): void {
$boardUsers = $this->permissionService->findUsers($board->getId()); $boardUsers = $this->permissionService->findUsers($board->getId());
if ($boardUsers === null || \count($boardUsers) === 0) { if ($boardUsers === null || \count($boardUsers) === 0) {
return; return;
@@ -746,7 +708,7 @@ class BoardService {
/** /**
* Clean a given board data from the Cache * Clean a given board data from the Cache
*/ */
private function clearBoardFromCache(Board $board) { private function clearBoardFromCache(Board $board): void {
$boardId = $board->getId(); $boardId = $board->getId();
$boardOwnerId = $board->getOwner(); $boardOwnerId = $board->getOwner();
@@ -755,13 +717,15 @@ class BoardService {
unset($this->boardsCachePartial[$boardId]); unset($this->boardsCachePartial[$boardId]);
} }
private function enrichWithCards($board) { private function enrichWithCards(Board $board): void {
$stacks = $this->stackMapper->findAll($board->getId()); $stacks = $this->stackMapper->findAll($board->getId());
foreach ($stacks as $stack) { foreach ($stacks as $stack) {
$cards = $this->cardMapper->findAllByStack($stack->getId()); $cards = $this->cardMapper->findAllByStack($stack->getId());
$fullCards = []; $fullCards = [];
foreach ($cards as $card) { foreach ($cards as $card) {
$fullCard = $this->cardMapper->find($card->getId()); $fullCard = $this->cardMapper->find($card->getId());
$assignedUsers = $this->assignedUsersMapper->findAll($card->getId());
$fullCard->setAssignedUsers($assignedUsers);
array_push($fullCards, $fullCard); array_push($fullCards, $fullCard);
} }
$stack->setCards($fullCards); $stack->setCards($fullCards);
+47 -77
View File
@@ -64,10 +64,14 @@ class CardService {
) { ) {
} }
public function enrichCards($cards) { /**
* @param Card[] $cards
* @return CardDetails[]
*/
public function enrichCards(array $cards): array {
$user = $this->userManager->get($this->userId); $user = $this->userManager->get($this->userId);
$cardIds = array_map(function (Card $card) use ($user) { $cardIds = array_map(function (Card $card) use ($user): int {
// Everything done in here might be heavy as it is executed for every card // Everything done in here might be heavy as it is executed for every card
$cardId = $card->getId(); $cardId = $card->getId();
$this->cardMapper->mapOwner($card); $this->cardMapper->mapOwner($card);
@@ -120,7 +124,8 @@ class CardService {
); );
} }
public function fetchDeleted($boardId) { /** @return Card[] */
public function fetchDeleted($boardId): array {
$this->cardServiceValidator->check(compact('boardId')); $this->cardServiceValidator->check(compact('boardId'));
$this->permissionService->checkPermission($this->boardMapper, $boardId, Acl::PERMISSION_READ); $this->permissionService->checkPermission($this->boardMapper, $boardId, Acl::PERMISSION_READ);
$cards = $this->cardMapper->findDeleted($boardId); $cards = $this->cardMapper->findDeleted($boardId);
@@ -129,13 +134,12 @@ class CardService {
} }
/** /**
* @return \OCA\Deck\Db\RelationalEntity
* @throws \OCA\Deck\NoPermissionException * @throws \OCA\Deck\NoPermissionException
* @throws \OCP\AppFramework\Db\DoesNotExistException * @throws \OCP\AppFramework\Db\DoesNotExistException
* @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException * @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException
* @throws BadRequestException * @throws BadRequestException
*/ */
public function find(int $cardId) { public function find(int $cardId): Card {
$this->permissionService->checkPermission($this->cardMapper, $cardId, Acl::PERMISSION_READ); $this->permissionService->checkPermission($this->cardMapper, $cardId, Acl::PERMISSION_READ);
$card = $this->cardMapper->find($cardId); $card = $this->cardMapper->find($cardId);
[$card] = $this->enrichCards([$card]); [$card] = $this->enrichCards([$card]);
@@ -152,7 +156,10 @@ class CardService {
return $card; return $card;
} }
public function findCalendarEntries($boardId) { /**
* @return Card[]
*/
public function findCalendarEntries(int $boardId): array {
try { try {
$this->permissionService->checkPermission($this->boardMapper, $boardId, Acl::PERMISSION_READ); $this->permissionService->checkPermission($this->boardMapper, $boardId, Acl::PERMISSION_READ);
} catch (NoPermissionException $e) { } catch (NoPermissionException $e) {
@@ -163,20 +170,13 @@ class CardService {
} }
/** /**
* @param $title
* @param $stackId
* @param $type
* @param integer $order
* @param $description
* @param $owner
* @return \OCP\AppFramework\Db\Entity
* @throws StatusException * @throws StatusException
* @throws \OCA\Deck\NoPermissionException * @throws \OCA\Deck\NoPermissionException
* @throws \OCP\AppFramework\Db\DoesNotExistException * @throws \OCP\AppFramework\Db\DoesNotExistException
* @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException * @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException
* @throws BadrequestException * @throws BadrequestException
*/ */
public function create($title, $stackId, $type, $order, $owner, $description = '', $duedate = null) { public function create(string $title, int $stackId, string $type, int $order, string $owner, string $description = '', $duedate = null): Card {
$this->cardServiceValidator->check(compact('title', 'stackId', 'type', 'order', 'owner')); $this->cardServiceValidator->check(compact('title', 'stackId', 'type', 'order', 'owner'));
$this->permissionService->checkPermission($this->stackMapper, $stackId, Acl::PERMISSION_EDIT); $this->permissionService->checkPermission($this->stackMapper, $stackId, Acl::PERMISSION_EDIT);
@@ -203,19 +203,13 @@ class CardService {
} }
/** /**
* @param $id
* @return \OCP\AppFramework\Db\Entity
* @throws StatusException * @throws StatusException
* @throws \OCA\Deck\NoPermissionException * @throws \OCA\Deck\NoPermissionException
* @throws \OCP\AppFramework\Db\DoesNotExistException * @throws \OCP\AppFramework\Db\DoesNotExistException
* @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException * @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException
* @throws BadRequestException * @throws BadRequestException
*/ */
public function delete($id) { public function delete(int $id): Card {
if (is_numeric($id) === false) {
throw new BadRequestException('card id must be a number');
}
$this->permissionService->checkPermission($this->cardMapper, $id, Acl::PERMISSION_EDIT); $this->permissionService->checkPermission($this->cardMapper, $id, Acl::PERMISSION_EDIT);
if ($this->boardService->isArchived($this->cardMapper, $id)) { if ($this->boardService->isArchived($this->cardMapper, $id)) {
throw new StatusException('Operation not allowed. This board is archived.'); throw new StatusException('Operation not allowed. This board is archived.');
@@ -233,25 +227,13 @@ class CardService {
} }
/** /**
* @param $id
* @param $title
* @param $stackId
* @param $type
* @param $owner
* @param $description
* @param $order
* @param $duedate
* @param $deletedAt
* @param $archived
* @param $done
* @return \OCP\AppFramework\Db\Entity
* @throws StatusException * @throws StatusException
* @throws \OCA\Deck\NoPermissionException * @throws \OCA\Deck\NoPermissionException
* @throws \OCP\AppFramework\Db\DoesNotExistException * @throws \OCP\AppFramework\Db\DoesNotExistException
* @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException * @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException
* @throws BadRequestException * @throws BadRequestException
*/ */
public function update($id, $title, $stackId, $type, $owner, $description = '', $order = 0, $duedate = null, $deletedAt = null, $archived = null, ?OptionalNullableValue $done = null) { public function update(int $id, string $title, int $stackId, string $type, string $owner, string $description = '', int $order = 0, ?string $duedate = null, ?int $deletedAt = null, ?bool $archived = null, ?OptionalNullableValue $done = null): Card {
$this->cardServiceValidator->check(compact('id', 'title', 'stackId', 'type', 'owner', 'order')); $this->cardServiceValidator->check(compact('id', 'title', 'stackId', 'type', 'owner', 'order'));
$this->permissionService->checkPermission($this->cardMapper, $id, Acl::PERMISSION_EDIT, allowDeletedCard: true); $this->permissionService->checkPermission($this->cardMapper, $id, Acl::PERMISSION_EDIT, allowDeletedCard: true);
@@ -356,7 +338,7 @@ class CardService {
if ($resetDuedateNotification) { if ($resetDuedateNotification) {
$this->notificationHelper->markDuedateAsRead($card); $this->notificationHelper->markDuedateAsRead($card);
} }
$this->changeHelper->cardChanged($card->getId(), true); $this->changeHelper->cardChanged($card->getId());
$this->eventDispatcher->dispatchTyped(new CardUpdatedEvent($card, $changes->getBefore())); $this->eventDispatcher->dispatchTyped(new CardUpdatedEvent($card, $changes->getBefore()));
@@ -398,16 +380,13 @@ class CardService {
} }
/** /**
* @param $id
* @param $title
* @return \OCP\AppFramework\Db\Entity
* @throws StatusException * @throws StatusException
* @throws \OCA\Deck\NoPermissionException * @throws \OCA\Deck\NoPermissionException
* @throws \OCP\AppFramework\Db\DoesNotExistException * @throws \OCP\AppFramework\Db\DoesNotExistException
* @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException * @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException
* @throws BadRequestException * @throws BadRequestException
*/ */
public function rename($id, $title) { public function rename(int $id, string $title): Card {
$this->cardServiceValidator->check(compact('id', 'title')); $this->cardServiceValidator->check(compact('id', 'title'));
$this->permissionService->checkPermission($this->cardMapper, $id, Acl::PERMISSION_EDIT); $this->permissionService->checkPermission($this->cardMapper, $id, Acl::PERMISSION_EDIT);
@@ -418,30 +397,27 @@ class CardService {
if ($card->getArchived()) { if ($card->getArchived()) {
throw new StatusException('Operation not allowed. This card is archived.'); throw new StatusException('Operation not allowed. This card is archived.');
} }
$changes = new ChangeSet($card);
$card->setTitle($title); $card->setTitle($title);
$this->changeHelper->cardChanged($card->getId(), false); $this->changeHelper->cardChanged($card->getId(), false);
$update = $this->cardMapper->update($card); $update = $this->cardMapper->update($card);
$this->eventDispatcher->dispatchTyped(new CardUpdatedEvent($card)); $this->eventDispatcher->dispatchTyped(new CardUpdatedEvent($card, $changes->getBefore()));
return $update; return $update;
} }
/** /**
* @param $id * @return list<Card>
* @param $stackId
* @param $order
* @return array
* @throws StatusException * @throws StatusException
* @throws \OCA\Deck\NoPermissionException * @throws \OCA\Deck\NoPermissionException
* @throws \OCP\AppFramework\Db\DoesNotExistException * @throws \OCP\AppFramework\Db\DoesNotExistException
* @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException * @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException
* @throws BadRequestException * @throws BadRequestException
*/ */
public function reorder(int $id, int $stackId, int $order) { public function reorder(int $id, int $stackId, int $order): array {
$this->cardServiceValidator->check(compact('id', 'stackId', 'order')); $this->cardServiceValidator->check(compact('id', 'stackId', 'order'));
$this->permissionService->checkPermission($this->cardMapper, $id, Acl::PERMISSION_EDIT); $this->permissionService->checkPermission($this->cardMapper, $id, Acl::PERMISSION_EDIT);
$this->permissionService->checkPermission($this->stackMapper, $stackId, Acl::PERMISSION_EDIT); $this->permissionService->checkPermission($this->stackMapper, $stackId, Acl::PERMISSION_EDIT);
@@ -459,73 +435,69 @@ class CardService {
$changes->setAfter($card); $changes->setAfter($card);
$this->activityManager->triggerUpdateEvents(ActivityManager::DECK_OBJECT_CARD, $changes, ActivityManager::SUBJECT_CARD_UPDATE); $this->activityManager->triggerUpdateEvents(ActivityManager::DECK_OBJECT_CARD, $changes, ActivityManager::SUBJECT_CARD_UPDATE);
$cards = $this->cardMapper->findAll($stackId); $cardsToReorder = $this->cardMapper->findAll($stackId);
$result = []; $result = [];
$i = 0; $i = 0;
foreach ($cards as $card) { foreach ($cardsToReorder as $cardToReorder) {
if ($card->getArchived()) { if ($cardToReorder->getArchived()) {
throw new StatusException('Operation not allowed. This card is archived.'); throw new StatusException('Operation not allowed. This card is archived.');
} }
if ($card->id === $id) { if ($cardToReorder->id === $id) {
$card->setOrder($order); $cardToReorder->setOrder($order);
$card->setLastModified(time()); $cardToReorder->setLastModified(time());
} }
if ($i === $order) { if ($i === $order) {
$i++; $i++;
} }
if ($card->id !== $id) { if ($cardToReorder->id !== $id) {
$card->setOrder($i++); $cardToReorder->setOrder($i++);
} }
$this->cardMapper->update($card); $this->cardMapper->update($cardToReorder);
$result[$card->getOrder()] = $card; $result[$cardToReorder->getOrder()] = $cardToReorder;
} }
$this->changeHelper->cardChanged($id, false); $this->changeHelper->cardChanged($id, false);
$this->eventDispatcher->dispatchTyped(new CardUpdatedEvent($card)); $this->eventDispatcher->dispatchTyped(new CardUpdatedEvent($card, $changes->getBefore()));
return array_values($result); return array_values($result);
} }
/** /**
* @param $id
* @return \OCP\AppFramework\Db\Entity
* @throws StatusException * @throws StatusException
* @throws \OCA\Deck\NoPermissionException * @throws \OCA\Deck\NoPermissionException
* @throws \OCP\AppFramework\Db\DoesNotExistException * @throws \OCP\AppFramework\Db\DoesNotExistException
* @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException * @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException
* @throws BadRequestException * @throws BadRequestException
*/ */
public function archive($id) { public function archive(int $id): Card {
$this->cardServiceValidator->check(compact('id')); $this->cardServiceValidator->check(compact('id'));
$this->permissionService->checkPermission($this->cardMapper, $id, Acl::PERMISSION_EDIT); $this->permissionService->checkPermission($this->cardMapper, $id, Acl::PERMISSION_EDIT);
if ($this->boardService->isArchived($this->cardMapper, $id)) { if ($this->boardService->isArchived($this->cardMapper, $id)) {
throw new StatusException('Operation not allowed. This board is archived.'); throw new StatusException('Operation not allowed. This board is archived.');
} }
$card = $this->cardMapper->find($id); $card = $this->cardMapper->find($id);
$changes = new ChangeSet($card);
$card->setArchived(true); $card->setArchived(true);
$newCard = $this->cardMapper->update($card); $newCard = $this->cardMapper->update($card);
$this->notificationHelper->markDuedateAsRead($card); $this->notificationHelper->markDuedateAsRead($card);
$this->activityManager->triggerEvent(ActivityManager::DECK_OBJECT_CARD, $newCard, ActivityManager::SUBJECT_CARD_UPDATE_ARCHIVE); $this->activityManager->triggerEvent(ActivityManager::DECK_OBJECT_CARD, $newCard, ActivityManager::SUBJECT_CARD_UPDATE_ARCHIVE);
$this->changeHelper->cardChanged($id, false); $this->changeHelper->cardChanged($id, false);
$this->eventDispatcher->dispatchTyped(new CardUpdatedEvent($card)); $this->eventDispatcher->dispatchTyped(new CardUpdatedEvent($card, $changes->getBefore()));
return $newCard; return $newCard;
} }
/** /**
* @param $id
* @return \OCP\AppFramework\Db\Entity
* @throws StatusException * @throws StatusException
* @throws \OCA\Deck\NoPermissionException * @throws \OCA\Deck\NoPermissionException
* @throws \OCP\AppFramework\Db\DoesNotExistException * @throws \OCP\AppFramework\Db\DoesNotExistException
* @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException * @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException
* @throws BadRequestException * @throws BadRequestException
*/ */
public function unarchive($id) { public function unarchive(int $id): Card {
$this->cardServiceValidator->check(compact('id')); $this->cardServiceValidator->check(compact('id'));
@@ -534,19 +506,18 @@ class CardService {
throw new StatusException('Operation not allowed. This board is archived.'); throw new StatusException('Operation not allowed. This board is archived.');
} }
$card = $this->cardMapper->find($id); $card = $this->cardMapper->find($id);
$changes = new ChangeSet($card);
$card->setArchived(false); $card->setArchived(false);
$newCard = $this->cardMapper->update($card); $newCard = $this->cardMapper->update($card);
$this->activityManager->triggerEvent(ActivityManager::DECK_OBJECT_CARD, $newCard, ActivityManager::SUBJECT_CARD_UPDATE_UNARCHIVE); $this->activityManager->triggerEvent(ActivityManager::DECK_OBJECT_CARD, $newCard, ActivityManager::SUBJECT_CARD_UPDATE_UNARCHIVE);
$this->changeHelper->cardChanged($id, false); $this->changeHelper->cardChanged($id, false);
$this->eventDispatcher->dispatchTyped(new CardUpdatedEvent($card)); $this->eventDispatcher->dispatchTyped(new CardUpdatedEvent($card, $changes->getBefore()));
return $newCard; return $newCard;
} }
/** /**
* @param $id
* @return \OCA\Deck\Db\Card
* @throws StatusException * @throws StatusException
* @throws \OCA\Deck\NoPermissionException * @throws \OCA\Deck\NoPermissionException
* @throws \OCP\AppFramework\Db\DoesNotExistException * @throws \OCP\AppFramework\Db\DoesNotExistException
@@ -559,13 +530,14 @@ class CardService {
throw new StatusException('Operation not allowed. This board is archived.'); throw new StatusException('Operation not allowed. This board is archived.');
} }
$card = $this->cardMapper->find($id); $card = $this->cardMapper->find($id);
$changes = new ChangeSet($card);
$card->setDone(new \DateTime()); $card->setDone(new \DateTime());
$newCard = $this->cardMapper->update($card); $newCard = $this->cardMapper->update($card);
$this->notificationHelper->markDuedateAsRead($card); $this->notificationHelper->markDuedateAsRead($card);
$this->activityManager->triggerEvent(ActivityManager::DECK_OBJECT_CARD, $newCard, ActivityManager::SUBJECT_CARD_UPDATE_DONE); $this->activityManager->triggerEvent(ActivityManager::DECK_OBJECT_CARD, $newCard, ActivityManager::SUBJECT_CARD_UPDATE_DONE);
$this->changeHelper->cardChanged($id, false); $this->changeHelper->cardChanged($id, false);
$this->eventDispatcher->dispatchTyped(new CardUpdatedEvent($card)); $this->eventDispatcher->dispatchTyped(new CardUpdatedEvent($card, $changes->getBefore()));
return $newCard; return $newCard;
} }
@@ -585,26 +557,25 @@ class CardService {
throw new StatusException('Operation not allowed. This board is archived.'); throw new StatusException('Operation not allowed. This board is archived.');
} }
$card = $this->cardMapper->find($id); $card = $this->cardMapper->find($id);
$changes = new ChangeSet($card);
$card->setDone(null); $card->setDone(null);
$newCard = $this->cardMapper->update($card); $newCard = $this->cardMapper->update($card);
$this->activityManager->triggerEvent(ActivityManager::DECK_OBJECT_CARD, $newCard, ActivityManager::SUBJECT_CARD_UPDATE_UNDONE); $this->activityManager->triggerEvent(ActivityManager::DECK_OBJECT_CARD, $newCard, ActivityManager::SUBJECT_CARD_UPDATE_UNDONE);
$this->changeHelper->cardChanged($id, false); $this->changeHelper->cardChanged($id, false);
$this->eventDispatcher->dispatchTyped(new CardUpdatedEvent($card)); $this->eventDispatcher->dispatchTyped(new CardUpdatedEvent($card, $changes->getBefore()));
return $newCard; return $newCard;
} }
/** /**
* @param $cardId
* @param $labelId
* @throws StatusException * @throws StatusException
* @throws \OCA\Deck\NoPermissionException * @throws \OCA\Deck\NoPermissionException
* @throws \OCP\AppFramework\Db\DoesNotExistException * @throws \OCP\AppFramework\Db\DoesNotExistException
* @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException * @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException
* @throws BadRequestException * @throws BadRequestException
*/ */
public function assignLabel($cardId, $labelId) { public function assignLabel(int $cardId, int $labelId): Card {
$this->cardServiceValidator->check(compact('cardId', 'labelId')); $this->cardServiceValidator->check(compact('cardId', 'labelId'));
$this->permissionService->checkPermission($this->cardMapper, $cardId, Acl::PERMISSION_EDIT); $this->permissionService->checkPermission($this->cardMapper, $cardId, Acl::PERMISSION_EDIT);
@@ -626,18 +597,16 @@ class CardService {
$this->activityManager->triggerEvent(ActivityManager::DECK_OBJECT_CARD, $card, ActivityManager::SUBJECT_LABEL_ASSIGN, ['label' => $label]); $this->activityManager->triggerEvent(ActivityManager::DECK_OBJECT_CARD, $card, ActivityManager::SUBJECT_LABEL_ASSIGN, ['label' => $label]);
$this->eventDispatcher->dispatchTyped(new CardUpdatedEvent($card)); $this->eventDispatcher->dispatchTyped(new CardUpdatedEvent($card));
return $card;
} }
/** /**
* @param $cardId
* @param $labelId
* @throws StatusException
* @throws \OCA\Deck\NoPermissionException * @throws \OCA\Deck\NoPermissionException
* @throws \OCP\AppFramework\Db\DoesNotExistException * @throws \OCP\AppFramework\Db\DoesNotExistException
* @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException * @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException
* @throws BadRequestException * @throws BadRequestException
*/ */
public function removeLabel($cardId, $labelId) { public function removeLabel(int $cardId, int $labelId): Card {
$this->cardServiceValidator->check(compact('cardId', 'labelId')); $this->cardServiceValidator->check(compact('cardId', 'labelId'));
@@ -657,6 +626,7 @@ class CardService {
$this->activityManager->triggerEvent(ActivityManager::DECK_OBJECT_CARD, $card, ActivityManager::SUBJECT_LABEL_UNASSING, ['label' => $label]); $this->activityManager->triggerEvent(ActivityManager::DECK_OBJECT_CARD, $card, ActivityManager::SUBJECT_LABEL_UNASSING, ['label' => $label]);
$this->eventDispatcher->dispatchTyped(new CardUpdatedEvent($card)); $this->eventDispatcher->dispatchTyped(new CardUpdatedEvent($card));
return $card;
} }
public function getCardUrl(int $cardId): string { public function getCardUrl(int $cardId): string {
+1 -1
View File
@@ -66,7 +66,7 @@ class CirclesService {
$circlesManager->startSession($federatedUser); $circlesManager->startSession($federatedUser);
$circle = $circlesManager->getCircle($circleId); $circle = $circlesManager->getCircle($circleId);
$member = $circle->getInitiator(); $member = $circle->getInitiator();
$isUserInCircle = $member !== null && $member->getLevel() >= Member::LEVEL_MEMBER; $isUserInCircle = $member->getLevel() >= Member::LEVEL_MEMBER;
if (!isset($this->userCircleCache[$circleId])) { if (!isset($this->userCircleCache[$circleId])) {
$this->userCircleCache[$circleId] = []; $this->userCircleCache[$circleId] = [];
+13 -30
View File
@@ -22,8 +22,6 @@ use OCP\IUserManager;
use OutOfBoundsException; use OutOfBoundsException;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
use function is_numeric;
class CommentService { class CommentService {
public function __construct( public function __construct(
@@ -36,12 +34,9 @@ class CommentService {
) { ) {
} }
public function list(string $cardId, int $limit = 20, int $offset = 0): DataResponse { public function list(int $cardId, int $limit = 20, int $offset = 0): DataResponse {
if (!is_numeric($cardId)) {
throw new BadRequestException('A valid card id must be provided');
}
$this->permissionService->checkPermission($this->cardMapper, $cardId, Acl::PERMISSION_READ); $this->permissionService->checkPermission($this->cardMapper, $cardId, Acl::PERMISSION_READ);
$comments = $this->commentsManager->getForObject(Application::COMMENT_ENTITY_TYPE, $cardId, $limit, $offset); $comments = $this->commentsManager->getForObject(Application::COMMENT_ENTITY_TYPE, (string)$cardId, $limit, $offset);
$result = []; $result = [];
foreach ($comments as $comment) { foreach ($comments as $comment) {
$formattedComment = $this->formatComment($comment); $formattedComment = $this->formatComment($comment);
@@ -96,13 +91,13 @@ class CommentService {
* @throws BadRequestException * @throws BadRequestException
* @throws NotFoundException|NoPermissionException * @throws NotFoundException|NoPermissionException
*/ */
public function create(int $cardId, string $message, string $replyTo = '0'): DataResponse { public function create(int $cardId, string $message, int $replyTo = 0): DataResponse {
$this->permissionService->checkPermission($this->cardMapper, $cardId, Acl::PERMISSION_READ); $this->permissionService->checkPermission($this->cardMapper, $cardId, Acl::PERMISSION_READ);
// Check if parent is a comment on the same card // Check if parent is a comment on the same card
if ($replyTo !== '0') { if ($replyTo !== 0) {
try { try {
$comment = $this->commentsManager->get($replyTo); $comment = $this->commentsManager->get((string)$replyTo);
if ($comment->getObjectType() !== Application::COMMENT_ENTITY_TYPE || (int)$comment->getObjectId() !== $cardId) { if ($comment->getObjectType() !== Application::COMMENT_ENTITY_TYPE || (int)$comment->getObjectId() !== $cardId) {
throw new CommentNotFoundException(); throw new CommentNotFoundException();
} }
@@ -115,7 +110,7 @@ class CommentService {
$comment = $this->commentsManager->create('users', $this->userId, Application::COMMENT_ENTITY_TYPE, (string)$cardId); $comment = $this->commentsManager->create('users', $this->userId, Application::COMMENT_ENTITY_TYPE, (string)$cardId);
$comment->setMessage($message); $comment->setMessage($message);
$comment->setVerb('comment'); $comment->setVerb('comment');
$comment->setParentId($replyTo); $comment->setParentId((string)$replyTo);
$this->commentsManager->save($comment); $this->commentsManager->save($comment);
return new DataResponse($this->formatComment($comment, true)); return new DataResponse($this->formatComment($comment, true));
} catch (\InvalidArgumentException $e) { } catch (\InvalidArgumentException $e) {
@@ -128,14 +123,8 @@ class CommentService {
} }
} }
public function update(string $cardId, string $commentId, string $message): DataResponse { public function update(int $cardId, int $commentId, string $message): DataResponse {
if (!is_numeric($cardId)) { $comment = $this->get($cardId, $commentId);
throw new BadRequestException('A valid card id must be provided');
}
if (!is_numeric($commentId)) {
throw new BadRequestException('A valid comment id must be provided');
}
$comment = $this->get((int)$cardId, (int)$commentId);
if ($comment->getActorType() !== 'users' || $comment->getActorId() !== $this->userId) { if ($comment->getActorType() !== 'users' || $comment->getActorId() !== $this->userId) {
throw new NoPermissionException('Only authors are allowed to edit their comment.'); throw new NoPermissionException('Only authors are allowed to edit their comment.');
} }
@@ -145,18 +134,12 @@ class CommentService {
return new DataResponse($this->formatComment($comment)); return new DataResponse($this->formatComment($comment));
} }
public function delete(string $cardId, string $commentId): DataResponse { public function delete(int $cardId, int $commentId): DataResponse {
if (!is_numeric($cardId)) {
throw new BadRequestException('A valid card id must be provided');
}
if (!is_numeric($commentId)) {
throw new BadRequestException('A valid comment id must be provided');
}
$this->permissionService->checkPermission($this->cardMapper, $cardId, Acl::PERMISSION_READ); $this->permissionService->checkPermission($this->cardMapper, $cardId, Acl::PERMISSION_READ);
try { try {
$comment = $this->commentsManager->get($commentId); $comment = $this->commentsManager->get((string)$commentId);
if ($comment->getObjectType() !== Application::COMMENT_ENTITY_TYPE || $comment->getObjectId() !== $cardId) { if ($comment->getObjectType() !== Application::COMMENT_ENTITY_TYPE || (int)$comment->getObjectId() !== $cardId) {
throw new CommentNotFoundException(); throw new CommentNotFoundException();
} }
} catch (CommentNotFoundException $e) { } catch (CommentNotFoundException $e) {
@@ -165,11 +148,11 @@ class CommentService {
if ($comment->getActorType() !== 'users' || $comment->getActorId() !== $this->userId) { if ($comment->getActorType() !== 'users' || $comment->getActorId() !== $this->userId) {
throw new NoPermissionException('Only authors are allowed to edit their comment.'); throw new NoPermissionException('Only authors are allowed to edit their comment.');
} }
$this->commentsManager->delete($commentId); $this->commentsManager->delete((string)$commentId);
return new DataResponse([]); return new DataResponse([]);
} }
private function formatComment(IComment $comment, $addReplyTo = false): array { private function formatComment(IComment $comment, bool $addReplyTo = false): array {
$actorDisplayName = $this->userManager->getDisplayName($comment->getActorId()) ?? $comment->getActorId(); $actorDisplayName = $this->userManager->getDisplayName($comment->getActorId()) ?? $comment->getActorId();
$formattedComment = [ $formattedComment = [
+22 -17
View File
@@ -48,7 +48,8 @@ class ConfigService {
} }
public function getAll(): array { public function getAll(): array {
if ($this->getUserId() === null) { $userId = $this->getUserId();
if ($userId === null) {
return []; return [];
} }
@@ -57,7 +58,7 @@ class ConfigService {
'cardDetailsInModal' => $this->isCardDetailsInModal(), 'cardDetailsInModal' => $this->isCardDetailsInModal(),
'cardIdBadge' => $this->isCardIdBadgeEnabled() 'cardIdBadge' => $this->isCardIdBadgeEnabled()
]; ];
if ($this->groupManager->isAdmin($this->getUserId())) { if ($this->groupManager->isAdmin($userId)) {
$data['groupLimit'] = $this->get('groupLimit'); $data['groupLimit'] = $this->get('groupLimit');
} }
return $data; return $data;
@@ -95,44 +96,48 @@ class ConfigService {
} }
public function isCalendarEnabled(?int $boardId = null): bool { public function isCalendarEnabled(?int $boardId = null): bool {
if ($this->getUserId() === null) { $userId = $this->getUserId();
if ($userId === null) {
return false; return false;
} }
$appConfigState = $this->config->getAppValue(Application::APP_ID, 'calendar', 'yes') === 'yes'; $appConfigState = $this->config->getAppValue(Application::APP_ID, 'calendar', 'yes') === 'yes';
$defaultState = (bool)$this->config->getUserValue($this->getUserId(), Application::APP_ID, 'calendar', $appConfigState); $defaultState = (bool)$this->config->getUserValue($userId, Application::APP_ID, 'calendar', $appConfigState);
if ($boardId === null) { if ($boardId === null) {
return $defaultState; return $defaultState;
} }
return (bool)$this->config->getUserValue($this->getUserId(), Application::APP_ID, 'board:' . $boardId . ':calendar', $defaultState); return (bool)$this->config->getUserValue($userId, Application::APP_ID, 'board:' . $boardId . ':calendar', $defaultState);
} }
public function isCardDetailsInModal(?int $boardId = null): bool { public function isCardDetailsInModal(?int $boardId = null): bool {
if ($this->getUserId() === null) { $userId = $this->getUserId();
if ($userId === null) {
return false; return false;
} }
$defaultState = (bool)$this->config->getUserValue($this->getUserId(), Application::APP_ID, 'cardDetailsInModal', true); $defaultState = (bool)$this->config->getUserValue($userId, Application::APP_ID, 'cardDetailsInModal', true);
if ($boardId === null) { if ($boardId === null) {
return $defaultState; return $defaultState;
} }
return (bool)$this->config->getUserValue($this->getUserId(), Application::APP_ID, 'board:' . $boardId . ':cardDetailsInModal', $defaultState); return (bool)$this->config->getUserValue($userId, Application::APP_ID, 'board:' . $boardId . ':cardDetailsInModal', $defaultState);
} }
public function isCardIdBadgeEnabled(): bool { public function isCardIdBadgeEnabled(): bool {
if ($this->getUserId() === null) { $userId = $this->getUserId();
if ($userId === null) {
return false; return false;
} }
$appConfigState = $this->config->getAppValue(Application::APP_ID, 'cardIdBadge', 'yes') === 'no'; $appConfigState = $this->config->getAppValue(Application::APP_ID, 'cardIdBadge', 'yes') === 'no';
$defaultState = (bool)$this->config->getUserValue($this->getUserId(), Application::APP_ID, 'cardIdBadge', $appConfigState); $defaultState = (bool)$this->config->getUserValue($userId, Application::APP_ID, 'cardIdBadge', $appConfigState);
return (bool)$this->config->getUserValue($this->getUserId(), Application::APP_ID, 'cardIdBadge', $defaultState); return (bool)$this->config->getUserValue($userId, Application::APP_ID, 'cardIdBadge', $defaultState);
} }
public function set($key, $value) { public function set($key, $value) {
if ($this->getUserId() === null) { $userId = $this->getUserId();
if ($userId === null) {
throw new NoPermissionException('Must be logged in to set user config'); throw new NoPermissionException('Must be logged in to set user config');
} }
@@ -140,21 +145,21 @@ class ConfigService {
[$scope] = explode(':', $key, 2); [$scope] = explode(':', $key, 2);
switch ($scope) { switch ($scope) {
case 'groupLimit': case 'groupLimit':
if (!$this->groupManager->isAdmin($this->getUserId())) { if (!$this->groupManager->isAdmin($userId)) {
throw new NoPermissionException('You must be admin to set the group limit'); throw new NoPermissionException('You must be admin to set the group limit');
} }
$result = $this->setGroupLimit($value); $result = $this->setGroupLimit($value);
break; break;
case 'calendar': case 'calendar':
$this->config->setUserValue($this->getUserId(), Application::APP_ID, 'calendar', (string)$value); $this->config->setUserValue($userId, Application::APP_ID, 'calendar', (string)$value);
$result = $value; $result = $value;
break; break;
case 'cardDetailsInModal': case 'cardDetailsInModal':
$this->config->setUserValue($this->getUserId(), Application::APP_ID, 'cardDetailsInModal', (string)$value); $this->config->setUserValue($userId, Application::APP_ID, 'cardDetailsInModal', (string)$value);
$result = $value; $result = $value;
break; break;
case 'cardIdBadge': case 'cardIdBadge':
$this->config->setUserValue($this->getUserId(), Application::APP_ID, 'cardIdBadge', (string)$value); $this->config->setUserValue($userId, Application::APP_ID, 'cardIdBadge', (string)$value);
$result = $value; $result = $value;
break; break;
case 'board': case 'board':
@@ -162,7 +167,7 @@ class ConfigService {
if ($boardConfigKey === 'notify-due' && !in_array($value, [self::SETTING_BOARD_NOTIFICATION_DUE_ALL, self::SETTING_BOARD_NOTIFICATION_DUE_ASSIGNED, self::SETTING_BOARD_NOTIFICATION_DUE_OFF], true)) { if ($boardConfigKey === 'notify-due' && !in_array($value, [self::SETTING_BOARD_NOTIFICATION_DUE_ALL, self::SETTING_BOARD_NOTIFICATION_DUE_ASSIGNED, self::SETTING_BOARD_NOTIFICATION_DUE_OFF], true)) {
throw new BadRequestException('Board notification option must be one of: off, assigned, all'); throw new BadRequestException('Board notification option must be one of: off, assigned, all');
} }
$this->config->setUserValue($this->getUserId(), Application::APP_ID, $key, (string)$value); $this->config->setUserValue($userId, Application::APP_ID, $key, (string)$value);
$result = $value; $result = $value;
} }
return $result; return $result;
+73 -11
View File
@@ -9,6 +9,7 @@ namespace OCA\Deck\Service;
use OCA\Deck\AppInfo\Application; use OCA\Deck\AppInfo\Application;
use OCA\Deck\BadRequestException; use OCA\Deck\BadRequestException;
use OCA\Deck\Db\Board;
use OCA\Deck\Db\BoardMapper; use OCA\Deck\Db\BoardMapper;
use OCP\IConfig; use OCP\IConfig;
use OCP\IL10N; use OCP\IL10N;
@@ -21,6 +22,8 @@ class DefaultBoardService {
private $cardService; private $cardService;
private $config; private $config;
private $l10n; private $l10n;
private LabelService $labelService;
private AttachmentService $attachmentService;
public function __construct( public function __construct(
IL10N $l10n, IL10N $l10n,
@@ -29,6 +32,8 @@ class DefaultBoardService {
StackService $stackService, StackService $stackService,
CardService $cardService, CardService $cardService,
IConfig $config, IConfig $config,
LabelService $labelService,
AttachmentService $attachmentService,
) { ) {
$this->boardService = $boardService; $this->boardService = $boardService;
$this->stackService = $stackService; $this->stackService = $stackService;
@@ -36,6 +41,8 @@ class DefaultBoardService {
$this->config = $config; $this->config = $config;
$this->boardMapper = $boardMapper; $this->boardMapper = $boardMapper;
$this->l10n = $l10n; $this->l10n = $l10n;
$this->labelService = $labelService;
$this->attachmentService = $attachmentService;
} }
/** /**
@@ -59,10 +66,13 @@ class DefaultBoardService {
return false; return false;
} }
private function getDefaultBoardData(): array {
$defaultBoardDataJson = file_get_contents(__DIR__ . '/fixtures/default-board.json');
return json_decode($defaultBoardDataJson, true);
}
/** /**
* @param $title
* @param $userId * @param $userId
* @param $color
* @return \OCP\AppFramework\Db\Entity * @return \OCP\AppFramework\Db\Entity
* @throws \OCA\Deck\NoPermissionException * @throws \OCA\Deck\NoPermissionException
* @throws \OCA\Deck\StatusException * @throws \OCA\Deck\StatusException
@@ -71,19 +81,71 @@ class DefaultBoardService {
* @throws BadRequestException * @throws BadRequestException
*/ */
public function createDefaultBoard(string $title, string $userId, string $color) { public function createDefaultBoard(string $title, string $userId, string $color) {
$defaultBoard = $this->boardService->create($title, $userId, $color); $boardData = $this->getDefaultBoardData();
$defaultStacks = [];
$defaultCards = [];
/** @var Board $defaultBoard */
$defaultBoard = $this->boardService->create(
$boardData['title'] ?? $title,
$userId,
$boardData['color'] ?? $color,
);
$boardId = $defaultBoard->getId(); $boardId = $defaultBoard->getId();
$additionLabels = [];
$translatedLabelTitles = [
'Read more inside' => $this->l10n->t('Read more inside'),
];
$translatedStackTitles = [
'Custom lists - click to rename!' => $this->l10n->t('Custom lists - click to rename!'),
'To Do' => $this->l10n->t('To Do'),
'In Progress' => $this->l10n->t('In Progress'),
'Done' => $this->l10n->t('Done'),
];
$translatedCardTitles = [
'1. Open to learn more about boards and cards' => $this->l10n->t('1. Open to learn more about boards and cards'),
'2. Drag cards left and right, up and down' => $this->l10n->t('2. Drag cards left and right, up and down'),
'3. Apply rich formatting and link content' => $this->l10n->t('3. Apply rich formatting and link content'),
'4. Share, comment and collaborate!' => $this->l10n->t('4. Share, comment and collaborate!'),
'Create your first card!' => $this->l10n->t('Create your first card!'),
];
$defaultStacks[] = $this->stackService->create($this->l10n->t('To do'), $boardId, 1); foreach ($boardData['addition_labels'] as $labelData) {
$defaultStacks[] = $this->stackService->create($this->l10n->t('Doing'), $boardId, 1); $additionLabels[] = $this->labelService->create(
$defaultStacks[] = $this->stackService->create($this->l10n->t('Done'), $boardId, 1); $translatedLabelTitles[$labelData['title']] ?? $labelData['title'],
$labelData['color'],
$boardId
);
}
$defaultCards[] = $this->cardService->create($this->l10n->t('Example Task 3'), $defaultStacks[0]->getId(), 'text', 0, $userId); $defaultLabels = array_merge($defaultBoard->getLabels() ?? [], $additionLabels);
$defaultCards[] = $this->cardService->create($this->l10n->t('Example Task 2'), $defaultStacks[1]->getId(), 'text', 0, $userId);
$defaultCards[] = $this->cardService->create($this->l10n->t('Example Task 1'), $defaultStacks[2]->getId(), 'text', 0, $userId); foreach ($boardData['stacks'] as $stackData) {
$stack = $this->stackService->create(
$translatedStackTitles[$stackData['title']] ?? $stackData['title'],
$boardId,
$stackData['order']
);
foreach ($stackData['cards'] as $cardData) {
$card = $this->cardService->create(
$translatedCardTitles[$cardData['title']] ?? $cardData['title'],
$stack->getId(),
$cardData['type'],
$cardData['order'],
$userId,
$cardData['description'],
);
foreach ($defaultLabels as $defaultLabel) {
if ($defaultLabel && in_array($defaultLabel->getTitle(), $cardData['labels'])) {
$this->cardService->assignLabel($card->getId(), $defaultLabel->getId());
}
}
if (!empty($cardData['has_example_attachment'])) {
$this->attachmentService->create($card->getId(), 'file', 'DEFAULT_SAMPLE_FILE');
}
}
}
return $defaultBoard; return $defaultBoard;
} }
+9 -1
View File
@@ -162,7 +162,15 @@ class FilesAppService implements IAttachmentService, ICustomAttachmentService {
} }
public function create(Attachment $attachment) { public function create(Attachment $attachment) {
$file = $this->getUploadedFile(); if ($attachment->getData() === 'DEFAULT_SAMPLE_FILE' && !$this->request->getUploadedFile('file')) {
$file = [
'name' => 'Nextcloud sample image - add your image here!.jpg',
'tmp_name' => __DIR__ . '/../../img/sample-image.jpg',
];
} else {
$file = $this->getUploadedFile();
}
$fileName = $file['name']; $fileName = $file['name'];
// get shares for current card // get shares for current card
+8 -11
View File
@@ -124,18 +124,15 @@ class FullTextSearchService {
/** /**
* @param int $cardId
*
* @return Board
* @throws DoesNotExistException * @throws DoesNotExistException
* @throws MultipleObjectsReturnedException * @throws MultipleObjectsReturnedException
*/ */
public function getBoardFromCardId(int $cardId): Board { public function getBoardFromCardId(int $cardId): Board {
$boardId = (int)$this->cardMapper->findBoardId($cardId); $boardId = $this->cardMapper->findBoardId($cardId);
/** @var Board $board */ if ($boardId === null) {
$board = $this->boardMapper->find($boardId); throw new DoesNotExistException("Board '$cardId' does not exist");
}
return $board; return $this->boardMapper->find($boardId);
} }
@@ -145,7 +142,7 @@ class FullTextSearchService {
* @return Card[] * @return Card[]
*/ */
private function getCardsFromStack(int $stackId): array { private function getCardsFromStack(int $stackId): array {
return $this->cardMapper->findAll($stackId, null, null); return $this->cardMapper->findAll($stackId);
} }
@@ -155,7 +152,7 @@ class FullTextSearchService {
* @return Stack[] * @return Stack[]
*/ */
private function getStacksFromBoard(int $boardId): array { private function getStacksFromBoard(int $boardId): array {
return $this->stackMapper->findAll($boardId, null, null); return $this->stackMapper->findAll($boardId);
} }
@@ -165,6 +162,6 @@ class FullTextSearchService {
* @return Board[] * @return Board[]
*/ */
private function getBoardsFromUser(string $userId): array { private function getBoardsFromUser(string $userId): array {
return $this->boardMapper->findAllByUser($userId, null, null, null); return $this->boardMapper->findAllByUser($userId);
} }
} }
+18 -19
View File
@@ -19,29 +19,25 @@ use OCP\Comments\IComment;
abstract class ABoardImportService { abstract class ABoardImportService {
/** @var string */ /** @var string */
public static $name = ''; public static $name = '';
/** @var BoardImportService */ private BoardImportService $boardImportService;
private $boardImportService; protected bool $needValidateData = true;
/** @var bool */
protected $needValidateData = true;
/** @var Stack[] */ /** @var Stack[] */
protected $stacks = []; protected array $stacks = [];
/** @var Label[] */ /** @var Label[] */
protected $labels = []; protected array $labels = [];
/** @var Card[] */ /** @var Card[] */
protected $cards = []; protected array $cards = [];
/** @var Acl[] */ /** @var Acl[] */
protected $acls = []; protected array $acls = [];
/** @var IComment[][] */ /** @var IComment[][] */
protected $comments = []; protected array $comments = [];
/** @var Assignment[] */ /** @var Assignment[] */
protected $assignments = []; protected array $assignments = [];
/** @var string[][] */ /** @var int[][] */
protected $labelCardAssignments = []; protected array $labelCardAssignments = [];
/** /**
* Configure import service * Configure import service
*
* @return void
*/ */
abstract public function bootstrap(): void; abstract public function bootstrap(): void;
@@ -68,10 +64,13 @@ abstract class ABoardImportService {
abstract public function getCardAssignments(): array; abstract public function getCardAssignments(): array;
/**
* @return array<int, array<int, int>>
*/
abstract public function getCardLabelAssignment(): array; abstract public function getCardLabelAssignment(): array;
/** /**
* @return IComment[][]|array * @return array<int, array<string, IComment>>
*/ */
abstract public function getComments(): array; abstract public function getComments(): array;
@@ -98,16 +97,16 @@ abstract class ABoardImportService {
$this->acls[$code] = $acl; $this->acls[$code] = $acl;
} }
public function updateComment(string $cardId, string $commentId, IComment $comment): void { public function updateComment(int $cardId, string $commentId, IComment $comment): void {
$this->comments[$cardId][$commentId] = $comment; $this->comments[$cardId][$commentId] = $comment;
} }
public function updateCardAssignment(string $cardId, string $assignmentId, Entity $assignment): void { public function updateCardAssignment(int $cardId, int $assignmentId, Entity $assignment): void {
$this->assignments[$cardId][$assignmentId] = $assignment; $this->assignments[$cardId][$assignmentId] = $assignment;
} }
public function updateCardLabelsAssignment(string $cardId, string $assignmentId, string $assignment): void { public function updateCardLabelsAssignment(int $cardId, int $assignmentId, int $labelId): void {
$this->labelCardAssignments[$cardId][$assignmentId] = $assignment; $this->labelCardAssignments[$cardId][$assignmentId] = $labelId;
} }
public function setImportService(BoardImportService $service): void { public function setImportService(BoardImportService $service): void {
+16 -20
View File
@@ -209,14 +209,15 @@ class BoardImportService {
public function importBoard(): void { public function importBoard(): void {
$board = $this->getImportSystem()->getBoard(); $board = $this->getImportSystem()->getBoard();
if ($board === null) {
throw new \LogicException('Import board not found');
}
if (!$this->userManager->userExists($board->getOwner())) { if (!$this->userManager->userExists($board->getOwner())) {
throw new \Exception('Target owner ' . $board->getOwner() . ' not found. Please provide a mapping through the import config.'); throw new \Exception('Target owner ' . $board->getOwner() . ' not found. Please provide a mapping through the import config.');
} }
if ($board) { $this->boardMapper->insert($board);
$this->boardMapper->insert($board); $this->board = $board;
$this->board = $board;
}
} }
public function getBoard(bool $reset = false): Board { public function getBoard(bool $reset = false): Board {
@@ -292,12 +293,7 @@ class BoardImportService {
} }
} }
/** public function assignCardToLabel(int $cardId, int $labelId): self {
* @param mixed $cardId
* @param mixed $labelId
* @return self
*/
public function assignCardToLabel($cardId, $labelId): self {
$this->cardMapper->assignLabel( $this->cardMapper->assignLabel(
$cardId, $cardId,
$labelId $labelId
@@ -307,14 +303,14 @@ class BoardImportService {
public function assignCardsToLabels(): void { public function assignCardsToLabels(): void {
$data = $this->getImportSystem()->getCardLabelAssignment(); $data = $this->getImportSystem()->getCardLabelAssignment();
foreach ($data as $cardId => $assignemnt) { foreach ($data as $cardId => $assignment) {
foreach ($assignemnt as $assignmentId => $labelId) { foreach ($assignment as $assignmentId => $labelId) {
try { try {
$this->assignCardToLabel( $this->assignCardToLabel(
$cardId, (int)$cardId,
$labelId $labelId
); );
$this->getImportSystem()->updateCardLabelsAssignment($cardId, $assignmentId, $labelId); $this->getImportSystem()->updateCardLabelsAssignment((int)$cardId, (int)$assignmentId, $labelId);
} catch (\Exception $e) { } catch (\Exception $e) {
$this->addError('Failed to assign label ' . $labelId . ' to ' . $cardId, $e); $this->addError('Failed to assign label ' . $labelId . ' to ' . $cardId, $e);
} }
@@ -326,20 +322,20 @@ class BoardImportService {
$allComments = $this->getImportSystem()->getComments(); $allComments = $this->getImportSystem()->getComments();
foreach ($allComments as $cardId => $comments) { foreach ($allComments as $cardId => $comments) {
foreach ($comments as $commentId => $comment) { foreach ($comments as $commentId => $comment) {
$this->insertComment($cardId, $comment); $this->insertComment((int)$cardId, $comment);
$this->getImportSystem()->updateComment($cardId, $commentId, $comment); $this->getImportSystem()->updateComment((int)$cardId, $commentId, $comment);
} }
} }
} }
private function insertComment(string $cardId, IComment $comment): void { private function insertComment(int $cardId, IComment $comment): void {
$comment->setObject('deckCard', $cardId); $comment->setObject('deckCard', (string)$cardId);
$comment->setVerb('comment'); $comment->setVerb('comment');
// Check if parent is a comment on the same card // Check if parent is a comment on the same card
if ($comment->getParentId() !== '0') { if ($comment->getParentId() !== '0') {
try { try {
$parent = $this->commentsManager->get($comment->getParentId()); $parent = $this->commentsManager->get($comment->getParentId());
if ($parent->getObjectType() !== Application::COMMENT_ENTITY_TYPE || $parent->getObjectId() !== $cardId) { if ($parent->getObjectType() !== Application::COMMENT_ENTITY_TYPE || (int)$parent->getObjectId() !== $cardId) {
throw new CommentNotFoundException(); throw new CommentNotFoundException();
} }
} catch (CommentNotFoundException $e) { } catch (CommentNotFoundException $e) {
@@ -362,7 +358,7 @@ class BoardImportService {
foreach ($assignments as $assignment) { foreach ($assignments as $assignment) {
try { try {
$assignment = $this->assignmentMapper->insert($assignment); $assignment = $this->assignmentMapper->insert($assignment);
$this->getImportSystem()->updateCardAssignment($cardId, (string)$assignment->getId(), $assignment); $this->getImportSystem()->updateCardAssignment((int)$cardId, $assignment->getId(), $assignment);
$this->addOutput('Assignment ' . $assignment->getParticipant() . ' added'); $this->addOutput('Assignment ' . $assignment->getParticipant() . ' added');
} catch (NotFoundException $e) { } catch (NotFoundException $e) {
$this->addError('No origin or mapping found for card "' . $cardId . '" and ' . $assignment->getTypeString() . ' assignment "' . $assignment->getParticipant(), $e); $this->addError('No origin or mapping found for card "' . $cardId . '" and ' . $assignment->getTypeString() . ' assignment "' . $assignment->getParticipant(), $e);
@@ -16,6 +16,7 @@ use OCA\Deck\Db\Card;
use OCA\Deck\Db\Label; use OCA\Deck\Db\Label;
use OCA\Deck\Db\Stack; use OCA\Deck\Db\Stack;
use OCA\Deck\Service\Importer\ABoardImportService; use OCA\Deck\Service\Importer\ABoardImportService;
use OCP\Comments\IComment;
use OCP\IUser; use OCP\IUser;
use OCP\IUserManager; use OCP\IUserManager;
@@ -118,6 +119,7 @@ class DeckJsonService extends ABoardImportService {
$comments[$this->cards[$sourceCard->id]->getId()][$commentOriginal->id] = $comment; $comments[$this->cards[$sourceCard->id]->getId()][$commentOriginal->id] = $comment;
} }
} }
/** @var array<int, array<string, IComment>> */
return $comments; return $comments;
} }
@@ -17,7 +17,7 @@ use Psr\Log\LoggerInterface;
class TrelloApiService extends TrelloJsonService { class TrelloApiService extends TrelloJsonService {
/** @var string */ /** @var string */
public static $name = 'Trello API'; public static $name = 'Trello API';
protected $needValidateData = false; protected bool $needValidateData = false;
/** @var IClient */ /** @var IClient */
private $httpClient; private $httpClient;
/** @var LoggerInterface */ /** @var LoggerInterface */
@@ -176,7 +176,7 @@ class TrelloApiService extends TrelloJsonService {
if (empty($queryString['limit'])) { if (empty($queryString['limit'])) {
return []; return [];
} }
if (count($data) < $queryString['limit']) { if ((count($data) < $queryString['limit']) || (count($data) === 0)) {
return []; return [];
} }
$queryString['before'] = end($data)->id; $queryString['before'] = end($data)->id;

Some files were not shown because too many files have changed in this diff Show More