Initial commit
This commit is contained in:
7
.eslintrc.js
Normal file
7
.eslintrc.js
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
// SPDX-FileCopyrightText: Andrea Rigoni Garola <andrea.rgn@gmail.com>
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
module.exports = {
|
||||||
|
extends: [
|
||||||
|
'@nextcloud',
|
||||||
|
]
|
||||||
|
}
|
||||||
3
.gitattributes
vendored
Normal file
3
.gitattributes
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# SPDX-FileCopyrightText: Andrea Rigoni Garola <andrea.rgn@gmail.com>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
/js/* binary
|
||||||
47
.github/workflows/lint-eslint.yml
vendored
Normal file
47
.github/workflows/lint-eslint.yml
vendored
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
# This workflow is provided via the organization template repository
|
||||||
|
#
|
||||||
|
# https://github.com/nextcloud/.github
|
||||||
|
# https://docs.github.com/en/actions/learn-github-actions/sharing-workflows-with-your-organization
|
||||||
|
# SPDX-FileCopyrightText: Nextcloud contributors
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
|
name: Lint
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
- master
|
||||||
|
- stable*
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
lint:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
name: eslint
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Read package.json node and npm engines version
|
||||||
|
uses: skjnldsv/read-package-engines-version-actions@v1.2
|
||||||
|
id: versions
|
||||||
|
with:
|
||||||
|
fallbackNode: '^16'
|
||||||
|
fallbackNpm: '^7'
|
||||||
|
|
||||||
|
- name: Set up node ${{ steps.versions.outputs.nodeVersion }}
|
||||||
|
uses: actions/setup-node@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
|
||||||
33
.github/workflows/lint-info-xml.yml
vendored
Normal file
33
.github/workflows/lint-info-xml.yml
vendored
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
# This workflow is provided via the organization template repository
|
||||||
|
#
|
||||||
|
# https://github.com/nextcloud/.github
|
||||||
|
# https://docs.github.com/en/actions/learn-github-actions/sharing-workflows-with-your-organization
|
||||||
|
# SPDX-FileCopyrightText: Nextcloud contributors
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
|
name: Lint
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
- stable*
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
xml-linters:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
name: info.xml lint
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Download schema
|
||||||
|
run: wget https://raw.githubusercontent.com/nextcloud/appstore/master/nextcloudappstore/api/v1/release/info.xsd
|
||||||
|
|
||||||
|
- name: Lint info.xml
|
||||||
|
uses: ChristophWurst/xmllint-action@v1
|
||||||
|
with:
|
||||||
|
xml-file: ./appinfo/info.xml
|
||||||
|
xml-schema-file: ./info.xsd
|
||||||
37
.github/workflows/lint-php-cs.yml
vendored
Normal file
37
.github/workflows/lint-php-cs.yml
vendored
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
# This workflow is provided via the organization template repository
|
||||||
|
#
|
||||||
|
# https://github.com/nextcloud/.github
|
||||||
|
# https://docs.github.com/en/actions/learn-github-actions/sharing-workflows-with-your-organization
|
||||||
|
# SPDX-FileCopyrightText: Nextcloud contributors
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
|
name: Lint
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
- stable*
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
lint:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
name: php-cs
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Set up php ${{ matrix.php-versions }}
|
||||||
|
uses: shivammathur/setup-php@v2
|
||||||
|
with:
|
||||||
|
php-version: "7.4"
|
||||||
|
coverage: none
|
||||||
|
|
||||||
|
- 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 )
|
||||||
49
.github/workflows/lint-php.yml
vendored
Normal file
49
.github/workflows/lint-php.yml
vendored
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
# This workflow is provided via the organization template repository
|
||||||
|
#
|
||||||
|
# https://github.com/nextcloud/.github
|
||||||
|
# https://docs.github.com/en/actions/learn-github-actions/sharing-workflows-with-your-organization
|
||||||
|
# SPDX-FileCopyrightText: Nextcloud contributors
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
|
name: Lint
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
- stable*
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
php-lint:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
php-versions: ["7.4", "8.0", "8.1"]
|
||||||
|
|
||||||
|
name: php-lint
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Set up php ${{ matrix.php-versions }}
|
||||||
|
uses: shivammathur/setup-php@v2
|
||||||
|
with:
|
||||||
|
php-version: ${{ matrix.php-versions }}
|
||||||
|
coverage: none
|
||||||
|
|
||||||
|
- name: Lint
|
||||||
|
run: composer run lint
|
||||||
|
|
||||||
|
summary:
|
||||||
|
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
Normal file
46
.github/workflows/lint-stylelint.yml
vendored
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
# This workflow is provided via the organization template repository
|
||||||
|
#
|
||||||
|
# https://github.com/nextcloud/.github
|
||||||
|
# https://docs.github.com/en/actions/learn-github-actions/sharing-workflows-with-your-organization
|
||||||
|
# SPDX-FileCopyrightText: Nextcloud contributors
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
|
name: Lint
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
- stable*
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
lint:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
name: stylelint
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Read package.json node and npm engines version
|
||||||
|
uses: skjnldsv/read-package-engines-version-actions@v1.1
|
||||||
|
id: versions
|
||||||
|
with:
|
||||||
|
fallbackNode: '^16'
|
||||||
|
fallbackNpm: '^7'
|
||||||
|
|
||||||
|
- name: Set up node ${{ steps.versions.outputs.nodeVersion }}
|
||||||
|
uses: actions/setup-node@v2
|
||||||
|
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
|
||||||
115
.github/workflows/phpunit-mysql.yml
vendored
Normal file
115
.github/workflows/phpunit-mysql.yml
vendored
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
# SPDX-FileCopyrightText: Nextcloud contributors
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
name: PHPUnit
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
- stable*
|
||||||
|
|
||||||
|
env:
|
||||||
|
# Location of the phpunit.xml and phpunit.integration.xml files
|
||||||
|
PHPUNIT_CONFIG: ./tests/phpunit.xml
|
||||||
|
PHPUNIT_INTEGRATION_CONFIG: ./tests/phpunit.integration.xml
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
phpunit-mysql:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
php-versions: ['7.4', '8.0', '8.1']
|
||||||
|
server-versions: ['master']
|
||||||
|
|
||||||
|
services:
|
||||||
|
mysql:
|
||||||
|
image: mariadb:10.5
|
||||||
|
ports:
|
||||||
|
- 4444:3306/tcp
|
||||||
|
env:
|
||||||
|
MYSQL_ROOT_PASSWORD: rootpassword
|
||||||
|
options: --health-cmd="mysqladmin ping" --health-interval 5s --health-timeout 2s --health-retries 5
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Set app env
|
||||||
|
run: |
|
||||||
|
# Split and keep last
|
||||||
|
echo "APP_NAME=${GITHUB_REPOSITORY##*/}" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Enable ONLY_FULL_GROUP_BY MySQL option
|
||||||
|
run: |
|
||||||
|
echo "SET GLOBAL sql_mode=(SELECT CONCAT(@@sql_mode,',ONLY_FULL_GROUP_BY'));" | mysql -h 127.0.0.1 -P 4444 -u root -prootpassword
|
||||||
|
echo "SELECT @@sql_mode;" | mysql -h 127.0.0.1 -P 4444 -u root -prootpassword
|
||||||
|
|
||||||
|
- name: Checkout server
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
submodules: true
|
||||||
|
repository: nextcloud/server
|
||||||
|
ref: ${{ matrix.server-versions }}
|
||||||
|
|
||||||
|
- name: Checkout app
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
path: apps/${{ env.APP_NAME }}
|
||||||
|
|
||||||
|
- name: Set up php ${{ matrix.php-versions }}
|
||||||
|
uses: shivammathur/setup-php@v2
|
||||||
|
with:
|
||||||
|
php-version: ${{ matrix.php-versions }}
|
||||||
|
tools: phpunit
|
||||||
|
extensions: mbstring, iconv, fileinfo, intl, mysql, pdo_mysql
|
||||||
|
coverage: none
|
||||||
|
|
||||||
|
- name: Set up PHPUnit
|
||||||
|
working-directory: apps/${{ env.APP_NAME }}
|
||||||
|
run: composer i
|
||||||
|
|
||||||
|
- name: Set up Nextcloud
|
||||||
|
env:
|
||||||
|
DB_PORT: 4444
|
||||||
|
run: |
|
||||||
|
mkdir data
|
||||||
|
./occ maintenance:install --verbose --database=mysql --database-name=nextcloud --database-host=127.0.0.1 --database-port=$DB_PORT --database-user=root --database-pass=rootpassword --admin-user admin --admin-pass password
|
||||||
|
./occ app:enable ${{ env.APP_NAME }}
|
||||||
|
|
||||||
|
- name: Check PHPUnit config file existence
|
||||||
|
id: check_phpunit
|
||||||
|
uses: andstor/file-existence-action@v1
|
||||||
|
with:
|
||||||
|
files: apps/${{ env.APP_NAME }}/${{ env.PHPUNIT_CONFIG }}
|
||||||
|
|
||||||
|
- name: Run Nextcloud
|
||||||
|
run: php -S localhost:8080 &
|
||||||
|
|
||||||
|
- name: PHPUnit
|
||||||
|
# Only run if phpunit config file exists
|
||||||
|
if: steps.check_phpunit.outputs.files_exists == 'true'
|
||||||
|
working-directory: apps/${{ env.APP_NAME }}
|
||||||
|
run: ./vendor/phpunit/phpunit/phpunit -c ${{ env.PHPUNIT_CONFIG }}
|
||||||
|
|
||||||
|
- name: Check PHPUnit integration config file existence
|
||||||
|
id: check_integration
|
||||||
|
uses: andstor/file-existence-action@v1
|
||||||
|
with:
|
||||||
|
files: apps/${{ env.APP_NAME }}/${{ env.PHPUNIT_INTEGRATION_CONFIG }}
|
||||||
|
|
||||||
|
- name: PHPUnit integration
|
||||||
|
# Only run if phpunit integration config file exists
|
||||||
|
if: steps.check_integration.outputs.files_exists == 'true'
|
||||||
|
working-directory: apps/${{ env.APP_NAME }}
|
||||||
|
run: ./vendor/phpunit/phpunit/phpunit -c ${{ env.PHPUNIT_INTEGRATION_CONFIG }}
|
||||||
|
|
||||||
|
summary:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: phpunit-mysql
|
||||||
|
|
||||||
|
if: always()
|
||||||
|
|
||||||
|
name: phpunit-mysql-summary
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Summary status
|
||||||
|
run: if ${{ needs.phpunit-mysql.result != 'success' }}; then exit 1; fi
|
||||||
109
.github/workflows/phpunit-oci.yml
vendored
Normal file
109
.github/workflows/phpunit-oci.yml
vendored
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
# SPDX-FileCopyrightText: Nextcloud contributors
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
name: PHPUnit
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
- stable*
|
||||||
|
|
||||||
|
env:
|
||||||
|
# Location of the phpunit.xml and phpunit.integration.xml files
|
||||||
|
PHPUNIT_CONFIG: ./tests/phpunit.xml
|
||||||
|
PHPUNIT_INTEGRATION_CONFIG: ./tests/phpunit.integration.xml
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
phpunit-oci:
|
||||||
|
runs-on: ubuntu-20.04
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
php-versions: ['8.0']
|
||||||
|
server-versions: ['master']
|
||||||
|
|
||||||
|
services:
|
||||||
|
oracle:
|
||||||
|
image: deepdiver/docker-oracle-xe-11g # 'wnameless/oracle-xe-11g-r2'
|
||||||
|
ports:
|
||||||
|
- 1521:1521/tcp
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Set app env
|
||||||
|
run: |
|
||||||
|
# Split and keep last
|
||||||
|
echo "APP_NAME=${GITHUB_REPOSITORY##*/}" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Checkout server
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
submodules: true
|
||||||
|
repository: nextcloud/server
|
||||||
|
ref: ${{ matrix.server-versions }}
|
||||||
|
|
||||||
|
- name: Checkout app
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
path: apps/${{ env.APP_NAME }}
|
||||||
|
|
||||||
|
- name: Set up php ${{ matrix.php-versions }}
|
||||||
|
uses: shivammathur/setup-php@v2
|
||||||
|
with:
|
||||||
|
php-version: ${{ matrix.php-versions }}
|
||||||
|
extensions: mbstring, fileinfo, intl, sqlite, pdo_sqlite, oci8
|
||||||
|
tools: phpunit
|
||||||
|
coverage: none
|
||||||
|
|
||||||
|
- name: Set up PHPUnit
|
||||||
|
working-directory: apps/${{ env.APP_NAME }}
|
||||||
|
run: composer i
|
||||||
|
|
||||||
|
- name: Set up Nextcloud
|
||||||
|
env:
|
||||||
|
DB_PORT: 1521
|
||||||
|
run: |
|
||||||
|
mkdir data
|
||||||
|
./occ maintenance:install --verbose --database=oci --database-name=XE --database-host=127.0.0.1 --database-port=$DB_PORT --database-user=autotest --database-pass=owncloud --admin-user admin --admin-pass admin
|
||||||
|
./occ app:enable ${{ env.APP_NAME }}
|
||||||
|
|
||||||
|
- name: Check PHPUnit config file existence
|
||||||
|
id: check_phpunit
|
||||||
|
uses: andstor/file-existence-action@v1
|
||||||
|
with:
|
||||||
|
files: apps/${{ env.APP_NAME }}/${{ env.PHPUNIT_CONFIG }}
|
||||||
|
|
||||||
|
- name: PHPUnit
|
||||||
|
# Only run if phpunit config file exists
|
||||||
|
if: steps.check_phpunit.outputs.files_exists == 'true'
|
||||||
|
working-directory: apps/${{ env.APP_NAME }}
|
||||||
|
run: ./vendor/phpunit/phpunit/phpunit -c ${{ env.PHPUNIT_CONFIG }}
|
||||||
|
|
||||||
|
- name: Check PHPUnit integration config file existence
|
||||||
|
id: check_integration
|
||||||
|
uses: andstor/file-existence-action@v1
|
||||||
|
with:
|
||||||
|
files: apps/${{ env.APP_NAME }}/${{ env.PHPUNIT_INTEGRATION_CONFIG }}
|
||||||
|
|
||||||
|
- name: Run Nextcloud
|
||||||
|
# Only run if phpunit integration config file exists
|
||||||
|
if: steps.check_integration.outputs.files_exists == 'true'
|
||||||
|
run: php -S localhost:8080 &
|
||||||
|
|
||||||
|
- name: PHPUnit integration
|
||||||
|
# Only run if phpunit integration config file exists
|
||||||
|
if: steps.check_integration.outputs.files_exists == 'true'
|
||||||
|
working-directory: apps/${{ env.APP_NAME }}
|
||||||
|
run: ./vendor/phpunit/phpunit/phpunit -c ${{ env.PHPUNIT_INTEGRATION_CONFIG }}
|
||||||
|
|
||||||
|
summary:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: phpunit-oci
|
||||||
|
|
||||||
|
if: always()
|
||||||
|
|
||||||
|
name: phpunit-oci-summary
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Summary status
|
||||||
|
run: if ${{ needs.phpunit-oci.result != 'success' }}; then exit 1; fi
|
||||||
114
.github/workflows/phpunit-pgsql.yml
vendored
Normal file
114
.github/workflows/phpunit-pgsql.yml
vendored
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
# SPDX-FileCopyrightText: Nextcloud contributors
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
name: PHPUnit
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
- stable*
|
||||||
|
|
||||||
|
env:
|
||||||
|
# Location of the phpunit.xml and phpunit.integration.xml files
|
||||||
|
PHPUNIT_CONFIG: ./tests/phpunit.xml
|
||||||
|
PHPUNIT_INTEGRATION_CONFIG: ./tests/phpunit.integration.xml
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
phpunit-pgsql:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
php-versions: ['8.0']
|
||||||
|
server-versions: ['master']
|
||||||
|
|
||||||
|
services:
|
||||||
|
postgres:
|
||||||
|
image: postgres
|
||||||
|
ports:
|
||||||
|
- 4444:5432/tcp
|
||||||
|
env:
|
||||||
|
POSTGRES_USER: root
|
||||||
|
POSTGRES_PASSWORD: rootpassword
|
||||||
|
POSTGRES_DB: nextcloud
|
||||||
|
options: --health-cmd pg_isready --health-interval 5s --health-timeout 2s --health-retries 5
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Set app env
|
||||||
|
run: |
|
||||||
|
# Split and keep last
|
||||||
|
echo "APP_NAME=${GITHUB_REPOSITORY##*/}" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Checkout server
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
submodules: true
|
||||||
|
repository: nextcloud/server
|
||||||
|
ref: ${{ matrix.server-versions }}
|
||||||
|
|
||||||
|
- name: Checkout app
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
path: apps/${{ env.APP_NAME }}
|
||||||
|
|
||||||
|
- name: Set up php ${{ matrix.php-versions }}
|
||||||
|
uses: shivammathur/setup-php@v2
|
||||||
|
with:
|
||||||
|
php-version: ${{ matrix.php-versions }}
|
||||||
|
tools: phpunit
|
||||||
|
extensions: mbstring, iconv, fileinfo, intl, pgsql, pdo_pgsql
|
||||||
|
coverage: none
|
||||||
|
|
||||||
|
- name: Set up PHPUnit
|
||||||
|
working-directory: apps/${{ env.APP_NAME }}
|
||||||
|
run: composer i
|
||||||
|
|
||||||
|
- name: Set up Nextcloud
|
||||||
|
env:
|
||||||
|
DB_PORT: 4444
|
||||||
|
run: |
|
||||||
|
mkdir data
|
||||||
|
./occ maintenance:install --verbose --database=pgsql --database-name=nextcloud --database-host=127.0.0.1 --database-port=$DB_PORT --database-user=root --database-pass=rootpassword --admin-user admin --admin-pass password
|
||||||
|
./occ app:enable ${{ env.APP_NAME }}
|
||||||
|
|
||||||
|
- name: Check PHPUnit config file existence
|
||||||
|
id: check_phpunit
|
||||||
|
uses: andstor/file-existence-action@v1
|
||||||
|
with:
|
||||||
|
files: apps/${{ env.APP_NAME }}/${{ env.PHPUNIT_CONFIG }}
|
||||||
|
|
||||||
|
- name: PHPUnit
|
||||||
|
# Only run if phpunit config file exists
|
||||||
|
if: steps.check_phpunit.outputs.files_exists == 'true'
|
||||||
|
working-directory: apps/${{ env.APP_NAME }}
|
||||||
|
run: ./vendor/phpunit/phpunit/phpunit -c ${{ env.PHPUNIT_CONFIG }}
|
||||||
|
|
||||||
|
- name: Check PHPUnit integration config file existence
|
||||||
|
id: check_integration
|
||||||
|
uses: andstor/file-existence-action@v1
|
||||||
|
with:
|
||||||
|
files: apps/${{ env.APP_NAME }}/${{ env.PHPUNIT_INTEGRATION_CONFIG }}
|
||||||
|
|
||||||
|
- name: Run Nextcloud
|
||||||
|
# Only run if phpunit integration config file exists
|
||||||
|
if: steps.check_integration.outputs.files_exists == 'true'
|
||||||
|
run: php -S localhost:8080 &
|
||||||
|
|
||||||
|
- name: PHPUnit integration
|
||||||
|
# Only run if phpunit integration config file exists
|
||||||
|
if: steps.check_integration.outputs.files_exists == 'true'
|
||||||
|
working-directory: apps/${{ env.APP_NAME }}
|
||||||
|
run: ./vendor/phpunit/phpunit/phpunit -c ${{ env.PHPUNIT_INTEGRATION_CONFIG }}
|
||||||
|
|
||||||
|
summary:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: phpunit-pgsql
|
||||||
|
|
||||||
|
if: always()
|
||||||
|
|
||||||
|
name: phpunit-pgsql-summary
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Summary status
|
||||||
|
run: if ${{ needs.phpunit-pgsql.result != 'success' }}; then exit 1; fi
|
||||||
103
.github/workflows/phpunit-sqlite.yml
vendored
Normal file
103
.github/workflows/phpunit-sqlite.yml
vendored
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
# SPDX-FileCopyrightText: Nextcloud contributors
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
name: PHPUnit
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
- stable*
|
||||||
|
|
||||||
|
env:
|
||||||
|
# Location of the phpunit.xml and phpunit.integration.xml files
|
||||||
|
PHPUNIT_CONFIG: ./tests/phpunit.xml
|
||||||
|
PHPUNIT_INTEGRATION_CONFIG: ./tests/phpunit.integration.xml
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
phpunit-sqlite:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
php-versions: ['8.0']
|
||||||
|
server-versions: ['master']
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Set app env
|
||||||
|
run: |
|
||||||
|
# Split and keep last
|
||||||
|
echo "APP_NAME=${GITHUB_REPOSITORY##*/}" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Checkout server
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
submodules: true
|
||||||
|
repository: nextcloud/server
|
||||||
|
ref: ${{ matrix.server-versions }}
|
||||||
|
|
||||||
|
- name: Checkout app
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
path: apps/${{ env.APP_NAME }}
|
||||||
|
|
||||||
|
- name: Set up php ${{ matrix.php-versions }}
|
||||||
|
uses: shivammathur/setup-php@v2
|
||||||
|
with:
|
||||||
|
php-version: ${{ matrix.php-versions }}
|
||||||
|
tools: phpunit
|
||||||
|
extensions: mbstring, iconv, fileinfo, intl, sqlite, pdo_sqlite
|
||||||
|
coverage: none
|
||||||
|
|
||||||
|
- name: Set up PHPUnit
|
||||||
|
working-directory: apps/${{ env.APP_NAME }}
|
||||||
|
run: composer i
|
||||||
|
|
||||||
|
- name: Set up Nextcloud
|
||||||
|
env:
|
||||||
|
DB_PORT: 4444
|
||||||
|
run: |
|
||||||
|
mkdir data
|
||||||
|
./occ maintenance:install --verbose --database=sqlite --database-name=nextcloud --database-host=127.0.0.1 --database-port=$DB_PORT --database-user=root --database-pass=rootpassword --admin-user admin --admin-pass password
|
||||||
|
./occ app:enable ${{ env.APP_NAME }}
|
||||||
|
|
||||||
|
- name: Check PHPUnit config file existence
|
||||||
|
id: check_phpunit
|
||||||
|
uses: andstor/file-existence-action@v1
|
||||||
|
with:
|
||||||
|
files: apps/${{ env.APP_NAME }}/${{ env.PHPUNIT_CONFIG }}
|
||||||
|
|
||||||
|
- name: PHPUnit
|
||||||
|
# Only run if phpunit config file exists
|
||||||
|
if: steps.check_phpunit.outputs.files_exists == 'true'
|
||||||
|
working-directory: apps/${{ env.APP_NAME }}
|
||||||
|
run: ./vendor/phpunit/phpunit/phpunit -c ${{ env.PHPUNIT_CONFIG }}
|
||||||
|
|
||||||
|
- name: Check PHPUnit integration config file existence
|
||||||
|
id: check_integration
|
||||||
|
uses: andstor/file-existence-action@v1
|
||||||
|
with:
|
||||||
|
files: apps/${{ env.APP_NAME }}/${{ env.PHPUNIT_INTEGRATION_CONFIG }}
|
||||||
|
|
||||||
|
- name: Run Nextcloud
|
||||||
|
# Only run if phpunit integration config file exists
|
||||||
|
if: steps.check_integration.outputs.files_exists == 'true'
|
||||||
|
run: php -S localhost:8080 &
|
||||||
|
|
||||||
|
- name: PHPUnit integration
|
||||||
|
# Only run if phpunit integration config file exists
|
||||||
|
if: steps.check_integration.outputs.files_exists == 'true'
|
||||||
|
working-directory: apps/${{ env.APP_NAME }}
|
||||||
|
run: ./vendor/phpunit/phpunit/phpunit -c ${{ env.PHPUNIT_INTEGRATION_CONFIG }}
|
||||||
|
|
||||||
|
summary:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: phpunit-sqlite
|
||||||
|
|
||||||
|
if: always()
|
||||||
|
|
||||||
|
name: phpunit-sqlite-summary
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Summary status
|
||||||
|
run: if ${{ needs.phpunit-sqlite.result != 'success' }}; then exit 1; fi
|
||||||
15
.github/workflows/reuse.yml
vendored
Normal file
15
.github/workflows/reuse.yml
vendored
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
# SPDX-FileCopyrightText: 2022 Free Software Foundation Europe e.V. <https://fsfe.org>
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: CC0-1.0
|
||||||
|
|
||||||
|
name: REUSE Compliance Check
|
||||||
|
|
||||||
|
on: [push, pull_request]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- name: REUSE Compliance Check
|
||||||
|
uses: fsfe/reuse-action@v1
|
||||||
10
.gitignore
vendored
Normal file
10
.gitignore
vendored
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
# SPDX-FileCopyrightText: Andrea Rigoni Garola <andrea.rgn@gmail.com>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
*.iml
|
||||||
|
.idea
|
||||||
|
/.php-cs-fixer.cache
|
||||||
|
/.php_cs.cache
|
||||||
|
/build/
|
||||||
|
/vendor/
|
||||||
|
js/*hot-update.*
|
||||||
|
node_modules/
|
||||||
20
.php-cs-fixer.dist.php
Normal file
20
.php-cs-fixer.dist.php
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
// SPDX-FileCopyrightText: Andrea Rigoni Garola <andrea.rgn@gmail.com>
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
|
require_once './vendor/autoload.php';
|
||||||
|
|
||||||
|
use Nextcloud\CodingStandard\Config;
|
||||||
|
|
||||||
|
$config = new Config();
|
||||||
|
$config
|
||||||
|
->getFinder()
|
||||||
|
->ignoreVCSIgnored(true)
|
||||||
|
->notPath('build')
|
||||||
|
->notPath('l10n')
|
||||||
|
->notPath('src')
|
||||||
|
->notPath('vendor')
|
||||||
|
->in(__DIR__);
|
||||||
|
return $config;
|
||||||
12
.reuse/dep5
Normal file
12
.reuse/dep5
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
|
||||||
|
Upstream-Name: Photo Reduce
|
||||||
|
Upstream-Contact: Andrea Rigoni Garola <andrea.rgn@gmail.com>
|
||||||
|
Source: https://github.com/nextcloud/profiler
|
||||||
|
|
||||||
|
Files: package-lock.json package.json composer.json composer.lock
|
||||||
|
Copyright: Andrea Rigoni Garola <andrea.rgn@gmail.com>
|
||||||
|
License: AGPL-3.0-or-later
|
||||||
|
|
||||||
|
Files: l10n/*.js l10n/*.json
|
||||||
|
Copyright: Nextcloud translators <https://www.transifex.com/nextcloud/>
|
||||||
|
License: AGPL-3.0-or-later
|
||||||
@@ -219,8 +219,8 @@ If you develop a new program, and you want it to be of the greatest possible use
|
|||||||
|
|
||||||
To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found.
|
To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found.
|
||||||
|
|
||||||
ncc_photo_reduce
|
<one line to give the program's name and a brief idea of what it does.>
|
||||||
Copyright (C) 2023 YFinGames
|
Copyright (C) <year> <name of author>
|
||||||
|
|
||||||
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 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.
|
||||||
|
|
||||||
121
LICENSES/CC0-1.0.txt
Normal file
121
LICENSES/CC0-1.0.txt
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
Creative Commons Legal Code
|
||||||
|
|
||||||
|
CC0 1.0 Universal
|
||||||
|
|
||||||
|
CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
|
||||||
|
LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
|
||||||
|
ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
|
||||||
|
INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
|
||||||
|
REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
|
||||||
|
PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
|
||||||
|
THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
|
||||||
|
HEREUNDER.
|
||||||
|
|
||||||
|
Statement of Purpose
|
||||||
|
|
||||||
|
The laws of most jurisdictions throughout the world automatically confer
|
||||||
|
exclusive Copyright and Related Rights (defined below) upon the creator
|
||||||
|
and subsequent owner(s) (each and all, an "owner") of an original work of
|
||||||
|
authorship and/or a database (each, a "Work").
|
||||||
|
|
||||||
|
Certain owners wish to permanently relinquish those rights to a Work for
|
||||||
|
the purpose of contributing to a commons of creative, cultural and
|
||||||
|
scientific works ("Commons") that the public can reliably and without fear
|
||||||
|
of later claims of infringement build upon, modify, incorporate in other
|
||||||
|
works, reuse and redistribute as freely as possible in any form whatsoever
|
||||||
|
and for any purposes, including without limitation commercial purposes.
|
||||||
|
These owners may contribute to the Commons to promote the ideal of a free
|
||||||
|
culture and the further production of creative, cultural and scientific
|
||||||
|
works, or to gain reputation or greater distribution for their Work in
|
||||||
|
part through the use and efforts of others.
|
||||||
|
|
||||||
|
For these and/or other purposes and motivations, and without any
|
||||||
|
expectation of additional consideration or compensation, the person
|
||||||
|
associating CC0 with a Work (the "Affirmer"), to the extent that he or she
|
||||||
|
is an owner of Copyright and Related Rights in the Work, voluntarily
|
||||||
|
elects to apply CC0 to the Work and publicly distribute the Work under its
|
||||||
|
terms, with knowledge of his or her Copyright and Related Rights in the
|
||||||
|
Work and the meaning and intended legal effect of CC0 on those rights.
|
||||||
|
|
||||||
|
1. Copyright and Related Rights. A Work made available under CC0 may be
|
||||||
|
protected by copyright and related or neighboring rights ("Copyright and
|
||||||
|
Related Rights"). Copyright and Related Rights include, but are not
|
||||||
|
limited to, the following:
|
||||||
|
|
||||||
|
i. the right to reproduce, adapt, distribute, perform, display,
|
||||||
|
communicate, and translate a Work;
|
||||||
|
ii. moral rights retained by the original author(s) and/or performer(s);
|
||||||
|
iii. publicity and privacy rights pertaining to a person's image or
|
||||||
|
likeness depicted in a Work;
|
||||||
|
iv. rights protecting against unfair competition in regards to a Work,
|
||||||
|
subject to the limitations in paragraph 4(a), below;
|
||||||
|
v. rights protecting the extraction, dissemination, use and reuse of data
|
||||||
|
in a Work;
|
||||||
|
vi. database rights (such as those arising under Directive 96/9/EC of the
|
||||||
|
European Parliament and of the Council of 11 March 1996 on the legal
|
||||||
|
protection of databases, and under any national implementation
|
||||||
|
thereof, including any amended or successor version of such
|
||||||
|
directive); and
|
||||||
|
vii. other similar, equivalent or corresponding rights throughout the
|
||||||
|
world based on applicable law or treaty, and any national
|
||||||
|
implementations thereof.
|
||||||
|
|
||||||
|
2. Waiver. To the greatest extent permitted by, but not in contravention
|
||||||
|
of, applicable law, Affirmer hereby overtly, fully, permanently,
|
||||||
|
irrevocably and unconditionally waives, abandons, and surrenders all of
|
||||||
|
Affirmer's Copyright and Related Rights and associated claims and causes
|
||||||
|
of action, whether now known or unknown (including existing as well as
|
||||||
|
future claims and causes of action), in the Work (i) in all territories
|
||||||
|
worldwide, (ii) for the maximum duration provided by applicable law or
|
||||||
|
treaty (including future time extensions), (iii) in any current or future
|
||||||
|
medium and for any number of copies, and (iv) for any purpose whatsoever,
|
||||||
|
including without limitation commercial, advertising or promotional
|
||||||
|
purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
|
||||||
|
member of the public at large and to the detriment of Affirmer's heirs and
|
||||||
|
successors, fully intending that such Waiver shall not be subject to
|
||||||
|
revocation, rescission, cancellation, termination, or any other legal or
|
||||||
|
equitable action to disrupt the quiet enjoyment of the Work by the public
|
||||||
|
as contemplated by Affirmer's express Statement of Purpose.
|
||||||
|
|
||||||
|
3. Public License Fallback. Should any part of the Waiver for any reason
|
||||||
|
be judged legally invalid or ineffective under applicable law, then the
|
||||||
|
Waiver shall be preserved to the maximum extent permitted taking into
|
||||||
|
account Affirmer's express Statement of Purpose. In addition, to the
|
||||||
|
extent the Waiver is so judged Affirmer hereby grants to each affected
|
||||||
|
person a royalty-free, non transferable, non sublicensable, non exclusive,
|
||||||
|
irrevocable and unconditional license to exercise Affirmer's Copyright and
|
||||||
|
Related Rights in the Work (i) in all territories worldwide, (ii) for the
|
||||||
|
maximum duration provided by applicable law or treaty (including future
|
||||||
|
time extensions), (iii) in any current or future medium and for any number
|
||||||
|
of copies, and (iv) for any purpose whatsoever, including without
|
||||||
|
limitation commercial, advertising or promotional purposes (the
|
||||||
|
"License"). The License shall be deemed effective as of the date CC0 was
|
||||||
|
applied by Affirmer to the Work. Should any part of the License for any
|
||||||
|
reason be judged legally invalid or ineffective under applicable law, such
|
||||||
|
partial invalidity or ineffectiveness shall not invalidate the remainder
|
||||||
|
of the License, and in such case Affirmer hereby affirms that he or she
|
||||||
|
will not (i) exercise any of his or her remaining Copyright and Related
|
||||||
|
Rights in the Work or (ii) assert any associated claims and causes of
|
||||||
|
action with respect to the Work, in either case contrary to Affirmer's
|
||||||
|
express Statement of Purpose.
|
||||||
|
|
||||||
|
4. Limitations and Disclaimers.
|
||||||
|
|
||||||
|
a. No trademark or patent rights held by Affirmer are waived, abandoned,
|
||||||
|
surrendered, licensed or otherwise affected by this document.
|
||||||
|
b. Affirmer offers the Work as-is and makes no representations or
|
||||||
|
warranties of any kind concerning the Work, express, implied,
|
||||||
|
statutory or otherwise, including without limitation warranties of
|
||||||
|
title, merchantability, fitness for a particular purpose, non
|
||||||
|
infringement, or the absence of latent or other defects, accuracy, or
|
||||||
|
the present or absence of errors, whether or not discoverable, all to
|
||||||
|
the greatest extent permissible under applicable law.
|
||||||
|
c. Affirmer disclaims responsibility for clearing rights of other persons
|
||||||
|
that may apply to the Work or any use thereof, including without
|
||||||
|
limitation any person's Copyright and Related Rights in the Work.
|
||||||
|
Further, Affirmer disclaims responsibility for obtaining any necessary
|
||||||
|
consents, permissions or other rights required for any use of the
|
||||||
|
Work.
|
||||||
|
d. Affirmer understands and acknowledges that Creative Commons is not a
|
||||||
|
party to this document and has no duty or obligation with respect to
|
||||||
|
this CC0 or use of the Work.
|
||||||
163
Makefile
Normal file
163
Makefile
Normal file
@@ -0,0 +1,163 @@
|
|||||||
|
# SPDX-FileCopyrightText: Bernhard Posselt <dev@bernhard-posselt.com>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
|
# Generic Makefile for building and packaging a Nextcloud app which uses npm and
|
||||||
|
# Composer.
|
||||||
|
#
|
||||||
|
# Dependencies:
|
||||||
|
# * make
|
||||||
|
# * which
|
||||||
|
# * curl: used if phpunit and composer are not installed to fetch them from the web
|
||||||
|
# * tar: for building the archive
|
||||||
|
# * npm: for building and testing everything JS
|
||||||
|
#
|
||||||
|
# If no composer.json is in the app root directory, the Composer step
|
||||||
|
# will be skipped. The same goes for the package.json which can be located in
|
||||||
|
# the app root or the js/ directory.
|
||||||
|
#
|
||||||
|
# The npm command by launches the npm build script:
|
||||||
|
#
|
||||||
|
# npm run build
|
||||||
|
#
|
||||||
|
# The npm test command launches the npm test script:
|
||||||
|
#
|
||||||
|
# npm run test
|
||||||
|
#
|
||||||
|
# The idea behind this is to be completely testing and build tool agnostic. All
|
||||||
|
# build tools and additional package managers should be installed locally in
|
||||||
|
# your project, since this won't pollute people's global namespace.
|
||||||
|
#
|
||||||
|
# The following npm scripts in your package.json install and update the bower
|
||||||
|
# and npm dependencies and use gulp as build system (notice how everything is
|
||||||
|
# run from the node_modules folder):
|
||||||
|
#
|
||||||
|
# "scripts": {
|
||||||
|
# "test": "node node_modules/gulp-cli/bin/gulp.js karma",
|
||||||
|
# "prebuild": "npm install && node_modules/bower/bin/bower install && node_modules/bower/bin/bower update",
|
||||||
|
# "build": "node node_modules/gulp-cli/bin/gulp.js"
|
||||||
|
# },
|
||||||
|
|
||||||
|
app_name=$(notdir $(CURDIR))
|
||||||
|
build_tools_directory=$(CURDIR)/build/tools
|
||||||
|
source_build_directory=$(CURDIR)/build/artifacts/source
|
||||||
|
source_package_name=$(source_build_directory)/$(app_name)
|
||||||
|
appstore_build_directory=$(CURDIR)/build/artifacts
|
||||||
|
appstore_package_name=$(appstore_build_directory)/$(app_name)
|
||||||
|
npm=$(shell which npm 2> /dev/null)
|
||||||
|
composer=$(shell which composer 2> /dev/null)
|
||||||
|
|
||||||
|
all: build
|
||||||
|
|
||||||
|
# Fetches the PHP and JS dependencies and compiles the JS. If no composer.json
|
||||||
|
# is present, the composer step is skipped, if no package.json or js/package.json
|
||||||
|
# is present, the npm step is skipped
|
||||||
|
.PHONY: build
|
||||||
|
build:
|
||||||
|
ifneq (,$(wildcard $(CURDIR)/composer.json))
|
||||||
|
make composer
|
||||||
|
endif
|
||||||
|
ifneq (,$(wildcard $(CURDIR)/package.json))
|
||||||
|
make npm
|
||||||
|
endif
|
||||||
|
ifneq (,$(wildcard $(CURDIR)/js/package.json))
|
||||||
|
make npm
|
||||||
|
endif
|
||||||
|
|
||||||
|
# Installs and updates the composer dependencies. If composer is not installed
|
||||||
|
# a copy is fetched from the web
|
||||||
|
.PHONY: composer
|
||||||
|
composer:
|
||||||
|
ifeq (, $(composer))
|
||||||
|
@echo "No composer command available, downloading a copy from the web"
|
||||||
|
mkdir -p $(build_tools_directory)
|
||||||
|
curl -sS https://getcomposer.org/installer | php
|
||||||
|
mv composer.phar $(build_tools_directory)
|
||||||
|
php $(build_tools_directory)/composer.phar install --prefer-dist
|
||||||
|
else
|
||||||
|
composer install --prefer-dist
|
||||||
|
endif
|
||||||
|
|
||||||
|
# Installs npm dependencies
|
||||||
|
.PHONY: npm
|
||||||
|
npm:
|
||||||
|
ifeq (,$(wildcard $(CURDIR)/package.json))
|
||||||
|
cd js && $(npm) run build
|
||||||
|
else
|
||||||
|
npm run build
|
||||||
|
endif
|
||||||
|
|
||||||
|
# Removes the appstore build
|
||||||
|
.PHONY: clean
|
||||||
|
clean:
|
||||||
|
rm -rf ./build
|
||||||
|
|
||||||
|
# Same as clean but also removes dependencies installed by composer, bower and
|
||||||
|
# npm
|
||||||
|
.PHONY: distclean
|
||||||
|
distclean: clean
|
||||||
|
rm -rf vendor
|
||||||
|
rm -rf node_modules
|
||||||
|
rm -rf js/vendor
|
||||||
|
rm -rf js/node_modules
|
||||||
|
|
||||||
|
# Builds the source and appstore package
|
||||||
|
.PHONY: dist
|
||||||
|
dist:
|
||||||
|
make source
|
||||||
|
make appstore
|
||||||
|
|
||||||
|
# Builds the source package
|
||||||
|
.PHONY: source
|
||||||
|
source:
|
||||||
|
rm -rf $(source_build_directory)
|
||||||
|
mkdir -p $(source_build_directory)
|
||||||
|
tar cvzf $(source_package_name).tar.gz \
|
||||||
|
--exclude-vcs \
|
||||||
|
--exclude="../$(app_name)/build" \
|
||||||
|
--exclude="../$(app_name)/js/node_modules" \
|
||||||
|
--exclude="../$(app_name)/node_modules" \
|
||||||
|
--exclude="../$(app_name)/*.log" \
|
||||||
|
--exclude="../$(app_name)/js/*.log" \
|
||||||
|
../$(app_name) \
|
||||||
|
|
||||||
|
# Builds the source package for the app store, ignores php tests, js tests
|
||||||
|
# and build related folders that are unnecessary for an appstore release
|
||||||
|
.PHONY: appstore
|
||||||
|
appstore:
|
||||||
|
rm -rf $(appstore_build_directory)
|
||||||
|
mkdir -p $(appstore_build_directory)
|
||||||
|
tar cvzf $(appstore_package_name).tar.gz \
|
||||||
|
--exclude-vcs \
|
||||||
|
--exclude="../$(app_name)/build" \
|
||||||
|
--exclude="../$(app_name)/tests" \
|
||||||
|
--exclude="../$(app_name)/Makefile" \
|
||||||
|
--exclude="../$(app_name)/*.log" \
|
||||||
|
--exclude="../$(app_name)/phpunit*xml" \
|
||||||
|
--exclude="../$(app_name)/composer.*" \
|
||||||
|
--exclude="../$(app_name)/node_modules" \
|
||||||
|
--exclude="../$(app_name)/js/node_modules" \
|
||||||
|
--exclude="../$(app_name)/js/tests" \
|
||||||
|
--exclude="../$(app_name)/js/test" \
|
||||||
|
--exclude="../$(app_name)/js/*.log" \
|
||||||
|
--exclude="../$(app_name)/js/package.json" \
|
||||||
|
--exclude="../$(app_name)/js/bower.json" \
|
||||||
|
--exclude="../$(app_name)/js/karma.*" \
|
||||||
|
--exclude="../$(app_name)/js/protractor.*" \
|
||||||
|
--exclude="../$(app_name)/package.json" \
|
||||||
|
--exclude="../$(app_name)/bower.json" \
|
||||||
|
--exclude="../$(app_name)/karma.*" \
|
||||||
|
--exclude="../$(app_name)/protractor\.*" \
|
||||||
|
--exclude="../$(app_name)/.*" \
|
||||||
|
--exclude="../$(app_name)/js/.*" \
|
||||||
|
--exclude="../$(app_name)/webpack.config.js" \
|
||||||
|
--exclude="../$(app_name)/stylelint.config.js" \
|
||||||
|
--exclude="../$(app_name)/CHANGELOG.md" \
|
||||||
|
--exclude="../$(app_name)/README.md" \
|
||||||
|
--exclude="../$(app_name)/package-lock.json" \
|
||||||
|
--exclude="../$(app_name)/LICENSES" \
|
||||||
|
../$(app_name) \
|
||||||
|
|
||||||
|
.PHONY: test
|
||||||
|
test: composer
|
||||||
|
$(CURDIR)/vendor/phpunit/phpunit/phpunit -c phpunit.xml
|
||||||
|
$(CURDIR)/vendor/phpunit/phpunit/phpunit -c phpunit.integration.xml
|
||||||
57
README.md
57
README.md
@@ -1,2 +1,57 @@
|
|||||||
# ncc_photo_reduce
|
<!--
|
||||||
|
SPDX-FileCopyrightText: Andrea Rigoni Garola <andrea.rgn@gmail.com>
|
||||||
|
SPDX-License-Identifier: CC0-1.0
|
||||||
|
-->
|
||||||
|
|
||||||
|
# Photo Reduce
|
||||||
|
Place this app in **nextcloud/apps/**
|
||||||
|
|
||||||
|
## Building the app
|
||||||
|
|
||||||
|
The app can be built by using the provided Makefile by running:
|
||||||
|
|
||||||
|
make
|
||||||
|
|
||||||
|
This requires the following things to be present:
|
||||||
|
* make
|
||||||
|
* which
|
||||||
|
* tar: for building the archive
|
||||||
|
* curl: used if phpunit and composer are not installed to fetch them from the web
|
||||||
|
* npm: for building and testing everything JS, only required if a package.json is placed inside the **js/** folder
|
||||||
|
|
||||||
|
The make command will install or update Composer dependencies if a composer.json is present and also **npm run build** if a package.json is present in the **js/** folder. The npm **build** script should use local paths for build systems and package managers, so people that simply want to build the app won't need to install npm libraries globally, e.g.:
|
||||||
|
|
||||||
|
**package.json**:
|
||||||
|
```json
|
||||||
|
"scripts": {
|
||||||
|
"test": "node node_modules/gulp-cli/bin/gulp.js karma",
|
||||||
|
"prebuild": "npm install && node_modules/bower/bin/bower install && node_modules/bower/bin/bower update",
|
||||||
|
"build": "node node_modules/gulp-cli/bin/gulp.js"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Publish to App Store
|
||||||
|
|
||||||
|
First get an account for the [App Store](http://apps.nextcloud.com/) then run:
|
||||||
|
|
||||||
|
make && make appstore
|
||||||
|
|
||||||
|
The archive is located in build/artifacts/appstore and can then be uploaded to the App Store.
|
||||||
|
|
||||||
|
## Running tests
|
||||||
|
You can use the provided Makefile to run all tests by using:
|
||||||
|
|
||||||
|
make test
|
||||||
|
|
||||||
|
This will run the PHP unit and integration tests and if a package.json is present in the **js/** folder will execute **npm run test**
|
||||||
|
|
||||||
|
Of course you can also install [PHPUnit](http://phpunit.de/getting-started.html) and use the configurations directly:
|
||||||
|
|
||||||
|
phpunit -c phpunit.xml
|
||||||
|
|
||||||
|
or:
|
||||||
|
|
||||||
|
phpunit -c phpunit.integration.xml
|
||||||
|
|
||||||
|
for integration tests
|
||||||
|
|||||||
27
appinfo/info.xml
Normal file
27
appinfo/info.xml
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
<?xml version="1.0"?>
|
||||||
|
<info xmlns:xsi= "http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:noNamespaceSchemaLocation="https://apps.nextcloud.com/schema/apps/info.xsd">
|
||||||
|
<!--
|
||||||
|
SPDX-FileCopyrightText: Andrea Rigoni Garola <andrea.rgn@gmail.com>
|
||||||
|
SPDX-License-Identifier: CC0-1.0
|
||||||
|
-->
|
||||||
|
<id>photoreduce</id>
|
||||||
|
<name>Photo Reduce</name>
|
||||||
|
<summary>Automatically reduce size of photos and organize folders</summary>
|
||||||
|
<description><![CDATA[Automatically reduce size of photos and organize folders]]></description>
|
||||||
|
<version>0.0.1</version>
|
||||||
|
<licence>agpl</licence>
|
||||||
|
<author mail="andrea.rgn@gmail.com" >Andrea Rigoni Garola</author>
|
||||||
|
<namespace>PhotoReduce</namespace>
|
||||||
|
<category>multimedia</category>
|
||||||
|
<bugs>https://gitea.mildstone.org/YFinGames/ncc_photo_reduce</bugs>
|
||||||
|
<dependencies>
|
||||||
|
<nextcloud min-version="27" max-version="27"/>
|
||||||
|
</dependencies>
|
||||||
|
<navigations>
|
||||||
|
<navigation>
|
||||||
|
<name>Photo Reduce</name>
|
||||||
|
<route>photoreduce.page.index</route>
|
||||||
|
</navigation>
|
||||||
|
</navigations>
|
||||||
|
</info>
|
||||||
25
appinfo/routes.php
Normal file
25
appinfo/routes.php
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
// SPDX-FileCopyrightText: Andrea Rigoni Garola <andrea.rgn@gmail.com>
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create your routes in here. The name is the lowercase name of the controller
|
||||||
|
* without the controller part, the stuff after the hash is the method.
|
||||||
|
* e.g. page#index -> OCA\PhotoReduce\Controller\PageController->index()
|
||||||
|
*
|
||||||
|
* The controller class has to be registered in the application.php file since
|
||||||
|
* it's instantiated in there
|
||||||
|
*/
|
||||||
|
return [
|
||||||
|
'resources' => [
|
||||||
|
'note' => ['url' => '/notes'],
|
||||||
|
'note_api' => ['url' => '/api/0.1/notes']
|
||||||
|
],
|
||||||
|
'routes' => [
|
||||||
|
['name' => 'page#index', 'url' => '/', 'verb' => 'GET'],
|
||||||
|
['name' => 'note_api#preflighted_cors', 'url' => '/api/0.1/{path}',
|
||||||
|
'verb' => 'OPTIONS', 'requirements' => ['path' => '.+']]
|
||||||
|
]
|
||||||
|
];
|
||||||
5
babel.config.js
Normal file
5
babel.config.js
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
// SPDX-FileCopyrightText: Andrea Rigoni Garola <andrea.rgn@gmail.com>
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
const babelConfig = require('@nextcloud/babel-config')
|
||||||
|
|
||||||
|
module.exports = babelConfig
|
||||||
38
composer.json
Normal file
38
composer.json
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
{
|
||||||
|
"name": "nextcloud/photoreduce",
|
||||||
|
"description": "Automatically reduce size of photos and organize folders",
|
||||||
|
"type": "project",
|
||||||
|
"license": "AGPL-3.0-or-later",
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Andrea Rigoni Garola"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"require-dev": {
|
||||||
|
"phpunit/phpunit": "^9",
|
||||||
|
"sabre/dav": "^4.1",
|
||||||
|
"sabre/xml": "^2.2",
|
||||||
|
"symfony/event-dispatcher": "^5.3.11",
|
||||||
|
"nextcloud/ocp": "dev-stable27",
|
||||||
|
"psalm/phar": "^5.17.0",
|
||||||
|
"nextcloud/coding-standard": "^v1.1.1"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"lint": "find . -name \\*.php -not -path './vendor/*' -print0 | xargs -0 -n1 php -l",
|
||||||
|
"cs:check": "php-cs-fixer fix --dry-run --diff",
|
||||||
|
"cs:fix": "php-cs-fixer fix",
|
||||||
|
"psalm": "psalm.phar --threads=1",
|
||||||
|
"psalm:update-baseline": "psalm.phar --threads=1 --update-baseline",
|
||||||
|
"psalm:update-baseline:force": "psalm.phar --threads=1 --update-baseline --set-baseline=tests/psalm-baseline.xml",
|
||||||
|
"psalm:clear": "psalm.phar --clear-cache && psalm --clear-global-cache",
|
||||||
|
"psalm:fix": "psalm.phar --alter --issues=InvalidReturnType,InvalidNullableReturnType,MissingParamType,InvalidFalsableReturnType"
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"allow-plugins": {
|
||||||
|
"composer/package-versions-deprecated": true
|
||||||
|
},
|
||||||
|
"platform": {
|
||||||
|
"php": "8.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
56
img/app.svg
Normal file
56
img/app.svg
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
|
xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
height="32"
|
||||||
|
width="32"
|
||||||
|
version="1"
|
||||||
|
viewBox="0 0 32 32"
|
||||||
|
id="svg4"
|
||||||
|
sodipodi:docname="app.svg"
|
||||||
|
inkscape:version="0.92.1 r">
|
||||||
|
<metadata
|
||||||
|
id="metadata10">
|
||||||
|
<rdf:RDF>
|
||||||
|
<cc:Work
|
||||||
|
rdf:about="">
|
||||||
|
<dc:format>image/svg+xml</dc:format>
|
||||||
|
<dc:type
|
||||||
|
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||||
|
</cc:Work>
|
||||||
|
</rdf:RDF>
|
||||||
|
</metadata>
|
||||||
|
<defs
|
||||||
|
id="defs8" />
|
||||||
|
<sodipodi:namedview
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1"
|
||||||
|
objecttolerance="10"
|
||||||
|
gridtolerance="10"
|
||||||
|
guidetolerance="10"
|
||||||
|
inkscape:pageopacity="0"
|
||||||
|
inkscape:pageshadow="2"
|
||||||
|
inkscape:window-width="789"
|
||||||
|
inkscape:window-height="480"
|
||||||
|
id="namedview6"
|
||||||
|
showgrid="false"
|
||||||
|
inkscape:zoom="7.375"
|
||||||
|
inkscape:cx="-8.3389831"
|
||||||
|
inkscape:cy="16"
|
||||||
|
inkscape:window-x="0"
|
||||||
|
inkscape:window-y="27"
|
||||||
|
inkscape:window-maximized="0"
|
||||||
|
inkscape:current-layer="svg4" />
|
||||||
|
<path
|
||||||
|
d="M13.733 0a.915.915 0 0 0-.933.934V3.6c-1.182.304-2.243.794-3.267 1.4L7.6 3.068a.93.93 0 0 0-1.334 0l-3.2 3.2a.93.93 0 0 0 0 1.334L5 9.535c-.607 1.024-1.097 2.085-1.4 3.267H.933a.915.915 0 0 0-.933.934v4.533c0 .53.403.934.933.934H3.6c.303 1.182.793 2.243 1.4 3.267l-1.934 1.935a.93.93 0 0 0 0 1.333l3.2 3.2a.93.93 0 0 0 1.333 0L9.532 27c1.024.61 2.085 1.097 3.266 1.4v2.667c0 .53.402.933.932.933h4.534c.53 0 .933-.403.933-.935V28.4c1.18-.305 2.24-.795 3.265-1.4L24.4 28.93a.93.93 0 0 0 1.332 0l3.2-3.2a.93.93 0 0 0 0-1.333L27 22.465c.607-1.024 1.096-2.085 1.4-3.266h2.665a.915.915 0 0 0 .935-.933v-4.534a.915.915 0 0 0-.934-.933H28.4c-.304-1.182-.792-2.243-1.4-3.267L28.932 7.6a.93.93 0 0 0 0-1.334l-3.2-3.2a.93.93 0 0 0-1.333 0L22.465 5c-1.024-.607-2.084-1.097-3.266-1.4V.933A.915.915 0 0 0 18.267 0zM16 8.87A7.134 7.134 0 0 1 23.13 16 7.134 7.134 0 0 1 16 23.133c-3.936 0-7.13-3.196-7.13-7.132S12.063 8.87 16 8.87z"
|
||||||
|
display="block"
|
||||||
|
fill="#fff"
|
||||||
|
id="path2"
|
||||||
|
style="fill:#ffffff" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.4 KiB |
2
img/app.svg.license
Normal file
2
img/app.svg.license
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
SPDX-FileCopyrightText: Andrea Rigoni Garola <andrea.rgn@gmail.com>
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
37
lib/AppInfo/Application.php
Normal file
37
lib/AppInfo/Application.php
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
// SPDX-FileCopyrightText: Andrea Rigoni Garola <andrea.rgn@gmail.com>
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
|
namespace OCA\PhotoReduce\AppInfo;
|
||||||
|
|
||||||
|
use OCP\AppFramework\App;
|
||||||
|
use OCP\AppFramework\Bootstrap\IBootContext;
|
||||||
|
use OCP\AppFramework\Bootstrap\IBootstrap;
|
||||||
|
use OCP\AppFramework\Bootstrap\IRegistrationContext;
|
||||||
|
|
||||||
|
class Application extends App implements IBootstrap {
|
||||||
|
public const APP_ID = 'photoreduce';
|
||||||
|
|
||||||
|
public function __construct() {
|
||||||
|
parent::__construct(self::APP_ID);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function register(IRegistrationContext $context): void {
|
||||||
|
/*
|
||||||
|
* For further information about the app bootstrapping, please refer to our documentation:
|
||||||
|
* https://docs.nextcloud.com/server/latest/developer_manual/app_development/bootstrap.html
|
||||||
|
*/
|
||||||
|
// Register your services, event listeners, etc.
|
||||||
|
}
|
||||||
|
|
||||||
|
public function boot(IBootContext $context): void {
|
||||||
|
/*
|
||||||
|
* For further information about the app bootstrapping, please refer to our documentation:
|
||||||
|
* https://docs.nextcloud.com/server/latest/developer_manual/app_development/bootstrap.html
|
||||||
|
*/
|
||||||
|
// Prepare your app.
|
||||||
|
}
|
||||||
|
}
|
||||||
25
lib/Controller/Errors.php
Normal file
25
lib/Controller/Errors.php
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
// SPDX-FileCopyrightText: Andrea Rigoni Garola <andrea.rgn@gmail.com>
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
|
namespace OCA\PhotoReduce\Controller;
|
||||||
|
|
||||||
|
use Closure;
|
||||||
|
|
||||||
|
use OCA\PhotoReduce\Service\NoteNotFound;
|
||||||
|
use OCP\AppFramework\Http;
|
||||||
|
|
||||||
|
use OCP\AppFramework\Http\DataResponse;
|
||||||
|
|
||||||
|
trait Errors {
|
||||||
|
protected function handleNotFound(Closure $callback): DataResponse {
|
||||||
|
try {
|
||||||
|
return new DataResponse($callback());
|
||||||
|
} catch (NoteNotFound $e) {
|
||||||
|
$message = ['message' => $e->getMessage()];
|
||||||
|
return new DataResponse($message, Http::STATUS_NOT_FOUND);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
81
lib/Controller/NoteApiController.php
Normal file
81
lib/Controller/NoteApiController.php
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
// SPDX-FileCopyrightText: Andrea Rigoni Garola <andrea.rgn@gmail.com>
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
|
namespace OCA\PhotoReduce\Controller;
|
||||||
|
|
||||||
|
use OCA\PhotoReduce\AppInfo\Application;
|
||||||
|
use OCA\PhotoReduce\Service\NoteService;
|
||||||
|
use OCP\AppFramework\ApiController;
|
||||||
|
use OCP\AppFramework\Http\DataResponse;
|
||||||
|
use OCP\IRequest;
|
||||||
|
|
||||||
|
class NoteApiController extends ApiController {
|
||||||
|
private NoteService $service;
|
||||||
|
private ?string $userId;
|
||||||
|
|
||||||
|
use Errors;
|
||||||
|
|
||||||
|
public function __construct(IRequest $request,
|
||||||
|
NoteService $service,
|
||||||
|
?string $userId) {
|
||||||
|
parent::__construct(Application::APP_ID, $request);
|
||||||
|
$this->service = $service;
|
||||||
|
$this->userId = $userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @CORS
|
||||||
|
* @NoCSRFRequired
|
||||||
|
* @NoAdminRequired
|
||||||
|
*/
|
||||||
|
public function index(): DataResponse {
|
||||||
|
return new DataResponse($this->service->findAll($this->userId));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @CORS
|
||||||
|
* @NoCSRFRequired
|
||||||
|
* @NoAdminRequired
|
||||||
|
*/
|
||||||
|
public function show(int $id): DataResponse {
|
||||||
|
return $this->handleNotFound(function () use ($id) {
|
||||||
|
return $this->service->find($id, $this->userId);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @CORS
|
||||||
|
* @NoCSRFRequired
|
||||||
|
* @NoAdminRequired
|
||||||
|
*/
|
||||||
|
public function create(string $title, string $content): DataResponse {
|
||||||
|
return new DataResponse($this->service->create($title, $content,
|
||||||
|
$this->userId));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @CORS
|
||||||
|
* @NoCSRFRequired
|
||||||
|
* @NoAdminRequired
|
||||||
|
*/
|
||||||
|
public function update(int $id, string $title,
|
||||||
|
string $content): DataResponse {
|
||||||
|
return $this->handleNotFound(function () use ($id, $title, $content) {
|
||||||
|
return $this->service->update($id, $title, $content, $this->userId);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @CORS
|
||||||
|
* @NoCSRFRequired
|
||||||
|
* @NoAdminRequired
|
||||||
|
*/
|
||||||
|
public function destroy(int $id): DataResponse {
|
||||||
|
return $this->handleNotFound(function () use ($id) {
|
||||||
|
return $this->service->delete($id, $this->userId);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
71
lib/Controller/NoteController.php
Normal file
71
lib/Controller/NoteController.php
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
// SPDX-FileCopyrightText: Andrea Rigoni Garola <andrea.rgn@gmail.com>
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
|
namespace OCA\PhotoReduce\Controller;
|
||||||
|
|
||||||
|
use OCA\PhotoReduce\AppInfo\Application;
|
||||||
|
use OCA\PhotoReduce\Service\NoteService;
|
||||||
|
use OCP\AppFramework\Controller;
|
||||||
|
use OCP\AppFramework\Http\DataResponse;
|
||||||
|
use OCP\IRequest;
|
||||||
|
|
||||||
|
class NoteController extends Controller {
|
||||||
|
private NoteService $service;
|
||||||
|
private ?string $userId;
|
||||||
|
|
||||||
|
use Errors;
|
||||||
|
|
||||||
|
public function __construct(IRequest $request,
|
||||||
|
NoteService $service,
|
||||||
|
?string $userId) {
|
||||||
|
parent::__construct(Application::APP_ID, $request);
|
||||||
|
$this->service = $service;
|
||||||
|
$this->userId = $userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @NoAdminRequired
|
||||||
|
*/
|
||||||
|
public function index(): DataResponse {
|
||||||
|
return new DataResponse($this->service->findAll($this->userId));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @NoAdminRequired
|
||||||
|
*/
|
||||||
|
public function show(int $id): DataResponse {
|
||||||
|
return $this->handleNotFound(function () use ($id) {
|
||||||
|
return $this->service->find($id, $this->userId);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @NoAdminRequired
|
||||||
|
*/
|
||||||
|
public function create(string $title, string $content): DataResponse {
|
||||||
|
return new DataResponse($this->service->create($title, $content,
|
||||||
|
$this->userId));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @NoAdminRequired
|
||||||
|
*/
|
||||||
|
public function update(int $id, string $title,
|
||||||
|
string $content): DataResponse {
|
||||||
|
return $this->handleNotFound(function () use ($id, $title, $content) {
|
||||||
|
return $this->service->update($id, $title, $content, $this->userId);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @NoAdminRequired
|
||||||
|
*/
|
||||||
|
public function destroy(int $id): DataResponse {
|
||||||
|
return $this->handleNotFound(function () use ($id) {
|
||||||
|
return $this->service->delete($id, $this->userId);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
29
lib/Controller/PageController.php
Normal file
29
lib/Controller/PageController.php
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
// SPDX-FileCopyrightText: Andrea Rigoni Garola <andrea.rgn@gmail.com>
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
|
namespace OCA\PhotoReduce\Controller;
|
||||||
|
|
||||||
|
use OCA\PhotoReduce\AppInfo\Application;
|
||||||
|
use OCP\AppFramework\Controller;
|
||||||
|
use OCP\AppFramework\Http\TemplateResponse;
|
||||||
|
use OCP\IRequest;
|
||||||
|
use OCP\Util;
|
||||||
|
|
||||||
|
class PageController extends Controller {
|
||||||
|
public function __construct(IRequest $request) {
|
||||||
|
parent::__construct(Application::APP_ID, $request);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @NoAdminRequired
|
||||||
|
* @NoCSRFRequired
|
||||||
|
*/
|
||||||
|
public function index(): TemplateResponse {
|
||||||
|
Util::addScript(Application::APP_ID, 'photoreduce-main');
|
||||||
|
|
||||||
|
return new TemplateResponse(Application::APP_ID, 'main');
|
||||||
|
}
|
||||||
|
}
|
||||||
34
lib/Db/Note.php
Normal file
34
lib/Db/Note.php
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
// SPDX-FileCopyrightText: Andrea Rigoni Garola <andrea.rgn@gmail.com>
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
|
namespace OCA\PhotoReduce\Db;
|
||||||
|
|
||||||
|
use JsonSerializable;
|
||||||
|
|
||||||
|
use OCP\AppFramework\Db\Entity;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @method getId(): int
|
||||||
|
* @method getTitle(): string
|
||||||
|
* @method setTitle(string $title): void
|
||||||
|
* @method getContent(): string
|
||||||
|
* @method setContent(string $content): void
|
||||||
|
* @method getUserId(): string
|
||||||
|
* @method setUserId(string $userId): void
|
||||||
|
*/
|
||||||
|
class Note extends Entity implements JsonSerializable {
|
||||||
|
protected string $title = '';
|
||||||
|
protected string $content = '';
|
||||||
|
protected string $userId = '';
|
||||||
|
|
||||||
|
public function jsonSerialize(): array {
|
||||||
|
return [
|
||||||
|
'id' => $this->id,
|
||||||
|
'title' => $this->title,
|
||||||
|
'content' => $this->content
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
48
lib/Db/NoteMapper.php
Normal file
48
lib/Db/NoteMapper.php
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
// SPDX-FileCopyrightText: Andrea Rigoni Garola <andrea.rgn@gmail.com>
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
|
namespace OCA\PhotoReduce\Db;
|
||||||
|
|
||||||
|
use OCP\AppFramework\Db\DoesNotExistException;
|
||||||
|
use OCP\AppFramework\Db\QBMapper;
|
||||||
|
use OCP\DB\QueryBuilder\IQueryBuilder;
|
||||||
|
use OCP\IDBConnection;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @template-extends QBMapper<Note>
|
||||||
|
*/
|
||||||
|
class NoteMapper extends QBMapper {
|
||||||
|
public function __construct(IDBConnection $db) {
|
||||||
|
parent::__construct($db, 'photoreduce', Note::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException
|
||||||
|
* @throws DoesNotExistException
|
||||||
|
*/
|
||||||
|
public function find(int $id, string $userId): Note {
|
||||||
|
/* @var $qb IQueryBuilder */
|
||||||
|
$qb = $this->db->getQueryBuilder();
|
||||||
|
$qb->select('*')
|
||||||
|
->from('photoreduce')
|
||||||
|
->where($qb->expr()->eq('id', $qb->createNamedParameter($id, IQueryBuilder::PARAM_INT)))
|
||||||
|
->andWhere($qb->expr()->eq('user_id', $qb->createNamedParameter($userId)));
|
||||||
|
return $this->findEntity($qb);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $userId
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function findAll(string $userId): array {
|
||||||
|
/* @var $qb IQueryBuilder */
|
||||||
|
$qb = $this->db->getQueryBuilder();
|
||||||
|
$qb->select('*')
|
||||||
|
->from('photoreduce')
|
||||||
|
->where($qb->expr()->eq('user_id', $qb->createNamedParameter($userId)));
|
||||||
|
return $this->findEntities($qb);
|
||||||
|
}
|
||||||
|
}
|
||||||
50
lib/Migration/Version000000Date20181013124731.php
Normal file
50
lib/Migration/Version000000Date20181013124731.php
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
// SPDX-FileCopyrightText: Andrea Rigoni Garola <andrea.rgn@gmail.com>
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
|
namespace OCA\PhotoReduce\Migration;
|
||||||
|
|
||||||
|
use Closure;
|
||||||
|
use OCP\DB\ISchemaWrapper;
|
||||||
|
use OCP\Migration\IOutput;
|
||||||
|
use OCP\Migration\SimpleMigrationStep;
|
||||||
|
|
||||||
|
class Version000000Date20181013124731 extends SimpleMigrationStep {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param IOutput $output
|
||||||
|
* @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper`
|
||||||
|
* @param array $options
|
||||||
|
* @return null|ISchemaWrapper
|
||||||
|
*/
|
||||||
|
public function changeSchema(IOutput $output, Closure $schemaClosure, array $options) {
|
||||||
|
/** @var ISchemaWrapper $schema */
|
||||||
|
$schema = $schemaClosure();
|
||||||
|
|
||||||
|
if (!$schema->hasTable('photoreduce')) {
|
||||||
|
$table = $schema->createTable('photoreduce');
|
||||||
|
$table->addColumn('id', 'integer', [
|
||||||
|
'autoincrement' => true,
|
||||||
|
'notnull' => true,
|
||||||
|
]);
|
||||||
|
$table->addColumn('title', 'string', [
|
||||||
|
'notnull' => true,
|
||||||
|
'length' => 200
|
||||||
|
]);
|
||||||
|
$table->addColumn('user_id', 'string', [
|
||||||
|
'notnull' => true,
|
||||||
|
'length' => 200,
|
||||||
|
]);
|
||||||
|
$table->addColumn('content', 'text', [
|
||||||
|
'notnull' => true,
|
||||||
|
'default' => ''
|
||||||
|
]);
|
||||||
|
|
||||||
|
$table->setPrimaryKey(['id']);
|
||||||
|
$table->addIndex(['user_id'], 'photoreduce_user_id_index');
|
||||||
|
}
|
||||||
|
return $schema;
|
||||||
|
}
|
||||||
|
}
|
||||||
10
lib/Service/NoteNotFound.php
Normal file
10
lib/Service/NoteNotFound.php
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
// SPDX-FileCopyrightText: Andrea Rigoni Garola <andrea.rgn@gmail.com>
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
|
namespace OCA\PhotoReduce\Service;
|
||||||
|
|
||||||
|
class NoteNotFound extends \Exception {
|
||||||
|
}
|
||||||
84
lib/Service/NoteService.php
Normal file
84
lib/Service/NoteService.php
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
// SPDX-FileCopyrightText: Andrea Rigoni Garola <andrea.rgn@gmail.com>
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
|
namespace OCA\PhotoReduce\Service;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
|
||||||
|
use OCA\PhotoReduce\Db\Note;
|
||||||
|
use OCA\PhotoReduce\Db\NoteMapper;
|
||||||
|
|
||||||
|
use OCP\AppFramework\Db\DoesNotExistException;
|
||||||
|
use OCP\AppFramework\Db\MultipleObjectsReturnedException;
|
||||||
|
|
||||||
|
class NoteService {
|
||||||
|
private NoteMapper $mapper;
|
||||||
|
|
||||||
|
public function __construct(NoteMapper $mapper) {
|
||||||
|
$this->mapper = $mapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return list<Note>
|
||||||
|
*/
|
||||||
|
public function findAll(string $userId): array {
|
||||||
|
return $this->mapper->findAll($userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return never
|
||||||
|
*/
|
||||||
|
private function handleException(Exception $e) {
|
||||||
|
if ($e instanceof DoesNotExistException ||
|
||||||
|
$e instanceof MultipleObjectsReturnedException) {
|
||||||
|
throw new NoteNotFound($e->getMessage());
|
||||||
|
} else {
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function find(int $id, string $userId): Note {
|
||||||
|
try {
|
||||||
|
return $this->mapper->find($id, $userId);
|
||||||
|
|
||||||
|
// in order to be able to plug in different storage backends like files
|
||||||
|
// for instance it is a good idea to turn storage related exceptions
|
||||||
|
// into service related exceptions so controllers and service users
|
||||||
|
// have to deal with only one type of exception
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$this->handleException($e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function create(string $title, string $content, string $userId): Note {
|
||||||
|
$note = new Note();
|
||||||
|
$note->setTitle($title);
|
||||||
|
$note->setContent($content);
|
||||||
|
$note->setUserId($userId);
|
||||||
|
return $this->mapper->insert($note);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function update(int $id, string $title, string $content, string $userId): Note {
|
||||||
|
try {
|
||||||
|
$note = $this->mapper->find($id, $userId);
|
||||||
|
$note->setTitle($title);
|
||||||
|
$note->setContent($content);
|
||||||
|
return $this->mapper->update($note);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$this->handleException($e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function delete(int $id, string $userId): Note {
|
||||||
|
try {
|
||||||
|
$note = $this->mapper->find($id, $userId);
|
||||||
|
$this->mapper->delete($note);
|
||||||
|
return $note;
|
||||||
|
} catch (Exception $e) {
|
||||||
|
$this->handleException($e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
43
package.json
Normal file
43
package.json
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
{
|
||||||
|
"name": "photoreduce",
|
||||||
|
"description": "Automatically reduce size of photos and organize folders",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"author": "Andrea Rigoni Garola <andrea.rgn@gmail.com>",
|
||||||
|
"contributors": [],
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://gitea.mildstone.org/YFinGames/ncc_photo_reduce"
|
||||||
|
},
|
||||||
|
"license": "agpl",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"build": "webpack --node-env production --progress",
|
||||||
|
"dev": "webpack --node-env development --progress",
|
||||||
|
"watch": "webpack --node-env development --progress --watch",
|
||||||
|
"serve": "webpack --node-env development serve --progress",
|
||||||
|
"lint": "eslint --ext .js,.vue src",
|
||||||
|
"lint:fix": "eslint --ext .js,.vue src --fix",
|
||||||
|
"stylelint": "stylelint css/*.css css/*.scss src/**/*.scss src/**/*.vue",
|
||||||
|
"stylelint:fix": "stylelint css/*.css css/*.scss src/**/*.scss src/**/*.vue --fix"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@nextcloud/axios": "^1.10.0",
|
||||||
|
"@nextcloud/dialogs": "^3.1.4",
|
||||||
|
"@nextcloud/router": "^2.0.0",
|
||||||
|
"@nextcloud/vue": "^5.4.0",
|
||||||
|
"vue": "^2.7.0"
|
||||||
|
},
|
||||||
|
"browserslist": [
|
||||||
|
"extends @nextcloud/browserslist-config"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": "^16.0.0",
|
||||||
|
"npm": "^7.0.0 || ^8.0.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@nextcloud/babel-config": "^1.0.0",
|
||||||
|
"@nextcloud/browserslist-config": "^2.2.0",
|
||||||
|
"@nextcloud/eslint-config": "^8.0.0",
|
||||||
|
"@nextcloud/stylelint-config": "^2.1.2",
|
||||||
|
"@nextcloud/webpack-vue-config": "^5.2.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
38
psalm.xml
Normal file
38
psalm.xml
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
<?xml version="1.0"?>
|
||||||
|
<psalm
|
||||||
|
errorLevel="4"
|
||||||
|
resolveFromConfigFile="true"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xmlns="https://getpsalm.org/schema/config"
|
||||||
|
xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd"
|
||||||
|
errorBaseline="tests/psalm-baseline.xml"
|
||||||
|
>
|
||||||
|
<!--
|
||||||
|
SPDX-FileCopyrightText: Andrea Rigoni Garola <andrea.rgn@gmail.com>
|
||||||
|
SPDX-License-Identifier: CC0-1.0
|
||||||
|
-->
|
||||||
|
<projectFiles>
|
||||||
|
<directory name="lib" />
|
||||||
|
<ignoreFiles>
|
||||||
|
<directory name="vendor" />
|
||||||
|
</ignoreFiles>
|
||||||
|
</projectFiles>
|
||||||
|
<extraFiles>
|
||||||
|
<directory name="vendor" />
|
||||||
|
<ignoreFiles>
|
||||||
|
<directory name="vendor/phpunit/php-code-coverage" />
|
||||||
|
<directory name="vendor/psalm" />
|
||||||
|
</ignoreFiles>
|
||||||
|
</extraFiles>
|
||||||
|
<issueHandlers>
|
||||||
|
<UndefinedDocblockClass>
|
||||||
|
<errorLevel type="suppress">
|
||||||
|
<referencedClass name="OC\AppFramework\OCS\BaseResponse"/>
|
||||||
|
<referencedClass name="Doctrine\DBAL\Schema\Schema" />
|
||||||
|
<referencedClass name="Doctrine\DBAL\Schema\SchemaException" />
|
||||||
|
<referencedClass name="Doctrine\DBAL\Driver\Statement" />
|
||||||
|
<referencedClass name="Doctrine\DBAL\Schema\Table" />
|
||||||
|
</errorLevel>
|
||||||
|
</UndefinedDocblockClass>
|
||||||
|
</issueHandlers>
|
||||||
|
</psalm>
|
||||||
241
src/App.vue
Normal file
241
src/App.vue
Normal file
@@ -0,0 +1,241 @@
|
|||||||
|
<template>
|
||||||
|
<!--
|
||||||
|
SPDX-FileCopyrightText: Andrea Rigoni Garola <andrea.rgn@gmail.com>
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
-->
|
||||||
|
<div id="content" class="app-photoreduce">
|
||||||
|
<AppNavigation>
|
||||||
|
<AppNavigationNew v-if="!loading"
|
||||||
|
:text="t('photoreduce', 'New note')"
|
||||||
|
:disabled="false"
|
||||||
|
button-id="new-photoreduce-button"
|
||||||
|
button-class="icon-add"
|
||||||
|
@click="newNote" />
|
||||||
|
<ul>
|
||||||
|
<AppNavigationItem v-for="note in notes"
|
||||||
|
:key="note.id"
|
||||||
|
:title="note.title ? note.title : t('photoreduce', 'New note')"
|
||||||
|
:class="{active: currentNoteId === note.id}"
|
||||||
|
@click="openNote(note)">
|
||||||
|
<template slot="actions">
|
||||||
|
<ActionButton v-if="note.id === -1"
|
||||||
|
icon="icon-close"
|
||||||
|
@click="cancelNewNote(note)">
|
||||||
|
{{
|
||||||
|
t('photoreduce', 'Cancel note creation') }}
|
||||||
|
</ActionButton>
|
||||||
|
<ActionButton v-else
|
||||||
|
icon="icon-delete"
|
||||||
|
@click="deleteNote(note)">
|
||||||
|
{{
|
||||||
|
t('photoreduce', 'Delete note') }}
|
||||||
|
</ActionButton>
|
||||||
|
</template>
|
||||||
|
</AppNavigationItem>
|
||||||
|
</ul>
|
||||||
|
</AppNavigation>
|
||||||
|
<AppContent>
|
||||||
|
<div v-if="currentNote">
|
||||||
|
<input ref="title"
|
||||||
|
v-model="currentNote.title"
|
||||||
|
type="text"
|
||||||
|
:disabled="updating">
|
||||||
|
<textarea ref="content" v-model="currentNote.content" :disabled="updating" />
|
||||||
|
<input type="button"
|
||||||
|
class="primary"
|
||||||
|
:value="t('photoreduce', 'Save')"
|
||||||
|
:disabled="updating || !savePossible"
|
||||||
|
@click="saveNote">
|
||||||
|
</div>
|
||||||
|
<div v-else id="emptycontent">
|
||||||
|
<div class="icon-file" />
|
||||||
|
<h2>{{
|
||||||
|
t('photoreduce', 'Create a note to get started') }}</h2>
|
||||||
|
</div>
|
||||||
|
</AppContent>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import ActionButton from '@nextcloud/vue/dist/Components/ActionButton'
|
||||||
|
import AppContent from '@nextcloud/vue/dist/Components/AppContent'
|
||||||
|
import AppNavigation from '@nextcloud/vue/dist/Components/AppNavigation'
|
||||||
|
import AppNavigationItem from '@nextcloud/vue/dist/Components/AppNavigationItem'
|
||||||
|
import AppNavigationNew from '@nextcloud/vue/dist/Components/AppNavigationNew'
|
||||||
|
|
||||||
|
import '@nextcloud/dialogs/styles/toast.scss'
|
||||||
|
import { generateUrl } from '@nextcloud/router'
|
||||||
|
import { showError, showSuccess } from '@nextcloud/dialogs'
|
||||||
|
import axios from '@nextcloud/axios'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'App',
|
||||||
|
components: {
|
||||||
|
ActionButton,
|
||||||
|
AppContent,
|
||||||
|
AppNavigation,
|
||||||
|
AppNavigationItem,
|
||||||
|
AppNavigationNew,
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
notes: [],
|
||||||
|
currentNoteId: null,
|
||||||
|
updating: false,
|
||||||
|
loading: true,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
/**
|
||||||
|
* Return the currently selected note object
|
||||||
|
* @returns {Object|null}
|
||||||
|
*/
|
||||||
|
currentNote() {
|
||||||
|
if (this.currentNoteId === null) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return this.notes.find((note) => note.id === this.currentNoteId)
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if a note is selected and its title is not empty
|
||||||
|
* @returns {Boolean}
|
||||||
|
*/
|
||||||
|
savePossible() {
|
||||||
|
return this.currentNote && this.currentNote.title !== ''
|
||||||
|
},
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Fetch list of notes when the component is loaded
|
||||||
|
*/
|
||||||
|
async mounted() {
|
||||||
|
try {
|
||||||
|
const response = await axios.get(generateUrl('/apps/photoreduce/notes'))
|
||||||
|
this.notes = response.data
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
showError(t('notestutorial', 'Could not fetch notes'))
|
||||||
|
}
|
||||||
|
this.loading = false
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
/**
|
||||||
|
* Create a new note and focus the note content field automatically
|
||||||
|
* @param {Object} note Note object
|
||||||
|
*/
|
||||||
|
openNote(note) {
|
||||||
|
if (this.updating) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.currentNoteId = note.id
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.$refs.content.focus()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Action tiggered when clicking the save button
|
||||||
|
* create a new note or save
|
||||||
|
*/
|
||||||
|
saveNote() {
|
||||||
|
if (this.currentNoteId === -1) {
|
||||||
|
this.createNote(this.currentNote)
|
||||||
|
} else {
|
||||||
|
this.updateNote(this.currentNote)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Create a new note and focus the note content field automatically
|
||||||
|
* The note is not yet saved, therefore an id of -1 is used until it
|
||||||
|
* has been persisted in the backend
|
||||||
|
*/
|
||||||
|
newNote() {
|
||||||
|
if (this.currentNoteId !== -1) {
|
||||||
|
this.currentNoteId = -1
|
||||||
|
this.notes.push({
|
||||||
|
id: -1,
|
||||||
|
title: '',
|
||||||
|
content: '',
|
||||||
|
})
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.$refs.title.focus()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Abort creating a new note
|
||||||
|
*/
|
||||||
|
cancelNewNote() {
|
||||||
|
this.notes.splice(this.notes.findIndex((note) => note.id === -1), 1)
|
||||||
|
this.currentNoteId = null
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Create a new note by sending the information to the server
|
||||||
|
* @param {Object} note Note object
|
||||||
|
*/
|
||||||
|
async createNote(note) {
|
||||||
|
this.updating = true
|
||||||
|
try {
|
||||||
|
const response = await axios.post(generateUrl('/apps/photoreduce/notes'), note)
|
||||||
|
const index = this.notes.findIndex((match) => match.id === this.currentNoteId)
|
||||||
|
this.$set(this.notes, index, response.data)
|
||||||
|
this.currentNoteId = response.data.id
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
showError(t('notestutorial', 'Could not create the note'))
|
||||||
|
}
|
||||||
|
this.updating = false
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Update an existing note on the server
|
||||||
|
* @param {Object} note Note object
|
||||||
|
*/
|
||||||
|
async updateNote(note) {
|
||||||
|
this.updating = true
|
||||||
|
try {
|
||||||
|
await axios.put(generateUrl(`/apps/photoreduce/notes/${note.id}`), note)
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
showError(t('notestutorial', 'Could not update the note'))
|
||||||
|
}
|
||||||
|
this.updating = false
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Delete a note, remove it from the frontend and show a hint
|
||||||
|
* @param {Object} note Note object
|
||||||
|
*/
|
||||||
|
async deleteNote(note) {
|
||||||
|
try {
|
||||||
|
await axios.delete(generateUrl(`/apps/photoreduce/notes/${note.id}`))
|
||||||
|
this.notes.splice(this.notes.indexOf(note), 1)
|
||||||
|
if (this.currentNoteId === note.id) {
|
||||||
|
this.currentNoteId = null
|
||||||
|
}
|
||||||
|
showSuccess(t('photoreduce', 'Note deleted'))
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
showError(t('photoreduce', 'Could not delete the note'))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<style scoped>
|
||||||
|
#app-content > div {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
padding: 20px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type='text'] {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
textarea {
|
||||||
|
flex-grow: 1;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
19
src/main.js
Normal file
19
src/main.js
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
/**
|
||||||
|
* SPDX-FileCopyrightText: 2018 John Molakvoæ <skjnldsv@protonmail.com>
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { generateFilePath } from '@nextcloud/router'
|
||||||
|
|
||||||
|
import Vue from 'vue'
|
||||||
|
import App from './App'
|
||||||
|
|
||||||
|
// eslint-disable-next-line
|
||||||
|
__webpack_public_path__ = generateFilePath(appName, '', 'js/')
|
||||||
|
|
||||||
|
Vue.mixin({ methods: { t, n } })
|
||||||
|
|
||||||
|
export default new Vue({
|
||||||
|
el: '#content',
|
||||||
|
render: h => h(App),
|
||||||
|
})
|
||||||
5
stylelint.config.js
Normal file
5
stylelint.config.js
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
// SPDX-FileCopyrightText: Nextcloud contributors
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
const stylelintConfig = require('@nextcloud/stylelint-config')
|
||||||
|
|
||||||
|
module.exports = stylelintConfig
|
||||||
6
templates/main.php
Normal file
6
templates/main.php
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?php
|
||||||
|
declare(strict_types=1);
|
||||||
|
// SPDX-FileCopyrightText: Andrea Rigoni Garola <andrea.rgn@gmail.com>
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
?>
|
||||||
|
<div id="content"></div>
|
||||||
64
tests/Integration/NoteIntegrationTest.php
Normal file
64
tests/Integration/NoteIntegrationTest.php
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
// SPDX-FileCopyrightText: Andrea Rigoni Garola <andrea.rgn@gmail.com>
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
|
namespace OCA\PhotoReduce\Tests\Integration\Controller;
|
||||||
|
|
||||||
|
use OCA\PhotoReduce\Controller\NoteController;
|
||||||
|
use OCA\PhotoReduce\Db\Note;
|
||||||
|
use OCA\PhotoReduce\Db\NoteMapper;
|
||||||
|
|
||||||
|
use OCP\AppFramework\App;
|
||||||
|
use OCP\IRequest;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
class NoteIntegrationTest extends TestCase {
|
||||||
|
private NoteController $controller;
|
||||||
|
private QBMapper $mapper;
|
||||||
|
private string $userId = 'john';
|
||||||
|
|
||||||
|
public function setUp(): void {
|
||||||
|
$app = new App('photoreduce');
|
||||||
|
$container = $app->getContainer();
|
||||||
|
|
||||||
|
// only replace the user id
|
||||||
|
$container->registerService('userId', function () {
|
||||||
|
return $this->userId;
|
||||||
|
});
|
||||||
|
|
||||||
|
// we do not care about the request but the controller needs it
|
||||||
|
$container->registerService(IRequest::class, function () {
|
||||||
|
return $this->createMock(IRequest::class);
|
||||||
|
});
|
||||||
|
|
||||||
|
$this->controller = $container->get(NoteController::class);
|
||||||
|
$this->mapper = $container->get(NoteMapper::class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testUpdate(): void {
|
||||||
|
// create a new note that should be updated
|
||||||
|
$note = new Note();
|
||||||
|
$note->setTitle('old_title');
|
||||||
|
$note->setContent('old_content');
|
||||||
|
$note->setUserId($this->userId);
|
||||||
|
|
||||||
|
$id = $this->mapper->insert($note)->getId();
|
||||||
|
|
||||||
|
// fromRow does not set the fields as updated
|
||||||
|
$updatedNote = Note::fromRow([
|
||||||
|
'id' => $id,
|
||||||
|
'user_id' => $this->userId
|
||||||
|
]);
|
||||||
|
$updatedNote->setContent('content');
|
||||||
|
$updatedNote->setTitle('title');
|
||||||
|
|
||||||
|
$result = $this->controller->update($id, 'title', 'content');
|
||||||
|
|
||||||
|
$this->assertEquals($updatedNote, $result->getData());
|
||||||
|
|
||||||
|
// clean up
|
||||||
|
$this->mapper->delete($result->getData());
|
||||||
|
}
|
||||||
|
}
|
||||||
16
tests/Unit/Controller/NoteApiControllerTest.php
Normal file
16
tests/Unit/Controller/NoteApiControllerTest.php
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
// SPDX-FileCopyrightText: Andrea Rigoni Garola <andrea.rgn@gmail.com>
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
|
namespace OCA\PhotoReduce\Tests\Unit\Controller;
|
||||||
|
|
||||||
|
use OCA\PhotoReduce\Controller\NoteApiController;
|
||||||
|
|
||||||
|
class NoteApiControllerTest extends NoteControllerTest {
|
||||||
|
public function setUp(): void {
|
||||||
|
parent::setUp();
|
||||||
|
$this->controller = new NoteApiController($this->request, $this->service, $this->userId);
|
||||||
|
}
|
||||||
|
}
|
||||||
58
tests/Unit/Controller/NoteControllerTest.php
Normal file
58
tests/Unit/Controller/NoteControllerTest.php
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
// SPDX-FileCopyrightText: Andrea Rigoni Garola <andrea.rgn@gmail.com>
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
|
namespace OCA\PhotoReduce\Tests\Unit\Controller;
|
||||||
|
|
||||||
|
use OCA\PhotoReduce\Controller\NoteController;
|
||||||
|
|
||||||
|
use OCA\PhotoReduce\Service\NoteNotFound;
|
||||||
|
use OCA\PhotoReduce\Service\NoteService;
|
||||||
|
|
||||||
|
use OCP\AppFramework\Http;
|
||||||
|
use OCP\IRequest;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
class NoteControllerTest extends TestCase {
|
||||||
|
protected NoteController $controller;
|
||||||
|
protected string $userId = 'john';
|
||||||
|
protected $service;
|
||||||
|
protected $request;
|
||||||
|
|
||||||
|
public function setUp(): void {
|
||||||
|
$this->request = $this->getMockBuilder(IRequest::class)->getMock();
|
||||||
|
$this->service = $this->getMockBuilder(NoteService::class)
|
||||||
|
->disableOriginalConstructor()
|
||||||
|
->getMock();
|
||||||
|
$this->controller = new NoteController($this->request, $this->service, $this->userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testUpdate(): void {
|
||||||
|
$note = 'just check if this value is returned correctly';
|
||||||
|
$this->service->expects($this->once())
|
||||||
|
->method('update')
|
||||||
|
->with($this->equalTo(3),
|
||||||
|
$this->equalTo('title'),
|
||||||
|
$this->equalTo('content'),
|
||||||
|
$this->equalTo($this->userId))
|
||||||
|
->will($this->returnValue($note));
|
||||||
|
|
||||||
|
$result = $this->controller->update(3, 'title', 'content');
|
||||||
|
|
||||||
|
$this->assertEquals($note, $result->getData());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public function testUpdateNotFound(): void {
|
||||||
|
// test the correct status code if no note is found
|
||||||
|
$this->service->expects($this->once())
|
||||||
|
->method('update')
|
||||||
|
->will($this->throwException(new NoteNotFound()));
|
||||||
|
|
||||||
|
$result = $this->controller->update(3, 'title', 'content');
|
||||||
|
|
||||||
|
$this->assertEquals(Http::STATUS_NOT_FOUND, $result->getStatus());
|
||||||
|
}
|
||||||
|
}
|
||||||
27
tests/Unit/Controller/PageControllerTest.php
Normal file
27
tests/Unit/Controller/PageControllerTest.php
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
// SPDX-FileCopyrightText: Andrea Rigoni Garola <andrea.rgn@gmail.com>
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
|
namespace OCA\PhotoReduce\Tests\Unit\Controller;
|
||||||
|
|
||||||
|
use OCP\AppFramework\Http\TemplateResponse;
|
||||||
|
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
class PageControllerTest extends TestCase {
|
||||||
|
private PageController $controller;
|
||||||
|
|
||||||
|
public function setUp(): void {
|
||||||
|
$request = $this->getMockBuilder(\OCP\IRequest::class)->getMock();
|
||||||
|
$this->controller = new PageController($request);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testIndex(): void {
|
||||||
|
$result = $this->controller->index();
|
||||||
|
|
||||||
|
$this->assertEquals('main', $result->getTemplateName());
|
||||||
|
$this->assertTrue($result instanceof TemplateResponse);
|
||||||
|
}
|
||||||
|
}
|
||||||
66
tests/Unit/Service/NoteServiceTest.php
Normal file
66
tests/Unit/Service/NoteServiceTest.php
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
// SPDX-FileCopyrightText: Andrea Rigoni Garola <andrea.rgn@gmail.com>
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
|
namespace OCA\PhotoReduce\Tests\Unit\Service;
|
||||||
|
|
||||||
|
use OCA\PhotoReduce\Db\Note;
|
||||||
|
use OCA\PhotoReduce\Db\NoteMapper;
|
||||||
|
|
||||||
|
use OCA\PhotoReduce\Service\NoteNotFound;
|
||||||
|
|
||||||
|
use OCA\PhotoReduce\Service\NoteService;
|
||||||
|
use OCP\AppFramework\Db\DoesNotExistException;
|
||||||
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
|
class NoteServiceTest extends TestCase {
|
||||||
|
private NoteService $service;
|
||||||
|
private string $userId = 'john';
|
||||||
|
private $mapper;
|
||||||
|
|
||||||
|
public function setUp(): void {
|
||||||
|
$this->mapper = $this->getMockBuilder(NoteMapper::class)
|
||||||
|
->disableOriginalConstructor()
|
||||||
|
->getMock();
|
||||||
|
$this->service = new NoteService($this->mapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testUpdate(): void {
|
||||||
|
// the existing note
|
||||||
|
$note = Note::fromRow([
|
||||||
|
'id' => 3,
|
||||||
|
'title' => 'yo',
|
||||||
|
'content' => 'nope'
|
||||||
|
]);
|
||||||
|
$this->mapper->expects($this->once())
|
||||||
|
->method('find')
|
||||||
|
->with($this->equalTo(3))
|
||||||
|
->will($this->returnValue($note));
|
||||||
|
|
||||||
|
// the note when updated
|
||||||
|
$updatedNote = Note::fromRow(['id' => 3]);
|
||||||
|
$updatedNote->setTitle('title');
|
||||||
|
$updatedNote->setContent('content');
|
||||||
|
$this->mapper->expects($this->once())
|
||||||
|
->method('update')
|
||||||
|
->with($this->equalTo($updatedNote))
|
||||||
|
->will($this->returnValue($updatedNote));
|
||||||
|
|
||||||
|
$result = $this->service->update(3, 'title', 'content', $this->userId);
|
||||||
|
|
||||||
|
$this->assertEquals($updatedNote, $result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testUpdateNotFound(): void {
|
||||||
|
$this->expectException(NoteNotFound::class);
|
||||||
|
// test the correct status code if no note is found
|
||||||
|
$this->mapper->expects($this->once())
|
||||||
|
->method('find')
|
||||||
|
->with($this->equalTo(3))
|
||||||
|
->will($this->throwException(new DoesNotExistException('')));
|
||||||
|
|
||||||
|
$this->service->update(3, 'title', 'content', $this->userId);
|
||||||
|
}
|
||||||
|
}
|
||||||
7
tests/bootstrap.php
Normal file
7
tests/bootstrap.php
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
// SPDX-FileCopyrightText: Andrea Rigoni Garola <andrea.rgn@gmail.com>
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
|
require_once __DIR__ . '/../../../tests/bootstrap.php';
|
||||||
11
tests/phpunit.integration.xml
Normal file
11
tests/phpunit.integration.xml
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<phpunit bootstrap="bootstrap.php" colors="true">
|
||||||
|
<!--
|
||||||
|
SPDX-FileCopyrightText: Andrea Rigoni Garola <andrea.rgn@gmail.com>
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
-->
|
||||||
|
<testsuites>
|
||||||
|
<testsuite name="integration">
|
||||||
|
<directory>./Integration</directory>
|
||||||
|
</testsuite>
|
||||||
|
</testsuites>
|
||||||
|
</phpunit>
|
||||||
11
tests/phpunit.xml
Normal file
11
tests/phpunit.xml
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<phpunit bootstrap="bootstrap.php" colors="true">
|
||||||
|
<!--
|
||||||
|
SPDX-FileCopyrightText: Andrea Rigoni Garola <andrea.rgn@gmail.com>
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
-->
|
||||||
|
<testsuites>
|
||||||
|
<testsuite name="unit">
|
||||||
|
<directory>./Unit</directory>
|
||||||
|
</testsuite>
|
||||||
|
</testsuites>
|
||||||
|
</phpunit>
|
||||||
3
tests/psalm-baseline.xml
Normal file
3
tests/psalm-baseline.xml
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<files psalm-version="4.x-dev@">
|
||||||
|
</files>
|
||||||
2
tests/psalm-baseline.xml.license
Normal file
2
tests/psalm-baseline.xml.license
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
SPDX-FileCopyrightText: Andrea Rigoni Garola <andrea.rgn@gmail.com>
|
||||||
|
SPDX-License-Identifier: CC0-1.0
|
||||||
5
webpack.config.js
Normal file
5
webpack.config.js
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
// SPDX-FileCopyrightText: Andrea Rigoni Garola <andrea.rgn@gmail.com>
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
const webpackConfig = require('@nextcloud/webpack-vue-config')
|
||||||
|
|
||||||
|
module.exports = webpackConfig
|
||||||
Reference in New Issue
Block a user