Compare commits
340 Commits
v1.10.0-be
...
backport/4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9932a7b972 | ||
|
|
e10ab29f28 | ||
|
|
0ac494df9d | ||
|
|
a8ce89d487 | ||
|
|
2fcbbbbc23 | ||
|
|
4f673fe598 | ||
|
|
02ba8f85ce | ||
|
|
f88d53d7f1 | ||
|
|
6a5eecea76 | ||
|
|
b61667c90b | ||
|
|
6864846d84 | ||
|
|
962256acb0 | ||
|
|
9db8c9af80 | ||
|
|
88bd868e76 | ||
|
|
3c88551ad5 | ||
|
|
0872b1b606 | ||
|
|
65242c2b6c | ||
|
|
a3527542f9 | ||
|
|
28e4b12428 | ||
|
|
628cd333f1 | ||
|
|
ccfaa4886a | ||
|
|
0ef238da0e | ||
|
|
333b904335 | ||
|
|
32a956af02 | ||
|
|
b7dad3134d | ||
|
|
42f76117f2 | ||
|
|
be1c646bd7 | ||
|
|
137fece0b8 | ||
|
|
81e5922dfd | ||
|
|
ccc216d388 | ||
|
|
3f7ab93df9 | ||
|
|
a621aacac0 | ||
|
|
bb7ed1bc31 | ||
|
|
4d0f7ddef3 | ||
|
|
bd13cc09d8 | ||
|
|
cb3925faeb | ||
|
|
ac11571a7d | ||
|
|
fd4564450b | ||
|
|
acbd42d6a4 | ||
|
|
458831dfc7 | ||
|
|
8a5098951c | ||
|
|
99260ae966 | ||
|
|
ea592251f9 | ||
|
|
6c80674ce0 | ||
|
|
060e8cd7db | ||
|
|
f05bd86be9 | ||
|
|
1ce7856cae | ||
|
|
678e4c9569 | ||
|
|
4fe4216cb5 | ||
|
|
85e7305d8d | ||
|
|
090378580c | ||
|
|
3f8efb5368 | ||
|
|
c1f2fd452b | ||
|
|
576c47bdef | ||
|
|
e907aeb8c0 | ||
|
|
c51119d051 | ||
|
|
ee72cb4660 | ||
|
|
d4f2ed263f | ||
|
|
e6f8796b63 | ||
|
|
846443d003 | ||
|
|
144c27d403 | ||
|
|
f374084989 | ||
|
|
0656e53508 | ||
|
|
7506126ef7 | ||
|
|
5214721875 | ||
|
|
34f7b726cd | ||
|
|
ea5baa5339 | ||
|
|
7c3aa301ac | ||
|
|
313bc90ce9 | ||
|
|
be06c7ab70 | ||
|
|
5964da382e | ||
|
|
eaa2facc29 | ||
|
|
13e836760a | ||
|
|
73815827e1 | ||
|
|
34107ad06f | ||
|
|
90535b3c30 | ||
|
|
312376e596 | ||
|
|
04dbe45f29 | ||
|
|
bc38340daf | ||
|
|
9b89897d74 | ||
|
|
f411181dcf | ||
|
|
080465d48a | ||
|
|
2358645037 | ||
|
|
c6545c7c00 | ||
|
|
3baa2fc1a0 | ||
|
|
fd297dc92e | ||
|
|
4608e4b77b | ||
|
|
524b8ba90e | ||
|
|
569e568136 | ||
|
|
d9c563d5bf | ||
|
|
f51956891e | ||
|
|
053ecdacee | ||
|
|
8f755ee3eb | ||
|
|
fa6d819409 | ||
|
|
fa17dfac3a | ||
|
|
92b20efa89 | ||
|
|
2849177998 | ||
|
|
0bbeedb7e3 | ||
|
|
918f22f7a6 | ||
|
|
ecac9b772b | ||
|
|
f0a4469be5 | ||
|
|
f47f4983d9 | ||
|
|
c9b8735804 | ||
|
|
c9d7647200 | ||
|
|
4a8410f0fc | ||
|
|
4fbb383c0f | ||
|
|
98361ff096 | ||
|
|
31f2ca49d5 | ||
|
|
e0e6f86801 | ||
|
|
0bc4e1fa7b | ||
|
|
0d137a372c | ||
|
|
2d1ad75f35 | ||
|
|
e381e021d6 | ||
|
|
59f0813aa5 | ||
|
|
90d14c544f | ||
|
|
02ca7acf4f | ||
|
|
0725b55773 | ||
|
|
1219c0aed0 | ||
|
|
da57b9c7e1 | ||
|
|
e67822167b | ||
|
|
20e3729b86 | ||
|
|
5c3d88dae0 | ||
|
|
2fb86b11ec | ||
|
|
5d9c504842 | ||
|
|
1610968ed7 | ||
|
|
aad4aafc03 | ||
|
|
40e3987ae6 | ||
|
|
d3a77e4a11 | ||
|
|
c93189ff9a | ||
|
|
bca7b3386c | ||
|
|
3c7cc0b09c | ||
|
|
8e35b67a0d | ||
|
|
bfb292c54d | ||
|
|
ab6c775f5c | ||
|
|
e3e9e4596a | ||
|
|
fefe77c245 | ||
|
|
42108cb223 | ||
|
|
72d7a245d2 | ||
|
|
e187a665d5 | ||
|
|
01ab23dc82 | ||
|
|
004814ace7 | ||
|
|
326a38557b | ||
|
|
6e43ed5fce | ||
|
|
8b92610f67 | ||
|
|
6cc919b62d | ||
|
|
3ff47580ad | ||
|
|
a1a2120d43 | ||
|
|
273222bb21 | ||
|
|
1255e5a6c9 | ||
|
|
dace366b6c | ||
|
|
a2f5414c48 | ||
|
|
1887527197 | ||
|
|
6d35c2f1c1 | ||
|
|
51626698c4 | ||
|
|
5ed2b23f76 | ||
|
|
502b5acb69 | ||
|
|
03b5257fea | ||
|
|
39b158f92d | ||
|
|
3163136b73 | ||
|
|
9791444167 | ||
|
|
77acf95106 | ||
|
|
6505cc99ce | ||
|
|
54d1a40eeb | ||
|
|
ab577a95eb | ||
|
|
8537d4acef | ||
|
|
d0fda8a546 | ||
|
|
031f69523d | ||
|
|
3864f29bbd | ||
|
|
ee1b32fcd7 | ||
|
|
6ad6b1680f | ||
|
|
b158d79403 | ||
|
|
2d823e3321 | ||
|
|
9a153356c2 | ||
|
|
eaf5e6ff29 | ||
|
|
585a02a3d2 | ||
|
|
30acd27c94 | ||
|
|
71db398e9a | ||
|
|
d8f44e2c94 | ||
|
|
20c8cb12e3 | ||
|
|
60eadb7035 | ||
|
|
17e71c5c9a | ||
|
|
289c0f72dd | ||
|
|
c2a326d538 | ||
|
|
438bb8cb0d | ||
|
|
b98f8e3b07 | ||
|
|
333206c549 | ||
|
|
5359ec34ef | ||
|
|
ab7da85964 | ||
|
|
7ecb215957 | ||
|
|
df89839b51 | ||
|
|
03ed40df67 | ||
|
|
ad1429b4a2 | ||
|
|
eb03cd6814 | ||
|
|
566846fe4d | ||
|
|
f4c09fefd9 | ||
|
|
bd6e632055 | ||
|
|
75ddc058b8 | ||
|
|
8558ce432a | ||
|
|
7586084980 | ||
|
|
863de715a4 | ||
|
|
04abf47653 | ||
|
|
45c9a8ba27 | ||
|
|
3d10521524 | ||
|
|
ef616f37bd | ||
|
|
e64c36ffce | ||
|
|
837d925536 | ||
|
|
feaeb90f76 | ||
|
|
2ea94a12ad | ||
|
|
c8cf8238b8 | ||
|
|
16f41f74b3 | ||
|
|
3a78eea105 | ||
|
|
8e158972f4 | ||
|
|
b12a1306eb | ||
|
|
1175c8b605 | ||
|
|
4b06ef3ac0 | ||
|
|
5f59f485f2 | ||
|
|
2f28015b91 | ||
|
|
675aff612d | ||
|
|
9ad65af60e | ||
|
|
2210c05e28 | ||
|
|
a189139a04 | ||
|
|
c6a209c63a | ||
|
|
1968d8493b | ||
|
|
a94eb0cd31 | ||
|
|
b2bbf31f46 | ||
|
|
2b959c10dd | ||
|
|
81669635c5 | ||
|
|
7350a2a811 | ||
|
|
e677cbcdac | ||
|
|
6d7461d0d1 | ||
|
|
86d94166f0 | ||
|
|
a664545ebf | ||
|
|
360bd4192b | ||
|
|
da3f2b8ee4 | ||
|
|
78aa411e09 | ||
|
|
2d318c0ac1 | ||
|
|
b58f598044 | ||
|
|
bad2a19be6 | ||
|
|
a89bdab5e3 | ||
|
|
5b2c03f733 | ||
|
|
552e10d79d | ||
|
|
40f39fa21a | ||
|
|
d5c1833638 | ||
|
|
36dd98c93e | ||
|
|
ccbec7f5ac | ||
|
|
0359e89b22 | ||
|
|
e9f8634334 | ||
|
|
1889c0c4f9 | ||
|
|
1ac501db79 | ||
|
|
c9ddd4f997 | ||
|
|
e8a0d7c47c | ||
|
|
8ace53bfe2 | ||
|
|
89c3490015 | ||
|
|
72bc2f438a | ||
|
|
1183459608 | ||
|
|
a4e6c7b746 | ||
|
|
b99fb6e8f6 | ||
|
|
f613a60f7c | ||
|
|
7c77cde669 | ||
|
|
9a7bde4cbd | ||
|
|
b807a71f1f | ||
|
|
33942236f0 | ||
|
|
3dd3301ffc | ||
|
|
0d310d317e | ||
|
|
82515e5731 | ||
|
|
4cd4707559 | ||
|
|
90c14861c4 | ||
|
|
17a51501ec | ||
|
|
86707f18dd | ||
|
|
a28cd746f1 | ||
|
|
10ad995cd0 | ||
|
|
856c5f50c4 | ||
|
|
0a9d96d964 | ||
|
|
1d15921a57 | ||
|
|
b9826cb29f | ||
|
|
94c13bbbdb | ||
|
|
549f9c9045 | ||
|
|
ac16521404 | ||
|
|
4be298f537 | ||
|
|
10dead9d9f | ||
|
|
f9cb601a99 | ||
|
|
efdd3b0056 | ||
|
|
a18f550dc1 | ||
|
|
d808b9fef7 | ||
|
|
f4742259f2 | ||
|
|
3fe5da0ee1 | ||
|
|
17ae3a9a39 | ||
|
|
1ff588a15a | ||
|
|
2bb04ef298 | ||
|
|
055dda909e | ||
|
|
f364709318 | ||
|
|
84de1c0198 | ||
|
|
413017d333 | ||
|
|
5c069e5413 | ||
|
|
9d9cdb0f95 | ||
|
|
74f45e2e54 | ||
|
|
f82c48ad29 | ||
|
|
ece052a20d | ||
|
|
8a9a36ed10 | ||
|
|
82f2a2c363 | ||
|
|
3016659c81 | ||
|
|
b9ab0f14ab | ||
|
|
2d90ed7414 | ||
|
|
5d87f05994 | ||
|
|
9ef183bcaa | ||
|
|
34f18fbd7d | ||
|
|
27fa2bbc54 | ||
|
|
03a88327a5 | ||
|
|
67ac21e688 | ||
|
|
e90f32ca32 | ||
|
|
b21e65027c | ||
|
|
b628532def | ||
|
|
e0b2755b7c | ||
|
|
9e1fbb9852 | ||
|
|
047ca3e203 | ||
|
|
bfcd5357e3 | ||
|
|
461ed6b81f | ||
|
|
ee2969fc5b | ||
|
|
ca32ebd9cb | ||
|
|
4a29617fed | ||
|
|
8eab97a92a | ||
|
|
f453f69cce | ||
|
|
8ca84147e8 | ||
|
|
5e3ddd83fc | ||
|
|
6b985463d8 | ||
|
|
25e0ae2670 | ||
|
|
614c6fbdba | ||
|
|
8efe415558 | ||
|
|
f3ad2a3709 | ||
|
|
25dd71ba04 | ||
|
|
a514e6e168 | ||
|
|
cb5553ea94 | ||
|
|
335ee31c7c | ||
|
|
9b4379727d | ||
|
|
a5fe2f59be | ||
|
|
1b58f7854e | ||
|
|
b6effa468f | ||
|
|
2b512b88c4 | ||
|
|
5cb61cba90 | ||
|
|
0657efe239 |
@@ -1,29 +0,0 @@
|
||||
{
|
||||
"image": "ghcr.io/juliushaertl/nextcloud-dev-php80:latest",
|
||||
"forwardPorts": [80],
|
||||
"containerEnv": {
|
||||
"NEXTCLOUD_AUTOINSTALL_APPS": "deck",
|
||||
"XDEBUG_MODE": "debug"
|
||||
},
|
||||
"customizations": {
|
||||
"vscode": {
|
||||
"extensions": [
|
||||
"felixfbecker.php-intellisense",
|
||||
"octref.vetur"
|
||||
],
|
||||
"settings": {
|
||||
"php.suggest.basic": false,
|
||||
"git.alwaysSignOff": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"workspaceMount": "source=${localWorkspaceFolder},target=/var/www/html/apps-extra/deck,type=bind",
|
||||
"workspaceFolder": "/var/www/html/apps-extra/deck",
|
||||
"overrideCommand": true,
|
||||
"postAttachCommand": "bash ./.devcontainer/setup.sh",
|
||||
"portsAttributes": {
|
||||
"80": {
|
||||
"label": "Webserver"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
(
|
||||
cd /tmp && /usr/local/bin/bootstrap.sh apache2ctl start
|
||||
)
|
||||
|
||||
composer install --no-dev
|
||||
npm ci
|
||||
npm run dev
|
||||
@@ -3,10 +3,7 @@ root = true
|
||||
[*]
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
indent_size = tab
|
||||
indent_style = tab
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.{js,vue}]
|
||||
indent_style = tab
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
extends: [
|
||||
'@nextcloud',
|
||||
],
|
||||
@@ -8,7 +7,6 @@ module.exports = {
|
||||
'jsdoc/require-param-type': ['off'],
|
||||
'jsdoc/check-param-names': ['off'],
|
||||
'jsdoc/no-undefined-types': ['off'],
|
||||
'jsdoc/require-property-description': ['off'],
|
||||
'import/no-named-as-default-member': ['off'],
|
||||
'jsdoc/require-property-description' : ['off']
|
||||
},
|
||||
}
|
||||
|
||||
37
.github/dependabot.yml
vendored
@@ -2,7 +2,7 @@ version: 2
|
||||
updates:
|
||||
- package-ecosystem: npm
|
||||
directory: "/"
|
||||
target-branch: "main"
|
||||
target-branch: "master"
|
||||
schedule:
|
||||
interval: weekly
|
||||
day: saturday
|
||||
@@ -11,41 +11,6 @@ updates:
|
||||
open-pull-requests-limit: 10
|
||||
reviewers:
|
||||
- juliushaertl
|
||||
|
||||
- package-ecosystem: npm
|
||||
target-branch: stable25
|
||||
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: stable24
|
||||
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:
|
||||
|
||||
2
.github/pull_request_template.md
vendored
@@ -1,6 +1,6 @@
|
||||
|
||||
* Resolves: # <!-- related github issue -->
|
||||
* Target version: main
|
||||
* Target version: master
|
||||
|
||||
### Summary
|
||||
|
||||
|
||||
14
.github/workflows/appbuild.yml
vendored
@@ -1,11 +1,7 @@
|
||||
name: Package build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- master
|
||||
- stable*
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
@@ -13,7 +9,7 @@ jobs:
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [16.x]
|
||||
node-version: [14.x]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
@@ -24,14 +20,14 @@ jobs:
|
||||
- name: Set up npm7
|
||||
run: npm i -g npm@7
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@2.25.1
|
||||
uses: shivammathur/setup-php@2.18.0
|
||||
with:
|
||||
php-version: '7.4'
|
||||
tools: composer
|
||||
- name: install dependencies
|
||||
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
|
||||
wget https://github.com/ChristophWurst/krankerl/releases/download/v0.12.2/krankerl_0.12.2_amd64.deb
|
||||
sudo dpkg -i krankerl_0.12.2_amd64.deb
|
||||
- name: package
|
||||
run: |
|
||||
uname -a
|
||||
|
||||
53
.github/workflows/appstore-build-publish.yml
vendored
@@ -10,7 +10,7 @@ on:
|
||||
types: [published]
|
||||
|
||||
env:
|
||||
PHP_VERSION: 8.1
|
||||
PHP_VERSION: 7.4
|
||||
|
||||
jobs:
|
||||
build_and_publish:
|
||||
@@ -21,42 +21,42 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Check actor permission
|
||||
uses: skjnldsv/check-actor-permission@e591dbfe838300c007028e1219ca82cc26e8d7c5 # v2.1
|
||||
uses: skjnldsv/check-actor-permission@v2
|
||||
with:
|
||||
require: write
|
||||
|
||||
- name: Set app env
|
||||
run: |
|
||||
# Split and keep last
|
||||
# Split and keep last
|
||||
echo "APP_NAME=${GITHUB_REPOSITORY##*/}" >> $GITHUB_ENV
|
||||
echo "APP_VERSION=${GITHUB_REF##*/}" >> $GITHUB_ENV
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
path: ${{ env.APP_NAME }}
|
||||
|
||||
- name: Get appinfo data
|
||||
id: appinfo
|
||||
uses: skjnldsv/xpath-action@7e6a7c379d0e9abc8acaef43df403ab4fc4f770c # master
|
||||
uses: skjnldsv/xpath-action@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@0ce2ed60f6df073a62a77c0a4958dd0fc68e32e7 # v2.1
|
||||
uses: skjnldsv/read-package-engines-version-actions@v1.2
|
||||
id: versions
|
||||
# Continue if no package.json
|
||||
continue-on-error: true
|
||||
with:
|
||||
path: ${{ env.APP_NAME }}
|
||||
fallbackNode: "^16"
|
||||
fallbackNpm: "^7"
|
||||
fallbackNode: "^12"
|
||||
fallbackNpm: "^6"
|
||||
|
||||
- name: Set up node ${{ steps.versions.outputs.nodeVersion }}
|
||||
# Skip if no package.json
|
||||
if: ${{ steps.versions.outputs.nodeVersion }}
|
||||
uses: actions/setup-node@8c91899e586c5b171469028077307d293428b516 # v3
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ steps.versions.outputs.nodeVersion }}
|
||||
|
||||
@@ -66,16 +66,14 @@ jobs:
|
||||
run: npm i -g npm@"${{ steps.versions.outputs.npmVersion }}"
|
||||
|
||||
- name: Set up php ${{ env.PHP_VERSION }}
|
||||
uses: shivammathur/setup-php@2.25.1 # v2
|
||||
uses: shivammathur/setup-php@2.18.0
|
||||
with:
|
||||
php-version: ${{ env.PHP_VERSION }}
|
||||
coverage: none
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Check composer.json
|
||||
id: check_composer
|
||||
uses: andstor/file-existence-action@20b4d2e596410855db8f9ca21e96fbe18e12930b # v2
|
||||
uses: andstor/file-existence-action@v1
|
||||
with:
|
||||
files: "${{ env.APP_NAME }}/composer.json"
|
||||
|
||||
@@ -93,29 +91,16 @@ jobs:
|
||||
npm ci
|
||||
npm run build
|
||||
|
||||
- name: Check Krankerl config
|
||||
id: krankerl
|
||||
uses: andstor/file-existence-action@20b4d2e596410855db8f9ca21e96fbe18e12930b # v2
|
||||
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
|
||||
wget https://github.com/ChristophWurst/krankerl/releases/download/v0.13.0/krankerl_0.13.0_amd64.deb
|
||||
sudo dpkg -i krankerl_0.13.0_amd64.deb
|
||||
|
||||
- name: Package ${{ env.APP_NAME }} ${{ env.APP_VERSION }} with krankerl
|
||||
if: steps.krankerl.outputs.files_exists == 'true'
|
||||
- name: Package ${{ env.APP_NAME }} ${{ env.APP_VERSION }}
|
||||
# Try krankerl, fallback to makefile
|
||||
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
|
||||
krankerl package || make appstore
|
||||
|
||||
- name: Checkout server ${{ fromJSON(steps.appinfo.outputs.result).nextcloud.min-version }}
|
||||
continue-on-error: true
|
||||
@@ -126,7 +111,7 @@ jobs:
|
||||
unzip latest-$NCVERSION.zip
|
||||
|
||||
- name: Checkout server master fallback
|
||||
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3
|
||||
uses: actions/checkout@v3
|
||||
if: ${{ steps.server-checkout.outcome != 'success' }}
|
||||
with:
|
||||
repository: nextcloud/server
|
||||
@@ -148,7 +133,7 @@ jobs:
|
||||
tar -zcvf ${{ env.APP_NAME }}.tar.gz ${{ env.APP_NAME }}
|
||||
|
||||
- name: Attach tarball to github release
|
||||
uses: svenstaro/upload-release-action@7319e4733ec7a184d739a6f412c40ffc339b69c7 # v2
|
||||
uses: svenstaro/upload-release-action@v2
|
||||
id: attach_to_release
|
||||
with:
|
||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -158,7 +143,7 @@ jobs:
|
||||
overwrite: true
|
||||
|
||||
- name: Upload app to Nextcloud appstore
|
||||
uses: nextcloud-releases/nextcloud-appstore-push-action@a011fe619bcf6e77ddebc96f9908e1af4071b9c1 # v1
|
||||
uses: nextcloud-releases/nextcloud-appstore-push-action@v1
|
||||
with:
|
||||
app_name: ${{ env.APP_NAME }}
|
||||
appstore_token: ${{ secrets.APPSTORE_TOKEN }}
|
||||
|
||||
13
.github/workflows/command-rebase.yml
vendored
@@ -9,21 +9,16 @@ on:
|
||||
issue_comment:
|
||||
types: created
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
rebase:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: none
|
||||
|
||||
# On pull requests and if the comment starts with `/rebase`
|
||||
if: github.event.issue.pull_request != '' && startsWith(github.event.comment.body, '/rebase')
|
||||
|
||||
steps:
|
||||
- name: Add reaction on start
|
||||
uses: peter-evans/create-or-update-comment@ca08ebd5dc95aa0cd97021e9708fcd6b87138c9b # v3.0.1
|
||||
uses: peter-evans/create-or-update-comment@v2
|
||||
with:
|
||||
token: ${{ secrets.COMMAND_BOT_PAT }}
|
||||
repository: ${{ github.event.repository.full_name }}
|
||||
@@ -31,18 +26,18 @@ jobs:
|
||||
reaction-type: "+1"
|
||||
|
||||
- name: Checkout the latest code
|
||||
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
token: ${{ secrets.COMMAND_BOT_PAT }}
|
||||
|
||||
- name: Automatic Rebase
|
||||
uses: cirrus-actions/rebase@6e572f08c244e2f04f9beb85a943eb618218714d # 1.7
|
||||
uses: cirrus-actions/rebase@1.5
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.COMMAND_BOT_PAT }}
|
||||
|
||||
- name: Add reaction on failure
|
||||
uses: peter-evans/create-or-update-comment@ca08ebd5dc95aa0cd97021e9708fcd6b87138c9b # v3.0.1
|
||||
uses: peter-evans/create-or-update-comment@v2
|
||||
if: failure()
|
||||
with:
|
||||
token: ${{ secrets.COMMAND_BOT_PAT }}
|
||||
|
||||
124
.github/workflows/cypress.yml
vendored
@@ -1,124 +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: [14.x]
|
||||
# containers: [1, 2, 3]
|
||||
php-versions: [ '8.0' ]
|
||||
databases: [ 'sqlite' ]
|
||||
server-versions: [ 'stable27' ]
|
||||
|
||||
steps:
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
- name: Set up npm7
|
||||
run: npm i -g npm@7
|
||||
|
||||
- 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@v3
|
||||
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@v3
|
||||
with:
|
||||
path: apps/${{ env.APP_NAME }}
|
||||
|
||||
- name: Checkout text
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
repository: nextcloud/text
|
||||
ref: ${{ env.text_app_ref }}
|
||||
path: apps/text
|
||||
|
||||
- name: Set up php ${{ matrix.php-versions }}
|
||||
uses: shivammathur/setup-php@2.25.1
|
||||
with:
|
||||
php-version: ${{ matrix.php-versions }}
|
||||
extensions: mbstring, iconv, fileinfo, intl, sqlite, pdo_sqlite, zip, gd, apcu
|
||||
ini-values:
|
||||
apc.enable_cli=on
|
||||
coverage: none
|
||||
|
||||
- name: Set up Nextcloud
|
||||
env:
|
||||
DB_PORT: 4444
|
||||
PHP_CLI_SERVER_WORKERS: 10
|
||||
run: |
|
||||
mkdir data
|
||||
php 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
|
||||
php occ config:system:set memcache.local --value="\\OC\\Memcache\\APCu"
|
||||
php occ config:system:set debug --value=true --type=boolean
|
||||
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
|
||||
cd apps/deck
|
||||
composer install --no-dev
|
||||
npm ci
|
||||
npm run build
|
||||
cd ../../
|
||||
curl -v http://localhost:8081/index.php/login
|
||||
|
||||
- name: Cypress run
|
||||
uses: cypress-io/github-action@v5
|
||||
with:
|
||||
record: true
|
||||
parallel: false
|
||||
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@v3
|
||||
if: failure()
|
||||
with:
|
||||
name: Upload screenshots
|
||||
path: apps/${{ env.APP_NAME }}/cypress/screenshots/
|
||||
retention-days: 5
|
||||
|
||||
- name: Upload nextcloud logs
|
||||
uses: actions/upload-artifact@v3
|
||||
if: failure()
|
||||
with:
|
||||
name: Upload nextcloud log
|
||||
path: data/nextcloud.log
|
||||
retention-days: 5
|
||||
15
.github/workflows/dependabot-approve-merge.yml
vendored
@@ -8,33 +8,22 @@ name: Dependabot
|
||||
on:
|
||||
pull_request_target:
|
||||
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.actor == 'dependabot[bot]'
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
# for hmarr/auto-approve-action to approve PRs
|
||||
pull-requests: write
|
||||
|
||||
steps:
|
||||
# Github actions bot approve
|
||||
- uses: hmarr/auto-approve-action@b40d6c9ed2fa10c9a2749eca7eb004418a705501 # v2
|
||||
- uses: hmarr/auto-approve-action@v2
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
# Nextcloud bot approve and merge request
|
||||
- uses: ahmadnassri/action-dependabot-auto-merge@45fc124d949b19b6b8bf6645b6c9d55f4f9ac61a # v2
|
||||
- uses: ahmadnassri/action-dependabot-auto-merge@v2
|
||||
with:
|
||||
target: minor
|
||||
github-token: ${{ secrets.DEPENDABOT_AUTOMERGE_TOKEN }}
|
||||
|
||||
31
.github/workflows/fixup.yml
vendored
@@ -5,29 +5,16 @@
|
||||
|
||||
name: Pull request checks
|
||||
|
||||
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
|
||||
on: pull_request
|
||||
|
||||
jobs:
|
||||
commit-message-check:
|
||||
if: github.event.pull_request.draft == false
|
||||
commit-message-check:
|
||||
name: Block fixup and squash commits
|
||||
|
||||
permissions:
|
||||
pull-requests: write
|
||||
name: Block fixup and squash commits
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Run check
|
||||
uses: xt0rted/block-autosquash-commits-action@79880c36b4811fe549cfffe20233df88876024e7 # v2
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
steps:
|
||||
- name: Run check
|
||||
uses: xt0rted/block-autosquash-commits-action@v2
|
||||
with:
|
||||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
78
.github/workflows/integration.yml
vendored
@@ -2,17 +2,8 @@ name: Integration tests
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- '.github/workflows/integration.yml'
|
||||
- 'appinfo/**'
|
||||
- 'lib/**'
|
||||
- 'templates/**'
|
||||
- 'tests/**'
|
||||
- 'composer.json'
|
||||
- 'composer.lock'
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- master
|
||||
- stable*
|
||||
|
||||
@@ -26,9 +17,9 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
php-versions: ['8.1']
|
||||
php-versions: ['7.4']
|
||||
databases: ['sqlite', 'mysql', 'pgsql']
|
||||
server-versions: ['stable27']
|
||||
server-versions: ['stable24']
|
||||
|
||||
name: php${{ matrix.php-versions }}-${{ matrix.databases }}-${{ matrix.server-versions }}
|
||||
|
||||
@@ -63,7 +54,7 @@ jobs:
|
||||
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
|
||||
cd build/integration && composer require --dev phpunit/phpunit:~8
|
||||
|
||||
- name: Checkout app
|
||||
uses: actions/checkout@v3
|
||||
@@ -71,17 +62,16 @@ jobs:
|
||||
path: apps/${{ env.APP_NAME }}
|
||||
|
||||
- name: Set up php ${{ matrix.php-versions }}
|
||||
uses: shivammathur/setup-php@2.25.1
|
||||
uses: shivammathur/setup-php@2.18.0
|
||||
with:
|
||||
php-version: ${{ matrix.php-versions }}
|
||||
extensions: mbstring, iconv, fileinfo, intl, sqlite, pdo_sqlite, mysql, pdo_mysql, pgsql, pdo_pgsql, apcu
|
||||
ini-values:
|
||||
apc.enable_cli=on
|
||||
tools: phpunit
|
||||
extensions: mbstring, iconv, fileinfo, intl, sqlite, pdo_sqlite, mysql, pdo_mysql, pgsql, pdo_pgsql,
|
||||
coverage: none
|
||||
|
||||
- name: Set up dependencies
|
||||
- name: Set up PHPUnit
|
||||
working-directory: apps/${{ env.APP_NAME }}
|
||||
run: composer i --no-dev
|
||||
run: composer i
|
||||
|
||||
- name: Set up Nextcloud
|
||||
run: |
|
||||
@@ -92,63 +82,11 @@ jobs:
|
||||
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 '/home/runner/work/${{ env.APP_NAME }}/${{ env.APP_NAME }}/query.log'
|
||||
php -S localhost:8080 &
|
||||
|
||||
- name: Run behat
|
||||
working-directory: apps/${{ env.APP_NAME }}/tests/integration
|
||||
run: ./run.sh
|
||||
|
||||
- name: Query count
|
||||
if: ${{ matrix.databases == 'mysql' }}
|
||||
uses: actions/github-script@v6
|
||||
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 /home/runner/work/${{ env.APP_NAME }}/${{ env.APP_NAME }}/query.log | wc -l"`, [], options)
|
||||
msg = myOutput
|
||||
const queryCount = parseInt(myOutput, 10)
|
||||
|
||||
myOutput = ''
|
||||
await exec.exec('cat', ['/home/runner/work/${{ env.APP_NAME }}/${{ env.APP_NAME }}/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
|
||||
})
|
||||
}
|
||||
|
||||
62
.github/workflows/lint-eslint.yml
vendored
@@ -1,62 +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
|
||||
#
|
||||
# Use lint-eslint together with lint-eslint-when-unrelated to make eslint a required check for GitHub actions
|
||||
# https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/defining-the-mergeability-of-pull-requests/troubleshooting-required-status-checks#handling-skipped-but-required-checks
|
||||
|
||||
name: Lint
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- '.github/workflows/**'
|
||||
- 'src/**'
|
||||
- 'appinfo/info.xml'
|
||||
- 'package.json'
|
||||
- 'package-lock.json'
|
||||
- 'tsconfig.json'
|
||||
- '.eslintrc.*'
|
||||
- '.eslintignore'
|
||||
- '**.js'
|
||||
- '**.ts'
|
||||
- '**.vue'
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: lint-eslint-${{ github.head_ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
name: eslint
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3
|
||||
|
||||
- name: Read package.json node and npm engines version
|
||||
uses: skjnldsv/read-package-engines-version-actions@0ce2ed60f6df073a62a77c0a4958dd0fc68e32e7 # v2.1
|
||||
id: versions
|
||||
with:
|
||||
fallbackNode: '^16'
|
||||
fallbackNpm: '^7'
|
||||
|
||||
- name: Set up node ${{ steps.versions.outputs.nodeVersion }}
|
||||
uses: actions/setup-node@8c91899e586c5b171469028077307d293428b516 # v3
|
||||
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
|
||||
run: npm ci
|
||||
|
||||
- name: Lint
|
||||
run: npm run lint
|
||||
39
.github/workflows/lint-php-cs.yml
vendored
@@ -1,39 +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
|
||||
|
||||
name: Lint
|
||||
|
||||
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@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3
|
||||
|
||||
- name: Set up php
|
||||
uses: shivammathur/setup-php@2.25.1 # v2
|
||||
with:
|
||||
php-version: 8.1
|
||||
coverage: none
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Install dependencies
|
||||
run: composer i
|
||||
|
||||
- name: Lint
|
||||
run: composer run cs:check || ( echo 'Please run `composer run cs:fix` to format your code' && exit 1 )
|
||||
59
.github/workflows/lint-php.yml
vendored
@@ -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
|
||||
|
||||
name: Lint
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- master
|
||||
- stable*
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: lint-php-${{ github.head_ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
php-lint:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
php-versions: [ "8.0", "8.1", "8.2" ]
|
||||
|
||||
name: php-lint
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3
|
||||
|
||||
- name: Set up php ${{ matrix.php-versions }}
|
||||
uses: shivammathur/setup-php@2.25.1 # v2
|
||||
with:
|
||||
php-version: ${{ matrix.php-versions }}
|
||||
coverage: none
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Lint
|
||||
run: composer run lint
|
||||
|
||||
summary:
|
||||
permissions:
|
||||
contents: none
|
||||
runs-on: ubuntu-latest
|
||||
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
|
||||
46
.github/workflows/lint-stylelint.yml
vendored
@@ -1,46 +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
|
||||
|
||||
name: Lint
|
||||
|
||||
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@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3
|
||||
|
||||
- name: Read package.json node and npm engines version
|
||||
uses: skjnldsv/read-package-engines-version-actions@0ce2ed60f6df073a62a77c0a4958dd0fc68e32e7 # v2.1
|
||||
id: versions
|
||||
with:
|
||||
fallbackNode: '^16'
|
||||
fallbackNpm: '^7'
|
||||
|
||||
- name: Set up node ${{ steps.versions.outputs.nodeVersion }}
|
||||
uses: actions/setup-node@8c91899e586c5b171469028077307d293428b516 # v3
|
||||
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
|
||||
run: npm ci
|
||||
|
||||
- name: Lint
|
||||
run: npm run stylelint
|
||||
88
.github/workflows/lint.yml
vendored
Normal file
@@ -0,0 +1,88 @@
|
||||
name: Lint
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- stable*
|
||||
|
||||
jobs:
|
||||
php:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
php-versions: ['7.4', '8.0', '8.1']
|
||||
|
||||
name: php${{ matrix.php-versions }} lint
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Set up php${{ matrix.php-versions }}
|
||||
uses: shivammathur/setup-php@2.18.0
|
||||
with:
|
||||
php-version: ${{ matrix.php-versions }}
|
||||
coverage: none
|
||||
- name: Lint
|
||||
run: composer run lint
|
||||
|
||||
php-cs-fixer:
|
||||
name: php-cs check
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
- name: Set up php
|
||||
uses: shivammathur/setup-php@2.18.0
|
||||
with:
|
||||
php-version: 7.4
|
||||
coverage: none
|
||||
- name: Install dependencies
|
||||
run: composer i
|
||||
- name: Run coding standards check
|
||||
run: composer run cs:check
|
||||
|
||||
node:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [14.x]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Use node ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
- name: Set up npm7
|
||||
run: npm i -g npm@7
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
- name: ESLint
|
||||
run: npm run lint
|
||||
|
||||
stylelint:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
node-version: [14.x]
|
||||
|
||||
name: stylelint node${{ matrix.node-version }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Set up node ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
|
||||
- name: Set up npm7
|
||||
run: npm i -g npm@7
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Lint
|
||||
run: npm run stylelint
|
||||
6
.github/workflows/nightly.yml
vendored
@@ -25,7 +25,7 @@ jobs:
|
||||
- name: Set up npm7
|
||||
run: npm i -g npm@7
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@2.25.1
|
||||
uses: shivammathur/setup-php@2.18.0
|
||||
with:
|
||||
php-version: '7.4'
|
||||
tools: composer
|
||||
@@ -44,14 +44,14 @@ jobs:
|
||||
git config --local user.name "GitHub Action"
|
||||
git tag -f nightly
|
||||
- name: Push tag
|
||||
uses: juliushaertl/github-push-action@main
|
||||
uses: juliushaertl/github-push-action@master
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
tags: true
|
||||
force: true
|
||||
- name: Create Release
|
||||
id: create_release
|
||||
uses: juliushaertl/action-release@main
|
||||
uses: juliushaertl/action-release@master
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
tag: nightly
|
||||
|
||||
71
.github/workflows/npm-audit-fix.yml
vendored
@@ -1,71 +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
|
||||
|
||||
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", "stable26", "stable25", "stable24"]
|
||||
|
||||
name: npm-audit-fix-${{ matrix.branches }}
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3
|
||||
with:
|
||||
ref: ${{ matrix.branches }}
|
||||
|
||||
- name: Read package.json node and npm engines version
|
||||
uses: skjnldsv/read-package-engines-version-actions@0ce2ed60f6df073a62a77c0a4958dd0fc68e32e7 # v2.1
|
||||
id: versions
|
||||
with:
|
||||
fallbackNode: '^16'
|
||||
fallbackNpm: '^7'
|
||||
|
||||
- name: Set up node ${{ steps.versions.outputs.nodeVersion }}
|
||||
uses: actions/setup-node@8c91899e586c5b171469028077307d293428b516 # v3
|
||||
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
|
||||
run: |
|
||||
npm audit fix
|
||||
|
||||
- name: Run npm ci and npm run build
|
||||
if: always()
|
||||
run: |
|
||||
npm ci
|
||||
npm run build --if-present
|
||||
|
||||
- name: Create Pull Request
|
||||
if: always()
|
||||
uses: peter-evans/create-pull-request@284f54f989303d2699d373481a0cfa13ad5a6666 # v3
|
||||
with:
|
||||
token: ${{ secrets.COMMAND_BOT_PAT }}
|
||||
commit-message: "chore(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: |
|
||||
Auto-generated fix of npm audit
|
||||
labels: |
|
||||
dependencies
|
||||
3. to review
|
||||
14
.github/workflows/phpunit.yml
vendored
@@ -2,14 +2,6 @@ name: PHPUnit
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- '.github/workflows/phpunit.yml'
|
||||
- 'appinfo/**'
|
||||
- 'lib/**'
|
||||
- 'templates/**'
|
||||
- 'tests/**'
|
||||
- 'composer.json'
|
||||
- 'composer.lock'
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
@@ -26,9 +18,9 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
php-versions: ['8.0', '8.1']
|
||||
php-versions: ['7.4', '8.0', '8.1']
|
||||
databases: ['sqlite', 'mysql', 'pgsql']
|
||||
server-versions: ['stable27']
|
||||
server-versions: ['stable24']
|
||||
|
||||
name: php${{ matrix.php-versions }}-${{ matrix.databases }}-${{ matrix.server-versions }}
|
||||
|
||||
@@ -70,7 +62,7 @@ jobs:
|
||||
path: apps/${{ env.APP_NAME }}
|
||||
|
||||
- name: Set up php ${{ matrix.php-versions }}
|
||||
uses: shivammathur/setup-php@2.25.1
|
||||
uses: shivammathur/setup-php@2.18.0
|
||||
with:
|
||||
php-version: ${{ matrix.php-versions }}
|
||||
tools: phpunit
|
||||
|
||||
12
.github/workflows/psalm.yml
vendored
@@ -13,10 +13,6 @@ on:
|
||||
- main
|
||||
- stable*
|
||||
|
||||
concurrency:
|
||||
group: psalm-${{ github.head_ref || github.run_id }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
static-analysis:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -24,15 +20,13 @@ jobs:
|
||||
name: Nextcloud
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Set up php
|
||||
uses: shivammathur/setup-php@2.25.1 # v2
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: 8.1
|
||||
php-version: 7.4
|
||||
coverage: none
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Install dependencies
|
||||
run: composer i
|
||||
|
||||
19
.github/workflows/update-nextcloud-ocp.yml
vendored
@@ -17,24 +17,22 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
branches: ["master", "stable26", "stable25", "stable24"]
|
||||
branches: ["master", "stable25", "stable24", "stable23"]
|
||||
|
||||
name: update-nextcloud-ocp-${{ matrix.branches }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
ref: ${{ matrix.branches }}
|
||||
submodules: true
|
||||
|
||||
- name: Set up php8.1
|
||||
uses: shivammathur/setup-php@2.25.1 # v2
|
||||
- name: Set up php7.4
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: 8.1
|
||||
php-version: 7.4
|
||||
extensions: ctype,curl,dom,fileinfo,gd,intl,json,mbstring,openssl,pdo_sqlite,posix,sqlite,xml,zip
|
||||
coverage: none
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Composer install
|
||||
run: composer install
|
||||
@@ -47,15 +45,14 @@ jobs:
|
||||
run: |
|
||||
git clean -f 3rdparty
|
||||
git clean -f vendor
|
||||
git clean -f vendor-bin
|
||||
git checkout 3rdparty vendor vendor-bin
|
||||
git checkout 3rdparty vendor
|
||||
continue-on-error: true
|
||||
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@284f54f989303d2699d373481a0cfa13ad5a6666 # v3
|
||||
uses: peter-evans/create-pull-request@v3
|
||||
with:
|
||||
token: ${{ secrets.COMMAND_BOT_PAT }}
|
||||
commit-message: "chore(dev-deps): Bump nextcloud/ocp package"
|
||||
commit-message: Update psalm baseline
|
||||
committer: GitHub <noreply@github.com>
|
||||
author: nextcloud-command <nextcloud-command@users.noreply.github.com>
|
||||
signoff: true
|
||||
|
||||
2
.gitignore
vendored
@@ -3,11 +3,9 @@ js/
|
||||
build/
|
||||
css/style.css
|
||||
css/vendor.css
|
||||
cypress/videos/
|
||||
tests/integration/vendor/
|
||||
tests/integration/composer.lock
|
||||
tests/.phpunit.result.cache
|
||||
vendor/
|
||||
.php_cs.cache
|
||||
\.idea/
|
||||
settings.json
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[main]
|
||||
host = https://www.transifex.com
|
||||
lang_map = hu_HU: hu, nb_NO: nb, sk_SK: sk, th_TH: th, ja_JP: ja, bg_BG: bg, cs_CZ: cs, fi_FI: fi
|
||||
lang_map = bg_BG: bg, cs_CZ: cs, fi_FI: fi, hu_HU: hu, nb_NO: nb, sk_SK: sk, th_TH: th, ja_JP: ja
|
||||
|
||||
[o:nextcloud:p:nextcloud:r:deck]
|
||||
file_filter = translationfiles/<lang>/deck.po
|
||||
|
||||
126
CHANGELOG.md
@@ -1,115 +1,51 @@
|
||||
# Changelog
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
## 1.10.0-beta.2
|
||||
## 1.7.4
|
||||
|
||||
### Fixed
|
||||
|
||||
- fix: Properly overwrite z-index of datepicker above modal @juliushaertl [#4664](https://github.com/nextcloud/deck/pull/4664)
|
||||
- Use the color-primary-element* variables @szaimen [#4673](https://github.com/nextcloud/deck/pull/4673)
|
||||
- Dependency updates
|
||||
- Gracefully handle not found card for a share [#4569](https://github.com/nextcloud/deck/pull/4569)
|
||||
- fix: Use passed userid when getting attachment folder [#4541](https://github.com/nextcloud/deck/pull/4541)
|
||||
- fix: Append datetime picker to body to avoid cut off [#4646](https://github.com/nextcloud/deck/pull/4646)
|
||||
- Permanently delete deck cards marked as deleted after 5 min in a cron job [#4302](https://github.com/nextcloud/deck/pull/4302)
|
||||
- Fix : Overlapping expiry dates on tags [#4538](https://github.com/nextcloud/deck/pull/4538)
|
||||
- Update dependencies
|
||||
|
||||
### Other
|
||||
|
||||
- feat: Add devcontainer and update dev docs @juliushaertl [#4683](https://github.com/nextcloud/deck/pull/4683)
|
||||
- chore(CI): Adjust testing matrix for Nextcloud 27 on stable27 @nickvergessen [#4691](https://github.com/nextcloud/deck/pull/4691)
|
||||
|
||||
## 1.10.0-beta.1
|
||||
|
||||
### Added
|
||||
|
||||
- Compatibility with Nextcloud 27
|
||||
## 1.7.3
|
||||
|
||||
### Fixed
|
||||
|
||||
- fix(references): Mute NoPermissionException as it is expected to happen for references @juliushaertl [#4514](https://github.com/nextcloud/deck/pull/4514)
|
||||
- fix(cards): Fix card sizing by limiting too wide style rules @juliushaertl [#4512](https://github.com/nextcloud/deck/pull/4512)
|
||||
- fix: Adapt NcEmptyContent usages to new slots @juliushaertl [#4561](https://github.com/nextcloud/deck/pull/4561)
|
||||
- Gracefully handle not found card for a share @mejo- [#4566](https://github.com/nextcloud/deck/pull/4566)
|
||||
- Prevent tag itself being edit button if user lacks permissions @joshtrichards [#4574](https://github.com/nextcloud/deck/pull/4574)
|
||||
- chore: Remove unused @nextcloud/vue-dashboard @juliushaertl [#4586](https://github.com/nextcloud/deck/pull/4586)
|
||||
- Update Description.vue: Fixes the issue of hidden text by menu bar @pschopen [#4617](https://github.com/nextcloud/deck/pull/4617)
|
||||
- allow user to toggle visibility of the calendar for a deck board @schiessle [#4622](https://github.com/nextcloud/deck/pull/4622)
|
||||
- fix: Append datetime picker to body to avoid cut off @juliushaertl [#4643](https://github.com/nextcloud/deck/pull/4643)
|
||||
- fix: Bring back overdue column by removing faulty condition @juliushaertl [#4660](https://github.com/nextcloud/deck/pull/4660)
|
||||
- fix(sessions): Do not send close request without token @juliushaertl [#4510](https://github.com/nextcloud/deck/pull/4510)
|
||||
- tests(integration): Add test for multiple board shares to the same user @juliushaertl [#4494](https://github.com/nextcloud/deck/pull/4494)
|
||||
- fix(API): Fix board API details parameter to work as expected @nickvergessen [#4518](https://github.com/nextcloud/deck/pull/4518)
|
||||
- Fix : Overlapping expiry dates on tags @Jerome-Herbinet [#4535](https://github.com/nextcloud/deck/pull/4535)
|
||||
- Fix consistency of a "Create card" wording with its equivalent for Notes ("New card") @Jerome-Herbinet [#4534](https://github.com/nextcloud/deck/pull/4534)
|
||||
- tests(integration): Add integration tests for due dates @juliushaertl [#4489](https://github.com/nextcloud/deck/pull/4489)
|
||||
- Better display of card dates (creation and change dates) @Jerome-Herbinet [#4604](https://github.com/nextcloud/deck/pull/4604)
|
||||
- Refactors lib\Activity\DeckProvider.php to improve code readability. @fsamapoor [#4648](https://github.com/nextcloud/deck/pull/4648)
|
||||
- Converts 'strpos()' calls to improve code readability. @fsamapoor [#4657](https://github.com/nextcloud/deck/pull/4657)
|
||||
- feat: add validators to check values in services @juliushaertl [#4176](https://github.com/nextcloud/deck/pull/4176)
|
||||
- Add integration test for attachment handling on cards [#4178](https://github.com/nextcloud/deck/pull/4178)
|
||||
- disables autocomplete on card creation @juliushaertl [#4182](https://github.com/nextcloud/deck/pull/4182)
|
||||
- minor style fixes [#4202](https://github.com/nextcloud/deck/pull/4202)
|
||||
|
||||
|
||||
## 1.9.0-beta.1
|
||||
|
||||
### Added
|
||||
|
||||
- Export Board @david-loe [#3065](https://github.com/nextcloud/deck/pull/3065)
|
||||
- basic notify_push usage with session handling @alangecker [#3876](https://github.com/nextcloud/deck/pull/3876)
|
||||
- feat(Description): Use text as editor if available @juliushaertl [#4399](https://github.com/nextcloud/deck/pull/4399)
|
||||
- Improve reference provider and add reference widgets @julien-nc [#4422](https://github.com/nextcloud/deck/pull/4422)
|
||||
- live updates 🎉 @alangecker [#4273](https://github.com/nextcloud/deck/pull/4273)
|
||||
- Tag creation from card view @juliushaertl [#4344](https://github.com/nextcloud/deck/pull/4344)
|
||||
## 1.7.2
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fix component renaming so that acl works on shares again @small1 [#4315](https://github.com/nextcloud/deck/pull/4315)
|
||||
- fix(Sidebar): Only close sidebar on v-click-outside for specific targets @juliushaertl [#4350](https://github.com/nextcloud/deck/pull/4350)
|
||||
- add basic e2e tests for stack title @shoetten [#4206](https://github.com/nextcloud/deck/pull/4206)
|
||||
- App metadata: add links to user and developer documentation @p-bo [#4356](https://github.com/nextcloud/deck/pull/4356)
|
||||
- Update signature of Entity::markFieldUpdated @nickvergessen [#4398](https://github.com/nextcloud/deck/pull/4398)
|
||||
- Remove updated nightly information @xf- [#4419](https://github.com/nextcloud/deck/pull/4419)
|
||||
- perf: Register notifier and resource listener lazy @juliushaertl [#4439](https://github.com/nextcloud/deck/pull/4439)
|
||||
- perf: Lazy load dashboard components @juliushaertl [#4440](https://github.com/nextcloud/deck/pull/4440)
|
||||
- Optimise upcomming overview creation @Raudius [#3793](https://github.com/nextcloud/deck/pull/3793)
|
||||
- Performance boost @juliushaertl [#4452](https://github.com/nextcloud/deck/pull/4452)
|
||||
- Cache user membership for circles [#4132](https://github.com/nextcloud/deck/pull/4132)
|
||||
- Set event link also for notifications that get emitted from activities [#4118](https://github.com/nextcloud/deck/pull/4118)
|
||||
- Fix Card menu not displaying when description is not set [#4103](https://github.com/nextcloud/deck/pull/4103)
|
||||
- disable Create card button while no stack is chosen [#4019](https://github.com/nextcloud/deck/pull/4019)
|
||||
- to nextcloud/OCP package in stable24 [#4093](https://github.com/nextcloud/deck/pull/4093)
|
||||
- Fix attachment creator name: show display name [#4037](https://github.com/nextcloud/deck/pull/4037)
|
||||
- Use capped memory cache for board permissions [#3997](https://github.com/nextcloud/deck/pull/3997)
|
||||
- Improve CalDAV integration performance [#3995](https://github.com/nextcloud/deck/pull/3995)
|
||||
- Fetch attachment folder for the correct user during cron job [#3959](https://github.com/nextcloud/deck/pull/3959)
|
||||
- Switch to 'markdown-it-task-checkbox' for rendering of task lists [#3925](https://github.com/nextcloud/deck/pull/3925)
|
||||
- Prevent opening card and applyLabelFilter on card drag end [#3917](https://github.com/nextcloud/deck/pull/3917)
|
||||
- Fix for issue #3637 [#3901](https://github.com/nextcloud/deck/pull/3901)
|
||||
- Fix z-index for deck sidebar [#3885](https://github.com/nextcloud/deck/pull/3885)
|
||||
|
||||
### Other
|
||||
- Dependency updates
|
||||
|
||||
|
||||
## 1.8.0-beta.1
|
||||
### Enhancements
|
||||
|
||||
- Nextcloud 25 compatibility
|
||||
- Performance improvements
|
||||
- Use capped memory cache for board permissions @juliushaertl [#3980](https://github.com/nextcloud/deck/pull/3980)
|
||||
- Improve CalDAV integration performance @juliushaertl [#3982](https://github.com/nextcloud/deck/pull/3982)
|
||||
- Simpify query for getting shared files @juliushaertl [#3983](https://github.com/nextcloud/deck/pull/3983)
|
||||
- Accessibility improvements
|
||||
- Add a11y label for sidebar button @marcelklehr [#3986](https://github.com/nextcloud/deck/pull/3986)
|
||||
- Improve filter popover accessibility @juliushaertl [#3820](https://github.com/nextcloud/deck/pull/3820)
|
||||
- Set ids to skip to content/navigation @juliushaertl [#3924](https://github.com/nextcloud/deck/pull/3924)
|
||||
- Invert icons properly in dark mode @juliushaertl [#3939](https://github.com/nextcloud/deck/pull/3939)
|
||||
- Bump dependencies
|
||||
## 1.7.1
|
||||
|
||||
### Fixed
|
||||
|
||||
- set last modified when the card was found. Fixes #3763 @ylebre [#3796](https://github.com/nextcloud/deck/pull/3796)
|
||||
- Increase file count after sharing @luka-nextcloud [#3682](https://github.com/nextcloud/deck/pull/3682)
|
||||
- Align Duedate-delete icon properly - fixes nextcloud/deck#3791 @Ben-Ro [#3811](https://github.com/nextcloud/deck/pull/3811)
|
||||
- Fix for issue #3637 @flummer [#3833](https://github.com/nextcloud/deck/pull/3833)
|
||||
- Switch to 'markdown-it-task-checkbox' for rendering of task lists @q-wertz [#3898](https://github.com/nextcloud/deck/pull/3898)
|
||||
- Make rename functions accessibly by keyboard navigation @juliushaertl [#3813](https://github.com/nextcloud/deck/pull/3813)
|
||||
- Prevent opening card and applyLabelFilter on card drag end @eneiluj [#3916](https://github.com/nextcloud/deck/pull/3916)
|
||||
- Inserted required property in the rename list field, to prevent the l… @mstolf [#3862](https://github.com/nextcloud/deck/pull/3862)
|
||||
- Fix share provider for master changes @nickvergessen [#3942](https://github.com/nextcloud/deck/pull/3942)
|
||||
- Fetch attachment folder for the correct user during cron job @juliushaertl [#3952](https://github.com/nextcloud/deck/pull/3952)
|
||||
- Fix z-index for deck sidebar @Raudius [#3884](https://github.com/nextcloud/deck/pull/3884)
|
||||
|
||||
### Other
|
||||
|
||||
- Switch from OC::$server->get to OCP\Server::get @CarlSchwan [#3801](https://github.com/nextcloud/deck/pull/3801)
|
||||
- Add performance section in README @eneiluj [#3830](https://github.com/nextcloud/deck/pull/3830)
|
||||
- Fix static analysis by stubbing more circle methods @juliushaertl [#3900](https://github.com/nextcloud/deck/pull/3900)
|
||||
- fix(docs): fix links to JSON schemas for Trello @wiktor2200 [#3872](https://github.com/nextcloud/deck/pull/3872)
|
||||
- Move to OCP\Collaboration\Resources\LoadAdditionalScriptsEvent @juliushaertl [#3818](https://github.com/nextcloud/deck/pull/3818)
|
||||
- Rename settings to deck settings @PVince81 [#3928](https://github.com/nextcloud/deck/pull/3928)
|
||||
- SCSS cleanup @juliushaertl [#3803](https://github.com/nextcloud/deck/pull/3803)
|
||||
- Hide deprecated projects in sidebar and card details by default @Pytal [#3984](https://github.com/nextcloud/deck/pull/3984)
|
||||
- Align Duedate-delete icon properly - fixes nextcloud/deck#3791 [#3817](https://github.com/nextcloud/deck/pull/3817)
|
||||
- Increase file count after sharing [#3806](https://github.com/nextcloud/deck/pull/3806)
|
||||
- Fetch full board data after cloning [#3781](https://github.com/nextcloud/deck/pull/3781)
|
||||
|
||||
## 1.7.0
|
||||
|
||||
@@ -161,7 +97,7 @@ All notable changes to this project will be documented in this file.
|
||||
- Adapt the card modal to upstream changes [#3764](https://github.com/nextcloud/deck/pull/3764)
|
||||
- Fix text selection in dark mode and modal view [#3765](https://github.com/nextcloud/deck/pull/3765)
|
||||
- Add missing indices [#3754](https://github.com/nextcloud/deck/pull/3754)
|
||||
|
||||
- Handle qb mapper exception messages properly @juliushaertl [#3769](https://github.com/nextcloud/deck/pull/3769)
|
||||
|
||||
## 1.6.0-beta1
|
||||
|
||||
|
||||
10
Makefile
@@ -30,16 +30,6 @@ build: clean-dist install-deps build-js
|
||||
|
||||
release: clean-dist install-deps-nodev build-js
|
||||
|
||||
lint: lint-js lint-php
|
||||
|
||||
lint-js:
|
||||
npm run lint
|
||||
npm run stylelint
|
||||
|
||||
lint-php:
|
||||
composer run lint 1>/dev/null
|
||||
composer run cs:check
|
||||
|
||||
build-js: install-deps-js
|
||||
npm run build
|
||||
|
||||
|
||||
83
README.md
@@ -1,6 +1,6 @@
|
||||
# Deck
|
||||
|
||||
[](https://travis-ci.org/nextcloud/deck) [](https://codecov.io/github/nextcloud/deck) [](https://www.codacy.com/app/juliushaertl/deck?utm_source=github.com&utm_medium=referral&utm_content=nextcloud/deck&utm_campaign=Badge_Grade) [](https://scrutinizer-ci.com/g/nextcloud/deck/?branch=main) [](https://webchat.freenode.net/?channels=nextcloud-deck)
|
||||
[](https://travis-ci.org/nextcloud/deck) [](https://codecov.io/github/nextcloud/deck) [](https://www.codacy.com/app/juliushaertl/deck?utm_source=github.com&utm_medium=referral&utm_content=nextcloud/deck&utm_campaign=Badge_Grade) [](https://scrutinizer-ci.com/g/nextcloud/deck/?branch=master) [](https://webchat.freenode.net/?channels=nextcloud-deck)
|
||||
|
||||
|
||||
Deck is a kanban style organization tool aimed at personal planning and project organization for teams integrated with Nextcloud.
|
||||
@@ -20,71 +20,68 @@ Deck is a kanban style organization tool aimed at personal planning and project
|
||||
### 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)
|
||||
- [deck NG for Android and iOS](https://github.com/meltzow/deck-ng) - It is available in [Google Play Store](https://play.google.com/store/apps/details?id=net.meltzow.deckng) and [Apple App Store](https://apps.apple.com/us/app/deck-ng/id6443334702)
|
||||
|
||||
### 3rd-Party Integrations
|
||||
|
||||
- [trello-to-deck](https://github.com/maxammann/trello-to-deck) - Migrates cards from Trello
|
||||
- [mail2deck](https://github.com/newroco/mail2deck) - Provides an "email in" solution
|
||||
- [mail2deck](https://github.com/newroco/mail2deck) - Provides an "email in" solution
|
||||
- [A-deck](https://github.com/leoossa/A-deck) - Chrome Extension that allows to create new card in selected stack based on current tab
|
||||
|
||||
|
||||
## Installation/Update
|
||||
|
||||
The app can be installed through the app store within Nextcloud. You can also download the latest release from the [release page](https://github.com/nextcloud-releases/deck/releases).
|
||||
This app is supposed to work on the two latest Nextcloud versions.
|
||||
|
||||
## Performance limitations
|
||||
### Install latest release
|
||||
|
||||
Deck is not yet ready for intensive usage.
|
||||
A lot of database queries are generated when the number of boards, cards and attachments is high.
|
||||
For example, a user having access to 13 boards, with each board having on average 100 cards,
|
||||
and each card having on average 5 attachments,
|
||||
would generate 6500 database queries when doing the file related queries
|
||||
which would increase the page loading time significantly.
|
||||
You can download and install the latest release from the [Nextcloud app store](https://apps.nextcloud.com/apps/deck)
|
||||
|
||||
Improvements on Nextcloud server and Deck itself will improve the situation.
|
||||
### Install from git
|
||||
|
||||
If you want to run the latest development version from git source, you need to clone the repo to your apps folder:
|
||||
|
||||
```
|
||||
git clone https://github.com/nextcloud/deck.git
|
||||
cd deck
|
||||
make install-deps
|
||||
make build
|
||||
```
|
||||
|
||||
Please make sure you have installed the following dependencies: `make, which, tar, npm, curl, composer`
|
||||
|
||||
### Install the nightly builds
|
||||
|
||||
Instead of setting everything up manually, you can just [download the nightly build](https://github.com/nextcloud/deck/releases/tag/nightly) instead. These builds are updated every 24 hours, and are pre-configured with all the needed dependencies.
|
||||
|
||||
## Developing
|
||||
|
||||
There are multiple ways to develop on Deck. As you will need a Nextcloud server running, the individual options are described below.
|
||||
### PHP
|
||||
|
||||
### General build instructions
|
||||
Nothing to prepare, just dig into the code.
|
||||
|
||||
General build instructions for the app itself are the same for all options.
|
||||
### JavaScript
|
||||
|
||||
To build you will need to have [Node.js](https://nodejs.org/en/) and [Composer](https://getcomposer.org/) installed.
|
||||
Deck requires running a `make build-js` to install npm dependencies and build the JavaScript code using webpack. While developing you can also use `make watch` to rebuild everytime the code changes.
|
||||
|
||||
- Install PHP dependencies: `composer install --no-dev`
|
||||
- Install JS dependencies: `npm ci`
|
||||
- Build JavaScript for the frontend
|
||||
- Development build `npm run dev`
|
||||
- Watch for changes `npm run watch`
|
||||
- Production build `npm run build`
|
||||
#### Hot reloading
|
||||
|
||||
### GitHub Codespaces / VS Code devcontainer
|
||||
|
||||
- Open code spaces or the repository in VS Code to start the dev container
|
||||
- The container will automatically install all dependencies and build the app
|
||||
- Nextcloud will be installed from the master development branch and be available on a port exposed by the container
|
||||
|
||||
### Docker: Simple app development container
|
||||
|
||||
- Fork the app
|
||||
- Clone the repository: `git clone https://github.com/nextcloud/deck.git`
|
||||
- Go into deck directory: `cd deck`
|
||||
- Build the app as described in the general build instructions
|
||||
- Run Nextcloud development container and mount the apps source code into it
|
||||
Enable debug mode in your config.php `'debug' => true,`
|
||||
|
||||
Without SSL:
|
||||
```
|
||||
docker run --rm \
|
||||
-p 8080:80 \
|
||||
-v ~/path/to/app:/var/www/html/apps-extra/app \
|
||||
ghcr.io/juliushaertl/nextcloud-dev-php80:latest
|
||||
npx webpack-dev-server --config webpack.hot.js \
|
||||
--public localhost:3000 \
|
||||
--output-public-path 'http://localhost:3000/js/'
|
||||
```
|
||||
|
||||
### Full Nextcloud development environment
|
||||
With SSL:
|
||||
```
|
||||
npx webpack-dev-server --config webpack.dev.js --https \
|
||||
--cert ~/repos/nextcloud/nc-dev/data/ssl/nextcloud.local.crt \
|
||||
--key ~/repos/nextcloud/nc-dev/data/ssl/nextcloud.local.key \
|
||||
--public nextcloud.local:3000 \
|
||||
--output-public-path 'https://nextcloud.local:3000/js/'
|
||||
```
|
||||
|
||||
You need to setup a [development environment](https://docs.nextcloud.com/server/latest/developer_manual//getting_started/devenv.html) of the current Nextcloud version. You can also alternatively install & run the [nextcloud docker container](https://github.com/juliushaertl/nextcloud-docker-dev).
|
||||
After the finished installation, you can clone the deck project directly in the `/[nextcloud-docker-dev-dir]/workspace/server/apps/` folder.
|
||||
|
||||
### Running tests
|
||||
You can use the provided Makefile to run all tests by using:
|
||||
|
||||
@@ -20,7 +20,7 @@ Your report should include:
|
||||
- Reproduction steps
|
||||
|
||||
A member of the security team will confirm the vulnerability, determine its impact, and develop a fix.
|
||||
The fix will be applied to the main branch, tested, and packaged in the next security release.
|
||||
The fix will be applied to the master branch, tested, and packaged in the next security release.
|
||||
The vulnerability will be publicly announced after the release. Finally, your name will be added
|
||||
to the [hall of fame](https://hackerone.com/nextcloud/thanks) as a thank you from the entire Nextcloud community. Note our
|
||||
[threat model](https://nextcloud.com/security/threat-model) to know what is expected behavior.
|
||||
|
||||
@@ -16,13 +16,9 @@
|
||||
- 🚀 Get your project organized
|
||||
|
||||
</description>
|
||||
<version>1.10.0-beta.2</version>
|
||||
<version>1.7.4</version>
|
||||
<licence>agpl</licence>
|
||||
<author>Julius Härtl</author>
|
||||
<documentation>
|
||||
<user>https://deck.readthedocs.io/en/latest/User_documentation_en/</user>
|
||||
<developer>https://deck.readthedocs.io/en/latest/API/</developer>
|
||||
</documentation>
|
||||
<namespace>Deck</namespace>
|
||||
<types>
|
||||
<dav/>
|
||||
@@ -38,19 +34,13 @@
|
||||
<database min-version="9.4">pgsql</database>
|
||||
<database>sqlite</database>
|
||||
<database min-version="8.0">mysql</database>
|
||||
<nextcloud min-version="27" max-version="27"/>
|
||||
<nextcloud min-version="24" max-version="24"/>
|
||||
</dependencies>
|
||||
<background-jobs>
|
||||
<job>OCA\Deck\Cron\DeleteCron</job>
|
||||
<job>OCA\Deck\Cron\ScheduledNotifications</job>
|
||||
<job>OCA\Deck\Cron\CardDescriptionActivity</job>
|
||||
<job>OCA\Deck\Cron\SessionsCleanup</job>
|
||||
</background-jobs>
|
||||
<repair-steps>
|
||||
<live-migration>
|
||||
<step>OCA\Deck\Migration\DeletedCircleCleanup</step>
|
||||
</live-migration>
|
||||
</repair-steps>
|
||||
<commands>
|
||||
<command>OCA\Deck\Command\UserExport</command>
|
||||
<command>OCA\Deck\Command\BoardImport</command>
|
||||
|
||||
@@ -40,7 +40,6 @@ return [
|
||||
['name' => 'board#deleteAcl', 'url' => '/boards/{boardId}/acl/{aclId}', 'verb' => 'DELETE'],
|
||||
['name' => 'board#clone', 'url' => '/boards/{boardId}/clone', 'verb' => 'POST'],
|
||||
['name' => 'board#transferOwner', 'url' => '/boards/{boardId}/transferOwner', 'verb' => 'PUT'],
|
||||
['name' => 'board#export', 'url' => '/boards/{boardId}/export', 'verb' => 'GET'],
|
||||
|
||||
// stacks
|
||||
['name' => 'stack#index', 'url' => '/stacks/{boardId}', 'verb' => 'GET'],
|
||||
@@ -150,10 +149,5 @@ return [
|
||||
['name' => 'overview_api#upcomingCards', 'url' => '/api/v{apiVersion}/overview/upcoming', 'verb' => 'GET'],
|
||||
|
||||
['name' => 'search#search', 'url' => '/api/v{apiVersion}/search', 'verb' => 'GET'],
|
||||
|
||||
// sessions
|
||||
['name' => 'Session#create', 'url' => '/api/v{apiVersion}/session/create', 'verb' => 'PUT'],
|
||||
['name' => 'Session#sync', 'url' => '/api/v{apiVersion}/session/sync', 'verb' => 'POST'],
|
||||
['name' => 'Session#close', 'url' => '/api/v{apiVersion}/session/close', 'verb' => 'POST'],
|
||||
]
|
||||
];
|
||||
|
||||
@@ -17,9 +17,9 @@
|
||||
"phpunit/phpunit": "^9",
|
||||
"nextcloud/coding-standard": "^1.0.0",
|
||||
"symfony/event-dispatcher": "^4.0",
|
||||
"vimeo/psalm": "^5.4",
|
||||
"vimeo/psalm": "^4.3",
|
||||
"php-parallel-lint/php-parallel-lint": "^1.2",
|
||||
"nextcloud/ocp": "dev-stable27"
|
||||
"nextcloud/ocp": "dev-stable24"
|
||||
},
|
||||
"config": {
|
||||
"optimize-autoloader": true,
|
||||
@@ -28,7 +28,7 @@
|
||||
"composer/package-versions-deprecated": true
|
||||
},
|
||||
"platform": {
|
||||
"php": "8.0"
|
||||
"php": "7.4"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
@@ -36,14 +36,13 @@
|
||||
"cs:check": "php-cs-fixer fix --dry-run --diff",
|
||||
"cs:fix": "php-cs-fixer fix",
|
||||
"psalm": "psalm",
|
||||
"psalm:update-baseline": "psalm --update-baseline",
|
||||
"psalm:fix": "psalm --alter --issues=InvalidReturnType,InvalidNullableReturnType,MismatchingDocblockParamType,MismatchingDocblockReturnType,MissingParamType,InvalidFalsableReturnType",
|
||||
"test": [
|
||||
"@test:unit",
|
||||
"@test:integration"
|
||||
],
|
||||
"test:unit": "vendor/bin/phpunit -c tests/phpunit.xml",
|
||||
"test:integration": "vendor/bin/phpunit -c tests/phpunit.integration.xml && cd tests/integration && ./run.sh"
|
||||
"test:unit": "phpunit -c tests/phpunit.xml",
|
||||
"test:integration": "phpunit -c tests/phpunit.integration.xml && cd tests/integration && ./run.sh"
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
|
||||
977
composer.lock
generated
@@ -1,9 +0,0 @@
|
||||
.icon-deck {
|
||||
background-image: url(../img/deck-dark.svg);
|
||||
filter: var(--background-invert-if-dark);
|
||||
}
|
||||
|
||||
.icon-deck-white, .icon-deck.icon-white {
|
||||
background-image: url(../img/deck.svg);
|
||||
filter: var(--background-invert-if-dark);
|
||||
}
|
||||
1
css/deck.scss
Normal file
@@ -0,0 +1 @@
|
||||
@include icon-black-white('deck', 'deck', 1);
|
||||
@@ -1,8 +1,11 @@
|
||||
<?php
|
||||
/**
|
||||
* @copyright Copyright (c) 2022 Raul Ferreira Fuentes <raul@nextcloud.com>
|
||||
/*
|
||||
* @copyright Copyright (c) 2016 Julius Härtl <jus@bitgrid.net>
|
||||
*
|
||||
* @author Raul Ferreira Fuentes <raul@nextcloud.com>
|
||||
* @author Julius Härtl <jus@bitgrid.net>
|
||||
* @author Artem Anufrij <artem.anufrij@live.de>
|
||||
* @author Marin Treselj <marin@pixelipo.com>
|
||||
* @author Oskar Kurz <oskar.kurz@gmail.com>
|
||||
* @author Ryan Fletcher <ryan.fletcher@codepassion.ca>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
@@ -20,30 +23,6 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
namespace OCA\Deck\Model;
|
||||
|
||||
use OCA\Deck\Db\Board;
|
||||
|
||||
class BoardSummary extends Board {
|
||||
private Board $board;
|
||||
|
||||
public function __construct(Board $board) {
|
||||
parent::__construct();
|
||||
$this->board = $board;
|
||||
}
|
||||
|
||||
public function jsonSerialize(): array {
|
||||
return [
|
||||
'id' => $this->getId(),
|
||||
'title' => $this->getTitle()
|
||||
];
|
||||
}
|
||||
|
||||
protected function getter(string $name): mixed {
|
||||
return $this->board->getter($name);
|
||||
}
|
||||
|
||||
public function __call($name, $arguments) {
|
||||
return $this->board->__call($name, $arguments);
|
||||
}
|
||||
}
|
||||
@import 'icons';
|
||||
@import 'print';
|
||||
41
css/icons.scss
Normal file
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* Custom icons
|
||||
*/
|
||||
@include icon-black-white('deck', 'deck', 1);
|
||||
@include icon-black-white('archive', 'deck', 1);
|
||||
@include icon-black-white('circles', 'deck', 1);
|
||||
@include icon-black-white('clone', 'deck', 1);
|
||||
@include icon-black-white('filter', 'deck', 1);
|
||||
@include icon-black-white('filter_set', 'deck', 1);
|
||||
@include icon-black-white('attach', 'deck', 1);
|
||||
@include icon-black-white('reply', 'deck', 1);
|
||||
@include icon-black-white('notifications-dark', 'deck', 1);
|
||||
@include icon-black-white('description', 'deck', 1);
|
||||
|
||||
.icon-toggle-compact-collapsed {
|
||||
@include icon-color('toggle-view-expand', 'deck', $color-black);
|
||||
}
|
||||
|
||||
.icon-toggle-compact-expanded {
|
||||
@include icon-color('toggle-view-collapse', 'deck', $color-black);
|
||||
}
|
||||
.icon-activity {
|
||||
@include icon-color('activity-dark', 'activity', $color-black);
|
||||
}
|
||||
.icon-comment--unread {
|
||||
@include icon-color('comment', 'actions', $color-primary, 1, true);
|
||||
}
|
||||
|
||||
.avatardiv.circles {
|
||||
background: var(--color-primary);
|
||||
}
|
||||
|
||||
.icon-circles {
|
||||
opacity: 1;
|
||||
background-size: 20px;
|
||||
background-position: center center;
|
||||
}
|
||||
|
||||
.icon-colorpicker {
|
||||
background-image: url('../img/color_picker.svg');
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
const { defineConfig } = require('cypress')
|
||||
|
||||
module.exports = defineConfig({
|
||||
projectId: '1s7wkc',
|
||||
viewportWidth: 1280,
|
||||
viewportHeight: 720,
|
||||
e2e: {
|
||||
// We've imported your old cypress plugins here.
|
||||
// You may want to clean this up later by importing these.
|
||||
setupNodeEvents(on, config) {
|
||||
return require('./cypress/plugins/index.js')(on, config)
|
||||
},
|
||||
baseUrl: 'http://nextcloud.local/index.php',
|
||||
specPattern: 'cypress/e2e/**/*.{js,jsx,ts,tsx}',
|
||||
},
|
||||
})
|
||||
@@ -1,5 +0,0 @@
|
||||
module.exports = {
|
||||
extends: [
|
||||
'plugin:cypress/recommended',
|
||||
],
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
import { randUser } from '../utils/index.js'
|
||||
const user = randUser()
|
||||
const recipient = randUser()
|
||||
|
||||
describe('Board', function() {
|
||||
|
||||
before(function() {
|
||||
cy.createUser(user)
|
||||
cy.createUser(recipient)
|
||||
})
|
||||
|
||||
beforeEach(function() {
|
||||
cy.login(user)
|
||||
cy.visit('/apps/deck')
|
||||
})
|
||||
|
||||
it('Can create a board', function() {
|
||||
const board = 'TestBoard'
|
||||
|
||||
cy.intercept({
|
||||
method: 'POST',
|
||||
url: '/index.php/apps/deck/boards',
|
||||
}).as('createBoardRequest')
|
||||
|
||||
// Click "Add board"
|
||||
cy.get('#app-navigation-vue .app-navigation__list .app-navigation-entry')
|
||||
.eq(3).find('a').first().click({ force: true })
|
||||
|
||||
// Type the board title
|
||||
cy.get('.board-create form input[type=text]')
|
||||
.type(board, { force: true })
|
||||
|
||||
// Submit
|
||||
cy.get('.board-create form input[type=submit]')
|
||||
.first().click({ force: true })
|
||||
|
||||
cy.wait('@createBoardRequest').its('response.statusCode').should('equal', 200)
|
||||
|
||||
cy.get('.app-navigation__list .app-navigation-entry__children .app-navigation-entry')
|
||||
.contains(board).should('be.visible')
|
||||
})
|
||||
|
||||
it('Shows and hides the navigation', () => {
|
||||
cy.get('#app-navigation-vue .app-navigation__list .app-navigation-entry')
|
||||
.contains('Upcoming cards')
|
||||
.should('be.visible')
|
||||
cy.openLeftSidebar()
|
||||
cy.get('#app-navigation-vue .app-navigation__list .app-navigation-entry')
|
||||
.contains('Upcoming cards')
|
||||
.should('not.be.visible')
|
||||
cy.openLeftSidebar()
|
||||
cy.get('#app-navigation-vue .app-navigation__list .app-navigation-entry')
|
||||
.contains('Upcoming cards')
|
||||
.should('be.visible')
|
||||
})
|
||||
})
|
||||
@@ -1,129 +0,0 @@
|
||||
import { randUser } from '../utils/index.js'
|
||||
import { sampleBoard } from '../utils/sampleBoard'
|
||||
|
||||
const user = randUser()
|
||||
const boardData = sampleBoard()
|
||||
|
||||
const auth = {
|
||||
user: user.userId,
|
||||
password: user.password,
|
||||
}
|
||||
|
||||
const useModal = (useModal) => {
|
||||
return cy.request({
|
||||
method: 'POST',
|
||||
url: `${Cypress.env('baseUrl')}/ocs/v2.php/apps/deck/api/v1.0/config/cardDetailsInModal?format=json`,
|
||||
auth,
|
||||
body: { value: useModal },
|
||||
}).then((response) => {
|
||||
expect(response.status).to.eq(200)
|
||||
})
|
||||
}
|
||||
|
||||
describe('Card', function() {
|
||||
let boardId
|
||||
before(function() {
|
||||
cy.createUser(user)
|
||||
cy.login(user)
|
||||
cy.createExampleBoard({
|
||||
user,
|
||||
board: boardData,
|
||||
}).then((board) => {
|
||||
boardId = board.id
|
||||
})
|
||||
})
|
||||
|
||||
beforeEach(function() {
|
||||
cy.login(user)
|
||||
})
|
||||
|
||||
it('Can add a card', function() {
|
||||
cy.visit(`/apps/deck/#/board/${boardId}`)
|
||||
const newCardTitle = 'Write some cypress tests'
|
||||
|
||||
cy.getNavigationEntry(boardData.title)
|
||||
.first().click({ force: true })
|
||||
|
||||
cy.get('.board .stack').eq(0).within(() => {
|
||||
cy.get('.card:contains("Hello world")').should('be.visible')
|
||||
|
||||
cy.get('.button-vue[aria-label*="Add card"]')
|
||||
.first().click()
|
||||
|
||||
cy.get('.stack__card-add form input#new-stack-input-main')
|
||||
.type(newCardTitle)
|
||||
cy.get('.stack__card-add form input[type=submit]')
|
||||
.first().click()
|
||||
cy.get(`.card:contains("${newCardTitle}")`).should('be.visible')
|
||||
})
|
||||
})
|
||||
|
||||
describe('Modal', () => {
|
||||
beforeEach(function() {
|
||||
cy.login(user)
|
||||
useModal(true).then(() => {
|
||||
cy.visit(`/apps/deck/#/board/${boardId}`)
|
||||
})
|
||||
})
|
||||
|
||||
it('Can show card details modal', function() {
|
||||
cy.getNavigationEntry(boardData.title)
|
||||
.first().click({ force: true })
|
||||
|
||||
cy.get('.board .stack').eq(0).within(() => {
|
||||
cy.get('.card:contains("Hello world")').should('be.visible').click()
|
||||
})
|
||||
|
||||
cy.get('.modal__card').should('be.visible')
|
||||
cy.get('.app-sidebar-header__maintitle').contains('Hello world')
|
||||
})
|
||||
|
||||
it('Attachment from files app', () => {
|
||||
cy.get('.card:contains("Hello world")').should('be.visible').click()
|
||||
cy.get('.modal__card').should('be.visible')
|
||||
cy.get('.app-sidebar-tabs__tab [data-id="attachments"]').click()
|
||||
cy.get('button.icon-upload').should('be.visible')
|
||||
cy.get('button.icon-folder').should('be.visible')
|
||||
.click()
|
||||
cy.get('.oc-dialog #picker-filestable tr[data-entryname="welcome.txt"] td.filename').should('be.visible')
|
||||
.click()
|
||||
cy.get('.oc-dialog button.primary').click()
|
||||
cy.get('.attachment-list .basename').contains('welcome.txt')
|
||||
})
|
||||
|
||||
it('Shows the modal with the editor', () => {
|
||||
cy.get('.card:contains("Hello world")').should('be.visible').click()
|
||||
cy.intercept({ method: 'PUT', url: '**/apps/deck/cards/*' }).as('save')
|
||||
cy.get('.modal__card').should('be.visible')
|
||||
cy.get('.app-sidebar-header__maintitle').contains('Hello world')
|
||||
cy.get('.modal__card .ProseMirror h1').contains('Hello world').should('be.visible')
|
||||
cy.get('.modal__card .ProseMirror h1')
|
||||
.click()
|
||||
.type(' writing more text{enter}- List item{enter}with entries{enter}{enter}Paragraph')
|
||||
cy.wait('@save', { timeout: 7000 })
|
||||
|
||||
cy.reload()
|
||||
cy.get('.modal__card').should('be.visible')
|
||||
cy.get('.modal__card .ProseMirror h1').contains('Hello world writing more text').should('be.visible')
|
||||
cy.get('.modal__card .ProseMirror li').eq(0).contains('List item').should('be.visible')
|
||||
cy.get('.modal__card .ProseMirror li').eq(1).contains('with entries').should('be.visible')
|
||||
cy.get('.modal__card .ProseMirror p').contains('Paragraph').should('be.visible')
|
||||
})
|
||||
})
|
||||
|
||||
describe('Sidebar', () => {
|
||||
beforeEach(function() {
|
||||
cy.login(user)
|
||||
useModal(false).then(() => {
|
||||
cy.visit(`/apps/deck/#/board/${boardId}`)
|
||||
})
|
||||
})
|
||||
|
||||
it('Show the sidebar', () => {
|
||||
cy.get('.card:contains("Hello world")').should('be.visible').click()
|
||||
cy.get('#app-sidebar-vue')
|
||||
.find('.ProseMirror h1').contains('Hello world writing more text').should('be.visible')
|
||||
})
|
||||
})
|
||||
|
||||
})
|
||||
@@ -1,29 +0,0 @@
|
||||
import { randUser } from '../utils/index.js'
|
||||
const user = randUser()
|
||||
|
||||
describe('Deck dashboard', function() {
|
||||
before(function() {
|
||||
cy.createUser(user)
|
||||
})
|
||||
|
||||
beforeEach(function() {
|
||||
cy.login(user)
|
||||
cy.visit('/apps/deck')
|
||||
})
|
||||
|
||||
it('Can show the right title on the dashboard', function() {
|
||||
cy.get('.board-title h2')
|
||||
.should('have.length', 1).first()
|
||||
.should('have.text', 'Upcoming cards')
|
||||
})
|
||||
|
||||
it('Can see the default "Personal Board" created for user by default', function() {
|
||||
const defaultBoard = 'Personal'
|
||||
|
||||
cy.get('.app-navigation-entry-wrapper[icon=icon-deck]')
|
||||
.find('ul.app-navigation-entry__children .app-navigation-entry:contains(' + defaultBoard + ')')
|
||||
.first()
|
||||
.contains(defaultBoard)
|
||||
.should('be.visible')
|
||||
})
|
||||
})
|
||||
@@ -1,50 +0,0 @@
|
||||
import { randUser } from '../utils/index.js'
|
||||
import { sampleBoard } from '../utils/sampleBoard'
|
||||
const user = randUser()
|
||||
const recipient = randUser()
|
||||
|
||||
describe('Board', function() {
|
||||
before(function() {
|
||||
cy.createUser(user)
|
||||
cy.createUser(recipient)
|
||||
})
|
||||
|
||||
beforeEach(function() {
|
||||
cy.login(user)
|
||||
})
|
||||
|
||||
it('Share a board to a user', function() {
|
||||
const board = sampleBoard('Read only board')
|
||||
cy.createExampleBoard({ user, board }).then((board) => {
|
||||
const boardId = board.id
|
||||
cy.visit(`/apps/deck/#/board/${boardId}`)
|
||||
cy.get('.board-title').contains(board.title)
|
||||
|
||||
cy.shareBoardWithUi(recipient.userId)
|
||||
|
||||
cy.login(recipient)
|
||||
cy.visit(`/apps/deck/#/board/${boardId}`)
|
||||
cy.get('.board-title').contains(board.title)
|
||||
cy.get('.button-vue[aria-label*="Add card"]')
|
||||
.should('not.exist')
|
||||
})
|
||||
})
|
||||
|
||||
it('Share a board to a user as writable', function() {
|
||||
const board = sampleBoard('Editable board')
|
||||
cy.createExampleBoard({ user, board }).then((board) => {
|
||||
const boardId = board.id
|
||||
cy.visit(`/apps/deck/#/board/${boardId}`)
|
||||
cy.get('.board-title').contains(board.title)
|
||||
|
||||
cy.shareBoardWithUi(recipient.userId)
|
||||
cy.get(`[data-cy="acl-participant:${recipient.userId}"]`).find('[data-cy="action:permission-edit"]').click()
|
||||
|
||||
cy.login(recipient)
|
||||
cy.visit(`/apps/deck/#/board/${boardId}`)
|
||||
cy.get('.board-title').contains(board.title)
|
||||
cy.get('.button-vue[aria-label*="Add card"]')
|
||||
.first().click()
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,68 +0,0 @@
|
||||
import { randUser } from '../utils/index.js'
|
||||
const user = randUser()
|
||||
|
||||
const boardTitle = 'TestBoard'
|
||||
const testBoardData = {
|
||||
title: boardTitle,
|
||||
stacks: [
|
||||
{ title: 'Existing Stack1' },
|
||||
{ title: 'Existing Stack2' },
|
||||
],
|
||||
}
|
||||
|
||||
describe('Stack', function() {
|
||||
|
||||
before(function() {
|
||||
cy.createUser(user)
|
||||
cy.login(user)
|
||||
cy.createExampleBoard({
|
||||
user,
|
||||
board: testBoardData,
|
||||
})
|
||||
})
|
||||
|
||||
beforeEach(function() {
|
||||
cy.login(user)
|
||||
cy.visit('/apps/deck')
|
||||
|
||||
cy.openLeftSidebar()
|
||||
cy.getNavigationEntry(boardTitle)
|
||||
.click({ force: true })
|
||||
})
|
||||
|
||||
it('Can create a stack', function() {
|
||||
cy.get('#stack-add button').first().click()
|
||||
cy.focused().type('List 1')
|
||||
cy.get('#stack-add form input[type=submit]').first().click()
|
||||
|
||||
cy.contains('List 1').should('be.visible')
|
||||
})
|
||||
|
||||
it('Can edit a stack title', function() {
|
||||
cy.contains('Existing Stack1')
|
||||
cy.get('[data-cy-stack="Existing Stack1"]').within(() => {
|
||||
cy.contains('Existing Stack1').click()
|
||||
cy.focused().type(' renamed')
|
||||
cy.get('[data-cy="editStackTitleForm"] input[type="submit"]').click()
|
||||
})
|
||||
cy.contains('Existing Stack1 renamed').should('be.visible')
|
||||
})
|
||||
|
||||
it('Can abort a stack title edit via esc', function() {
|
||||
cy.contains('Existing Stack2').click()
|
||||
cy.focused().type(' with a new title, maybe?')
|
||||
cy.focused().type('{esc}')
|
||||
|
||||
cy.contains('Existing Stack2').should('be.visible')
|
||||
cy.contains('Existing Stack2 with a new title, maybe?').should('not.exist')
|
||||
})
|
||||
|
||||
it('Can abort a stack title edit via click outside', function() {
|
||||
cy.contains('Existing Stack2').click()
|
||||
cy.focused().type(' with a new title, maybe?')
|
||||
cy.get('[data-cy-stack="Existing Stack2"]').click('bottom')
|
||||
|
||||
cy.contains('Existing Stack2').should('be.visible')
|
||||
cy.contains('Existing Stack2 with a new title, maybe?').should('not.exist')
|
||||
})
|
||||
})
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"name": "Using fixtures to represent data",
|
||||
"email": "hello@cypress.io",
|
||||
"body": "Fixtures are a great way to mock data for responses to routes"
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
/// <reference types="cypress" />
|
||||
// ***********************************************************
|
||||
// This example plugins/index.js can be used to load plugins
|
||||
//
|
||||
// You can change the location of this file or turn off loading
|
||||
// the plugins file with the 'pluginsFile' configuration option.
|
||||
//
|
||||
// You can read more here:
|
||||
// https://on.cypress.io/plugins-guide
|
||||
// ***********************************************************
|
||||
|
||||
// This function is called when a project is opened or re-opened (e.g. due to
|
||||
// the project's config changing)
|
||||
|
||||
/**
|
||||
* @type {Cypress.PluginConfig}
|
||||
*/
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
module.exports = (on, config) => {
|
||||
// `on` is used to hook into various events Cypress emits
|
||||
// `config` is the resolved Cypress config
|
||||
}
|
||||
@@ -1,117 +0,0 @@
|
||||
/**
|
||||
* @copyright Copyright (c) 2019 John Molakvoæ <skjnldsv@protonmail.com>
|
||||
*
|
||||
* @author John Molakvoæ <skjnldsv@protonmail.com>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
import { addCommands } from '@nextcloud/cypress'
|
||||
|
||||
addCommands()
|
||||
|
||||
const url = Cypress.config('baseUrl').replace(/\/index.php\/?$/g, '')
|
||||
Cypress.env('baseUrl', url)
|
||||
|
||||
Cypress.Commands.add('openLeftSidebar', () => {
|
||||
cy.get('.app-navigation button.app-navigation-toggle').click()
|
||||
})
|
||||
|
||||
Cypress.Commands.add('deckCreateBoard', ({ user, password }, title) => {
|
||||
cy.login(user, password)
|
||||
|
||||
cy.get('.app-navigation button.app-navigation-toggle').click()
|
||||
cy.get('#app-navigation-vue .app-navigation__list .app-navigation-entry')
|
||||
.eq(3)
|
||||
.find('a')
|
||||
.first()
|
||||
.click({ force: true })
|
||||
|
||||
cy.get('.board-create form input[type=text]').type(title, { force: true })
|
||||
|
||||
cy.get('.board-create form input[type=submit]')
|
||||
.first()
|
||||
.click({ force: true })
|
||||
})
|
||||
|
||||
Cypress.Commands.add('deckCreateList', ({ user, password }, title) => {
|
||||
cy.login(user, password)
|
||||
|
||||
cy.get('.app-navigation button.app-navigation-toggle').click()
|
||||
cy.get('#app-navigation-vue .app-navigation__list .app-navigation-entry')
|
||||
.eq(3)
|
||||
.find('a.app-navigation-entry-link')
|
||||
.first()
|
||||
.click({ force: true })
|
||||
|
||||
cy.get('#stack-add button').first().click()
|
||||
cy.get('#stack-add form input#new-stack-input-main').type(title)
|
||||
cy.get('#stack-add form input[type=submit]').first().click()
|
||||
})
|
||||
|
||||
Cypress.Commands.add('createExampleBoard', ({ user, board }) => {
|
||||
const auth = {
|
||||
user: user.userId,
|
||||
password: user.password,
|
||||
}
|
||||
cy.request({
|
||||
method: 'POST',
|
||||
url: `${Cypress.env('baseUrl')}/index.php/apps/deck/api/v1.0/boards`,
|
||||
auth,
|
||||
body: { title: board.title, color: board.color ?? 'ff0000' },
|
||||
}).then((boardResponse) => {
|
||||
expect(boardResponse.status).to.eq(200)
|
||||
const boardData = boardResponse.body
|
||||
for (const stackIndex in board.stacks) {
|
||||
const stack = board.stacks[stackIndex]
|
||||
cy.request({
|
||||
method: 'POST',
|
||||
url: `${Cypress.env('baseUrl')}/index.php/apps/deck/api/v1.0/boards/${boardData.id}/stacks`,
|
||||
auth,
|
||||
body: { title: stack.title, order: 0 },
|
||||
}).then((stackResponse) => {
|
||||
const stackData = stackResponse.body
|
||||
for (const cardIndex in stack.cards) {
|
||||
const card = stack.cards[cardIndex]
|
||||
cy.request({
|
||||
method: 'POST',
|
||||
url: `${Cypress.env('baseUrl')}/index.php/apps/deck/api/v1.0/boards/${boardData.id}/stacks/${stackData.id}/cards`,
|
||||
auth,
|
||||
body: { title: card.title, description: card.description ?? '' },
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
cy.wrap(boardData)
|
||||
})
|
||||
})
|
||||
|
||||
Cypress.Commands.add('getNavigationEntry', (boardTitle) => {
|
||||
return cy.get('.app-navigation-entry-wrapper[icon=icon-deck]')
|
||||
.find('ul.app-navigation-entry__children .app-navigation-entry:contains(' + boardTitle + ')')
|
||||
.find('a.app-navigation-entry-link')
|
||||
})
|
||||
|
||||
Cypress.Commands.add('shareBoardWithUi', (userId) => {
|
||||
cy.get('[aria-label="Open details"]').click()
|
||||
cy.get('.app-sidebar').should('be.visible')
|
||||
cy.get('.multiselect__input').type(`${userId}`)
|
||||
cy.get('.multiselect__content .multiselect__element').first().contains(userId)
|
||||
cy.get('.multiselect__input').type('{enter}')
|
||||
|
||||
cy.get('.shareWithList').contains(userId)
|
||||
})
|
||||
@@ -1,12 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||
<title>Components App</title>
|
||||
</head>
|
||||
<body>
|
||||
<div data-cy-root></div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,27 +0,0 @@
|
||||
// ***********************************************************
|
||||
// This example support/component.js is processed and
|
||||
// loaded automatically before your test files.
|
||||
//
|
||||
// This is a great place to put global configuration and
|
||||
// behavior that modifies Cypress.
|
||||
//
|
||||
// You can change the location of this file or turn off
|
||||
// automatically serving support files with the
|
||||
// 'supportFile' configuration option.
|
||||
//
|
||||
// You can read more here:
|
||||
// https://on.cypress.io/configuration
|
||||
// ***********************************************************
|
||||
|
||||
// Import commands.js using ES2015 syntax:
|
||||
import './commands'
|
||||
|
||||
// Alternatively you can use CommonJS syntax:
|
||||
// require('./commands')
|
||||
|
||||
import { mount } from 'cypress/vue2'
|
||||
|
||||
Cypress.Commands.add('mount', mount)
|
||||
|
||||
// Example use:
|
||||
// cy.mount(MyComponent)
|
||||
@@ -1,20 +0,0 @@
|
||||
// ***********************************************************
|
||||
// This example support/index.js is processed and
|
||||
// loaded automatically before your test files.
|
||||
//
|
||||
// This is a great place to put global configuration and
|
||||
// behavior that modifies Cypress.
|
||||
//
|
||||
// You can change the location of this file or turn off
|
||||
// automatically serving support files with the
|
||||
// 'supportFile' configuration option.
|
||||
//
|
||||
// You can read more here:
|
||||
// https://on.cypress.io/configuration
|
||||
// ***********************************************************
|
||||
|
||||
// Import commands.js using ES2015 syntax:
|
||||
import './commands.js'
|
||||
|
||||
// Alternatively you can use CommonJS syntax:
|
||||
// require('./commands')
|
||||
@@ -1,4 +0,0 @@
|
||||
import { User } from '@nextcloud/cypress'
|
||||
|
||||
export const randHash = () => Math.random().toString(36).replace(/[^a-z]+/g, '').slice(0, 10)
|
||||
export const randUser = () => new User(randHash(), randHash())
|
||||
@@ -1,38 +0,0 @@
|
||||
/*
|
||||
* @copyright Copyright (c) 2022 Julius Härtl <jus@bitgrid.net>
|
||||
*
|
||||
* @author Julius Härtl <jus@bitgrid.net>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
export const sampleBoard = (title = 'MyTestBoard') => {
|
||||
return {
|
||||
title: title,
|
||||
color: '00ff00',
|
||||
stacks: [
|
||||
{
|
||||
title: 'TestList',
|
||||
cards: [
|
||||
{
|
||||
title: 'Hello world',
|
||||
description: '# Hello world',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
110
docs/API.md
@@ -1066,7 +1066,6 @@ Deck stores user and app configuration values globally and per board. The GET en
|
||||
| --- | --- |
|
||||
| calendar | Determines if the calendar/tasks integration through the CalDAV backend is enabled for the user (boolean) |
|
||||
| cardDetailsInModal | Determines if the bigger view is used (boolean) |
|
||||
| cardIdBadge | Determines if the ID badges are displayed on cards (boolean) |
|
||||
| groupLimit | Determines if creating new boards is limited to certain groups of the instance. The resulting output is an array of group objects with the id and the displayname (Admin only)|
|
||||
|
||||
```
|
||||
@@ -1080,7 +1079,6 @@ Deck stores user and app configuration values globally and per board. The GET en
|
||||
"data": {
|
||||
"calendar": true,
|
||||
"cardDetailsInModal": true,
|
||||
"cardIdBadge": true,
|
||||
"groupLimit": [
|
||||
{
|
||||
"id": "admin",
|
||||
@@ -1111,7 +1109,6 @@ Deck stores user and app configuration values globally and per board. The GET en
|
||||
| notify-due | `off`, `assigned` or `all` |
|
||||
| calendar | Boolean |
|
||||
| cardDetailsInModal | Boolean |
|
||||
| cardIdBadge | Boolean |
|
||||
|
||||
#### Example request
|
||||
|
||||
@@ -1394,110 +1391,3 @@ A bad request response is returned if invalid input values are provided. The res
|
||||
A not found response might be returned if:
|
||||
- The card for the given cardId could not be found
|
||||
- The comment could not be found
|
||||
|
||||
|
||||
## Sessions
|
||||
|
||||
### PUT /session/create - creates a new session
|
||||
|
||||
#### Request parameters
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ------- | ---------------------------------------------------- |
|
||||
| boardId | Integer | The id of the opened board |
|
||||
|
||||
```
|
||||
curl -X PUT 'https://admin:admin@nextcloud/ocs/v2.php/apps/deck/api/v1.0/session/create' \
|
||||
-H 'Accept: application/json' -H 'OCS-APIRequest: true' \
|
||||
-H 'Content-Type: application/json;charset=utf-8' \
|
||||
--data '{"boardId":1}'
|
||||
```
|
||||
|
||||
#### Response
|
||||
|
||||
##### 200 Success
|
||||
|
||||
```json
|
||||
{
|
||||
"ocs": {
|
||||
"meta": {
|
||||
"status": "ok",
|
||||
"statuscode": 200,
|
||||
"message": "OK"
|
||||
},
|
||||
"data": {
|
||||
"token": "+zcJHf4rC6dobVSbuNa3delkCSfTW8OvYWTyLFvSpIv80FjtgLIj0ARlxspsazNQ"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### POST /session/sync - notifies the server, that the session is still open
|
||||
|
||||
#### Request body
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ------- | ---------------------------------------------------- |
|
||||
| boardId | Integer | The id of the opened board |
|
||||
| token | String | The session token from the /sessions/create response |
|
||||
|
||||
|
||||
```
|
||||
curl -X POST 'https://admin:admin@nextcloud/ocs/v2.php/apps/deck/api/v1.0/session/create' \
|
||||
-H 'Accept: application/json' -H 'OCS-APIRequest: true' \
|
||||
-H 'Content-Type: application/json;charset=utf-8' \
|
||||
--data '{"boardId":1, "token":"X3DyyoFslArF0t0NBZXzZXzcy8feoX/OEytSNXZtPg9TpUgO5wrkJ38IW3T/FfpV"}'
|
||||
```
|
||||
|
||||
#### Response
|
||||
|
||||
##### 200 Success
|
||||
```json
|
||||
{
|
||||
"ocs": {
|
||||
"meta": {
|
||||
"status": "ok",
|
||||
"statuscode": 200,
|
||||
"message": "OK"
|
||||
},
|
||||
"data": []
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
##### 404 Not Found
|
||||
the provided token is invalid or expired
|
||||
|
||||
|
||||
### POST /session/close - closes the session
|
||||
|
||||
#### Request body
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | ------- | ---------------------------------------------------- |
|
||||
| boardId | Integer | The id of the opened board |
|
||||
| token | String | The session token from the /sessions/create response |
|
||||
|
||||
```
|
||||
curl -X POST 'https://admin:admin@nextcloud/ocs/v2.php/apps/deck/api/v1.0/session/close' \
|
||||
-H 'Accept: application/json' -H 'OCS-APIRequest: true' \
|
||||
-H 'Content-Type: application/json;charset=utf-8' \
|
||||
--data '{"boardId":1, "token":"X3DyyoFslArF0t0NBZXzZXzcy8feoX/OEytSNXZtPg9TpUgO5wrkJ38IW3T/FfpV"}'
|
||||
```
|
||||
|
||||
#### Response
|
||||
|
||||
##### 200 Success
|
||||
```json
|
||||
{
|
||||
"ocs": {
|
||||
"meta": {
|
||||
"status": "ok",
|
||||
"statuscode": 200,
|
||||
"message": "OK"
|
||||
},
|
||||
"data": []
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -90,7 +90,7 @@ Steps:
|
||||
* Create the configuration file
|
||||
* Execute the import informing the import file path, data file and source as `Trello JSON`
|
||||
|
||||
Create the configuration file respecting the [JSON Schema](https://github.com/nextcloud/deck/blob/main/lib/Service/Importer/fixtures/config-trelloJson-schema.json) for import `Trello JSON`
|
||||
Create the configuration file respecting the [JSON Schema](https://github.com/nextcloud/deck/blob/master/lib/Service/fixtures/config-trelloJson-schema.json) for import `Trello JSON`
|
||||
|
||||
Example configuration file:
|
||||
```json
|
||||
@@ -120,7 +120,7 @@ https://api.trello.com/1/members/me/boards?key={yourKey}&token={yourToken}&field
|
||||
This ID you will use in the configuration file in the `board` property
|
||||
* Create the configuration file
|
||||
|
||||
Create the configuration file respecting the [JSON Schema](https://github.com/nextcloud/deck/blob/main/lib/Service/Importer/fixtures/config-trelloApi-schema.json) for import `Trello JSON`
|
||||
Create the configuration file respecting the [JSON Schema](https://github.com/nextcloud/deck/blob/master/lib/Service/fixtures/config-trelloApi-schema.json) for import `Trello JSON`
|
||||
|
||||
Example configuration file:
|
||||
```json
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="32" width="32" version="1.0" viewbox="0 0 32 32">
|
||||
<path d="m16 1-10 18h11l-1 12 10-18h-11z"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 205 B |
@@ -1,4 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="32" width="32" version="1.0" viewBox="0 0 32 32">
|
||||
<path d="m16 1-10 18h11l-1 12 10-18h-11z" fill="#FFF"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 217 B |
1
img/archive-white.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><g transform="translate(0 -1036.362)" fill="#fff"><path d="M1.93 1041.296c-.185 0-.336.138-.336.31v9.842c0 .172.15.313.336.313h12.517c.185 0 .333-.14.333-.313v-9.842c0-.172-.148-.31-.333-.31H1.93zm4.124 1.507h4.223c.39 0 .705.314.705.704v.43c0 .39-.315.705-.705.705H6.054a.703.703 0 0 1-.705-.705v-.43c0-.39.314-.704.705-.704z"/><rect width="15.742" height="2.296" x=".136" y="1037.543" ry="0"/></g></svg>
|
||||
|
After Width: | Height: | Size: 488 B |
@@ -1,3 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" style="height: 240px; width: 240px;" viewBox="0 0 24 24">
|
||||
<path fill="currentColor" d="M12,15H10V13H12V15M18,15H14V13H18V15M8,11H6V9H8V11M18,11H10V9H18V11M20,20H4A2,2 0 0,1 2,18V6A2,2 0 0,1 4,4H20A2,2 0 0,1 22,6V18A2,2 0 0,1 20,20M4,6V18H20V6H4Z" />
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 298 B |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 58 58" width="512" height="512"><g fill="#000"><path d="M54.319 37.839C54.762 35.918 55 33.96 55 32c0-9.095-4.631-17.377-12.389-22.153a1 1 0 1 0-1.049 1.703C48.724 15.96 53 23.604 53 32c0 1.726-.2 3.451-.573 5.147A6.992 6.992 0 0 0 51 37c-3.86 0-7 3.141-7 7s3.14 7 7 7 7-3.141 7-7a7.006 7.006 0 0 0-3.681-6.161zM38.171 54.182A23.867 23.867 0 0 1 29 56a24.047 24.047 0 0 1-17.017-7.092A6.974 6.974 0 0 0 14 44c0-3.859-3.14-7-7-7s-7 3.141-7 7 3.14 7 7 7a6.952 6.952 0 0 0 3.381-.875C15.26 55.136 21.994 58 29 58c3.435 0 6.778-.663 9.936-1.971.51-.211.753-.796.542-1.307a1.001 1.001 0 0 0-1.307-.54zM4 31.213a1 1 0 0 0 1.068-.927c.712-10.089 7.586-18.52 17.22-21.314C23.142 11.874 25.825 14 29 14c3.86 0 7-3.141 7-7s-3.14-7-7-7c-3.851 0-6.985 3.127-6.999 6.975C11.42 9.922 3.851 19.12 3.073 30.146A.999.999 0 0 0 4 31.213z"/></g></svg>
|
||||
|
Before Width: | Height: | Size: 885 B |
@@ -1 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 58 58" width="512" height="512"><g fill="#fff"><path d="M54.319 37.839C54.762 35.918 55 33.96 55 32c0-9.095-4.631-17.377-12.389-22.153a1 1 0 1 0-1.049 1.703C48.724 15.96 53 23.604 53 32c0 1.726-.2 3.451-.573 5.147A6.992 6.992 0 0 0 51 37c-3.86 0-7 3.141-7 7s3.14 7 7 7 7-3.141 7-7a7.006 7.006 0 0 0-3.681-6.161zM38.171 54.182A23.867 23.867 0 0 1 29 56a24.047 24.047 0 0 1-17.017-7.092A6.974 6.974 0 0 0 14 44c0-3.859-3.14-7-7-7s-7 3.141-7 7 3.14 7 7 7a6.952 6.952 0 0 0 3.381-.875C15.26 55.136 21.994 58 29 58c3.435 0 6.778-.663 9.936-1.971.51-.211.753-.796.542-1.307a1.001 1.001 0 0 0-1.307-.54zM4 31.213a1 1 0 0 0 1.068-.927c.712-10.089 7.586-18.52 17.22-21.314C23.142 11.874 25.825 14 29 14c3.86 0 7-3.141 7-7s-3.14-7-7-7c-3.851 0-6.985 3.127-6.999 6.975C11.42 9.922 3.851 19.12 3.073 30.146A.999.999 0 0 0 4 31.213z"/></g></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 58 58" width="512" height="512"><g fill="#000"><path d="M54.319 37.839C54.762 35.918 55 33.96 55 32c0-9.095-4.631-17.377-12.389-22.153a1 1 0 1 0-1.049 1.703C48.724 15.96 53 23.604 53 32c0 1.726-.2 3.451-.573 5.147A6.992 6.992 0 0 0 51 37c-3.86 0-7 3.141-7 7s3.14 7 7 7 7-3.141 7-7a7.006 7.006 0 0 0-3.681-6.161zM38.171 54.182A23.867 23.867 0 0 1 29 56a24.047 24.047 0 0 1-17.017-7.092A6.974 6.974 0 0 0 14 44c0-3.859-3.14-7-7-7s-7 3.141-7 7 3.14 7 7 7a6.952 6.952 0 0 0 3.381-.875C15.26 55.136 21.994 58 29 58c3.435 0 6.778-.663 9.936-1.971.51-.211.753-.796.542-1.307a1.001 1.001 0 0 0-1.307-.54zM4 31.213a1 1 0 0 0 1.068-.927c.712-10.089 7.586-18.52 17.22-21.314C23.142 11.874 25.825 14 29 14c3.86 0 7-3.141 7-7s-3.14-7-7-7c-3.851 0-6.985 3.127-6.999 6.975C11.42 9.922 3.851 19.12 3.073 30.146A.999.999 0 0 0 4 31.213z"/></g></svg>
|
||||
|
Before Width: | Height: | Size: 885 B After Width: | Height: | Size: 885 B |
1
img/clone.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg width="16" height="16" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M11.8 13.8H2.2V4.2h9.6m1.2 0c0-.67-.53-1.2-1.2-1.2H2.2C1.53 3 1 3.53 1 4.2v9.6c0 .67.53 1.2 1.2 1.2h9.6c.67 0 1.2-.53 1.2-1.2"/><path d="m4.2 1c-0.67 0-1.2 0.54-1.2 1.2h10.8v10.8c0.67 0 1.2-0.53 1.2-1.2v-9.6c0-0.67-0.53-1.2-1.2-1.2z"/></svg>
|
||||
|
After Width: | Height: | Size: 327 B |
1
img/reply.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="16" width="16"><path d="M15 15s-.4-7.8-7-10V1L1 8l7 7v-4c5.1 0 7 4 7 4z"/></svg>
|
||||
|
After Width: | Height: | Size: 128 B |
14
l10n/sv.js
@@ -78,7 +78,7 @@ OC.L10N.register(
|
||||
"{user} has mentioned you in a comment on {deck-card}." : "{user} har nämnt dig i en kommentar i {deck-card}.",
|
||||
"The board \"%s\" has been shared with you by %s." : "Tavlan \"%s\" har delats med dig av %s.",
|
||||
"{user} has shared {deck-board} with you." : "{user} har delat {deck-board} med dig.",
|
||||
"Deck board" : "Deck-plank",
|
||||
"Deck board" : "Deck-tavla",
|
||||
"Owned by %1$s" : "Ägd av %1$s",
|
||||
"Deck boards, cards and comments" : "Deck tavlor, kort och kommentarer",
|
||||
"From %1$s, in %2$s/%3$s, owned by %4$s" : "Från %1$s, i %2$s/%3$s, ägd av %4$s",
|
||||
@@ -92,7 +92,7 @@ OC.L10N.register(
|
||||
"Later" : "Senare",
|
||||
"copy" : "kopiera",
|
||||
"To do" : "Att göra",
|
||||
"Doing" : "Gör",
|
||||
"Doing" : "Pågående",
|
||||
"Done" : "Klart",
|
||||
"Example Task 3" : "Exempeluppgift 3",
|
||||
"Example Task 2" : "Exempeluppgift 2",
|
||||
@@ -249,7 +249,7 @@ OC.L10N.register(
|
||||
"Write a description …" : "Ange en beskrivning ...",
|
||||
"Choose attachment" : "Välj bilaga",
|
||||
"(group)" : " (grupp)",
|
||||
"Todo items" : "Todo saker",
|
||||
"Todo items" : "Att göra saker",
|
||||
"{count} comments, {unread} unread" : "{count} kommentarer, {unread} olästa",
|
||||
"Edit card title" : "Ändra korttitel",
|
||||
"Assign to me" : "Tilldela till mig",
|
||||
@@ -289,7 +289,7 @@ OC.L10N.register(
|
||||
"Only assigned cards" : "Bara tilldelade kort",
|
||||
"No reminder" : "Ingen påminnelse",
|
||||
"An error occurred" : "Ett fel uppstod",
|
||||
"Are you sure you want to delete the board {title}? This will delete all the data of this board including archived cards." : "Är du säker på att du vill radera brädet {title}? Detta kommer radera all data som tillhör brädet inklusive arkiverade kort.",
|
||||
"Are you sure you want to delete the board {title}? This will delete all the data of this board including archived cards." : "Är du säker på att du vill radera tavla {title}? Detta kommer radera all data som tillhör tavlan inklusive arkiverade kort.",
|
||||
"Delete the board?" : "Ta bort tavlan?",
|
||||
"Loading filtered view" : "Laddar filtrerad vy",
|
||||
"No due" : "Inget slut",
|
||||
@@ -316,9 +316,9 @@ OC.L10N.register(
|
||||
"Share with a Deck card" : "Dela med ett Deck-kort",
|
||||
"Share {file} with a Deck card" : "Dela {file} med ett Deck-kort",
|
||||
"Share" : "Dela",
|
||||
"Are you sure you want to transfer the board {title} for {user}?" : "Är du säker på att du vill överföra brädet {title} för {user}?",
|
||||
"Transfer the board for {user} successfully" : "Överförde brädet för {user}",
|
||||
"Failed to transfer the board for {user}" : "Misslyckades med att överföra brädet för {user}",
|
||||
"Are you sure you want to transfer the board {title} for {user}?" : "Är du säker på att du vill överföra tavla {title} för {user}?",
|
||||
"Transfer the board for {user} successfully" : "Överförde tavlan för {user}",
|
||||
"Failed to transfer the board for {user}" : "Misslyckades med att överföra tavlan för {user}",
|
||||
"Add a new list" : "Lägg till en ny lista",
|
||||
"Are you sure you want to delete the board {title}? This will delete all the data of this board." : "Är du säker på att du vill radera tavla {title}? Detta kommer att radera all information från denna tavla."
|
||||
},
|
||||
|
||||
14
l10n/sv.json
@@ -76,7 +76,7 @@
|
||||
"{user} has mentioned you in a comment on {deck-card}." : "{user} har nämnt dig i en kommentar i {deck-card}.",
|
||||
"The board \"%s\" has been shared with you by %s." : "Tavlan \"%s\" har delats med dig av %s.",
|
||||
"{user} has shared {deck-board} with you." : "{user} har delat {deck-board} med dig.",
|
||||
"Deck board" : "Deck-plank",
|
||||
"Deck board" : "Deck-tavla",
|
||||
"Owned by %1$s" : "Ägd av %1$s",
|
||||
"Deck boards, cards and comments" : "Deck tavlor, kort och kommentarer",
|
||||
"From %1$s, in %2$s/%3$s, owned by %4$s" : "Från %1$s, i %2$s/%3$s, ägd av %4$s",
|
||||
@@ -90,7 +90,7 @@
|
||||
"Later" : "Senare",
|
||||
"copy" : "kopiera",
|
||||
"To do" : "Att göra",
|
||||
"Doing" : "Gör",
|
||||
"Doing" : "Pågående",
|
||||
"Done" : "Klart",
|
||||
"Example Task 3" : "Exempeluppgift 3",
|
||||
"Example Task 2" : "Exempeluppgift 2",
|
||||
@@ -247,7 +247,7 @@
|
||||
"Write a description …" : "Ange en beskrivning ...",
|
||||
"Choose attachment" : "Välj bilaga",
|
||||
"(group)" : " (grupp)",
|
||||
"Todo items" : "Todo saker",
|
||||
"Todo items" : "Att göra saker",
|
||||
"{count} comments, {unread} unread" : "{count} kommentarer, {unread} olästa",
|
||||
"Edit card title" : "Ändra korttitel",
|
||||
"Assign to me" : "Tilldela till mig",
|
||||
@@ -287,7 +287,7 @@
|
||||
"Only assigned cards" : "Bara tilldelade kort",
|
||||
"No reminder" : "Ingen påminnelse",
|
||||
"An error occurred" : "Ett fel uppstod",
|
||||
"Are you sure you want to delete the board {title}? This will delete all the data of this board including archived cards." : "Är du säker på att du vill radera brädet {title}? Detta kommer radera all data som tillhör brädet inklusive arkiverade kort.",
|
||||
"Are you sure you want to delete the board {title}? This will delete all the data of this board including archived cards." : "Är du säker på att du vill radera tavla {title}? Detta kommer radera all data som tillhör tavlan inklusive arkiverade kort.",
|
||||
"Delete the board?" : "Ta bort tavlan?",
|
||||
"Loading filtered view" : "Laddar filtrerad vy",
|
||||
"No due" : "Inget slut",
|
||||
@@ -314,9 +314,9 @@
|
||||
"Share with a Deck card" : "Dela med ett Deck-kort",
|
||||
"Share {file} with a Deck card" : "Dela {file} med ett Deck-kort",
|
||||
"Share" : "Dela",
|
||||
"Are you sure you want to transfer the board {title} for {user}?" : "Är du säker på att du vill överföra brädet {title} för {user}?",
|
||||
"Transfer the board for {user} successfully" : "Överförde brädet för {user}",
|
||||
"Failed to transfer the board for {user}" : "Misslyckades med att överföra brädet för {user}",
|
||||
"Are you sure you want to transfer the board {title} for {user}?" : "Är du säker på att du vill överföra tavla {title} för {user}?",
|
||||
"Transfer the board for {user} successfully" : "Överförde tavlan för {user}",
|
||||
"Failed to transfer the board for {user}" : "Misslyckades med att överföra tavlan för {user}",
|
||||
"Add a new list" : "Lägg till en ny lista",
|
||||
"Are you sure you want to delete the board {title}? This will delete all the data of this board." : "Är du säker på att du vill radera tavla {title}? Detta kommer att radera all information från denna tavla."
|
||||
},"pluralForm" :"nplurals=2; plural=(n != 1);"
|
||||
|
||||
@@ -173,7 +173,7 @@ OC.L10N.register(
|
||||
"Can manage" : "可以管理",
|
||||
"Owner" : "所有者",
|
||||
"Delete" : "删除",
|
||||
"Failed to create share with {displayName}" : "用 {displayName} 创建分享失败",
|
||||
"Failed to create share with {displayName}" : "用{displayName}创建分享失败",
|
||||
"Transfer" : "传输",
|
||||
"Archive all cards" : "归档所有卡片",
|
||||
"Delete list" : "删除列表",
|
||||
@@ -285,7 +285,7 @@ OC.L10N.register(
|
||||
"Create a card" : "创建一张卡片",
|
||||
"Message from {author} in {conversationName}" : "{conversationName} 会话中来自 {author} 的消息",
|
||||
"Something went wrong" : "发生了错误",
|
||||
"Failed to upload {name}" : "未能上传 {name}",
|
||||
"Failed to upload {name}" : "未能上传{name}",
|
||||
"Maximum file size of {size} exceeded" : "文件容量已超过 {size} 的上限",
|
||||
"Error creating the share" : "创建分享出错",
|
||||
"Share with a Deck card" : "分享给一张看板卡片",
|
||||
|
||||
@@ -171,7 +171,7 @@
|
||||
"Can manage" : "可以管理",
|
||||
"Owner" : "所有者",
|
||||
"Delete" : "删除",
|
||||
"Failed to create share with {displayName}" : "用 {displayName} 创建分享失败",
|
||||
"Failed to create share with {displayName}" : "用{displayName}创建分享失败",
|
||||
"Transfer" : "传输",
|
||||
"Archive all cards" : "归档所有卡片",
|
||||
"Delete list" : "删除列表",
|
||||
@@ -283,7 +283,7 @@
|
||||
"Create a card" : "创建一张卡片",
|
||||
"Message from {author} in {conversationName}" : "{conversationName} 会话中来自 {author} 的消息",
|
||||
"Something went wrong" : "发生了错误",
|
||||
"Failed to upload {name}" : "未能上传 {name}",
|
||||
"Failed to upload {name}" : "未能上传{name}",
|
||||
"Maximum file size of {size} exceeded" : "文件容量已超过 {size} 的上限",
|
||||
"Error creating the share" : "创建分享出错",
|
||||
"Share with a Deck card" : "分享给一张看板卡片",
|
||||
|
||||
@@ -45,9 +45,7 @@ use OCP\AppFramework\Db\DoesNotExistException;
|
||||
use OCP\AppFramework\Db\MultipleObjectsReturnedException;
|
||||
use OCP\Comments\IComment;
|
||||
use OCP\IUser;
|
||||
use OCP\Server;
|
||||
use OCP\L10N\IFactory;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
class ActivityManager {
|
||||
public const DECK_NOAUTHOR_COMMENT_SYSTEM_ENFORCED = 'DECK_NOAUTHOR_COMMENT_SYSTEM_ENFORCED';
|
||||
@@ -55,14 +53,14 @@ class ActivityManager {
|
||||
public const SUBJECT_PARAMS_MAX_LENGTH = 4000;
|
||||
public const SHORTENED_DESCRIPTION_MAX_LENGTH = 2000;
|
||||
|
||||
private IManager $manager;
|
||||
private ?string $userId;
|
||||
private PermissionService $permissionService;
|
||||
private BoardMapper $boardMapper;
|
||||
private CardMapper $cardMapper;
|
||||
private AclMapper $aclMapper;
|
||||
private StackMapper $stackMapper;
|
||||
private IFactory $l10nFactory;
|
||||
private $manager;
|
||||
private $userId;
|
||||
private $permissionService;
|
||||
private $boardMapper;
|
||||
private $cardMapper;
|
||||
private $aclMapper;
|
||||
private $stackMapper;
|
||||
private $l10nFactory;
|
||||
|
||||
public const DECK_OBJECT_BOARD = 'deck_board';
|
||||
public const DECK_OBJECT_CARD = 'deck_card';
|
||||
@@ -116,7 +114,7 @@ class ActivityManager {
|
||||
StackMapper $stackMapper,
|
||||
AclMapper $aclMapper,
|
||||
IFactory $l10nFactory,
|
||||
?string $userId
|
||||
$userId
|
||||
) {
|
||||
$this->manager = $manager;
|
||||
$this->permissionService = $permissionsService;
|
||||
@@ -312,10 +310,10 @@ class ActivityManager {
|
||||
try {
|
||||
$object = $this->findObjectForEntity($objectType, $entity);
|
||||
} catch (DoesNotExistException $e) {
|
||||
Server::get(LoggerInterface::class)->error('Could not create activity entry for ' . $subject . '. Entity not found.', (array)$entity);
|
||||
\OC::$server->getLogger()->error('Could not create activity entry for ' . $subject . '. Entity not found.', (array)$entity);
|
||||
return null;
|
||||
} catch (MultipleObjectsReturnedException $e) {
|
||||
Server::get(LoggerInterface::class)->error('Could not create activity entry for ' . $subject . '. Entity not found.', (array)$entity);
|
||||
\OC::$server->getLogger()->error('Could not create activity entry for ' . $subject . '. Entity not found.', (array)$entity);
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -325,6 +323,7 @@ class ActivityManager {
|
||||
*/
|
||||
$eventType = 'deck';
|
||||
$subjectParams = [];
|
||||
$message = null;
|
||||
switch ($subject) {
|
||||
// No need to enhance parameters since entity already contains the required data
|
||||
case self::SUBJECT_BOARD_CREATE:
|
||||
@@ -366,15 +365,7 @@ class ActivityManager {
|
||||
case self::SUBJECT_CARD_USER_ASSIGN:
|
||||
case self::SUBJECT_CARD_USER_UNASSIGN:
|
||||
$subjectParams = $this->findDetailsForCard($entity->getId(), $subject);
|
||||
|
||||
if (isset($additionalParams['after']) && $additionalParams['after'] instanceof \DateTimeInterface) {
|
||||
$additionalParams['after'] = $additionalParams['after']->format('c');
|
||||
}
|
||||
if (isset($additionalParams['before']) && $additionalParams['before'] instanceof \DateTimeInterface) {
|
||||
$additionalParams['before'] = $additionalParams['before']->format('c');
|
||||
}
|
||||
|
||||
break;
|
||||
break;
|
||||
case self::SUBJECT_ATTACHMENT_CREATE:
|
||||
case self::SUBJECT_ATTACHMENT_UPDATE:
|
||||
case self::SUBJECT_ATTACHMENT_DELETE:
|
||||
@@ -433,6 +424,10 @@ class ActivityManager {
|
||||
->setSubject($subject, $subjectParams)
|
||||
->setTimestamp(time());
|
||||
|
||||
if ($message !== null) {
|
||||
$event->setMessage($message);
|
||||
}
|
||||
|
||||
// FIXME: We currently require activities for comments even if they are disabled though settings
|
||||
// Get rid of this once the frontend fetches comments/activity individually
|
||||
if ($eventType === 'deck_comment') {
|
||||
|
||||
@@ -77,7 +77,7 @@ class DeckProvider implements IProvider {
|
||||
* @throws \InvalidArgumentException Should be thrown if your provider does not know this event
|
||||
* @since 11.0.0
|
||||
*/
|
||||
public function parse($language, IEvent $event, IEvent $previousEvent = null): IEvent {
|
||||
public function parse($language, IEvent $event, IEvent $previousEvent = null) {
|
||||
if ($event->getApp() !== 'deck') {
|
||||
throw new \InvalidArgumentException();
|
||||
}
|
||||
@@ -186,28 +186,28 @@ class DeckProvider implements IProvider {
|
||||
|
||||
private function getIcon(IEvent $event) {
|
||||
$event->setIcon($this->urlGenerator->getAbsoluteURL($this->urlGenerator->imagePath('deck', 'deck-dark.svg')));
|
||||
if (str_contains($event->getSubject(), '_update')) {
|
||||
if (strpos($event->getSubject(), '_update') !== false) {
|
||||
$event->setIcon($this->urlGenerator->getAbsoluteURL($this->urlGenerator->imagePath('files', 'change.svg')));
|
||||
}
|
||||
if (str_contains($event->getSubject(), '_create')) {
|
||||
if (strpos($event->getSubject(), '_create') !== false) {
|
||||
$event->setIcon($this->urlGenerator->getAbsoluteURL($this->urlGenerator->imagePath('files', 'add-color.svg')));
|
||||
}
|
||||
if (str_contains($event->getSubject(), '_delete')) {
|
||||
if (strpos($event->getSubject(), '_delete') !== false) {
|
||||
$event->setIcon($this->urlGenerator->getAbsoluteURL($this->urlGenerator->imagePath('files', 'delete-color.svg')));
|
||||
}
|
||||
if (str_contains($event->getSubject(), 'archive')) {
|
||||
if (strpos($event->getSubject(), 'archive') !== false) {
|
||||
$event->setIcon($this->urlGenerator->getAbsoluteURL($this->urlGenerator->imagePath('deck', 'archive.svg')));
|
||||
}
|
||||
if (str_contains($event->getSubject(), '_restore')) {
|
||||
if (strpos($event->getSubject(), '_restore') !== false) {
|
||||
$event->setIcon($this->urlGenerator->getAbsoluteURL($this->urlGenerator->imagePath('core', 'actions/history.svg')));
|
||||
}
|
||||
if (str_contains($event->getSubject(), 'attachment_')) {
|
||||
if (strpos($event->getSubject(), 'attachment_') !== false) {
|
||||
$event->setIcon($this->urlGenerator->getAbsoluteURL($this->urlGenerator->imagePath('core', 'places/files.svg')));
|
||||
}
|
||||
if (str_contains($event->getSubject(), 'comment_')) {
|
||||
if (strpos($event->getSubject(), 'comment_') !== false) {
|
||||
$event->setIcon($this->urlGenerator->getAbsoluteURL($this->urlGenerator->imagePath('core', 'actions/comment.svg')));
|
||||
}
|
||||
if (str_contains($event->getSubject(), 'label_')) {
|
||||
if (strpos($event->getSubject(), 'label_') !== false) {
|
||||
$event->setIcon($this->urlGenerator->getAbsoluteURL($this->urlGenerator->imagePath('core', 'actions/tag.svg')));
|
||||
}
|
||||
return $event;
|
||||
@@ -294,7 +294,7 @@ class DeckProvider implements IProvider {
|
||||
if (array_key_exists('comment', $subjectParams)) {
|
||||
/** @var IComment $comment */
|
||||
try {
|
||||
$comment = $this->commentsManager->get($subjectParams['comment']);
|
||||
$comment = $this->commentsManager->get((int)$subjectParams['comment']);
|
||||
$event->setParsedMessage($comment->getMessage());
|
||||
$params['comment'] = [
|
||||
'type' => 'highlight',
|
||||
@@ -312,19 +312,12 @@ class DeckProvider implements IProvider {
|
||||
$userLanguage = $this->config->getUserValue($event->getAuthor(), 'core', 'lang', $this->l10nFactory->findLanguage());
|
||||
$userLocale = $this->config->getUserValue($event->getAuthor(), 'core', 'locale', $this->l10nFactory->findLocale());
|
||||
$l10n = $this->l10nFactory->get('deck', $userLanguage, $userLocale);
|
||||
if (is_array($subjectParams['after'])) {
|
||||
// Unluckily there was a time when we stored jsonSerialized date objects in the database
|
||||
// Broken in 1.8.0 and fixed again in 1.8.1
|
||||
$date = new \DateTime($subjectParams['after']['date']);
|
||||
$date->setTimezone(new \DateTimeZone(\date_default_timezone_get()));
|
||||
} else {
|
||||
$date = new \DateTime($subjectParams['after']);
|
||||
$date->setTimezone(new \DateTimeZone(\date_default_timezone_get()));
|
||||
}
|
||||
$date = new \DateTime($subjectParams['after']);
|
||||
$date->setTimezone(new \DateTimeZone(\date_default_timezone_get()));
|
||||
$params['after'] = [
|
||||
'type' => 'highlight',
|
||||
'id' => 'dt:' . $subjectParams['after'],
|
||||
'name' => $l10n->l('datetime', $date),
|
||||
'name' => $l10n->l('datetime', $date)
|
||||
];
|
||||
}
|
||||
return $params;
|
||||
|
||||
@@ -29,7 +29,7 @@ class DescriptionSetting extends Setting {
|
||||
* @return string Lowercase a-z and underscore only identifier
|
||||
* @since 11.0.0
|
||||
*/
|
||||
public function getIdentifier(): string {
|
||||
public function getIdentifier() {
|
||||
return 'deck_card_description';
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ class DescriptionSetting extends Setting {
|
||||
* @return string A translated string
|
||||
* @since 11.0.0
|
||||
*/
|
||||
public function getName(): string {
|
||||
public function getName() {
|
||||
return $this->l->t('A <strong>card description</strong> inside the Deck app has been changed');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ class Filter implements \OCP\Activity\IFilter {
|
||||
* @return string Lowercase a-z and underscore only identifier
|
||||
* @since 11.0.0
|
||||
*/
|
||||
public function getIdentifier(): string {
|
||||
public function getIdentifier() {
|
||||
return 'deck';
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ class Filter implements \OCP\Activity\IFilter {
|
||||
* @return string A translated string
|
||||
* @since 11.0.0
|
||||
*/
|
||||
public function getName(): string {
|
||||
public function getName() {
|
||||
return $this->l10n->t('Deck');
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ class Filter implements \OCP\Activity\IFilter {
|
||||
* priority values. It is required to return a value between 0 and 100.
|
||||
* @since 11.0.0
|
||||
*/
|
||||
public function getPriority(): int {
|
||||
public function getPriority() {
|
||||
return 90;
|
||||
}
|
||||
|
||||
@@ -68,7 +68,7 @@ class Filter implements \OCP\Activity\IFilter {
|
||||
* @return string Full URL to an icon, empty string when none is given
|
||||
* @since 11.0.0
|
||||
*/
|
||||
public function getIcon(): string {
|
||||
public function getIcon() {
|
||||
return $this->urlGenerator->imagePath('deck', 'deck-dark.svg');
|
||||
}
|
||||
|
||||
@@ -77,7 +77,7 @@ class Filter implements \OCP\Activity\IFilter {
|
||||
* @return string[] An array of allowed apps from which activities should be displayed
|
||||
* @since 11.0.0
|
||||
*/
|
||||
public function filterTypes(array $types): array {
|
||||
public function filterTypes(array $types) {
|
||||
return array_merge($types, ['deck_comment']);
|
||||
}
|
||||
|
||||
@@ -85,7 +85,7 @@ class Filter implements \OCP\Activity\IFilter {
|
||||
* @return string[] An array of allowed apps from which activities should be displayed
|
||||
* @since 11.0.0
|
||||
*/
|
||||
public function allowedApps(): array {
|
||||
public function allowedApps() {
|
||||
return ['deck'];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ class Setting implements \OCP\Activity\ISetting {
|
||||
* @return string Lowercase a-z and underscore only identifier
|
||||
* @since 11.0.0
|
||||
*/
|
||||
public function getIdentifier(): string {
|
||||
public function getIdentifier() {
|
||||
return 'deck';
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ class Setting implements \OCP\Activity\ISetting {
|
||||
* @return string A translated string
|
||||
* @since 11.0.0
|
||||
*/
|
||||
public function getName(): string {
|
||||
public function getName() {
|
||||
return $this->l->t('Changes in the <strong>Deck app</strong>');
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ class Setting implements \OCP\Activity\ISetting {
|
||||
* priority values. It is required to return a value between 0 and 100.
|
||||
* @since 11.0.0
|
||||
*/
|
||||
public function getPriority(): int {
|
||||
public function getPriority() {
|
||||
return 90;
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ class Setting implements \OCP\Activity\ISetting {
|
||||
* @return bool True when the option can be changed for the stream
|
||||
* @since 11.0.0
|
||||
*/
|
||||
public function canChangeStream(): bool {
|
||||
public function canChangeStream() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -75,7 +75,7 @@ class Setting implements \OCP\Activity\ISetting {
|
||||
* @return bool True when the option can be changed for the stream
|
||||
* @since 11.0.0
|
||||
*/
|
||||
public function isDefaultEnabledStream(): bool {
|
||||
public function isDefaultEnabledStream() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -83,7 +83,7 @@ class Setting implements \OCP\Activity\ISetting {
|
||||
* @return bool True when the option can be changed for the mail
|
||||
* @since 11.0.0
|
||||
*/
|
||||
public function canChangeMail(): bool {
|
||||
public function canChangeMail() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -91,7 +91,7 @@ class Setting implements \OCP\Activity\ISetting {
|
||||
* @return bool True when the option can be changed for the stream
|
||||
* @since 11.0.0
|
||||
*/
|
||||
public function isDefaultEnabledMail(): bool {
|
||||
public function isDefaultEnabledMail() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ class SettingComment extends Setting {
|
||||
* @return string Lowercase a-z and underscore only identifier
|
||||
* @since 11.0.0
|
||||
*/
|
||||
public function getIdentifier(): string {
|
||||
public function getIdentifier() {
|
||||
return 'deck_comment';
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ class SettingComment extends Setting {
|
||||
* @return string A translated string
|
||||
* @since 11.0.0
|
||||
*/
|
||||
public function getName(): string {
|
||||
public function getName() {
|
||||
return $this->l->t('A <strong>comment</strong> was created on a card');
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ class SettingComment extends Setting {
|
||||
* @return bool True when the option can be changed for the stream
|
||||
* @since 11.0.0
|
||||
*/
|
||||
public function canChangeStream(): bool {
|
||||
public function canChangeStream() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,35 +25,28 @@ namespace OCA\Deck\AppInfo;
|
||||
|
||||
use Closure;
|
||||
use Exception;
|
||||
use OCA\Circles\Events\CircleDestroyedEvent;
|
||||
use OC\EventDispatcher\SymfonyAdapter;
|
||||
use OCA\Deck\Activity\CommentEventHandler;
|
||||
use OCA\Deck\Capabilities;
|
||||
use OCA\Deck\Collaboration\Resources\ResourceProvider;
|
||||
use OCA\Deck\Collaboration\Resources\ResourceProviderCard;
|
||||
use OCA\Deck\Dashboard\DeckWidget;
|
||||
use OCA\Deck\Db\Acl;
|
||||
use OCA\Deck\Db\AclMapper;
|
||||
use OCA\Deck\Db\AssignmentMapper;
|
||||
use OCA\Deck\Db\BoardMapper;
|
||||
use OCA\Deck\Db\CardMapper;
|
||||
use OCA\Deck\Event\AclCreatedEvent;
|
||||
use OCA\Deck\Event\AclDeletedEvent;
|
||||
use OCA\Deck\Event\AclUpdatedEvent;
|
||||
use OCA\Deck\Event\BoardUpdatedEvent;
|
||||
use OCA\Deck\Event\CardCreatedEvent;
|
||||
use OCA\Deck\Event\CardDeletedEvent;
|
||||
use OCA\Deck\Event\CardUpdatedEvent;
|
||||
use OCA\Deck\Event\SessionClosedEvent;
|
||||
use OCA\Deck\Event\SessionCreatedEvent;
|
||||
use OCA\Deck\Listeners\BeforeTemplateRenderedListener;
|
||||
use OCA\Deck\Listeners\ParticipantCleanupListener;
|
||||
use OCA\Deck\Listeners\FullTextSearchEventListener;
|
||||
use OCA\Deck\Listeners\ResourceAdditionalScriptsListener;
|
||||
use OCA\Deck\Listeners\ResourceListener;
|
||||
use OCA\Deck\Listeners\LiveUpdateListener;
|
||||
use OCA\Deck\Middleware\DefaultBoardMiddleware;
|
||||
use OCA\Deck\Middleware\ExceptionMiddleware;
|
||||
use OCA\Deck\Notification\Notifier;
|
||||
use OCA\Deck\Reference\BoardReferenceProvider;
|
||||
use OCA\Deck\Reference\CardReferenceProvider;
|
||||
use OCA\Deck\Reference\CommentReferenceProvider;
|
||||
use OCA\Deck\Search\CardCommentProvider;
|
||||
use OCA\Deck\Search\DeckProvider;
|
||||
use OCA\Deck\Service\PermissionService;
|
||||
@@ -64,17 +57,20 @@ use OCP\AppFramework\Bootstrap\IBootContext;
|
||||
use OCP\AppFramework\Bootstrap\IBootstrap;
|
||||
use OCP\AppFramework\Bootstrap\IRegistrationContext;
|
||||
use OCP\AppFramework\Http\Events\BeforeTemplateRenderedEvent;
|
||||
use OCP\Collaboration\Reference\RenderReferenceEvent;
|
||||
use OCP\Collaboration\Resources\IProviderManager;
|
||||
use OCP\Collaboration\Resources\LoadAdditionalScriptsEvent;
|
||||
use OCP\Comments\CommentsEntityEvent;
|
||||
use OCP\Comments\ICommentsManager;
|
||||
use OCP\EventDispatcher\Event;
|
||||
use OCP\EventDispatcher\IEventDispatcher;
|
||||
use OCP\Group\Events\GroupDeletedEvent;
|
||||
use OCP\IConfig;
|
||||
use OCP\IDBConnection;
|
||||
use OCP\IGroup;
|
||||
use OCP\IGroupManager;
|
||||
use OCP\IServerContainer;
|
||||
use OCP\IUser;
|
||||
use OCP\IUserManager;
|
||||
use OCP\Notification\IManager as NotificationManager;
|
||||
use OCP\Share\IManager;
|
||||
use OCP\User\Events\UserDeletedEvent;
|
||||
use OCP\Util;
|
||||
use Psr\Container\ContainerInterface;
|
||||
|
||||
@@ -83,21 +79,20 @@ class Application extends App implements IBootstrap {
|
||||
|
||||
public const COMMENT_ENTITY_TYPE = 'deckCard';
|
||||
|
||||
/** @var IServerContainer */
|
||||
private $server;
|
||||
|
||||
public function __construct(array $urlParams = []) {
|
||||
parent::__construct(self::APP_ID, $urlParams);
|
||||
|
||||
// TODO move this back to ::register after fixing the autoload issue
|
||||
// (and use a listener class)
|
||||
$container = $this->getContainer();
|
||||
$eventDispatcher = $container->get(IEventDispatcher::class);
|
||||
$eventDispatcher->addListener(RenderReferenceEvent::class, function () {
|
||||
Util::addScript(self::APP_ID, self::APP_ID . '-reference');
|
||||
});
|
||||
$this->server = \OC::$server;
|
||||
}
|
||||
|
||||
public function boot(IBootContext $context): void {
|
||||
$context->injectFn(Closure::fromCallable([$this, 'registerUserGroupHooks']));
|
||||
$context->injectFn(Closure::fromCallable([$this, 'registerCommentsEntity']));
|
||||
$context->injectFn(Closure::fromCallable([$this, 'registerCommentsEventHandler']));
|
||||
$context->injectFn(Closure::fromCallable([$this, 'registerNotifications']));
|
||||
$context->injectFn(Closure::fromCallable([$this, 'registerCollaborationResources']));
|
||||
|
||||
$context->injectFn(function (IManager $shareManager) {
|
||||
@@ -129,13 +124,8 @@ class Application extends App implements IBootstrap {
|
||||
$context->registerSearchProvider(CardCommentProvider::class);
|
||||
$context->registerDashboardWidget(DeckWidget::class);
|
||||
|
||||
// reference widget
|
||||
$context->registerReferenceProvider(CardReferenceProvider::class);
|
||||
$context->registerReferenceProvider(BoardReferenceProvider::class);
|
||||
$context->registerReferenceProvider(CommentReferenceProvider::class);
|
||||
|
||||
$context->registerEventListener(BeforeTemplateRenderedEvent::class, BeforeTemplateRenderedListener::class);
|
||||
|
||||
|
||||
// Event listening for full text search indexing
|
||||
$context->registerEventListener(CardCreatedEvent::class, FullTextSearchEventListener::class);
|
||||
$context->registerEventListener(CardUpdatedEvent::class, FullTextSearchEventListener::class);
|
||||
@@ -143,28 +133,47 @@ class Application extends App implements IBootstrap {
|
||||
$context->registerEventListener(AclCreatedEvent::class, FullTextSearchEventListener::class);
|
||||
$context->registerEventListener(AclUpdatedEvent::class, FullTextSearchEventListener::class);
|
||||
$context->registerEventListener(AclDeletedEvent::class, FullTextSearchEventListener::class);
|
||||
}
|
||||
|
||||
// Handling cache invalidation for collections
|
||||
$context->registerEventListener(AclCreatedEvent::class, ResourceListener::class);
|
||||
$context->registerEventListener(AclDeletedEvent::class, ResourceListener::class);
|
||||
public function registerNotifications(NotificationManager $notificationManager): void {
|
||||
$notificationManager->registerNotifierService(Notifier::class);
|
||||
}
|
||||
|
||||
$context->registerEventListener(UserDeletedEvent::class, ParticipantCleanupListener::class);
|
||||
$context->registerEventListener(GroupDeletedEvent::class, ParticipantCleanupListener::class);
|
||||
$context->registerEventListener(CircleDestroyedEvent::class, ParticipantCleanupListener::class);
|
||||
private function registerUserGroupHooks(IUserManager $userManager, IGroupManager $groupManager): void {
|
||||
$container = $this->getContainer();
|
||||
// Delete user/group acl entries when they get deleted
|
||||
$userManager->listen('\OC\User', 'postDelete', static function (IUser $user) use ($container) {
|
||||
// delete existing acl entries for deleted user
|
||||
/** @var AclMapper $aclMapper */
|
||||
$aclMapper = $container->query(AclMapper::class);
|
||||
$acls = $aclMapper->findByParticipant(Acl::PERMISSION_TYPE_USER, $user->getUID());
|
||||
foreach ($acls as $acl) {
|
||||
$aclMapper->delete($acl);
|
||||
}
|
||||
// delete existing user assignments
|
||||
$assignmentMapper = $container->query(AssignmentMapper::class);
|
||||
$assignments = $assignmentMapper->findByParticipant($user->getUID());
|
||||
foreach ($assignments as $assignment) {
|
||||
$assignmentMapper->delete($assignment);
|
||||
}
|
||||
|
||||
// Event listening for realtime updates via notify_push
|
||||
$context->registerEventListener(SessionCreatedEvent::class, LiveUpdateListener::class);
|
||||
$context->registerEventListener(SessionClosedEvent::class, LiveUpdateListener::class);
|
||||
$context->registerEventListener(BoardUpdatedEvent::class, LiveUpdateListener::class);
|
||||
$context->registerEventListener(CardCreatedEvent::class, LiveUpdateListener::class);
|
||||
$context->registerEventListener(CardUpdatedEvent::class, LiveUpdateListener::class);
|
||||
$context->registerEventListener(CardDeletedEvent::class, LiveUpdateListener::class);
|
||||
$context->registerEventListener(AclCreatedEvent::class, LiveUpdateListener::class);
|
||||
$context->registerEventListener(AclUpdatedEvent::class, LiveUpdateListener::class);
|
||||
$context->registerEventListener(AclDeletedEvent::class, LiveUpdateListener::class);
|
||||
/** @var BoardMapper $boardMapper */
|
||||
$boardMapper = $container->query(BoardMapper::class);
|
||||
$boards = $boardMapper->findAllByOwner($user->getUID());
|
||||
foreach ($boards as $board) {
|
||||
$boardMapper->delete($board);
|
||||
}
|
||||
});
|
||||
|
||||
$context->registerNotifierService(Notifier::class);
|
||||
$context->registerEventListener(LoadAdditionalScriptsEvent::class, ResourceAdditionalScriptsListener::class);
|
||||
$groupManager->listen('\OC\Group', 'postDelete', static function (IGroup $group) use ($container) {
|
||||
/** @var AclMapper $aclMapper */
|
||||
$aclMapper = $container->query(AclMapper::class);
|
||||
$aclMapper->findByParticipant(Acl::PERMISSION_TYPE_GROUP, $group->getGID());
|
||||
$acls = $aclMapper->findByParticipant(Acl::PERMISSION_TYPE_GROUP, $group->getGID());
|
||||
foreach ($acls as $acl) {
|
||||
$aclMapper->delete($acl);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public function registerCommentsEntity(IEventDispatcher $eventDispatcher): void {
|
||||
@@ -172,7 +181,6 @@ class Application extends App implements IBootstrap {
|
||||
$event->addEntityCollection(self::COMMENT_ENTITY_TYPE, function ($name) {
|
||||
/** @var CardMapper */
|
||||
$cardMapper = $this->getContainer()->get(CardMapper::class);
|
||||
/** @var PermissionService $permissionService */
|
||||
$permissionService = $this->getContainer()->get(PermissionService::class);
|
||||
|
||||
try {
|
||||
@@ -190,8 +198,16 @@ class Application extends App implements IBootstrap {
|
||||
});
|
||||
}
|
||||
|
||||
protected function registerCollaborationResources(IProviderManager $resourceManager): void {
|
||||
protected function registerCollaborationResources(IProviderManager $resourceManager, SymfonyAdapter $symfonyAdapter): void {
|
||||
$resourceManager->registerResourceProvider(ResourceProvider::class);
|
||||
$resourceManager->registerResourceProvider(ResourceProviderCard::class);
|
||||
|
||||
$symfonyAdapter->addListener('\OCP\Collaboration\Resources::loadAdditionalScripts', static function () {
|
||||
if (strpos(\OC::$server->getRequest()->getPathInfo(), '/call/') === 0) {
|
||||
// Talk integration has its own entrypoint which already includes collections handling
|
||||
return;
|
||||
}
|
||||
Util::addScript('deck', 'deck-collections');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,23 +32,20 @@ use OCP\AppFramework\QueryException;
|
||||
use OCP\Collaboration\Resources\IManager;
|
||||
use OCP\Collaboration\Resources\IProvider;
|
||||
use OCP\Collaboration\Resources\IResource;
|
||||
use OCP\IURLGenerator;
|
||||
use OCP\IUser;
|
||||
use OCP\Server;
|
||||
|
||||
class ResourceProvider implements IProvider {
|
||||
public const RESOURCE_TYPE = 'deck';
|
||||
|
||||
private BoardMapper $boardMapper;
|
||||
private PermissionService $permissionService;
|
||||
private IURLGenerator $urlGenerator;
|
||||
private $boardMapper;
|
||||
private $permissionService;
|
||||
|
||||
protected array $nodes = [];
|
||||
/** @var array */
|
||||
protected $nodes = [];
|
||||
|
||||
public function __construct(BoardMapper $boardMapper, PermissionService $permissionService, IURLGenerator $urlGenerator) {
|
||||
public function __construct(BoardMapper $boardMapper, PermissionService $permissionService) {
|
||||
$this->boardMapper = $boardMapper;
|
||||
$this->permissionService = $permissionService;
|
||||
$this->urlGenerator = $urlGenerator;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -73,14 +70,14 @@ class ResourceProvider implements IProvider {
|
||||
*/
|
||||
public function getResourceRichObject(IResource $resource): array {
|
||||
$board = $this->getBoard($resource);
|
||||
$link = $this->urlGenerator->linkToRoute('deck.page.index') . '#/board/' . $resource->getId();
|
||||
$link = \OC::$server->getURLGenerator()->linkToRoute('deck.page.index') . '#/board/' . $resource->getId();
|
||||
|
||||
return [
|
||||
'type' => self::RESOURCE_TYPE,
|
||||
'id' => $resource->getId(),
|
||||
'name' => $board->getTitle(),
|
||||
'link' => $link,
|
||||
'iconUrl' => $this->urlGenerator->imagePath('deck', 'deck-dark.svg')
|
||||
'iconUrl' => \OC::$server->getURLGenerator()->imagePath('deck', 'deck-dark.svg')
|
||||
];
|
||||
}
|
||||
|
||||
@@ -121,7 +118,7 @@ class ResourceProvider implements IProvider {
|
||||
public function invalidateAccessCache($boardId = null) {
|
||||
try {
|
||||
/** @var IManager $resourceManager */
|
||||
$resourceManager = Server::get(IManager::class);
|
||||
$resourceManager = \OC::$server->query(IManager::class);
|
||||
} catch (QueryException $e) {
|
||||
}
|
||||
if ($boardId !== null) {
|
||||
|
||||
@@ -37,16 +37,24 @@ use OCP\Collaboration\Resources\IResource;
|
||||
use OCP\Collaboration\Resources\ResourceException;
|
||||
use OCP\IURLGenerator;
|
||||
use OCP\IUser;
|
||||
use OCP\Server;
|
||||
|
||||
class ResourceProviderCard implements IProvider {
|
||||
public const RESOURCE_TYPE = 'deck-card';
|
||||
|
||||
private CardMapper $cardMapper;
|
||||
private BoardMapper $boardMapper;
|
||||
private PermissionService $permissionService;
|
||||
private IURLGenerator $urlGenerator;
|
||||
protected array $nodes = [];
|
||||
/** @var CardMapper */
|
||||
private $cardMapper;
|
||||
|
||||
/** @var BoardMapper */
|
||||
private $boardMapper;
|
||||
|
||||
/** @var PermissionService */
|
||||
private $permissionService;
|
||||
|
||||
/** @var IURLGenerator */
|
||||
private $urlGenerator;
|
||||
|
||||
/** @var array */
|
||||
protected $nodes = [];
|
||||
|
||||
public function __construct(CardMapper $cardMapper, BoardMapper $boardMapper, PermissionService $permissionService, IURLGenerator $urlGenerator) {
|
||||
$this->cardMapper = $cardMapper;
|
||||
@@ -139,7 +147,7 @@ class ResourceProviderCard implements IProvider {
|
||||
public function invalidateAccessCache($cardId = null) {
|
||||
try {
|
||||
/** @var IManager $resourceManager */
|
||||
$resourceManager = Server::get(IManager::class);
|
||||
$resourceManager = \OC::$server->query(IManager::class);
|
||||
} catch (QueryException $e) {
|
||||
}
|
||||
if ($cardId !== null) {
|
||||
|
||||
@@ -30,7 +30,8 @@ use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
class BoardImport extends Command {
|
||||
private BoardImportCommandService $boardImportCommandService;
|
||||
/** @var BoardImportCommandService */
|
||||
private $boardImportCommandService;
|
||||
|
||||
public function __construct(
|
||||
BoardImportCommandService $boardImportCommandService
|
||||
|
||||
@@ -27,7 +27,6 @@ use OCA\Deck\Db\AssignmentMapper;
|
||||
use OCA\Deck\Db\BoardMapper;
|
||||
use OCA\Deck\Db\CardMapper;
|
||||
use OCA\Deck\Db\StackMapper;
|
||||
use OCA\Deck\Model\CardDetails;
|
||||
use OCA\Deck\Service\BoardService;
|
||||
use OCP\AppFramework\Db\DoesNotExistException;
|
||||
use OCP\AppFramework\Db\MultipleObjectsReturnedException;
|
||||
@@ -102,9 +101,7 @@ class UserExport extends Command {
|
||||
$fullCard = $this->cardMapper->find($card->getId());
|
||||
$assignedUsers = $this->assignedUsersMapper->findAll($card->getId());
|
||||
$fullCard->setAssignedUsers($assignedUsers);
|
||||
|
||||
$cardDetails = new CardDetails($fullCard, $fullBoard);
|
||||
$data[$board->getId()]['stacks'][$stack->getId()]['cards'][] = $cardDetails->jsonSerialize();
|
||||
$data[$board->getId()]['stacks'][$stack->getId()]['cards'][] = (array)$fullCard->jsonSerialize();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ class AttachmentController extends Controller {
|
||||
* @throws \OCA\Deck\NotFoundException
|
||||
*/
|
||||
public function display($cardId, $attachmentId) {
|
||||
if (!str_contains($attachmentId, ':')) {
|
||||
if (strpos($attachmentId, ':') === false) {
|
||||
$type = 'deck_file';
|
||||
} else {
|
||||
[$type, $attachmentId] = explode(':', $attachmentId);
|
||||
@@ -76,7 +76,7 @@ class AttachmentController extends Controller {
|
||||
* @NoAdminRequired
|
||||
*/
|
||||
public function update($cardId, $attachmentId) {
|
||||
if (!str_contains($attachmentId, ':')) {
|
||||
if (strpos($attachmentId, ':') === false) {
|
||||
$type = 'deck_file';
|
||||
} else {
|
||||
[$type, $attachmentId] = explode(':', $attachmentId);
|
||||
@@ -88,7 +88,7 @@ class AttachmentController extends Controller {
|
||||
* @NoAdminRequired
|
||||
*/
|
||||
public function delete($cardId, $attachmentId) {
|
||||
if (!str_contains($attachmentId, ':')) {
|
||||
if (strpos($attachmentId, ':') === false) {
|
||||
$type = 'deck_file';
|
||||
} else {
|
||||
[$type, $attachmentId] = explode(':', $attachmentId);
|
||||
@@ -100,7 +100,7 @@ class AttachmentController extends Controller {
|
||||
* @NoAdminRequired
|
||||
*/
|
||||
public function restore($cardId, $attachmentId) {
|
||||
if (!str_contains($attachmentId, ':')) {
|
||||
if (strpos($attachmentId, ':') === false) {
|
||||
$type = 'deck_file';
|
||||
} else {
|
||||
[$type, $attachmentId] = explode(':', $attachmentId);
|
||||
|
||||
@@ -60,14 +60,12 @@ class BoardApiController extends ApiController {
|
||||
* @NoCSRFRequired
|
||||
*
|
||||
* Return all of the boards that the current user has access to.
|
||||
*
|
||||
* @param bool $details
|
||||
* @throws StatusException
|
||||
*/
|
||||
public function index(bool $details = false) {
|
||||
public function index($details = null) {
|
||||
$modified = $this->request->getHeader('If-Modified-Since');
|
||||
if ($modified === null || $modified === '') {
|
||||
$boards = $this->boardService->findAll(0, $details === true);
|
||||
$boards = $this->boardService->findAll(0, $details);
|
||||
} else {
|
||||
$date = Util::parseHTTPDate($modified);
|
||||
if (!$date) {
|
||||
|
||||
@@ -169,15 +169,4 @@ class BoardController extends ApiController {
|
||||
|
||||
return new DataResponse([], HTTP::STATUS_UNAUTHORIZED);
|
||||
}
|
||||
|
||||
/**
|
||||
* @NoAdminRequired
|
||||
* @param $boardId
|
||||
* @return Board
|
||||
* @throws \OCP\AppFramework\Db\DoesNotExistException
|
||||
* @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException
|
||||
*/
|
||||
public function export($boardId) {
|
||||
return $this->boardService->export($boardId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,12 +27,9 @@ use OCA\Deck\AppInfo\Application;
|
||||
use OCA\Deck\Service\ConfigService;
|
||||
use OCA\Deck\Service\PermissionService;
|
||||
use OCA\Files\Event\LoadSidebar;
|
||||
use OCA\Text\Event\LoadEditor;
|
||||
use OCA\Viewer\Event\LoadViewer;
|
||||
use OCP\AppFramework\Http\ContentSecurityPolicy;
|
||||
use OCP\Collaboration\Resources\LoadAdditionalScriptsEvent as CollaborationResourcesEvent;
|
||||
use OCP\EventDispatcher\IEventDispatcher;
|
||||
use OCP\IConfig;
|
||||
use OCP\IInitialStateService;
|
||||
use OCP\IRequest;
|
||||
use OCP\AppFramework\Http\TemplateResponse;
|
||||
@@ -44,17 +41,16 @@ use OCA\Deck\Db\Acl;
|
||||
use OCA\Deck\Service\CardService;
|
||||
|
||||
class PageController extends Controller {
|
||||
private PermissionService $permissionService;
|
||||
private IInitialStateService $initialState;
|
||||
private ConfigService $configService;
|
||||
private IEventDispatcher $eventDispatcher;
|
||||
private CardMapper $cardMapper;
|
||||
private IURLGenerator $urlGenerator;
|
||||
private CardService $cardService;
|
||||
private IConfig $config;
|
||||
private $permissionService;
|
||||
private $initialState;
|
||||
private $configService;
|
||||
private $eventDispatcher;
|
||||
private $cardMapper;
|
||||
private $urlGenerator;
|
||||
private $cardService;
|
||||
|
||||
public function __construct(
|
||||
string $AppName,
|
||||
$AppName,
|
||||
IRequest $request,
|
||||
PermissionService $permissionService,
|
||||
IInitialStateService $initialStateService,
|
||||
@@ -62,8 +58,7 @@ class PageController extends Controller {
|
||||
IEventDispatcher $eventDispatcher,
|
||||
CardMapper $cardMapper,
|
||||
IURLGenerator $urlGenerator,
|
||||
CardService $cardService,
|
||||
IConfig $config
|
||||
CardService $cardService
|
||||
) {
|
||||
parent::__construct($AppName, $request);
|
||||
|
||||
@@ -74,7 +69,6 @@ class PageController extends Controller {
|
||||
$this->cardMapper = $cardMapper;
|
||||
$this->urlGenerator = $urlGenerator;
|
||||
$this->cardService = $cardService;
|
||||
$this->config = $config;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -90,20 +84,13 @@ class PageController extends Controller {
|
||||
$this->initialState->provideInitialState(Application::APP_ID, 'config', $this->configService->getAll());
|
||||
|
||||
$this->eventDispatcher->dispatchTyped(new LoadSidebar());
|
||||
$this->eventDispatcher->dispatchTyped(new CollaborationResourcesEvent());
|
||||
if (class_exists(LoadEditor::class)) {
|
||||
$this->eventDispatcher->dispatchTyped(new LoadEditor());
|
||||
}
|
||||
if (class_exists(LoadViewer::class)) {
|
||||
$this->eventDispatcher->dispatchTyped(new LoadViewer());
|
||||
}
|
||||
|
||||
$response = new TemplateResponse('deck', 'main', [
|
||||
'id-app-content' => '#app-content-vue',
|
||||
'id-app-navigation' => '#app-navigation-vue',
|
||||
]);
|
||||
$response = new TemplateResponse('deck', 'main');
|
||||
|
||||
if ($this->config->getSystemValueBool('debug', false)) {
|
||||
if (\OC::$server->getConfig()->getSystemValueBool('debug', false)) {
|
||||
$csp = new ContentSecurityPolicy();
|
||||
$csp->addAllowedConnectDomain('*');
|
||||
$csp->addAllowedScriptDomain('*');
|
||||
|
||||
@@ -27,7 +27,6 @@ declare(strict_types=1);
|
||||
namespace OCA\Deck\Controller;
|
||||
|
||||
use OCA\Deck\Db\Card;
|
||||
use OCA\Deck\Model\CardDetails;
|
||||
use OCA\Deck\Service\SearchService;
|
||||
use OCP\AppFramework\Http\DataResponse;
|
||||
use OCP\AppFramework\OCSController;
|
||||
@@ -51,12 +50,9 @@ class SearchController extends OCSController {
|
||||
public function search(string $term, ?int $limit = null, ?int $cursor = null): DataResponse {
|
||||
$cards = $this->searchService->searchCards($term, $limit, $cursor);
|
||||
return new DataResponse(array_map(function (Card $card) {
|
||||
$board = $card->getRelatedBoard();
|
||||
$json = (new CardDetails($card, $board))->jsonSerialize();
|
||||
|
||||
$json['relatedBoard'] = $board;
|
||||
$json = $card->jsonSerialize();
|
||||
$json['relatedStack'] = $card->getRelatedStack();
|
||||
|
||||
$json['relatedBoard'] = $card->getRelatedBoard();
|
||||
return $json;
|
||||
}, $cards));
|
||||
}
|
||||
|
||||
@@ -1,91 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @copyright Copyright (c) 2022, chandi Langecker (git@chandi.it)
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace OCA\Deck\Controller;
|
||||
|
||||
use OCA\Deck\Service\SessionService;
|
||||
use OCA\Deck\Service\PermissionService;
|
||||
use OCA\Deck\Db\BoardMapper;
|
||||
use OCP\AppFramework\Db\DoesNotExistException;
|
||||
use OCP\AppFramework\Http\DataResponse;
|
||||
use OCP\AppFramework\OCSController;
|
||||
use OCP\IRequest;
|
||||
use OCA\Deck\Db\Acl;
|
||||
|
||||
class SessionController extends OCSController {
|
||||
private SessionService $sessionService;
|
||||
private PermissionService $permissionService;
|
||||
private BoardMapper $boardMapper;
|
||||
|
||||
public function __construct($appName,
|
||||
IRequest $request,
|
||||
SessionService $sessionService,
|
||||
PermissionService $permissionService,
|
||||
BoardMapper $boardMapper
|
||||
) {
|
||||
parent::__construct($appName, $request);
|
||||
$this->sessionService = $sessionService;
|
||||
$this->permissionService = $permissionService;
|
||||
$this->boardMapper = $boardMapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* @NoAdminRequired
|
||||
*/
|
||||
public function create(int $boardId): DataResponse {
|
||||
$this->permissionService->checkPermission($this->boardMapper, $boardId, Acl::PERMISSION_READ);
|
||||
|
||||
$session = $this->sessionService->initSession($boardId);
|
||||
return new DataResponse([
|
||||
'token' => $session->getToken(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* notifies the server that the session is still active
|
||||
* @NoAdminRequired
|
||||
* @param $boardId
|
||||
*/
|
||||
public function sync(int $boardId, string $token): DataResponse {
|
||||
$this->permissionService->checkPermission($this->boardMapper, $boardId, Acl::PERMISSION_READ);
|
||||
try {
|
||||
$this->sessionService->syncSession($boardId, $token);
|
||||
return new DataResponse([]);
|
||||
} catch (DoesNotExistException $e) {
|
||||
return new DataResponse([], 404);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* delete a session if existing
|
||||
* @NoAdminRequired
|
||||
* @NoCSRFRequired
|
||||
* @param $boardId
|
||||
*/
|
||||
public function close(int $boardId, string $token) {
|
||||
$this->permissionService->checkPermission($this->boardMapper, $boardId, Acl::PERMISSION_READ);
|
||||
$this->sessionService->closeSession($boardId, $token);
|
||||
return new DataResponse();
|
||||
}
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @copyright Copyright (c) 2022, chandi Langecker (git@chandi.it)
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
|
||||
namespace OCA\Deck\Cron;
|
||||
|
||||
use OCA\Deck\Service\SessionService;
|
||||
use OCP\AppFramework\Utility\ITimeFactory;
|
||||
use OCP\BackgroundJob\TimedJob;
|
||||
use OCP\ILogger;
|
||||
|
||||
class SessionsCleanup extends TimedJob {
|
||||
private $sessionService;
|
||||
private $documentService;
|
||||
private $logger;
|
||||
private $imageService;
|
||||
|
||||
|
||||
public function __construct(ITimeFactory $time,
|
||||
SessionService $sessionService,
|
||||
ILogger $logger) {
|
||||
parent::__construct($time);
|
||||
$this->sessionService = $sessionService;
|
||||
$this->logger = $logger;
|
||||
$this->setInterval(SessionService::SESSION_VALID_TIME);
|
||||
}
|
||||
|
||||
protected function run($argument) {
|
||||
$this->logger->debug('Run cleanup job for deck sessions');
|
||||
|
||||
$removedSessions = $this->sessionService->removeInactiveSessions();
|
||||
$this->logger->debug('Removed ' . $removedSessions . ' inactive sessions');
|
||||
}
|
||||
}
|
||||
@@ -59,21 +59,20 @@ class Calendar extends ExternalCalendar {
|
||||
}
|
||||
|
||||
public function getACL() {
|
||||
// the calendar should always have the read and the write-properties permissions
|
||||
// write-properties is needed to allow the user to toggle the visibility of shared deck calendars
|
||||
$acl = [
|
||||
[
|
||||
'privilege' => '{DAV:}read',
|
||||
'principal' => $this->getOwner(),
|
||||
'protected' => true,
|
||||
],
|
||||
[
|
||||
]
|
||||
];
|
||||
if ($this->backend->checkBoardPermission($this->board->getId(), Acl::PERMISSION_MANAGE)) {
|
||||
$acl[] = [
|
||||
'privilege' => '{DAV:}write-properties',
|
||||
'principal' => $this->getOwner(),
|
||||
'protected' => true,
|
||||
]
|
||||
];
|
||||
|
||||
];
|
||||
}
|
||||
return $acl;
|
||||
}
|
||||
|
||||
@@ -188,18 +187,12 @@ class Calendar extends ExternalCalendar {
|
||||
foreach ($properties as $key => $value) {
|
||||
switch ($key) {
|
||||
case '{DAV:}displayname':
|
||||
if (!$this->backend->checkBoardPermission($this->board->getId(), Acl::PERMISSION_MANAGE)) {
|
||||
throw new Forbidden('no permission to change the displayname');
|
||||
}
|
||||
if (mb_strpos($value, 'Deck: ') === 0) {
|
||||
$value = mb_substr($value, strlen('Deck: '));
|
||||
}
|
||||
$this->board->setTitle($value);
|
||||
break;
|
||||
case '{http://apple.com/ns/ical/}calendar-color':
|
||||
if (!$this->backend->checkBoardPermission($this->board->getId(), Acl::PERMISSION_MANAGE)) {
|
||||
throw new Forbidden('no permission to change the calendar color');
|
||||
}
|
||||
$color = substr($value, 1, 6);
|
||||
if (!preg_match('/[a-f0-9]{6}/i', $color)) {
|
||||
throw new InvalidDataException('No valid color provided');
|
||||
|
||||
@@ -59,7 +59,7 @@ class DeckCalendarBackend {
|
||||
}
|
||||
|
||||
public function getBoards(): array {
|
||||
return $this->boardService->findAll(-1, false, false);
|
||||
return $this->boardService->findAll(-1, null, false);
|
||||
}
|
||||
|
||||
public function getBoard(int $id): Board {
|
||||
|
||||
@@ -26,34 +26,18 @@ declare(strict_types=1);
|
||||
|
||||
namespace OCA\Deck\Dashboard;
|
||||
|
||||
use DateTime;
|
||||
use OCA\Deck\AppInfo\Application;
|
||||
use OCA\Deck\Db\Label;
|
||||
use OCA\Deck\Service\OverviewService;
|
||||
use OCP\Dashboard\IAPIWidget;
|
||||
use OCP\Dashboard\IButtonWidget;
|
||||
use OCP\Dashboard\IIconWidget;
|
||||
use OCP\Dashboard\Model\WidgetButton;
|
||||
use OCP\Dashboard\Model\WidgetItem;
|
||||
use OCP\IDateTimeFormatter;
|
||||
use OCP\Dashboard\IWidget;
|
||||
use OCP\IL10N;
|
||||
use OCP\IURLGenerator;
|
||||
use OCP\Util;
|
||||
|
||||
class DeckWidget implements IAPIWidget, IButtonWidget, IIconWidget {
|
||||
private IL10N $l10n;
|
||||
private OverviewService $dashboardService;
|
||||
private IURLGenerator $urlGenerator;
|
||||
private IDateTimeFormatter $dateTimeFormatter;
|
||||
class DeckWidget implements IWidget {
|
||||
|
||||
public function __construct(IL10N $l10n,
|
||||
OverviewService $dashboardService,
|
||||
IDateTimeFormatter $dateTimeFormatter,
|
||||
IURLGenerator $urlGenerator) {
|
||||
/**
|
||||
* @var IL10N
|
||||
*/
|
||||
private $l10n;
|
||||
|
||||
public function __construct(IL10N $l10n) {
|
||||
$this->l10n = $l10n;
|
||||
$this->dashboardService = $dashboardService;
|
||||
$this->urlGenerator = $urlGenerator;
|
||||
$this->dateTimeFormatter = $dateTimeFormatter;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -84,88 +68,17 @@ class DeckWidget implements IAPIWidget, IButtonWidget, IIconWidget {
|
||||
return 'icon-deck';
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getIconUrl(): string {
|
||||
return $this->urlGenerator->getAbsoluteURL(
|
||||
$this->urlGenerator->imagePath(Application::APP_ID, 'deck-dark.svg')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getUrl(): ?string {
|
||||
return $this->urlGenerator->getAbsoluteURL(
|
||||
$this->urlGenerator->linkToRoute(Application::APP_ID . '.page.index')
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function load(): void {
|
||||
Util::addScript('deck', 'deck-dashboard');
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getItems(string $userId, ?string $since = null, int $limit = 7): array {
|
||||
$upcomingCards = $this->dashboardService->findUpcomingCards($userId);
|
||||
$nowTimestamp = (new Datetime())->getTimestamp();
|
||||
$sinceTimestamp = $since !== null ? (new Datetime($since))->getTimestamp() : null;
|
||||
$upcomingCards = array_filter($upcomingCards, static function (array $card) use ($nowTimestamp, $sinceTimestamp) {
|
||||
if (isset($card['duedate'])) {
|
||||
$ts = (new Datetime($card['duedate']))->getTimestamp();
|
||||
return $ts > $nowTimestamp && ($sinceTimestamp === null || $ts > $sinceTimestamp);
|
||||
}
|
||||
return false;
|
||||
});
|
||||
usort($upcomingCards, static function ($a, $b) {
|
||||
$a = new Datetime($a['duedate']);
|
||||
$ta = $a->getTimestamp();
|
||||
$b = new Datetime($b['duedate']);
|
||||
$tb = $b->getTimestamp();
|
||||
return ($ta > $tb) ? 1 : -1;
|
||||
});
|
||||
$upcomingCards = array_slice($upcomingCards, 0, $limit);
|
||||
$urlGenerator = $this->urlGenerator;
|
||||
$dateTimeFormatter = $this->dateTimeFormatter;
|
||||
return array_map(static function (array $card) use ($urlGenerator, $dateTimeFormatter) {
|
||||
$formattedDueDate = $dateTimeFormatter->formatDateTime(new DateTime($card['duedate']));
|
||||
return new WidgetItem(
|
||||
$card['title'] . ' (' . $formattedDueDate . ')',
|
||||
implode(
|
||||
', ',
|
||||
array_map(static function (Label $label) {
|
||||
return $label->jsonSerialize()['title'];
|
||||
}, $card['labels'])
|
||||
),
|
||||
$urlGenerator->getAbsoluteURL(
|
||||
$urlGenerator->linkToRoute(Application::APP_ID . '.page.redirectToCard', ['cardId' => $card['id']])
|
||||
),
|
||||
$urlGenerator->getAbsoluteURL(
|
||||
$urlGenerator->imagePath(Application::APP_ID, 'deck-dark.svg')
|
||||
),
|
||||
$card['duedate']
|
||||
);
|
||||
}, $upcomingCards);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getWidgetButtons(string $userId): array {
|
||||
return [
|
||||
new WidgetButton(
|
||||
WidgetButton::TYPE_MORE,
|
||||
$this->urlGenerator->getAbsoluteURL(
|
||||
$this->urlGenerator->linkToRoute(Application::APP_ID . '.page.index')
|
||||
),
|
||||
$this->l10n->t('Load more')
|
||||
),
|
||||
];
|
||||
\OCP\Util::addScript('deck', 'deck-dashboard');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,66 +28,23 @@ use OCP\AppFramework\Db\MultipleObjectsReturnedException;
|
||||
use OCP\DB\QueryBuilder\IQueryBuilder;
|
||||
use OCP\IDBConnection;
|
||||
|
||||
/** @template-extends DeckMapper<Acl> */
|
||||
class AclMapper extends DeckMapper implements IPermissionMapper {
|
||||
public function __construct(IDBConnection $db) {
|
||||
parent::__construct($db, 'deck_board_acl', Acl::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param numeric $boardId
|
||||
* @param int|null $limit
|
||||
* @param int|null $offset
|
||||
* @return Acl[]
|
||||
* @throws \OCP\DB\Exception
|
||||
*/
|
||||
public function findAll($boardId, $limit = null, $offset = null) {
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb->select('id', 'board_id', 'type', 'participant', 'permission_edit', 'permission_share', 'permission_manage')
|
||||
->from('deck_board_acl')
|
||||
->where($qb->expr()->eq('board_id', $qb->createNamedParameter($boardId, IQueryBuilder::PARAM_INT)))
|
||||
->setMaxResults($limit)
|
||||
->setFirstResult($offset);
|
||||
|
||||
return $this->findEntities($qb);
|
||||
$sql = 'SELECT id, board_id, type, participant, permission_edit, permission_share, permission_manage FROM `*PREFIX*deck_board_acl` WHERE `board_id` = ? ';
|
||||
return $this->findEntities($sql, [$boardId], $limit, $offset);
|
||||
}
|
||||
|
||||
public function findIn(array $boardIds, ?int $limit = null, ?int $offset = null): array {
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb->select('id', 'board_id', 'type', 'participant', 'permission_edit', 'permission_share', 'permission_manage')
|
||||
->from('deck_board_acl')
|
||||
->where($qb->expr()->in('board_id', $qb->createParameter('boardIds')))
|
||||
->setMaxResults($limit)
|
||||
->setFirstResult($offset);
|
||||
|
||||
return iterator_to_array($this->chunkQuery($boardIds, function (array $ids) use ($qb) {
|
||||
$qb->setParameter('boardIds', $ids, IQueryBuilder::PARAM_INT_ARRAY);
|
||||
return $this->findEntities($qb);
|
||||
}));
|
||||
public function isOwner($userId, $aclId): bool {
|
||||
$sql = 'SELECT owner FROM `*PREFIX*deck_boards` WHERE `id` IN (SELECT board_id FROM `*PREFIX*deck_board_acl` WHERE id = ?)';
|
||||
$stmt = $this->execute($sql, [$aclId]);
|
||||
$row = $stmt->fetch();
|
||||
return ($row['owner'] === $userId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param numeric $userId
|
||||
* @param numeric $id
|
||||
* @return bool
|
||||
* @throws \OCP\DB\Exception
|
||||
*/
|
||||
public function isOwner($userId, $id): bool {
|
||||
$aclId = $id;
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb->select('acl.id')
|
||||
->from($this->getTableName(), 'acl')
|
||||
->innerJoin('acl', 'deck_boards', 'b', 'acl.board_id = b.id')
|
||||
->where($qb->expr()->eq('owner', $qb->createNamedParameter($userId, IQueryBuilder::PARAM_STR)))
|
||||
->andWhere($qb->expr()->eq('acl.id', $qb->createNamedParameter($aclId, IQueryBuilder::PARAM_INT)));
|
||||
|
||||
return count($qb->executeQuery()->fetchAll()) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param numeric $id
|
||||
* @return int|null
|
||||
*/
|
||||
public function findBoardId($id): ?int {
|
||||
try {
|
||||
$entity = $this->find($id);
|
||||
@@ -97,21 +54,9 @@ class AclMapper extends DeckMapper implements IPermissionMapper {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $type
|
||||
* @param string $participant
|
||||
* @return Acl[]
|
||||
* @throws \OCP\DB\Exception
|
||||
*/
|
||||
public function findByParticipant($type, $participant): array {
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
|
||||
$qb->select('*')
|
||||
->from($this->getTableName())
|
||||
->where($qb->expr()->eq('type', $qb->createNamedParameter($type, IQueryBuilder::PARAM_INT)))
|
||||
->andWhere($qb->expr()->eq('participant', $qb->createNamedParameter($participant, IQueryBuilder::PARAM_STR)));
|
||||
|
||||
return $this->findEntities($qb);
|
||||
$sql = 'SELECT * from *PREFIX*deck_board_acl WHERE type = ? AND participant = ?';
|
||||
return $this->findEntities($sql, [$type, $participant]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -125,12 +70,4 @@ class AclMapper extends DeckMapper implements IPermissionMapper {
|
||||
->andWhere($qb->expr()->eq('board_id', $qb->createNamedParameter($boardId, IQueryBuilder::PARAM_INT)));
|
||||
$qb->executeStatement();
|
||||
}
|
||||
|
||||
public function findByType(int $type): array {
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb->select('*')
|
||||
->from('deck_board_acl')
|
||||
->where($qb->expr()->eq('type', $qb->createNamedParameter($type, IQueryBuilder::PARAM_INT)));
|
||||
return $this->findEntities($qb);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,13 +28,14 @@ namespace OCA\Deck\Db;
|
||||
use OCA\Deck\NotFoundException;
|
||||
use OCA\Deck\Service\CirclesService;
|
||||
use OCP\AppFramework\Db\Entity;
|
||||
use OCP\AppFramework\Db\QBMapper;
|
||||
use OCP\DB\QueryBuilder\IQueryBuilder;
|
||||
use OCP\IDBConnection;
|
||||
use OCP\IGroupManager;
|
||||
use OCP\IUserManager;
|
||||
use PDO;
|
||||
|
||||
/** @template-extends DeckMapper<Assignment> */
|
||||
class AssignmentMapper extends DeckMapper implements IPermissionMapper {
|
||||
class AssignmentMapper extends QBMapper implements IPermissionMapper {
|
||||
|
||||
/** @var CardMapper */
|
||||
private $cardMapper;
|
||||
@@ -54,11 +55,14 @@ class AssignmentMapper extends DeckMapper implements IPermissionMapper {
|
||||
$this->circleService = $circleService;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Assignment[]
|
||||
*/
|
||||
public function findAll(int $cardId): array {
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb->select('*')
|
||||
->from('deck_assigned_users')
|
||||
->where($qb->expr()->eq('card_id', $qb->createNamedParameter($cardId, IQueryBuilder::PARAM_INT)));
|
||||
->where($qb->expr()->eq('card_id', $qb->createNamedParameter($cardId, PDO::PARAM_INT)));
|
||||
$users = $this->findEntities($qb);
|
||||
foreach ($users as $user) {
|
||||
$this->mapParticipant($user);
|
||||
@@ -66,35 +70,18 @@ class AssignmentMapper extends DeckMapper implements IPermissionMapper {
|
||||
return $users;
|
||||
}
|
||||
|
||||
public function findIn(array $cardIds): array {
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb->select('*')
|
||||
->from('deck_assigned_users')
|
||||
->where($qb->expr()->in('card_id', $qb->createParameter('cardIds')));
|
||||
|
||||
$users = iterator_to_array($this->chunkQuery($cardIds, function (array $ids) use ($qb) {
|
||||
$qb->setParameter('cardIds', $ids, IQueryBuilder::PARAM_INT_ARRAY);
|
||||
return $this->findEntities($qb);
|
||||
}));
|
||||
|
||||
foreach ($users as $user) {
|
||||
$this->mapParticipant($user);
|
||||
}
|
||||
return $users;
|
||||
}
|
||||
|
||||
public function findByParticipant(string $participant, $type = Assignment::TYPE_USER): array {
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb->select('*')
|
||||
->from('deck_assigned_users')
|
||||
->where($qb->expr()->eq('participant', $qb->createNamedParameter($participant, IQueryBuilder::PARAM_STR)))
|
||||
->andWhere($qb->expr()->eq('type', $qb->createNamedParameter($type, IQueryBuilder::PARAM_INT)));
|
||||
->where($qb->expr()->eq('participant', $qb->createNamedParameter($participant, PDO::PARAM_STR)))
|
||||
->andWhere($qb->expr()->eq('type', $qb->createNamedParameter($type, PDO::PARAM_INT)));
|
||||
return $this->findEntities($qb);
|
||||
}
|
||||
|
||||
|
||||
public function isOwner($userId, $id): bool {
|
||||
return $this->cardMapper->isOwner($userId, $id);
|
||||
public function isOwner($userId, $cardId): bool {
|
||||
return $this->cardMapper->isOwner($userId, $cardId);
|
||||
}
|
||||
|
||||
public function findBoardId($id): ?int {
|
||||
@@ -147,8 +134,8 @@ class AssignmentMapper extends DeckMapper implements IPermissionMapper {
|
||||
|
||||
private function getOrigin(Assignment $assignment) {
|
||||
if ($assignment->getType() === Assignment::TYPE_USER) {
|
||||
$origin = $this->userManager->userExists($assignment->getParticipant());
|
||||
return $origin ? new User($assignment->getParticipant(), $this->userManager) : null;
|
||||
$origin = $this->userManager->get($assignment->getParticipant());
|
||||
return $origin ? new User($origin) : null;
|
||||
}
|
||||
if ($assignment->getType() === Assignment::TYPE_GROUP) {
|
||||
$origin = $this->groupManager->get($assignment->getParticipant());
|
||||
|
||||
@@ -30,8 +30,8 @@ use OCP\AppFramework\Db\MultipleObjectsReturnedException;
|
||||
use OCP\DB\QueryBuilder\IQueryBuilder;
|
||||
use OCP\IDBConnection;
|
||||
use OCP\IUserManager;
|
||||
use PDO;
|
||||
|
||||
/** @template-extends DeckMapper<Attachment> */
|
||||
class AttachmentMapper extends DeckMapper implements IPermissionMapper {
|
||||
private $cardMapper;
|
||||
private $userManager;
|
||||
@@ -52,53 +52,70 @@ class AttachmentMapper extends DeckMapper implements IPermissionMapper {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $id
|
||||
* @return Attachment
|
||||
* @throws DoesNotExistException
|
||||
* @throws MultipleObjectsReturnedException
|
||||
* @throws \OCP\DB\Exception
|
||||
* @param $id
|
||||
* @return Entity|Attachment
|
||||
* @throws \OCP\AppFramework\Db\DoesNotExistException
|
||||
* @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException
|
||||
*/
|
||||
public function find($id) {
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb->select('*')
|
||||
->from($this->getTableName())
|
||||
->from('deck_attachment')
|
||||
->where($qb->expr()->eq('id', $qb->createNamedParameter($id, IQueryBuilder::PARAM_INT)));
|
||||
|
||||
return $this->findEntity($qb);
|
||||
$cursor = $qb->execute();
|
||||
$row = $cursor->fetch(PDO::FETCH_ASSOC);
|
||||
if ($row === false) {
|
||||
$cursor->closeCursor();
|
||||
throw new DoesNotExistException('Did expect one result but found none when executing query: ' . $qb->getSQL());
|
||||
}
|
||||
|
||||
$row2 = $cursor->fetch();
|
||||
$cursor->closeCursor();
|
||||
if ($row2 !== false) {
|
||||
throw new MultipleObjectsReturnedException('Did not expect more than one result when executing query: ' . $qb->getSQL());
|
||||
}
|
||||
|
||||
return $this->mapRowToEntity($row);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $cardId
|
||||
* @param string $data
|
||||
* @return Attachment
|
||||
* @throws DoesNotExistException
|
||||
* @throws MultipleObjectsReturnedException
|
||||
* @throws \OCP\DB\Exception
|
||||
*/
|
||||
public function findByData($cardId, $data) {
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb->select('*')
|
||||
->from($this->getTableName())
|
||||
->from('deck_attachment')
|
||||
->where($qb->expr()->eq('card_id', $qb->createNamedParameter($cardId, IQueryBuilder::PARAM_INT)))
|
||||
->andWhere($qb->expr()->eq('data', $qb->createNamedParameter($data, IQueryBuilder::PARAM_STR)));
|
||||
|
||||
return $this->findEntity($qb);
|
||||
$cursor = $qb->execute();
|
||||
$row = $cursor->fetch(PDO::FETCH_ASSOC);
|
||||
if ($row === false) {
|
||||
$cursor->closeCursor();
|
||||
throw new DoesNotExistException('Did expect one result but found none when executing query: ' . $qb->getSQL());
|
||||
}
|
||||
$cursor->closeCursor();
|
||||
return $this->mapRowToEntity($row);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find all attachments for a card
|
||||
*
|
||||
* @param $cardId
|
||||
* @return Entity[]
|
||||
* @throws \OCP\DB\Exception
|
||||
* @return array
|
||||
*/
|
||||
public function findAll($cardId) {
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb->select('*')
|
||||
->from($this->getTableName())
|
||||
->from('deck_attachment')
|
||||
->where($qb->expr()->eq('card_id', $qb->createNamedParameter($cardId, IQueryBuilder::PARAM_INT)))
|
||||
->andWhere($qb->expr()->eq('deleted_at', $qb->createNamedParameter(0, IQueryBuilder::PARAM_INT)));
|
||||
|
||||
|
||||
return $this->findEntities($qb);
|
||||
$entities = [];
|
||||
$cursor = $qb->execute();
|
||||
while ($row = $cursor->fetch()) {
|
||||
$entities[] = $this->mapRowToEntity($row);
|
||||
}
|
||||
$cursor->closeCursor();
|
||||
return $entities;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -111,7 +128,7 @@ class AttachmentMapper extends DeckMapper implements IPermissionMapper {
|
||||
$timeLimit = time() - (60 * 5);
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb->select('*')
|
||||
->from($this->getTableName())
|
||||
->from('deck_attachment')
|
||||
->where($qb->expr()->gt('deleted_at', $qb->createNamedParameter(0, IQueryBuilder::PARAM_INT)));
|
||||
if ($withOffset) {
|
||||
$qb
|
||||
@@ -122,7 +139,13 @@ class AttachmentMapper extends DeckMapper implements IPermissionMapper {
|
||||
->andWhere($qb->expr()->eq('card_id', $qb->createNamedParameter($cardId, IQueryBuilder::PARAM_INT)));
|
||||
}
|
||||
|
||||
return $this->findEntities($qb);
|
||||
$entities = [];
|
||||
$cursor = $qb->execute();
|
||||
while ($row = $cursor->fetch()) {
|
||||
$entities[] = $this->mapRowToEntity($row);
|
||||
}
|
||||
$cursor->closeCursor();
|
||||
return $entities;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -23,14 +23,6 @@
|
||||
|
||||
namespace OCA\Deck\Db;
|
||||
|
||||
/**
|
||||
* @method int getId()
|
||||
* @method string getTitle()
|
||||
* @method int getShared()
|
||||
* @method bool getArchived()
|
||||
* @method int getDeletedAt()
|
||||
* @method int getLastModified()
|
||||
*/
|
||||
class Board extends RelationalEntity {
|
||||
protected $title;
|
||||
protected $owner;
|
||||
@@ -44,7 +36,6 @@ class Board extends RelationalEntity {
|
||||
protected $users = [];
|
||||
protected $shared;
|
||||
protected $stacks = [];
|
||||
protected $activeSessions = [];
|
||||
protected $deletedAt = 0;
|
||||
protected $lastModified = 0;
|
||||
|
||||
@@ -60,7 +51,6 @@ class Board extends RelationalEntity {
|
||||
$this->addRelation('acl');
|
||||
$this->addRelation('shared');
|
||||
$this->addRelation('users');
|
||||
$this->addRelation('activeSessions');
|
||||
$this->addRelation('permissions');
|
||||
$this->addRelation('stacks');
|
||||
$this->addRelation('settings');
|
||||
|
||||
@@ -33,7 +33,6 @@ use OCP\IUserManager;
|
||||
use OCP\IGroupManager;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
/** @template-extends QBMapper<Board> */
|
||||
class BoardMapper extends QBMapper implements IPermissionMapper {
|
||||
private $labelMapper;
|
||||
private $aclMapper;
|
||||
@@ -43,9 +42,9 @@ class BoardMapper extends QBMapper implements IPermissionMapper {
|
||||
private $circlesService;
|
||||
private $logger;
|
||||
|
||||
/** @var CappedMemoryCache<Board[]> */
|
||||
/** @var CappedMemoryCache */
|
||||
private $userBoardCache;
|
||||
/** @var CappedMemoryCache<Board> */
|
||||
/** @var CappedMemoryCache */
|
||||
private $boardCache;
|
||||
|
||||
public function __construct(
|
||||
@@ -90,6 +89,9 @@ class BoardMapper extends QBMapper implements IPermissionMapper {
|
||||
$this->boardCache[$id] = $this->findEntity($qb);
|
||||
}
|
||||
|
||||
// FIXME is this necessary? it was NOT done with the old mapper
|
||||
// $this->mapOwner($board);
|
||||
|
||||
// Add labels
|
||||
if ($withLabels && $this->boardCache[$id]->getLabels() === null) {
|
||||
$labels = $this->labelMapper->findAll($id);
|
||||
@@ -105,47 +107,6 @@ class BoardMapper extends QBMapper implements IPermissionMapper {
|
||||
return $this->boardCache[$id];
|
||||
}
|
||||
|
||||
public function findBoardIds(string $userId): array {
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb->selectDistinct('b.id')
|
||||
->from($this->getTableName(), 'b')
|
||||
->leftJoin('b', 'deck_board_acl', 'acl', $qb->expr()->eq('b.id', 'acl.board_id'));
|
||||
|
||||
// Owned by the user
|
||||
$qb->where($qb->expr()->andX(
|
||||
$qb->expr()->eq('owner', $qb->createNamedParameter($userId, IQueryBuilder::PARAM_STR)),
|
||||
));
|
||||
|
||||
// Shared to the user
|
||||
$qb->orWhere($qb->expr()->andX(
|
||||
$qb->expr()->eq('acl.participant', $qb->createNamedParameter($userId, IQueryBuilder::PARAM_STR)),
|
||||
$qb->expr()->eq('acl.type', $qb->createNamedParameter(Acl::PERMISSION_TYPE_USER, IQueryBuilder::PARAM_INT)),
|
||||
));
|
||||
|
||||
// Shared to user groups of the user
|
||||
$groupIds = $this->groupManager->getUserGroupIds($this->userManager->get($userId));
|
||||
if (count($groupIds) !== 0) {
|
||||
$qb->orWhere($qb->expr()->andX(
|
||||
$qb->expr()->in('acl.participant', $qb->createNamedParameter($groupIds, IQueryBuilder::PARAM_STR_ARRAY)),
|
||||
$qb->expr()->eq('acl.type', $qb->createNamedParameter(Acl::PERMISSION_TYPE_GROUP, IQueryBuilder::PARAM_INT)),
|
||||
));
|
||||
}
|
||||
|
||||
// Shared to circles of the user
|
||||
$circles = $this->circlesService->getUserCircles($userId);
|
||||
if (count($circles) !== 0) {
|
||||
$qb->orWhere($qb->expr()->andX(
|
||||
$qb->expr()->in('acl.participant', $qb->createNamedParameter($circles, IQueryBuilder::PARAM_STR_ARRAY)),
|
||||
$qb->expr()->eq('acl.type', $qb->createNamedParameter(Acl::PERMISSION_TYPE_CIRCLE, IQueryBuilder::PARAM_INT)),
|
||||
));
|
||||
}
|
||||
|
||||
$result = $qb->executeQuery();
|
||||
return array_map(function (string $id) {
|
||||
return (int)$id;
|
||||
}, $result->fetchAll(\PDO::FETCH_COLUMN));
|
||||
}
|
||||
|
||||
public function findAllForUser(string $userId, ?int $since = null, bool $includeArchived = true, ?int $before = null,
|
||||
?string $term = null): array {
|
||||
$useCache = ($since === -1 && $includeArchived === true && $before === null && $term === null);
|
||||
@@ -156,21 +117,7 @@ class BoardMapper extends QBMapper implements IPermissionMapper {
|
||||
$userBoards = $this->findAllByUser($userId, null, null, $since, $includeArchived, $before, $term);
|
||||
$groupBoards = $this->findAllByGroups($userId, $groups, null, null, $since, $includeArchived, $before, $term);
|
||||
$circleBoards = $this->findAllByCircles($userId, null, null, $since, $includeArchived, $before, $term);
|
||||
$allBoards = array_values(array_unique(array_merge($userBoards, $groupBoards, $circleBoards)));
|
||||
|
||||
// Could be moved outside
|
||||
$acls = $this->aclMapper->findIn(array_map(function ($board) {
|
||||
return $board->getId();
|
||||
}, $allBoards));
|
||||
|
||||
/* @var Board $entry */
|
||||
foreach ($allBoards as $entry) {
|
||||
$boardAcls = array_values(array_filter($acls, function ($acl) use ($entry) {
|
||||
return $acl->getBoardId() === $entry->getId();
|
||||
}));
|
||||
$entry->setAcl($boardAcls);
|
||||
}
|
||||
|
||||
$allBoards = array_unique(array_merge($userBoards, $groupBoards, $circleBoards));
|
||||
foreach ($allBoards as $board) {
|
||||
$this->boardCache[$board->getId()] = $board;
|
||||
}
|
||||
@@ -184,9 +131,14 @@ class BoardMapper extends QBMapper implements IPermissionMapper {
|
||||
|
||||
/**
|
||||
* Find all boards for a given user
|
||||
*
|
||||
* @param $userId
|
||||
* @param null $limit
|
||||
* @param null $offset
|
||||
* @return array
|
||||
*/
|
||||
public function findAllByUser(string $userId, ?int $limit = null, ?int $offset = null, ?int $since = null,
|
||||
bool $includeArchived = true, ?int $before = null, ?string $term = null): array {
|
||||
bool $includeArchived = true, ?int $before = null, ?string $term = null) {
|
||||
// FIXME this used to be a UNION to get boards owned by $userId and the user shares in one single query
|
||||
// Is it possible with the query builder?
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
@@ -270,7 +222,11 @@ class BoardMapper extends QBMapper implements IPermissionMapper {
|
||||
$entry->setShared(1);
|
||||
}
|
||||
$entries = array_merge($entries, $sharedEntries);
|
||||
|
||||
/* @var Board $entry */
|
||||
foreach ($entries as $entry) {
|
||||
$acl = $this->aclMapper->findAll($entry->id);
|
||||
$entry->setAcl($acl);
|
||||
}
|
||||
return $entries;
|
||||
}
|
||||
|
||||
@@ -291,9 +247,15 @@ class BoardMapper extends QBMapper implements IPermissionMapper {
|
||||
|
||||
/**
|
||||
* Find all boards for a given user
|
||||
*
|
||||
* @param $userId
|
||||
* @param $groups
|
||||
* @param null $limit
|
||||
* @param null $offset
|
||||
* @return array
|
||||
*/
|
||||
public function findAllByGroups(string $userId, array $groups, ?int $limit = null, ?int $offset = null, ?int $since = null,
|
||||
bool $includeArchived = true, ?int $before = null, ?string $term = null): array {
|
||||
bool $includeArchived = true, ?int $before = null, ?string $term = null) {
|
||||
if (count($groups) <= 0) {
|
||||
return [];
|
||||
}
|
||||
@@ -343,6 +305,11 @@ class BoardMapper extends QBMapper implements IPermissionMapper {
|
||||
foreach ($entries as $entry) {
|
||||
$entry->setShared(2);
|
||||
}
|
||||
/* @var Board $entry */
|
||||
foreach ($entries as $entry) {
|
||||
$acl = $this->aclMapper->findAll($entry->id);
|
||||
$entry->setAcl($acl);
|
||||
}
|
||||
return $entries;
|
||||
}
|
||||
|
||||
@@ -399,6 +366,11 @@ class BoardMapper extends QBMapper implements IPermissionMapper {
|
||||
foreach ($entries as $entry) {
|
||||
$entry->setShared(2);
|
||||
}
|
||||
/* @var Board $entry */
|
||||
foreach ($entries as $entry) {
|
||||
$acl = $this->aclMapper->findAll($entry->id);
|
||||
$entry->setAcl($acl);
|
||||
}
|
||||
return $entries;
|
||||
}
|
||||
|
||||
@@ -442,8 +414,8 @@ class BoardMapper extends QBMapper implements IPermissionMapper {
|
||||
return parent::delete($entity);
|
||||
}
|
||||
|
||||
public function isOwner($userId, $id): bool {
|
||||
$board = $this->find($id);
|
||||
public function isOwner($userId, $boardId): bool {
|
||||
$board = $this->find($boardId);
|
||||
return ($board->getOwner() === $userId);
|
||||
}
|
||||
|
||||
@@ -452,11 +424,13 @@ class BoardMapper extends QBMapper implements IPermissionMapper {
|
||||
}
|
||||
|
||||
public function mapAcl(Acl &$acl) {
|
||||
$userManager = $this->userManager;
|
||||
$groupManager = $this->groupManager;
|
||||
$acl->resolveRelation('participant', function ($participant) use (&$acl, &$userManager, &$groupManager) {
|
||||
if ($acl->getType() === Acl::PERMISSION_TYPE_USER) {
|
||||
if ($this->userManager->userExists($acl->getParticipant())) {
|
||||
return new User($acl->getParticipant(), $this->userManager);
|
||||
$user = $userManager->get($participant);
|
||||
if ($user !== null) {
|
||||
return new User($user);
|
||||
}
|
||||
$this->logger->debug('User ' . $acl->getId() . ' not found when mapping acl ' . $acl->getParticipant());
|
||||
return null;
|
||||
@@ -494,8 +468,9 @@ class BoardMapper extends QBMapper implements IPermissionMapper {
|
||||
public function mapOwner(Board &$board) {
|
||||
$userManager = $this->userManager;
|
||||
$board->resolveRelation('owner', function ($owner) use (&$userManager) {
|
||||
if ($this->userManager->userExists($owner)) {
|
||||
return new User($owner, $userManager);
|
||||
$user = $userManager->get($owner);
|
||||
if ($user !== null) {
|
||||
return new User($user);
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
100
lib/Db/Card.php
@@ -27,44 +27,6 @@ use DateTime;
|
||||
use DateTimeZone;
|
||||
use Sabre\VObject\Component\VCalendar;
|
||||
|
||||
/**
|
||||
* @method string getTitle()
|
||||
* @method string getDescription()
|
||||
* @method string getDescriptionPrev()
|
||||
* @method int getStackId()
|
||||
* @method int getOrder()
|
||||
* @method int getLastModified()
|
||||
* @method int getCreatedAt()
|
||||
* @method bool getArchived()
|
||||
* @method bool getNotified()
|
||||
*
|
||||
* @method void setLabels(Label[] $labels)
|
||||
* @method null|Label[] getLabels()
|
||||
*
|
||||
* @method void setAssignedUsers(Assignment[] $users)
|
||||
* @method null|User[] getAssignedUsers()
|
||||
*
|
||||
* @method void setAttachments(Attachment[] $attachments)
|
||||
* @method null|Attachment[] getAttachments()
|
||||
*
|
||||
* @method void setAttachmentCount(int $count)
|
||||
* @method null|int getAttachmentCount()
|
||||
*
|
||||
* @method void setCommentsUnread(int $count)
|
||||
* @method null|int getCommentsUnread()
|
||||
*
|
||||
* @method void setCommentsCount(int $count)
|
||||
* @method null|int getCommentsCount()
|
||||
*
|
||||
* @method void setOwner(string $user)
|
||||
* @method null|string getOwner()
|
||||
*
|
||||
* @method void setRelatedStack(Stack $stack)
|
||||
* @method null|Stack getRelatedStack()
|
||||
*
|
||||
* @method void setRelatedBoard(Board $board)
|
||||
* @method null|Board getRelatedBoard()
|
||||
*/
|
||||
class Card extends RelationalEntity {
|
||||
public const TITLE_MAX_LENGTH = 255;
|
||||
|
||||
@@ -108,7 +70,6 @@ class Card extends RelationalEntity {
|
||||
$this->addType('archived', 'boolean');
|
||||
$this->addType('notified', 'boolean');
|
||||
$this->addType('deletedAt', 'integer');
|
||||
$this->addType('duedate', 'datetime');
|
||||
$this->addRelation('labels');
|
||||
$this->addRelation('assignedUsers');
|
||||
$this->addRelation('attachments');
|
||||
@@ -126,6 +87,50 @@ class Card extends RelationalEntity {
|
||||
$this->databaseType = $type;
|
||||
}
|
||||
|
||||
public function getDuedate($isoFormat = false) {
|
||||
if ($this->duedate === null) {
|
||||
return null;
|
||||
}
|
||||
$dt = new DateTime($this->duedate);
|
||||
if (!$isoFormat && $this->databaseType === 'mysql') {
|
||||
return $dt->format('Y-m-d H:i:s');
|
||||
}
|
||||
return $dt->format('c');
|
||||
}
|
||||
|
||||
public function jsonSerialize(): array {
|
||||
$json = parent::jsonSerialize();
|
||||
$json['overdue'] = self::DUEDATE_FUTURE;
|
||||
$due = $this->duedate ? strtotime($this->duedate) : false;
|
||||
if ($due !== false) {
|
||||
$today = new DateTime();
|
||||
$today->setTime(0, 0);
|
||||
|
||||
$match_date = new DateTime($this->duedate);
|
||||
|
||||
$match_date->setTime(0, 0);
|
||||
|
||||
$diff = $today->diff($match_date);
|
||||
$diffDays = (integer) $diff->format('%R%a'); // Extract days count in interval
|
||||
|
||||
if ($diffDays === 1) {
|
||||
$json['overdue'] = self::DUEDATE_NEXT;
|
||||
}
|
||||
if ($diffDays === 0) {
|
||||
$json['overdue'] = self::DUEDATE_NOW;
|
||||
}
|
||||
if ($diffDays < 0) {
|
||||
$json['overdue'] = self::DUEDATE_OVERDUE;
|
||||
}
|
||||
}
|
||||
$json['duedate'] = $this->getDuedate(true);
|
||||
unset($json['notified']);
|
||||
unset($json['descriptionPrev']);
|
||||
unset($json['relatedStack']);
|
||||
unset($json['relatedBoard']);
|
||||
return $json;
|
||||
}
|
||||
|
||||
public function getCalendarObject(): VCalendar {
|
||||
$calendar = new VCalendar();
|
||||
$event = $calendar->createComponent('VTODO');
|
||||
@@ -134,7 +139,7 @@ class Card extends RelationalEntity {
|
||||
$creationDate = new DateTime();
|
||||
$creationDate->setTimestamp($this->createdAt);
|
||||
$event->DTSTAMP = $creationDate;
|
||||
$event->DUE = new DateTime($this->getDuedate()->format('c'), new DateTimeZone('UTC'));
|
||||
$event->DUE = new DateTime($this->getDuedate(true), new DateTimeZone('UTC'));
|
||||
}
|
||||
$event->add('RELATED-TO', 'deck-stack-' . $this->getStackId());
|
||||
|
||||
@@ -158,21 +163,6 @@ class Card extends RelationalEntity {
|
||||
return $calendar;
|
||||
}
|
||||
|
||||
public function getDaysUntilDue(): ?int {
|
||||
if ($this->getDuedate() === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$today = new DateTime();
|
||||
$today->setTime(0, 0);
|
||||
|
||||
$matchDate = DateTime::createFromInterface($this->getDuedate());
|
||||
$matchDate->setTime(0, 0);
|
||||
|
||||
$diff = $today->diff($matchDate);
|
||||
return (int) $diff->format('%R%a'); // Extract days count in interval
|
||||
}
|
||||
|
||||
public function getCalendarPrefix(): string {
|
||||
return 'card';
|
||||
}
|
||||
|
||||
@@ -38,7 +38,6 @@ use OCP\IUser;
|
||||
use OCP\IUserManager;
|
||||
use OCP\Notification\IManager;
|
||||
|
||||
/** @template-extends QBMapper<Card> */
|
||||
class CardMapper extends QBMapper implements IPermissionMapper {
|
||||
|
||||
/** @var LabelMapper */
|
||||
@@ -239,28 +238,13 @@ class CardMapper extends QBMapper implements IPermissionMapper {
|
||||
return $this->findEntities($qb);
|
||||
}
|
||||
|
||||
public function findAllByBoardId(int $boardId, ?int $limit = null, ?int $offset = null): array {
|
||||
public function findAllWithDue($boardId) {
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb->select('c.*')
|
||||
->from('deck_cards', 'c')
|
||||
->innerJoin('c', 'deck_stacks', 's', 's.id = c.stack_id')
|
||||
->innerJoin('s', 'deck_boards', 'b', 'b.id = s.board_id')
|
||||
->where($qb->expr()->eq('board_id', $qb->createNamedParameter($boardId, IQueryBuilder::PARAM_INT)))
|
||||
->andWhere($qb->expr()->eq('archived', $qb->createNamedParameter(false, IQueryBuilder::PARAM_BOOL)))
|
||||
->setMaxResults($limit)
|
||||
->setFirstResult($offset)
|
||||
->orderBy('c.lastmodified')
|
||||
->addOrderBy('c.id');
|
||||
return $this->findEntities($qb);
|
||||
}
|
||||
|
||||
public function findAllWithDue(array $boardIds) {
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb->select('c.*')
|
||||
->from('deck_cards', 'c')
|
||||
->innerJoin('c', 'deck_stacks', 's', 's.id = c.stack_id')
|
||||
->innerJoin('s', 'deck_boards', 'b', 'b.id = s.board_id')
|
||||
->where($qb->expr()->in('s.board_id', $qb->createNamedParameter($boardIds, IQueryBuilder::PARAM_INT_ARRAY)))
|
||||
->where($qb->expr()->eq('s.board_id', $qb->createNamedParameter($boardId, IQueryBuilder::PARAM_INT)))
|
||||
->andWhere($qb->expr()->isNotNull('c.duedate'))
|
||||
->andWhere($qb->expr()->eq('c.archived', $qb->createNamedParameter(false, IQueryBuilder::PARAM_BOOL)))
|
||||
->andWhere($qb->expr()->eq('c.deleted_at', $qb->createNamedParameter(0, IQueryBuilder::PARAM_INT)))
|
||||
@@ -270,14 +254,14 @@ class CardMapper extends QBMapper implements IPermissionMapper {
|
||||
return $this->findEntities($qb);
|
||||
}
|
||||
|
||||
public function findToMeOrNotAssignedCards(array $boardIds, string $username) {
|
||||
public function findToMeOrNotAssignedCards($boardId, $username) {
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb->select('c.*')
|
||||
->from('deck_cards', 'c')
|
||||
->innerJoin('c', 'deck_stacks', 's', 's.id = c.stack_id')
|
||||
->innerJoin('s', 'deck_boards', 'b', 'b.id = s.board_id')
|
||||
->leftJoin('c', 'deck_assigned_users', 'u', 'c.id = u.card_id')
|
||||
->where($qb->expr()->in('s.board_id', $qb->createNamedParameter($boardIds, IQueryBuilder::PARAM_INT_ARRAY)))
|
||||
->where($qb->expr()->eq('s.board_id', $qb->createNamedParameter($boardId, IQueryBuilder::PARAM_INT)))
|
||||
->andWhere($qb->expr()->orX(
|
||||
$qb->expr()->eq('u.participant', $qb->createNamedParameter($username, IQueryBuilder::PARAM_STR)),
|
||||
$qb->expr()->isNull('u.participant'))
|
||||
@@ -576,10 +560,10 @@ class CardMapper extends QBMapper implements IPermissionMapper {
|
||||
$qb->execute();
|
||||
}
|
||||
|
||||
public function isOwner($userId, $id): bool {
|
||||
public function isOwner($userId, $cardId): bool {
|
||||
$sql = 'SELECT owner FROM `*PREFIX*deck_boards` WHERE `id` IN (SELECT board_id FROM `*PREFIX*deck_stacks` WHERE id IN (SELECT stack_id FROM `*PREFIX*deck_cards` WHERE id = ?))';
|
||||
$stmt = $this->db->prepare($sql);
|
||||
$stmt->bindParam(1, $id, \PDO::PARAM_INT, 0);
|
||||
$stmt->bindParam(1, $cardId, \PDO::PARAM_INT, 0);
|
||||
$stmt->execute();
|
||||
$row = $stmt->fetch();
|
||||
return ($row['owner'] === $userId);
|
||||
@@ -607,8 +591,9 @@ class CardMapper extends QBMapper implements IPermissionMapper {
|
||||
public function mapOwner(Card &$card) {
|
||||
$userManager = $this->userManager;
|
||||
$card->resolveRelation('owner', function ($owner) use (&$userManager) {
|
||||
if ($userManager->userExists($owner)) {
|
||||
return new User($owner, $this->userManager);
|
||||
$user = $userManager->get($owner);
|
||||
if ($user !== null) {
|
||||
return new User($user);
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
@@ -24,7 +24,6 @@
|
||||
namespace OCA\Deck\Db;
|
||||
|
||||
use OCP\ICacheFactory;
|
||||
use OCP\ICache;
|
||||
use OCP\IDBConnection;
|
||||
use OCP\IRequest;
|
||||
|
||||
@@ -32,16 +31,13 @@ class ChangeHelper {
|
||||
public const TYPE_BOARD = 'boardChanged';
|
||||
public const TYPE_CARD = 'cardChanged';
|
||||
|
||||
private IDBConnection $db;
|
||||
private ICache $cache;
|
||||
private IRequest $request;
|
||||
private ?string $userId;
|
||||
private $db;
|
||||
|
||||
public function __construct(
|
||||
IDBConnection $db,
|
||||
ICacheFactory $cacheFactory,
|
||||
IRequest $request,
|
||||
?string $userId
|
||||
$userId
|
||||
) {
|
||||
$this->db = $db;
|
||||
$this->cache = $cacheFactory->createDistributed('deck_changes');
|
||||
|
||||
@@ -23,46 +23,30 @@
|
||||
|
||||
namespace OCA\Deck\Db;
|
||||
|
||||
use Generator;
|
||||
use OCP\AppFramework\Db\Entity;
|
||||
use OCP\AppFramework\Db\QBMapper;
|
||||
use OCP\DB\QueryBuilder\IQueryBuilder;
|
||||
use OCP\AppFramework\Db\Mapper;
|
||||
|
||||
/**
|
||||
* @template T of Entity
|
||||
* @template-extends QBMapper<T>
|
||||
* Class DeckMapper
|
||||
*
|
||||
* @package OCA\Deck\Db
|
||||
* @deprecated use QBMapper
|
||||
*
|
||||
* TODO: Move to QBMapper once Nextcloud 14 is a minimum requirement
|
||||
*/
|
||||
abstract class DeckMapper extends QBMapper {
|
||||
class DeckMapper extends Mapper {
|
||||
|
||||
/**
|
||||
* @param $id
|
||||
* @return T
|
||||
* @return \OCP\AppFramework\Db\Entity if not found
|
||||
* @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException
|
||||
* @throws \OCP\AppFramework\Db\DoesNotExistException
|
||||
*/
|
||||
public function find($id) {
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb->select('*')
|
||||
->from($this->getTableName())
|
||||
->where($qb->expr()->eq('id', $qb->createNamedParameter($id, IQueryBuilder::PARAM_INT)));
|
||||
|
||||
return $this->findEntity($qb);
|
||||
$sql = 'SELECT * FROM `' . $this->tableName . '` ' . 'WHERE `id` = ?';
|
||||
return $this->findEntity($sql, [$id]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to split passed array into chunks of 1000 elements and
|
||||
* call a given callback for fetching query results
|
||||
*
|
||||
* Can be useful to limit to 1000 results per query for oracle compatiblity
|
||||
* but still iterate over all results
|
||||
*/
|
||||
public function chunkQuery(array $ids, callable $callback): Generator {
|
||||
$limit = 1000;
|
||||
while (!empty($ids)) {
|
||||
$slice = array_splice($ids, 0, $limit);
|
||||
foreach ($callback($slice) as $item) {
|
||||
yield $item;
|
||||
}
|
||||
}
|
||||
protected function execute($sql, array $params = [], $limit = null, $offset = null) {
|
||||
return parent::execute($sql, $params, $limit, $offset);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,111 +26,48 @@ namespace OCA\Deck\Db;
|
||||
use OCP\AppFramework\Db\DoesNotExistException;
|
||||
use OCP\AppFramework\Db\Entity;
|
||||
use OCP\AppFramework\Db\MultipleObjectsReturnedException;
|
||||
use OCP\DB\QueryBuilder\IQueryBuilder;
|
||||
use OCP\IDBConnection;
|
||||
|
||||
/** @template-extends DeckMapper<Label> */
|
||||
class LabelMapper extends DeckMapper implements IPermissionMapper {
|
||||
public function __construct(IDBConnection $db) {
|
||||
parent::__construct($db, 'deck_labels', Label::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param numeric $boardId
|
||||
* @param int|null $limit
|
||||
* @param int|null $offset
|
||||
* @return Label[]
|
||||
* @throws \OCP\DB\Exception
|
||||
*/
|
||||
public function findAll($boardId, $limit = null, $offset = null): array {
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb->select('*')
|
||||
->from($this->getTableName())
|
||||
->where($qb->expr()->eq('board_id', $qb->createNamedParameter($boardId, IQueryBuilder::PARAM_INT)))
|
||||
->setMaxResults($limit)
|
||||
->setFirstResult($offset);
|
||||
return $this->findEntities($qb);
|
||||
public function findAll($boardId, $limit = null, $offset = null) {
|
||||
$sql = 'SELECT * FROM `*PREFIX*deck_labels` WHERE `board_id` = ? ORDER BY `id`';
|
||||
return $this->findEntities($sql, [$boardId], $limit, $offset);
|
||||
}
|
||||
|
||||
public function delete(Entity $entity): Entity {
|
||||
public function delete(\OCP\AppFramework\Db\Entity $entity) {
|
||||
// delete assigned labels
|
||||
$this->deleteLabelAssignments($entity->getId());
|
||||
// delete label
|
||||
return parent::delete($entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param numeric $cardId
|
||||
* @param int|null $limit
|
||||
* @param int|null $offset
|
||||
* @return Label[]
|
||||
* @throws \OCP\DB\Exception
|
||||
*/
|
||||
public function findAssignedLabelsForCard($cardId, $limit = null, $offset = null): array {
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb->select('l.*', 'card_id')
|
||||
->from($this->getTableName(), 'l')
|
||||
->innerJoin('l', 'deck_assigned_labels', 'al', 'l.id = al.label_id')
|
||||
->where($qb->expr()->eq('card_id', $qb->createNamedParameter($cardId, IQueryBuilder::PARAM_INT)))
|
||||
->orderBy('l.id')
|
||||
->setMaxResults($limit)
|
||||
->setFirstResult($offset);
|
||||
|
||||
return $this->findEntities($qb);
|
||||
public function findAssignedLabelsForCard($cardId, $limit = null, $offset = null) {
|
||||
$sql = 'SELECT l.*,card_id FROM `*PREFIX*deck_assigned_labels` as al INNER JOIN *PREFIX*deck_labels as l ON l.id = al.label_id WHERE `card_id` = ? ORDER BY l.id';
|
||||
return $this->findEntities($sql, [$cardId], $limit, $offset);
|
||||
}
|
||||
public function findAssignedLabelsForBoard($boardId, $limit = null, $offset = null) {
|
||||
$sql = 'SELECT c.id as card_id, l.id as id, l.title as title, l.color as color FROM `*PREFIX*deck_cards` as c ' .
|
||||
' INNER JOIN `*PREFIX*deck_assigned_labels` as al ON al.card_id = c.id INNER JOIN `*PREFIX*deck_labels` as l ON al.label_id = l.id WHERE board_id=? ORDER BY l.id';
|
||||
return $this->findEntities($sql, [$boardId], $limit, $offset);
|
||||
}
|
||||
|
||||
public function findAssignedLabelsForCards($cardIds, $limit = null, $offset = null): array {
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb->select('l.*', 'card_id')
|
||||
->from($this->getTableName(), 'l')
|
||||
->innerJoin('l', 'deck_assigned_labels', 'al', 'l.id = al.label_id')
|
||||
->where($qb->expr()->in('card_id', $qb->createNamedParameter($cardIds, IQueryBuilder::PARAM_INT_ARRAY)))
|
||||
->orderBy('l.id')
|
||||
->setMaxResults($limit)
|
||||
->setFirstResult($offset);
|
||||
|
||||
return $this->findEntities($qb);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param numeric $boardId
|
||||
* @param int|null $limit
|
||||
* @param int|null $offset
|
||||
* @return Label[]
|
||||
* @throws \OCP\DB\Exception
|
||||
*/
|
||||
public function findAssignedLabelsForBoard($boardId, $limit = null, $offset = null): array {
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb->select('l.id as id', 'l.title as title', 'l.color as color')
|
||||
->selectAlias('c.id', 'card_id')
|
||||
->from($this->getTableName(), 'l')
|
||||
->innerJoin('l', 'deck_assigned_labels', 'al', 'al.label_id = l.id')
|
||||
->innerJoin('l', 'deck_cards', 'c', 'al.card_id = c.id')
|
||||
->where($qb->expr()->eq('board_id', $qb->createNamedParameter($boardId, IQueryBuilder::PARAM_INT)))
|
||||
->orderBy('l.id')
|
||||
->setMaxResults($limit)
|
||||
->setFirstResult($offset);
|
||||
|
||||
return $this->findEntities($qb);
|
||||
}
|
||||
|
||||
public function insert(Entity $entity): Entity {
|
||||
public function insert(Entity $entity) {
|
||||
$entity->setLastModified(time());
|
||||
return parent::insert($entity);
|
||||
}
|
||||
|
||||
public function update(Entity $entity, bool $updateModified = true): Entity {
|
||||
public function update(Entity $entity, $updateModified = true) {
|
||||
if ($updateModified) {
|
||||
$entity->setLastModified(time());
|
||||
}
|
||||
return parent::update($entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param numeric $boardId
|
||||
* @return array
|
||||
* @throws \OCP\DB\Exception
|
||||
*/
|
||||
|
||||
public function getAssignedLabelsForBoard($boardId) {
|
||||
$labels = $this->findAssignedLabelsForBoard($boardId);
|
||||
$result = [];
|
||||
@@ -143,51 +80,27 @@ class LabelMapper extends DeckMapper implements IPermissionMapper {
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param numeric $labelId
|
||||
* @return void
|
||||
* @throws \OCP\DB\Exception
|
||||
*/
|
||||
public function deleteLabelAssignments($labelId) {
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb->delete('deck_assigned_labels')
|
||||
->where($qb->expr()->eq('label_id', $qb->createNamedParameter($labelId, IQueryBuilder::PARAM_INT)));
|
||||
$qb->executeStatement();
|
||||
$sql = 'DELETE FROM `*PREFIX*deck_assigned_labels` WHERE label_id = ?';
|
||||
$stmt = $this->db->prepare($sql);
|
||||
$stmt->bindParam(1, $labelId, \PDO::PARAM_INT, 0);
|
||||
$stmt->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param numeric $cardId
|
||||
* @return void
|
||||
* @throws \OCP\DB\Exception
|
||||
*/
|
||||
public function deleteLabelAssignmentsForCard($cardId) {
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb->delete('deck_assigned_labels')
|
||||
->where($qb->expr()->eq('card_id', $qb->createNamedParameter($cardId, IQueryBuilder::PARAM_INT)));
|
||||
$qb->executeStatement();
|
||||
$sql = 'DELETE FROM `*PREFIX*deck_assigned_labels` WHERE card_id = ?';
|
||||
$stmt = $this->db->prepare($sql);
|
||||
$stmt->bindParam(1, $cardId, \PDO::PARAM_INT, 0);
|
||||
$stmt->execute();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $userId
|
||||
* @param numeric $labelId
|
||||
* @return bool
|
||||
* @throws \OCP\DB\Exception
|
||||
*/
|
||||
public function isOwner($userId, $labelId): bool {
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb->select('l.id')
|
||||
->from($this->getTableName(), 'l')
|
||||
->innerJoin('l', 'deck_boards', 'b', 'l.board_id = b.id')
|
||||
->where($qb->expr()->eq('l.id', $qb->createNamedParameter($labelId, IQueryBuilder::PARAM_INT)))
|
||||
->andWhere($qb->expr()->eq('b.owner', $qb->createNamedParameter($userId, IQueryBuilder::PARAM_STR)));
|
||||
|
||||
return count($qb->executeQuery()->fetchAll()) > 0;
|
||||
$sql = 'SELECT owner FROM `*PREFIX*deck_boards` WHERE `id` IN (SELECT board_id FROM `*PREFIX*deck_labels` WHERE id = ?)';
|
||||
$stmt = $this->execute($sql, [$labelId]);
|
||||
$row = $stmt->fetch();
|
||||
return ($row['owner'] === $userId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param numeric $id
|
||||
* @return int|null
|
||||
*/
|
||||
public function findBoardId($id): ?int {
|
||||
try {
|
||||
$entity = $this->find($id);
|
||||
|
||||
@@ -53,7 +53,7 @@ class RelationalEntity extends Entity implements \JsonSerializable {
|
||||
* @param string $attribute the name of the attribute
|
||||
* @since 7.0.0
|
||||
*/
|
||||
protected function markFieldUpdated(string $attribute): void {
|
||||
protected function markFieldUpdated($attribute) {
|
||||
if (!in_array($attribute, $this->_relations, true)) {
|
||||
parent::markFieldUpdated($attribute);
|
||||
}
|
||||
@@ -68,13 +68,10 @@ class RelationalEntity extends Entity implements \JsonSerializable {
|
||||
$reflection = new \ReflectionClass($this);
|
||||
$json = [];
|
||||
foreach ($properties as $property => $value) {
|
||||
if (!str_starts_with($property, '_') && $reflection->hasProperty($property)) {
|
||||
if (strpos($property, '_') !== 0 && $reflection->hasProperty($property)) {
|
||||
$propertyReflection = $reflection->getProperty($property);
|
||||
if (!$propertyReflection->isPrivate() && !in_array($property, $this->_resolvedProperties, true)) {
|
||||
$json[$property] = $this->getter($property);
|
||||
if ($json[$property] instanceof \DateTimeInterface) {
|
||||
$json[$property] = $json[$property]->format('c');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -127,9 +124,9 @@ class RelationalEntity extends Entity implements \JsonSerializable {
|
||||
}
|
||||
}
|
||||
|
||||
public function __call(string $methodName, array $args) {
|
||||
public function __call($methodName, $args) {
|
||||
$attr = lcfirst(substr($methodName, 7));
|
||||
if (array_key_exists($attr, $this->_resolvedProperties) && str_starts_with($methodName, 'resolve')) {
|
||||
if (array_key_exists($attr, $this->_resolvedProperties) && strpos($methodName, 'resolve') === 0) {
|
||||
if ($this->_resolvedProperties[$attr] !== null) {
|
||||
return $this->_resolvedProperties[$attr];
|
||||
}
|
||||
@@ -137,7 +134,7 @@ class RelationalEntity extends Entity implements \JsonSerializable {
|
||||
}
|
||||
|
||||
$attr = lcfirst(substr($methodName, 3));
|
||||
if (array_key_exists($attr, $this->_resolvedProperties) && str_starts_with($methodName, 'set')) {
|
||||
if (array_key_exists($attr, $this->_resolvedProperties) && strpos($methodName, 'set') === 0) {
|
||||
if (!is_scalar($args[0])) {
|
||||
$args[0] = $args[0]['primaryKey'];
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ class RelationalObject implements JsonSerializable {
|
||||
* RelationalObject constructor.
|
||||
*
|
||||
* @param $primaryKey string
|
||||
* @param callable|mixed $object
|
||||
* @param $object
|
||||
*/
|
||||
public function __construct($primaryKey, $object) {
|
||||
$this->primaryKey = $primaryKey;
|
||||
@@ -47,24 +47,16 @@ class RelationalObject implements JsonSerializable {
|
||||
);
|
||||
}
|
||||
|
||||
public function getObject() {
|
||||
if (is_callable($this->object)) {
|
||||
$this->object = call_user_func($this->object, $this);
|
||||
}
|
||||
|
||||
return $this->object;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method should be overwritten if object doesn't implement \JsonSerializable
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function getObjectSerialization() {
|
||||
if ($this->getObject() instanceof JsonSerializable) {
|
||||
return $this->getObject()->jsonSerialize();
|
||||
if ($this->object instanceof JsonSerializable) {
|
||||
return $this->object->jsonSerialize();
|
||||
} else {
|
||||
throw new \Exception('jsonSerialize is not implemented on ' . get_class($this->getObject()));
|
||||
throw new \Exception('jsonSerialize is not implemented on ' . get_class($this));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* @copyright Copyright (c) 2022, chandi Langecker (git@chandi.it)
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace OCA\Deck\Db;
|
||||
|
||||
use OCP\AppFramework\Db\Entity;
|
||||
|
||||
class Session extends Entity implements \JsonSerializable {
|
||||
public $id;
|
||||
protected $userId;
|
||||
protected $token;
|
||||
protected $lastContact;
|
||||
protected $boardId;
|
||||
|
||||
public function __construct() {
|
||||
$this->addType('id', 'integer');
|
||||
$this->addType('boardId', 'integer');
|
||||
$this->addType('lastContact', 'integer');
|
||||
}
|
||||
|
||||
public function jsonSerialize(): array {
|
||||
return [
|
||||
'id' => $this->id,
|
||||
'userId' => $this->userId,
|
||||
'token' => $this->token,
|
||||
'lastContact' => $this->lastContact,
|
||||
'boardId' => $this->boardId,
|
||||
];
|
||||
}
|
||||
}
|
||||