Initial commit

This commit is contained in:
Andrea Rigoni
2023-12-17 13:23:20 +01:00
parent 65e296bbed
commit 2a589e4b28
52 changed files with 2306 additions and 3 deletions

7
.eslintrc.js Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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

View File

@@ -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
View 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
View 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

View File

@@ -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
View 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
View 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
View 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
View 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
View 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
View File

@@ -0,0 +1,2 @@
SPDX-FileCopyrightText: Andrea Rigoni Garola <andrea.rgn@gmail.com>
SPDX-License-Identifier: AGPL-3.0-or-later

View 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
View 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);
}
}
}

View 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);
});
}
}

View 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);
});
}
}

View 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
View 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
View 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);
}
}

View 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;
}
}

View 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 {
}

View 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
View 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
View 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
View 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
View 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
View 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
View 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>

View 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());
}
}

View 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);
}
}

View 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());
}
}

View 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);
}
}

View 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
View 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';

View 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
View 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
View File

@@ -0,0 +1,3 @@
<?xml version="1.0" encoding="UTF-8"?>
<files psalm-version="4.x-dev@">
</files>

View 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
View 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