From 2a589e4b283f4759644e16218d57a2c256cc39b8 Mon Sep 17 00:00:00 2001 From: Andrea Rigoni Date: Sun, 17 Dec 2023 13:23:20 +0100 Subject: [PATCH] Initial commit --- .eslintrc.js | 7 + .gitattributes | 3 + .github/workflows/lint-eslint.yml | 47 ++++ .github/workflows/lint-info-xml.yml | 33 +++ .github/workflows/lint-php-cs.yml | 37 +++ .github/workflows/lint-php.yml | 49 ++++ .github/workflows/lint-stylelint.yml | 46 ++++ .github/workflows/phpunit-mysql.yml | 115 +++++++++ .github/workflows/phpunit-oci.yml | 109 ++++++++ .github/workflows/phpunit-pgsql.yml | 114 +++++++++ .github/workflows/phpunit-sqlite.yml | 103 ++++++++ .github/workflows/reuse.yml | 15 ++ .gitignore | 10 + .php-cs-fixer.dist.php | 20 ++ .reuse/dep5 | 12 + LICENSE => LICENSES/AGPL-3.0-or-later.txt | 4 +- LICENSES/CC0-1.0.txt | 121 +++++++++ Makefile | 163 ++++++++++++ README.md | 57 ++++- appinfo/info.xml | 27 ++ appinfo/routes.php | 25 ++ babel.config.js | 5 + composer.json | 38 +++ img/app.svg | 56 ++++ img/app.svg.license | 2 + lib/AppInfo/Application.php | 37 +++ lib/Controller/Errors.php | 25 ++ lib/Controller/NoteApiController.php | 81 ++++++ lib/Controller/NoteController.php | 71 ++++++ lib/Controller/PageController.php | 29 +++ lib/Db/Note.php | 34 +++ lib/Db/NoteMapper.php | 48 ++++ .../Version000000Date20181013124731.php | 50 ++++ lib/Service/NoteNotFound.php | 10 + lib/Service/NoteService.php | 84 ++++++ package.json | 43 ++++ psalm.xml | 38 +++ src/App.vue | 241 ++++++++++++++++++ src/main.js | 19 ++ stylelint.config.js | 5 + templates/main.php | 6 + tests/Integration/NoteIntegrationTest.php | 64 +++++ .../Unit/Controller/NoteApiControllerTest.php | 16 ++ tests/Unit/Controller/NoteControllerTest.php | 58 +++++ tests/Unit/Controller/PageControllerTest.php | 27 ++ tests/Unit/Service/NoteServiceTest.php | 66 +++++ tests/bootstrap.php | 7 + tests/phpunit.integration.xml | 11 + tests/phpunit.xml | 11 + tests/psalm-baseline.xml | 3 + tests/psalm-baseline.xml.license | 2 + webpack.config.js | 5 + 52 files changed, 2306 insertions(+), 3 deletions(-) create mode 100644 .eslintrc.js create mode 100644 .gitattributes create mode 100644 .github/workflows/lint-eslint.yml create mode 100644 .github/workflows/lint-info-xml.yml create mode 100644 .github/workflows/lint-php-cs.yml create mode 100644 .github/workflows/lint-php.yml create mode 100644 .github/workflows/lint-stylelint.yml create mode 100644 .github/workflows/phpunit-mysql.yml create mode 100644 .github/workflows/phpunit-oci.yml create mode 100644 .github/workflows/phpunit-pgsql.yml create mode 100644 .github/workflows/phpunit-sqlite.yml create mode 100644 .github/workflows/reuse.yml create mode 100644 .gitignore create mode 100644 .php-cs-fixer.dist.php create mode 100644 .reuse/dep5 rename LICENSE => LICENSES/AGPL-3.0-or-later.txt (99%) create mode 100644 LICENSES/CC0-1.0.txt create mode 100644 Makefile create mode 100644 appinfo/info.xml create mode 100644 appinfo/routes.php create mode 100644 babel.config.js create mode 100644 composer.json create mode 100644 img/app.svg create mode 100644 img/app.svg.license create mode 100644 lib/AppInfo/Application.php create mode 100644 lib/Controller/Errors.php create mode 100644 lib/Controller/NoteApiController.php create mode 100644 lib/Controller/NoteController.php create mode 100644 lib/Controller/PageController.php create mode 100644 lib/Db/Note.php create mode 100644 lib/Db/NoteMapper.php create mode 100644 lib/Migration/Version000000Date20181013124731.php create mode 100644 lib/Service/NoteNotFound.php create mode 100644 lib/Service/NoteService.php create mode 100644 package.json create mode 100644 psalm.xml create mode 100644 src/App.vue create mode 100644 src/main.js create mode 100644 stylelint.config.js create mode 100644 templates/main.php create mode 100644 tests/Integration/NoteIntegrationTest.php create mode 100644 tests/Unit/Controller/NoteApiControllerTest.php create mode 100644 tests/Unit/Controller/NoteControllerTest.php create mode 100644 tests/Unit/Controller/PageControllerTest.php create mode 100644 tests/Unit/Service/NoteServiceTest.php create mode 100644 tests/bootstrap.php create mode 100644 tests/phpunit.integration.xml create mode 100644 tests/phpunit.xml create mode 100644 tests/psalm-baseline.xml create mode 100644 tests/psalm-baseline.xml.license create mode 100644 webpack.config.js diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 0000000..1289fe8 --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,7 @@ +// SPDX-FileCopyrightText: Andrea Rigoni Garola +// SPDX-License-Identifier: AGPL-3.0-or-later +module.exports = { + extends: [ + '@nextcloud', + ] +} diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..fb642de --- /dev/null +++ b/.gitattributes @@ -0,0 +1,3 @@ +# SPDX-FileCopyrightText: Andrea Rigoni Garola +# SPDX-License-Identifier: AGPL-3.0-or-later +/js/* binary diff --git a/.github/workflows/lint-eslint.yml b/.github/workflows/lint-eslint.yml new file mode 100644 index 0000000..11b0b51 --- /dev/null +++ b/.github/workflows/lint-eslint.yml @@ -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 diff --git a/.github/workflows/lint-info-xml.yml b/.github/workflows/lint-info-xml.yml new file mode 100644 index 0000000..b99d626 --- /dev/null +++ b/.github/workflows/lint-info-xml.yml @@ -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 diff --git a/.github/workflows/lint-php-cs.yml b/.github/workflows/lint-php-cs.yml new file mode 100644 index 0000000..ed6dfc5 --- /dev/null +++ b/.github/workflows/lint-php-cs.yml @@ -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 ) diff --git a/.github/workflows/lint-php.yml b/.github/workflows/lint-php.yml new file mode 100644 index 0000000..9570b36 --- /dev/null +++ b/.github/workflows/lint-php.yml @@ -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 diff --git a/.github/workflows/lint-stylelint.yml b/.github/workflows/lint-stylelint.yml new file mode 100644 index 0000000..3d45c8e --- /dev/null +++ b/.github/workflows/lint-stylelint.yml @@ -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 diff --git a/.github/workflows/phpunit-mysql.yml b/.github/workflows/phpunit-mysql.yml new file mode 100644 index 0000000..30d768c --- /dev/null +++ b/.github/workflows/phpunit-mysql.yml @@ -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 diff --git a/.github/workflows/phpunit-oci.yml b/.github/workflows/phpunit-oci.yml new file mode 100644 index 0000000..1e3ea52 --- /dev/null +++ b/.github/workflows/phpunit-oci.yml @@ -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 diff --git a/.github/workflows/phpunit-pgsql.yml b/.github/workflows/phpunit-pgsql.yml new file mode 100644 index 0000000..bb0f251 --- /dev/null +++ b/.github/workflows/phpunit-pgsql.yml @@ -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 diff --git a/.github/workflows/phpunit-sqlite.yml b/.github/workflows/phpunit-sqlite.yml new file mode 100644 index 0000000..0f3f0dc --- /dev/null +++ b/.github/workflows/phpunit-sqlite.yml @@ -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 diff --git a/.github/workflows/reuse.yml b/.github/workflows/reuse.yml new file mode 100644 index 0000000..67da505 --- /dev/null +++ b/.github/workflows/reuse.yml @@ -0,0 +1,15 @@ +# SPDX-FileCopyrightText: 2022 Free Software Foundation Europe e.V. +# +# 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 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..cbbc11d --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +# SPDX-FileCopyrightText: Andrea Rigoni Garola +# SPDX-License-Identifier: AGPL-3.0-or-later +*.iml +.idea +/.php-cs-fixer.cache +/.php_cs.cache +/build/ +/vendor/ +js/*hot-update.* +node_modules/ diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php new file mode 100644 index 0000000..9bdaed3 --- /dev/null +++ b/.php-cs-fixer.dist.php @@ -0,0 +1,20 @@ + +// 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; diff --git a/.reuse/dep5 b/.reuse/dep5 new file mode 100644 index 0000000..f91755f --- /dev/null +++ b/.reuse/dep5 @@ -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 +Source: https://github.com/nextcloud/profiler + +Files: package-lock.json package.json composer.json composer.lock +Copyright: Andrea Rigoni Garola +License: AGPL-3.0-or-later + +Files: l10n/*.js l10n/*.json +Copyright: Nextcloud translators +License: AGPL-3.0-or-later diff --git a/LICENSE b/LICENSES/AGPL-3.0-or-later.txt similarity index 99% rename from LICENSE rename to LICENSES/AGPL-3.0-or-later.txt index 2cd4d62..0c97efd 100644 --- a/LICENSE +++ b/LICENSES/AGPL-3.0-or-later.txt @@ -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. - ncc_photo_reduce - Copyright (C) 2023 YFinGames + + Copyright (C) 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. diff --git a/LICENSES/CC0-1.0.txt b/LICENSES/CC0-1.0.txt new file mode 100644 index 0000000..0e259d4 --- /dev/null +++ b/LICENSES/CC0-1.0.txt @@ -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. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..8340779 --- /dev/null +++ b/Makefile @@ -0,0 +1,163 @@ +# SPDX-FileCopyrightText: Bernhard Posselt +# 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 diff --git a/README.md b/README.md index 586200b..05a3cbf 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,57 @@ -# ncc_photo_reduce + +# 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 diff --git a/appinfo/info.xml b/appinfo/info.xml new file mode 100644 index 0000000..95fae18 --- /dev/null +++ b/appinfo/info.xml @@ -0,0 +1,27 @@ + + + + photoreduce + Photo Reduce + Automatically reduce size of photos and organize folders + + 0.0.1 + agpl + Andrea Rigoni Garola + PhotoReduce + multimedia + https://gitea.mildstone.org/YFinGames/ncc_photo_reduce + + + + + + Photo Reduce + photoreduce.page.index + + + diff --git a/appinfo/routes.php b/appinfo/routes.php new file mode 100644 index 0000000..8b2e10a --- /dev/null +++ b/appinfo/routes.php @@ -0,0 +1,25 @@ + +// 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' => '.+']] + ] +]; diff --git a/babel.config.js b/babel.config.js new file mode 100644 index 0000000..8cc14a1 --- /dev/null +++ b/babel.config.js @@ -0,0 +1,5 @@ +// SPDX-FileCopyrightText: Andrea Rigoni Garola +// SPDX-License-Identifier: AGPL-3.0-or-later +const babelConfig = require('@nextcloud/babel-config') + +module.exports = babelConfig diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..650af86 --- /dev/null +++ b/composer.json @@ -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" + } + } +} diff --git a/img/app.svg b/img/app.svg new file mode 100644 index 0000000..fe370f8 --- /dev/null +++ b/img/app.svg @@ -0,0 +1,56 @@ + + + + + + image/svg+xml + + + + + + + + diff --git a/img/app.svg.license b/img/app.svg.license new file mode 100644 index 0000000..a30ddeb --- /dev/null +++ b/img/app.svg.license @@ -0,0 +1,2 @@ +SPDX-FileCopyrightText: Andrea Rigoni Garola +SPDX-License-Identifier: AGPL-3.0-or-later diff --git a/lib/AppInfo/Application.php b/lib/AppInfo/Application.php new file mode 100644 index 0000000..8c4a880 --- /dev/null +++ b/lib/AppInfo/Application.php @@ -0,0 +1,37 @@ + +// 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. + } +} diff --git a/lib/Controller/Errors.php b/lib/Controller/Errors.php new file mode 100644 index 0000000..566051e --- /dev/null +++ b/lib/Controller/Errors.php @@ -0,0 +1,25 @@ + +// 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); + } + } +} diff --git a/lib/Controller/NoteApiController.php b/lib/Controller/NoteApiController.php new file mode 100644 index 0000000..7670c62 --- /dev/null +++ b/lib/Controller/NoteApiController.php @@ -0,0 +1,81 @@ + +// 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); + }); + } +} diff --git a/lib/Controller/NoteController.php b/lib/Controller/NoteController.php new file mode 100644 index 0000000..fa3e749 --- /dev/null +++ b/lib/Controller/NoteController.php @@ -0,0 +1,71 @@ + +// 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); + }); + } +} diff --git a/lib/Controller/PageController.php b/lib/Controller/PageController.php new file mode 100644 index 0000000..3857929 --- /dev/null +++ b/lib/Controller/PageController.php @@ -0,0 +1,29 @@ + +// 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'); + } +} diff --git a/lib/Db/Note.php b/lib/Db/Note.php new file mode 100644 index 0000000..9417203 --- /dev/null +++ b/lib/Db/Note.php @@ -0,0 +1,34 @@ + +// 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 + ]; + } +} diff --git a/lib/Db/NoteMapper.php b/lib/Db/NoteMapper.php new file mode 100644 index 0000000..c1a041e --- /dev/null +++ b/lib/Db/NoteMapper.php @@ -0,0 +1,48 @@ + +// 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 + */ +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); + } +} diff --git a/lib/Migration/Version000000Date20181013124731.php b/lib/Migration/Version000000Date20181013124731.php new file mode 100644 index 0000000..8f7454f --- /dev/null +++ b/lib/Migration/Version000000Date20181013124731.php @@ -0,0 +1,50 @@ + +// 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; + } +} diff --git a/lib/Service/NoteNotFound.php b/lib/Service/NoteNotFound.php new file mode 100644 index 0000000..63a1587 --- /dev/null +++ b/lib/Service/NoteNotFound.php @@ -0,0 +1,10 @@ + +// SPDX-License-Identifier: AGPL-3.0-or-later + +namespace OCA\PhotoReduce\Service; + +class NoteNotFound extends \Exception { +} diff --git a/lib/Service/NoteService.php b/lib/Service/NoteService.php new file mode 100644 index 0000000..f13480b --- /dev/null +++ b/lib/Service/NoteService.php @@ -0,0 +1,84 @@ + +// 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 + */ + 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); + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..2825303 --- /dev/null +++ b/package.json @@ -0,0 +1,43 @@ +{ + "name": "photoreduce", + "description": "Automatically reduce size of photos and organize folders", + "version": "0.0.1", + "author": "Andrea Rigoni Garola ", + "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" + } +} diff --git a/psalm.xml b/psalm.xml new file mode 100644 index 0000000..3b05b3e --- /dev/null +++ b/psalm.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/App.vue b/src/App.vue new file mode 100644 index 0000000..8a84905 --- /dev/null +++ b/src/App.vue @@ -0,0 +1,241 @@ +