Compare commits

...
Author SHA1 Message Date
shimon e28cbce5ee small updates 2023-05-14 10:30:29 +03:00
shimon 767b5c8541 added internalIds to collections 2023-05-08 10:02:23 +03:00
shimon af80accc15 added internalIds to collections 2023-05-07 17:15:59 +03:00
shimon f3508e1701 Merge branch 'master' of github.com:appwrite/appwrite into __stream
 Conflicts:
	.gitmodules
	Dockerfile
	app/config/services.php
	app/config/specs/open-api3-latest-client.json
	app/config/specs/open-api3-latest-console.json
	app/config/specs/open-api3-latest-server.json
	app/config/specs/swagger2-latest-client.json
	app/config/specs/swagger2-latest-console.json
	app/config/specs/swagger2-latest-server.json
	app/console
	app/controllers/api/databases.php
	app/controllers/api/projects.php
	app/controllers/api/teams.php
	composer.json
	composer.lock
	src/Appwrite/Utopia/Response.php
2023-05-07 11:43:29 +03:00
shimon 3c69c211e9 added internalIds to collections 2023-05-07 11:31:27 +03:00
Torsten DittmannandGitHub 6700f90ba1 Merge pull request #5503 from appwrite/1.3.x
Sync master with 1.3.x
2023-05-04 21:15:53 +02:00
Torsten DittmannandGitHub 67ad856490 Merge pull request #5497 from appwrite/fix-bucket-permissions
Fix bucket permissions
2023-05-04 19:32:44 +02:00
Torsten DittmannandGitHub 25b1eb0ab2 Merge pull request #5502 from appwrite/master
Sync 1.3.x with Master
2023-05-04 19:14:31 +02:00
Torsten DittmannandGitHub dcefe3f9b7 Merge pull request #5457 from appwrite/fix-release-workflow-name
Fix the release github action
2023-05-04 18:18:51 +02:00
Christy JacobandGitHub 60f51580a7 Merge pull request #5246 from singhbhaskar/fix-4960-Database-usage-screen-shows-500-error-when_APP_USAGE_STATS-is-disabled
Fix: Updated error when _APP_USAGE_STATS is disabled
2023-05-04 20:47:50 +05:30
Torsten DittmannandGitHub bb7891b5bf Merge pull request #5492 from appwrite/fix-update-document
Fix update document merging existing data after pre-processing
2023-05-04 15:28:05 +02:00
Steven Nguyen d7bbfb55ed Prepare 1.3.4 release 2023-05-03 13:39:05 -07:00
Steven Nguyen c46a02b496 Update CHANGES.md 2023-05-03 12:15:37 -07:00
Steven Nguyen 4dd6a7977e Update create and update bucket APIs
Ensure the permissions and document security are updated in the
metadata collection.
2023-05-03 12:15:37 -07:00
Steven Nguyen afd2c7a065 Update migration to set bucket permissions in metadata table 2023-05-03 12:15:37 -07:00
Steven Nguyen 9898005465 Fix the version passed into the docker build command
Change the order of the tags to ensure the version passed into the
docker build command is the full major.minor.patch version.
2023-05-02 17:43:39 -07:00
Bhaskar Singh a4299a69ef Refactored the condition for strict inequality 2023-05-02 23:47:50 +05:30
Christy JacobandGitHub 2fcd78d784 Merge pull request #5360 from Atsurak/master
fix : wrong links for getting started for web
2023-05-02 23:32:46 +05:30
Christy JacobandGitHub 6ad4401365 Merge pull request #5484 from appwrite/update-readme
Update README.md
2023-05-02 19:21:55 +05:30
Vincent (Wen Yu) GeandGitHub 17acfd5d1c Update README-CN.md 2023-05-02 09:49:55 -04:00
Jake Barnby 30237ccfb1 Add test for updating existing document with relationship 2023-05-02 20:27:09 +12:00
3c5bebc34e Update README.md
Co-authored-by: Eldad A. Fux <eldad.fux@gmail.com>
2023-05-02 14:02:57 +05:45
Damodar LohaniandGitHub 970457045f Update README.md 2023-05-02 13:47:40 +05:45
Damodar LohaniandGitHub 789198d523 Update README.md 2023-05-02 13:38:47 +05:45
Jake Barnby 2eafda4ee2 Fix update document merging existing data after pre-processing instead of before 2023-05-02 19:53:11 +12:00
Damodar LohaniandGitHub a5fa150c25 Update README.md 2023-05-02 12:55:40 +05:45
Damodar LohaniandGitHub 6e65178ad5 Update README.md 2023-05-02 12:54:20 +05:45
Christy JacobandGitHub e7303245b2 Merge pull request #5473 from appwrite/eldadfux-patch-1
Update README.md
2023-05-01 20:07:44 +05:30
Eldad A. FuxandGitHub cfd6eee0da Update README.md 2023-05-01 15:34:13 +01:00
shimon 927bead147 bug fixes 2023-04-30 18:58:10 +03:00
Christy JacobandGitHub 80dd3644f8 Merge pull request #5394 from appwrite/docs-cloud
Move reference to env-var to self-hosted docs.
2023-04-30 00:46:56 +05:30
Steven Nguyen a0297bfb8b Fix the release github action job name 2023-04-28 12:47:51 -07:00
Torsten DittmannandGitHub 84c06a8ece Merge pull request #5456 from appwrite/1.3.x
Sync master with 1.3.x
2023-04-28 21:42:32 +02:00
Torsten DittmannandGitHub ba24c0a843 Merge pull request #5455 from appwrite/fix-1.3.1-migration
Prevent migration from overriding existing data
2023-04-28 21:34:04 +02:00
Steven Nguyen 1e5dc3ad6f Bump version to 1.3.3 2023-04-28 12:29:46 -07:00
Steven Nguyen d0cfad9085 Prevent migration from overriding existing data
For users already on 1.3.1, after upgrading to 1.3.2, the migration
script would override existing auths, password history, team prefs, and
(relationship) attribute options with empty values. This commit fixes
the migration to set to an existing value if there was one.
2023-04-28 12:20:30 -07:00
Christy JacobandGitHub e311b6bd0e Merge pull request #5453 from appwrite/update-docs-for-beta
Update docs for beta
2023-04-28 20:29:15 +05:30
Christy Jacob f0d7f6c7ed feat: update endpoint for Appwrite 1.3.x 2023-04-28 19:40:24 +05:30
Christy Jacob 913571654a feat: update endpoint for Appwrite 1.2.x 2023-04-28 19:36:30 +05:30
Christy Jacob 77fcdb847d feat: update endpoint for Appwrite 1.1.x 2023-04-28 19:33:02 +05:30
Christy JacobandGitHub 7e40b59707 Merge pull request #5448 from appwrite/feat-bump-sdk-generator
Bump sdk-generator and specs
2023-04-28 09:54:46 +05:30
Steven Nguyen 206553bac7 Bump sdk-generator and specs 2023-04-27 17:00:12 -07:00
Torsten DittmannandGitHub c3ad6a1260 Merge pull request #5443 from appwrite/1.3.x
Merge 1.3.x to Master
2023-04-27 21:49:39 +02:00
Torsten DittmannandGitHub b2c8e182a5 Merge pull request #5442 from appwrite/fix-project-deletion-leftovers
fix: project deletion leftovers
2023-04-27 20:19:09 +02:00
Torsten DittmannandGitHub ff5132867e Merge pull request #5444 from appwrite/feat-bump-abuse-audit-database
Feat bump abuse audit database
2023-04-27 20:16:39 +02:00
Steven Nguyen 11d523b050 Bump utopia abuse, audit, and database 2023-04-27 10:42:55 -07:00
Torsten DittmannandGitHub eac59e9d02 Merge pull request #5314 from appwrite/fix-specs-command
Update CONTRIBUTING.md to fix specs command
2023-04-27 18:22:58 +02:00
Torsten Dittmann e7c311044b chore: add changelog 2023-04-27 18:04:00 +02:00
Torsten Dittmann 7529f02bd6 fix: also delete webhooks on project deletion 2023-04-27 17:41:14 +02:00
Torsten Dittmann 31e65d954b fix: deletes worker for projects 2023-04-27 17:37:14 +02:00
Torsten Dittmann e1cf068652 fix: platforms query 2023-04-27 17:36:00 +02:00
Bhaskar Singh 7064f884aa Refactor usage init hook to remove unecessary
inject
2023-04-27 19:32:05 +05:30
Torsten Dittmann d45bc5e60f chore: upgrade console 2023-04-27 15:54:07 +02:00
shimon 6af8fd6660 bug fixes 2023-04-27 12:12:14 +03:00
Torsten DittmannandGitHub fb507ad83b Merge pull request #5432 from appwrite/chore-sync-1-3-x
chore: sync 1.3.x with master
2023-04-26 18:16:21 +02:00
Torsten Dittmann e9e83ae8ec Merge branch 'master' of https://github.com/appwrite/appwrite into chore-sync-1-3-x 2023-04-26 12:30:14 +02:00
Jake BarnbyandGitHub 4aa6ce3cfd Merge pull request #5426 from appwrite/fix-apple-getting-started
Fix typo in Apple Getting Started
2023-04-26 16:37:24 +12:00
Christy JacobandGitHub 0e5e3f6f6f Merge pull request #5362 from appwrite/update-cli
feat: update cli version
2023-04-25 23:49:29 +05:30
Torsten DittmannandGitHub 4ceae33a5a Merge pull request #5163 from yatharth1706/fix-5147-fix-error-description-in-case-of-duplicate-user
fix error description for duplicate user
2023-04-25 15:45:59 +02:00
Torsten DittmannandGitHub 713535bf79 Merge pull request #5285 from rinkuhasija/fix-5272-correct-documentation-model-attributeDatetime-Object-example
fix-models-attributeDatetime-object-example
2023-04-25 15:45:39 +02:00
Torsten DittmannandGitHub aaf5ab9024 Merge pull request #5382 from appwrite/fix-5381-var-missing-after-upgrade
Fix missing var after upgrade
2023-04-25 15:41:43 +02:00
Christy JacobandGitHub 1c4dc6a424 Merge pull request #3696 from appwrite/fix-event-multi-execution
Fix: event multi-execution
2023-04-25 17:31:45 +04:00
StevenandGitHub 2d0cf85edd Fix typo in Apple Getting Started 2023-04-24 12:03:41 -07:00
Torsten Dittmann 43a6945d30 fix: migration 2023-04-24 13:21:31 +02:00
Torsten Dittmann 4ebe485f05 remove: leftover from http.php 2023-04-24 13:11:39 +02:00
Torsten Dittmann 5ee3c592a4 chore: release 1.3.2 2023-04-24 13:10:28 +02:00
Torsten Dittmann ff82a24a4b feat: migration 2023-04-24 13:00:23 +02:00
shimon a187aa4bc5 bug fixes 2023-04-23 19:57:12 +03:00
shimon 60098d71ee adjustments 2023-04-23 10:43:18 +03:00
Shimon NewmanandGitHub 38c64996ac Update videos.php 2023-04-22 22:45:59 +03:00
Shimon NewmanandGitHub 89dfbfba25 Update hls-master.phtml 2023-04-22 22:39:06 +03:00
Bhaskar Singh 3865f48d32 Moved check for _APP_USAGE_STATS
to create init hook for App
2023-04-21 23:09:01 +05:30
Torsten Dittmann 5925d61a9c chore: update composer 2023-04-21 13:04:37 +02:00
Steven Nguyen 3f72b738fa Fix related creates 2023-04-20 18:44:53 -07:00
Torsten Dittmann 0230671717 chore: upgrade utopia-php/database 2023-04-20 21:10:48 +02:00
Steven Nguyen e8dee9296c Fix update document not merging existing data 2023-04-20 10:10:51 -07:00
Steven Nguyen 4113ae7ca5 Allow updating document with read-only relationship
A user should be able to update a document without touching the
relationship.

Relates: https://github.com/appwrite/appwrite/issues/5404
2023-04-19 17:21:41 -07:00
Steven Nguyen 37afb82d1c Delegate permissions check to database layer 2023-04-19 17:21:41 -07:00
Steven Nguyen 611dd9b86c Return 404 if a user doesn't have access to a collection
It is better to return 404 so that an end user doesn't know if the
collection actually exists but they don't have access or they really
don't have access.
2023-04-19 17:21:41 -07:00
Steven Nguyen 50bb69290f Ensure collections are created with permissions and documentSecurity 2023-04-19 17:21:41 -07:00
Steven Nguyen 471c832a4f Bump utopia-php/database version 2023-04-19 17:21:41 -07:00
Vincent (Wen Yu) Ge d965df4a05 Remove reference to env vars in references 2023-04-17 21:12:15 +00:00
shimon 578c8d9277 tests 2023-04-17 10:09:48 +03:00
shimon dbd58966db updates 2023-04-16 17:21:47 +03:00
shimon d6e1d0f6f6 updates 2023-04-16 11:43:35 +03:00
Steven Nguyen d41df04b6a Ensure vars are not lost during upgrade
Using a reference on an array element while iterating over it caused
some unexpected behavior. Instead of using a reference, this change
uses a key to allow us to edit the array element.

For reference on the problem, see https://stackoverflow.com/questions/70691375/foreach-with-reference-causing-arrays-last-element-to-repeat.
2023-04-14 16:18:31 -07:00
Jake BarnbyandGitHub d1740d2121 Merge pull request #5363 from appwrite/fix-nested-id-required
Fix auto-setting custom ID on nested documents
2023-04-14 22:37:24 +12:00
Jake Barnby 0dea80c9da Cache whether relation is a list 2023-04-14 22:03:16 +12:00
Jake Barnby fb130f3aa2 Merge remote-tracking branch 'origin/1.3.x' into fix-nested-id-required 2023-04-14 21:59:42 +12:00
Jake BarnbyandGitHub 78aadd2f39 Merge pull request #5361 from appwrite/fix-team-pres-empty-array
Ensure empty team prefs returns as JSON object rather array
2023-04-13 21:09:37 +12:00
Jake Barnby 08be9c4c89 Update changelog 2023-04-13 16:35:42 +12:00
Jake Barnby 975b044de0 Iterate relation by reference on update 2023-04-13 16:08:53 +12:00
Jake Barnby c7317b0468 Fix auto-setting custom ID on nested documents 2023-04-13 15:59:57 +12:00
Christy Jacob 0fcfeb50a6 feat: update cli version 2023-04-13 01:48:44 +04:00
Christy Jacob 5d4e0762bb feat: update cli version 2023-04-13 01:33:25 +04:00
Christy Jacob f30d11ed60 feat: update cli version 2023-04-13 01:23:49 +04:00
Steven Nguyen bb2b48193f Ensure empty team prefs returns as JSON object rather array 2023-04-12 12:06:48 -07:00
Prasanth KotaandGitHub 0a1fcfc889 Update GETTING_STARTED.md 2023-04-13 00:13:20 +05:30
shimon 741a87f464 updates 2023-04-12 18:18:56 +03:00
shimon db4506b0fe updates 2023-04-10 16:41:23 +03:00
shimon 14575a7e40 updates 2023-04-10 10:59:51 +03:00
shimon 1830138455 updates 2023-04-10 09:45:15 +03:00
shimon 4cd273e976 updates 2023-04-09 12:58:21 +03:00
shimon fc991e89f1 transcoding -> videos 2023-04-04 20:20:40 +03:00
shimon 9f6589d21b Merge branch 'master' of github.com:appwrite/appwrite into __stream
 Conflicts:
	docker-compose.yml
2023-04-04 17:10:31 +03:00
shimon c09f600e73 transcoding -> videos 2023-04-04 17:08:45 +03:00
shimon 2b60b18a3c timeline 2023-04-04 14:24:18 +03:00
shimon 053d688754 Merge remote-tracking branch 'origin/__stream' into __stream
# Conflicts:
#	app/config/locale/languages.php
2023-04-02 13:50:26 +03:00
shimon bccd1a96c0 timeline 2023-04-02 13:48:59 +03:00
StevenandGitHub 8129bd9156 Update CONTRIBUTING.md to fix specs command
The CLI expects name=value for arguments rather than just positional.
2023-03-31 11:53:27 -07:00
Rinku Hasija f5c7568287 fix-models-attributeDatetime-object-example
Signed-off-by: Rinku Hasija <rishabhasija5454@gmail.com>
2023-03-25 17:32:46 +05:30
Bhaskar Singh 01840b3801 Reverting console change from main repo 2023-03-24 21:46:12 +05:30
Bhaskar SinghandGitHub 700f4f33ed Merge branch 'appwrite:master' into fix-4960-Database-usage-screen-shows-500-error-when_APP_USAGE_STATS-is-disabled 2023-03-24 21:33:42 +05:30
shimon 4a8ef126bd Merge branch 'master' of github.com:appwrite/appwrite into __stream
 Conflicts:
	app/config/errors.php
	composer.json
	composer.lock
2023-03-23 18:06:11 +02:00
Bhaskar Singh 6324d78a0d Update the console app commit 2023-03-21 21:30:49 +05:30
Bhaskar Singh 5071172b00 Updating the console code 2023-03-21 21:26:22 +05:30
Bhaskar SinghandGitHub 39fb368c1d Merge branch 'appwrite:master' into fix-4960-Database-usage-screen-shows-500-error-when_APP_USAGE_STATS-is-disabled 2023-03-21 20:54:08 +05:30
Bhaskar Singh ac2085ffa8 Updated error when _APP_USAGE_STATS is disabled
for usage
2023-03-18 22:12:36 +05:30
Shimon NewmanandGitHub 61e3faa964 Update languages.php 2023-03-18 10:25:12 +02:00
shimon 22b4555cc0 subtitle lan code 2023-03-13 20:35:39 +02:00
shimon aa8334e7d8 subtitle lan code 2023-03-13 10:16:48 +02:00
shimon 8a8a2d37e4 subtitle lan code 2023-03-12 17:55:41 +02:00
shimon 883d48c2e5 subtitle lan code 2023-03-12 14:53:56 +02:00
shimon cb217b43b4 Updates 2023-03-09 09:57:10 +02:00
shimon 7340968d1f Merge branch 'master' of github.com:appwrite/appwrite into __stream
 Conflicts:
	app/config/specs/open-api3-latest-client.json
	app/config/specs/open-api3-latest-console.json
	app/config/specs/open-api3-latest-server.json
	app/config/specs/swagger2-latest-client.json
	app/config/specs/swagger2-latest-console.json
	app/config/specs/swagger2-latest-server.json
	app/console
2023-03-08 16:31:56 +02:00
shimon 5785145e58 Updates 2023-03-08 16:30:19 +02:00
shimon 9f94560a2c Updates 2023-03-08 16:29:40 +02:00
shimon 92f75bd3d9 Updates 2023-03-07 09:47:50 +02:00
shimon 658cfee38e Updates 2023-03-06 18:28:40 +02:00
shimon 26251e94f6 Updates 2023-03-06 17:58:26 +02:00
shimon dbad8c2a85 Updates 2023-03-05 19:30:53 +02:00
shimon d6d6a7753a Updates 2023-03-05 19:21:51 +02:00
shimon 7a0255d011 Updates 2023-03-05 11:24:24 +02:00
shimon 81ca8a5fc9 metadata extraction ffmpeg -> mediainfo 2023-03-02 18:44:20 +02:00
shimon 8d496d66d1 metadata extraction ffmpeg -> mediainfo 2023-03-02 15:05:15 +02:00
shimon cfb53be8ac metadata extraction ffmpeg -> mediainfo 2023-03-02 10:56:04 +02:00
yatharth b5f02bd6c8 fix error description for duplicate user 2023-02-25 20:36:09 +05:30
shimon 4b227c7428 video tests 2023-02-23 09:36:34 +02:00
shimon b9fa699c03 video tests 2023-02-22 13:16:33 +02:00
shimon a1918d1512 video tests 2023-02-21 11:21:47 +02:00
shimon d26aabb083 realtime events 2023-02-20 10:14:20 +02:00
shimon ca90711c30 realtime events 2023-02-20 10:02:51 +02:00
shimon d1c0e0d9a2 realtime events 2023-02-19 16:36:11 +02:00
shimon 4ac062edfe Merge branch 'master' of github.com:appwrite/appwrite into __stream
 Conflicts:
	.gitmodules
	Dockerfile
	app/config/specs/open-api3-latest-client.json
	app/config/specs/open-api3-latest-console.json
	app/config/specs/open-api3-latest-server.json
	app/config/specs/swagger2-latest-client.json
	app/config/specs/swagger2-latest-console.json
	app/config/specs/swagger2-latest-server.json
	app/console
	composer.lock
2023-02-19 10:51:50 +02:00
shimon f71f18095f realtime events 2023-02-19 10:46:49 +02:00
shimon e2df63b78a realtime events 2023-02-14 09:53:37 +02:00
shimon 1b6adce14f realtime events 2023-02-13 20:18:33 +02:00
shimon 3d1228ba13 sync with master
and renaming attr
2023-02-12 10:56:11 +02:00
shimon edaa93936f Merge branch 'master' of github.com:appwrite/appwrite into __stream
 Conflicts:
	composer.json
	composer.lock
2023-02-09 19:28:26 +02:00
shimon 65a1921d48 bla 2023-02-01 10:39:54 +02:00
shimon f7a1cb14f2 bla 2023-02-01 10:38:44 +02:00
shimon f1fa22c4fc usage test 2023-01-16 10:48:26 +02:00
shimon 331c9070b3 compose.lock 2023-01-12 15:50:33 +02:00
shimon 7c81a1ea88 compose.lock 2023-01-12 15:50:03 +02:00
shimon e4972cc126 compose.lock 2023-01-11 17:14:28 +02:00
shimon 1af48bd895 Merge branch 'master' of github.com:appwrite/appwrite into __stream
 Conflicts:
	.gitmodules
	app/config/errors.php
	app/config/specs/open-api3-latest-client.json
	app/config/specs/open-api3-latest-console.json
	app/config/specs/open-api3-latest-server.json
	app/config/specs/swagger2-latest-client.json
	app/config/specs/swagger2-latest-console.json
	app/config/specs/swagger2-latest-server.json
	composer.json
	composer.lock
	docker-compose.yml
	src/Appwrite/Extend/Exception.php
2023-01-11 16:31:26 +02:00
shimon 403e1c2d64 compose.lock 2023-01-11 16:24:26 +02:00
shimon 2bb45c36d8 compose.lock 2023-01-11 16:23:39 +02:00
shimon 7ea695937b Merge branch 'master' of github.com:appwrite/appwrite into __stream
 Conflicts:
	.gitmodules
	app/console
2022-11-27 20:05:28 +02:00
shimon 19388cc1a6 updates 2022-11-27 20:04:19 +02:00
shimon 7daa198f99 updates 2022-11-27 20:03:30 +02:00
shimon 89f325fe87 updates 2022-11-22 11:43:56 +02:00
shimon 19ad9914a3 updates 2022-11-22 11:15:48 +02:00
shimon 5546de13e3 aligning with master 2022-11-21 19:03:56 +02:00
shimon c55599516c Merge branch 'master' of github.com:appwrite/appwrite into __stream 2022-11-21 11:07:57 +02:00
shimon 0172a6c393 bla 2022-11-21 11:07:36 +02:00
shimon 19a38cd06a Merge branch 'master' of github.com:appwrite/appwrite into __stream
 Conflicts:
	app/config/specs/open-api3-latest-client.json
	app/config/specs/open-api3-latest-console.json
	app/config/specs/open-api3-latest-server.json
	app/config/specs/swagger2-latest-client.json
	app/config/specs/swagger2-latest-console.json
	app/config/specs/swagger2-latest-server.json
	app/workers/deletes.php
	composer.json
	composer.lock
	docker-compose.yml
2022-11-20 13:37:03 +02:00
shimon de6359cf9e audit 2022-09-29 14:46:08 +03:00
shimon 59c5c037c4 audit 2022-09-29 14:44:32 +03:00
shimon 104bd58c45 audit 2022-09-18 11:48:48 +03:00
shimon 110c38d03d Merge branch 'master' of github.com:appwrite/appwrite into __stream
 Conflicts:
	app/config/specs/open-api3-latest-client.json
	app/config/specs/open-api3-latest-console.json
	app/config/specs/open-api3-latest-server.json
	app/config/specs/swagger2-latest-client.json
	app/config/specs/swagger2-latest-console.json
	app/config/specs/swagger2-latest-server.json
	app/controllers/api/projects.php
	composer.json
	composer.lock
	phpunit.xml
2022-09-18 10:27:51 +03:00
shimon c9b0c6f906 sync with 0.16 2022-09-08 15:01:33 +03:00
shimon baea836574 sync with 0.16 2022-09-07 11:16:38 +03:00
shimon aef8c1b207 sync with 0.16 2022-09-06 18:20:51 +03:00
shimon 1aed3a1db5 Merge branch '0.16.x' of github.com:appwrite/appwrite into __stream
 Conflicts:
	.env
	app/config/collections.php
	app/config/roles.php
	app/config/specs/open-api3-latest-client.json
	app/config/specs/open-api3-latest-console.json
	app/config/specs/open-api3-latest-server.json
	app/config/specs/swagger2-latest-client.json
	app/config/specs/swagger2-latest-console.json
	app/config/specs/swagger2-latest-server.json
	app/controllers/api/account.php
	app/controllers/api/storage.php
	app/init.php
	composer.json
	composer.lock
	docker-compose.yml
	phpunit.xml
	src/Appwrite/Extend/Exception.php
	src/Appwrite/Utopia/Response.php
2022-09-06 11:14:11 +03:00
shimon 53b020fac0 test adjustments 2022-09-06 10:59:25 +03:00
shimon 9060d86145 test adjustments 2022-09-06 09:52:10 +03:00
shimon ca0017ca40 streamId 2022-09-05 17:29:03 +03:00
shimon 115b032f3a hls-playlist 2022-09-04 09:29:56 +03:00
shimon b13c9f58cc audio tracks 2022-08-31 18:52:42 +03:00
shimon fcd29baacc m3u8 tidy 2022-08-31 10:42:20 +03:00
shimon 735072f0a4 m3u8 tidy 2022-08-31 10:42:05 +03:00
shimon b26a28d4fb Multi audio tracks 2022-08-30 12:12:29 +03:00
shimon 33e608e625 Multi audio tracks 2022-08-30 12:12:03 +03:00
shimon 6d2d115fd3 addressing comments 2022-08-25 09:56:37 +03:00
shimon ed93e54480 addressing comments 2022-08-23 17:23:29 +03:00
shimon ebe794926c addressing comments 2022-08-23 17:17:40 +03:00
59b188755c Update src/Appwrite/Resque/Worker.php
Co-authored-by: Eldad A. Fux <eldad.fux@gmail.com>
2022-08-23 16:10:46 +03:00
5db53f7db8 Update src/Appwrite/Resque/Worker.php
Co-authored-by: Eldad A. Fux <eldad.fux@gmail.com>
2022-08-23 16:05:57 +03:00
e843c0059d Update src/Appwrite/Extend/Exception.php
Co-authored-by: Eldad A. Fux <eldad.fux@gmail.com>
2022-08-23 16:05:47 +03:00
93d777a412 Update src/Appwrite/Event/Transcoding.php
Co-authored-by: Eldad A. Fux <eldad.fux@gmail.com>
2022-08-23 16:05:32 +03:00
shimon 9ffd52d030 tests 2022-08-22 09:58:42 +03:00
Matej Bačo 7c2fa12414 Fix getting function ID 2022-08-17 13:42:06 +00:00
Matej Bačo c93b7aa452 Fix event-triggered multi-execution 2022-08-17 13:34:52 +00:00
shimon d6dc025331 sync with 0.16 2022-08-17 12:20:07 +03:00
shimon b9bca60e09 Merge branch 'master' of github.com:appwrite/appwrite into __stream
 Conflicts:
	.gitignore
	app/controllers/api/teams.php
	composer.json
	composer.lock
2022-08-17 12:11:59 +03:00
shimon f2fbdf8a7c transcoding gop 2022-08-07 09:59:42 +03:00
shimon e8c96e8452 dash manifest 2022-08-05 11:38:32 +03:00
shimon 6ca361d0b0 dash subtitles 2022-08-04 14:58:25 +03:00
shimon 2f1a116e22 video scopes 2022-08-03 18:50:21 +03:00
shimon fd3a4fd485 e2e tests 2022-08-03 16:08:14 +03:00
shimon 49f8eae3f6 mpeg-dash-stream 2022-07-28 17:34:54 +03:00
shimon fe6a47252e mpeg-dash-stream 2022-07-28 17:32:14 +03:00
shimon b403a829de dash 2022-07-26 13:32:59 +03:00
shimon e232f2bbd2 dash 2022-07-26 13:31:34 +03:00
shimon 6896ff3999 restore 2022-07-25 22:31:16 +03:00
shimon 1bcd2d6bd5 draft 2022-07-25 14:37:45 +03:00
shimon 8340198077 Merge branch 'master' of github.com:appwrite/appwrite into __stream 2022-07-25 13:41:30 +03:00
shimon 5b01232ecc update 2022-07-25 11:38:53 +03:00
shimon 076f333f77 update 2022-07-25 11:37:23 +03:00
shimon fbe63a884e hls stream 2022-07-19 10:51:25 +03:00
shimon cb0ab3c823 subtitles crud 2022-07-14 18:26:56 +03:00
shimon 817793a587 subtitles crud 2022-07-14 17:54:43 +03:00
shimon 2a02784b0c profiles crud 2022-07-14 16:09:29 +03:00
shimon a7d7879d37 metadata collecting
routes renaming
permissions
2022-07-13 18:48:38 +03:00
shimon 86b7215375 metadata collecting
routes renaming
permissions
2022-07-13 18:40:30 +03:00
shimon aeebbbe91d metadata collecting
routes renaming
permissions
2022-07-13 18:38:47 +03:00
shimon 4efbc92cf2 metadata collecting
routes renaming
permissions
2022-07-13 17:28:05 +03:00
shimon 7f44f45bf6 hls master name change 2022-07-12 12:13:37 +03:00
shimon 33d942c356 hls master name change 2022-07-11 13:51:47 +03:00
Matej Baco 4089b81b3b Added labels, rebuilt specs 2022-07-11 12:37:20 +02:00
shimon 9f4ebcf208 logger moved to App::shutdown 2022-07-11 11:22:43 +03:00
shimon ec8eaa1ddd gitignore large file 2022-07-07 09:40:11 +03:00
shimon 6c7c5c1891 small fix 2022-07-06 19:12:58 +03:00
shimon d8f4d097d1 updated dependencies 2022-07-05 14:35:39 +03:00
shimon bc43989aa4 Merge branch 'master' of github.com:appwrite/appwrite into __stream 2022-07-05 14:33:28 +03:00
shimon dc38b63160 small fix 2022-07-05 14:02:23 +03:00
shimon 01eaa65d6b small fix 2022-07-05 13:57:03 +03:00
shimon 5e903daf6f small fix 2022-07-05 13:51:28 +03:00
shimon e48e1dae2c small fix 2022-07-05 11:55:49 +03:00
shimon c2e599222e big file 2022-07-03 16:59:40 +03:00
shimon bb172f8a3e refactor worker flow 2022-06-27 16:13:01 +03:00
shimon 48320d40ab Merge remote-tracking branch 'origin/__stream' into __stream
# Conflicts:
#	app/config/collections.php
#	app/controllers/api/video.php
#	app/init.php
#	app/workers/transcoding.php
#	composer.lock
#	src/Appwrite/Event/Transcoding.php
#	src/Appwrite/Utopia/Response.php
#	tests/e2e/Services/Video/VideoCustomServerTest.php
2022-06-27 16:08:17 +03:00
shimon c89b028797 refactor worker flow 2022-06-27 16:06:46 +03:00
shimon c7534760a0 refactor worker flow 2022-06-22 17:44:03 +03:00
shimon d6007b24ec refactor worker flow 2022-06-22 08:56:57 +03:00
shimon c8c282afb2 restore 2022-06-21 11:45:49 +03:00
shimon 6491cea581 restore 2022-06-21 11:45:49 +03:00
shimon 100fb73323 view template 2022-06-21 11:45:49 +03:00
shimon aa6ff513a6 restore 2022-06-20 14:44:04 +03:00
shimon 56545a7344 restore 2022-06-19 11:52:56 +03:00
shimon e97957d46e Merge branch 'master' of github.com:appwrite/appwrite into __stream 2022-06-19 10:52:30 +03:00
shimon 329c43f860 view template 2022-06-16 23:01:42 +03:00
5853 changed files with 14261 additions and 6337 deletions
+7 -5
View File
@@ -23,15 +23,15 @@ _APP_DB_SCHEMA=appwrite
_APP_DB_USER=user
_APP_DB_PASS=password
_APP_DB_ROOT_PASS=rootsecretpassword
_APP_STORAGE_DEVICE=Local
_APP_STORAGE_DEVICE=local
_APP_STORAGE_S3_ACCESS_KEY=
_APP_STORAGE_S3_SECRET=
_APP_STORAGE_S3_REGION=us-east-1
_APP_STORAGE_S3_REGION=
_APP_STORAGE_S3_BUCKET=
_APP_STORAGE_DO_SPACES_ACCESS_KEY=
_APP_STORAGE_DO_SPACES_SECRET=
_APP_STORAGE_DO_SPACES_REGION=us-east-1
_APP_STORAGE_DO_SPACES_BUCKET=
_APP_STORAGE_DO_SPACES_REGION=fra1
_APP_STORAGE_DO_SPACES_BUCKET=videos-test
_APP_STORAGE_BACKBLAZE_ACCESS_KEY=
_APP_STORAGE_BACKBLAZE_SECRET=
_APP_STORAGE_BACKBLAZE_REGION=us-west-004
@@ -58,8 +58,10 @@ _APP_SMTP_USERNAME=
_APP_SMTP_PASSWORD=
_APP_SMS_PROVIDER=sms://username:password@mock
_APP_SMS_FROM=+123456789
_APP_STORAGE_LIMIT=30000000
_APP_STORAGE_LIMIT=30000000000000
_APP_STORAGE_PREVIEW_LIMIT=20000000
_APP_PHONE_PROVIDER=phone://mock
_APP_PHONE_FROM=+123456789
_APP_FUNCTIONS_SIZE_LIMIT=30000000
_APP_FUNCTIONS_TIMEOUT=900
_APP_FUNCTIONS_BUILD_TIMEOUT=900
+3 -3
View File
@@ -6,7 +6,7 @@ on:
jobs:
tests:
name: Unit & E2E
name: Release
runs-on: ubuntu-latest
steps:
@@ -37,9 +37,9 @@ jobs:
with:
images: appwrite/appwrite
tags: |
type=semver,pattern={{major}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}.{{minor}}.{{patch}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
- name: Build and push
uses: docker/build-push-action@v4
+4
View File
@@ -13,3 +13,7 @@ debug/
app/sdks
dev/yasd_init.php
.phpunit.result.cache
/tests/tmp/
/tests/resources/disk-a/very-large-file-1.mov
/tests/resources/disk-a/very-big-file-2.mov
/tests/resources/disk-a/video-srt.*
+1 -1
View File
@@ -1,4 +1,4 @@
[submodule "app/console"]
path = app/console
url = https://github.com/appwrite/console
branch = 2.3.3
branch = video-scopes-support
+20 -2
View File
@@ -1,12 +1,30 @@
# Version 1.3.4
## Bugs
- Update migration to properly migrate bucket permissiosn [#5497](https://github.com/appwrite/appwrite/pull/5497)
# Version 1.3.3
## Bugs
- Fixed migration resetting some data [#5455](https://github.com/appwrite/appwrite/pull/5455)
# Version 1.3.2
## Bugs
- Fixed auto-setting custom ID on nested documents [#5363](https://github.com/appwrite/appwrite/pull/5363)
- Fixed listDocuments not returning all the documents [#5395](https://github.com/appwrite/appwrite/pull/5395)
- Fixed deleting keys, webhooks, platforms and domains after deleting project [#5395](https://github.com/appwrite/appwrite/pull/5395)
# Version 1.3.1
## Bugs
- Fixed Migration issue regarding 500 error [4906](https://github.com/appwrite/appwrite/pull/5356)
- Fixed Migration issue regarding 500 error [#5356](https://github.com/appwrite/appwrite/pull/5356)
# Version 1.3.0
## Features
- Password dictionary setting allows to compare user's password against command password database [4906](https://github.com/appwrite/appwrite/pull/4906)
- Password dictionary setting allows to compare user's password against command password database [#4906](https://github.com/appwrite/appwrite/pull/4906)
- Password history setting allows to save user's last used password so that it may not be used again. Maximum number of history saved is 20, which can be configured. Minimum is 0 which means disabled. [#4866](https://github.com/appwrite/appwrite/pull/4866)
- Update APIs to check X-Appwrite-Timestamp header [#5024](https://github.com/appwrite/appwrite/pull/5024)
- Database relationships [#5238](https://github.com/appwrite/appwrite/pull/5238)
+1 -1
View File
@@ -321,7 +321,7 @@ The Runtimes for all supported cloud functions (multicore builds) can be found a
The following steps are used to generate a new console SDK:
1. Update the console spec file located at `app/config/specs/swagger2-<version-number>.console.json` using Appwrite Tasks. Run the `php app/cli.php specs <version-number> normal` command in a running `appwrite/appwrite` container.
1. Update the console spec file located at `app/config/specs/swagger2-<version-number>.console.json` using Appwrite Tasks. Run the `php app/cli.php specs version=<version-number> mode=normal` command in a running `appwrite/appwrite` container.
2. Generate a new SDK using the command `php app/cli.php sdks`.
3. Change your working dir using `cd app/sdks/console-web`.
4. Build the new SDK `npm run build`.
+10
View File
@@ -124,6 +124,13 @@ ENV _APP_SERVER=swoole \
_APP_LOGGING_PROVIDER= \
_APP_LOGGING_CONFIG=
RUN \
apk update \
&& apk add --no-cache\
mediainfo \
ffmpeg \
&& rm -rf /var/cache/apk/*
RUN \
if [ "$DEBUG" == "true" ]; then \
apk add boost boost-dev; \
@@ -142,12 +149,14 @@ COPY ./src /usr/src/code/src
# Set Volumes
RUN mkdir -p /storage/uploads && \
mkdir -p /storage/videos && \
mkdir -p /storage/cache && \
mkdir -p /storage/config && \
mkdir -p /storage/certificates && \
mkdir -p /storage/functions && \
mkdir -p /storage/debug && \
chown -Rf www-data.www-data /storage/uploads && chmod -Rf 0755 /storage/uploads && \
chown -Rf www-data.www-data /storage/videos && chmod -Rf 0755 /storage/videos && \
chown -Rf www-data.www-data /storage/cache && chmod -Rf 0755 /storage/cache && \
chown -Rf www-data.www-data /storage/config && chmod -Rf 0755 /storage/config && \
chown -Rf www-data.www-data /storage/certificates && chmod -Rf 0755 /storage/certificates && \
@@ -176,6 +185,7 @@ RUN chmod +x /usr/local/bin/doctor && \
chmod +x /usr/local/bin/worker-builds && \
chmod +x /usr/local/bin/worker-mails && \
chmod +x /usr/local/bin/worker-messaging && \
chmod +x /usr/local/bin/worker-videos && \
chmod +x /usr/local/bin/worker-webhooks
# Letsencrypt Permissions
+5 -6
View File
@@ -1,5 +1,4 @@
> 准备好迎接狂风暴'云'了吗? 🌩 ☂️
> Appwrite Cloud即将到来!你能够通过https://appwrite.io/cloud了解更多的资讯, 注册即可领取试用额度哦
> 好消息!Appwrite 云现已进入公开测试版!立即访问 cloud.appwrite.io 注册,体验无忧的托管服务。今天就加入我们的云端吧!☁️🎉
<br />
<p align="center">
@@ -24,7 +23,7 @@
[English](README.md) | 简体中文
[**宣布带有数据库关系的 Appwrite 1.3!了解最新消息!**](https://dev.to/appwrite/join-celebrations-appwrite-13-ships-relationships-57fc)
[**Appwrite 云公开测试版!立即注册!**](https://cloud.appwrite.io)
Appwrite是一个基于Docker的端到端开发者平台,其容器化的微服务库可应用于网页端,移动端,以及后端。Appwrite 通过视觉化界面简化了从零开始编写 API 的繁琐过程,在保证软件安全的前提下为开发者创造了一个高效的开发环境。
@@ -67,7 +66,7 @@ docker run -it --rm \
--volume /var/run/docker.sock:/var/run/docker.sock \
--volume "$(pwd)"/appwrite:/usr/src/code/appwrite:rw \
--entrypoint="install" \
appwrite/appwrite:1.3.1
appwrite/appwrite:1.3.4
```
### Windows
@@ -79,7 +78,7 @@ docker run -it --rm ^
--volume //var/run/docker.sock:/var/run/docker.sock ^
--volume "%cd%"/appwrite:/usr/src/code/appwrite:rw ^
--entrypoint="install" ^
appwrite/appwrite:1.3.1
appwrite/appwrite:1.3.4
```
#### PowerShell
@@ -89,7 +88,7 @@ docker run -it --rm `
--volume /var/run/docker.sock:/var/run/docker.sock `
--volume ${pwd}/appwrite:/usr/src/code/appwrite:rw `
--entrypoint="install" `
appwrite/appwrite:1.3.1
appwrite/appwrite:1.3.4
```
运行后,可以在浏览器上访问 http://localhost 找到 Appwrite 控制台。在非 Linux 的本机主机上完成安装后,服务器可能需要几分钟才能启动。
+7 -8
View File
@@ -1,12 +1,10 @@
> It's going to get cloudy! 🌩 ☂️
> The Appwrite Cloud is coming soon! You can learn more about our upcoming hosted solution and signup for free credits at: https://appwrite.io/cloud
> Great news! Appwrite Cloud is now in public beta! Sign up at [cloud.appwrite.io](https://cloud.appwrite.io) for a hassle-free, hosted experience. Join us in the Cloud today! ☁️🎉
<br />
<p align="center">
<a href="https://appwrite.io" target="_blank"><img width="260" height="39" src="https://appwrite.io/images/appwrite.svg" alt="Appwrite Logo"></a>
<br />
<br />
<b>A complete backend solution for your [Flutter / Vue / Angular / React / iOS / Android / *ANY OTHER*] app</b>
<b>Appwrite is a backend platform for developing Web, Mobile, and Flutter applications. Built with the open source community and optimized for developer experience in the coding languages you love.</b>
<br />
<br />
</p>
@@ -26,7 +24,7 @@
English | [简体中文](README-CN.md)
[**Announcing Appwrite 1.3 with Database Relationships! Learn what's new!**](https://dev.to/appwrite/join-celebrations-appwrite-13-ships-relationships-57fc)
[**Announcing Appwrite Cloud Public Beta! Sign up today!**](https://cloud.appwrite.io)
Appwrite is an end-to-end backend server for Web, Mobile, Native, or Backend apps packaged as a set of Docker<nobr> microservices. Appwrite abstracts the complexity and repetitiveness required to build a modern backend API from scratch and allows you to build secure apps faster.
@@ -51,6 +49,7 @@ Table of Contents:
- [CMD](#cmd)
- [PowerShell](#powershell)
- [Upgrade from an Older Version](#upgrade-from-an-older-version)
- [One-Click Setups](#one-click-setups)
- [Getting Started](#getting-started)
- [Services](#services)
- [SDKs](#sdks)
@@ -76,7 +75,7 @@ docker run -it --rm \
--volume /var/run/docker.sock:/var/run/docker.sock \
--volume "$(pwd)"/appwrite:/usr/src/code/appwrite:rw \
--entrypoint="install" \
appwrite/appwrite:1.3.1
appwrite/appwrite:1.3.4
```
### Windows
@@ -88,7 +87,7 @@ docker run -it --rm ^
--volume //var/run/docker.sock:/var/run/docker.sock ^
--volume "%cd%"/appwrite:/usr/src/code/appwrite:rw ^
--entrypoint="install" ^
appwrite/appwrite:1.3.1
appwrite/appwrite:1.3.4
```
#### PowerShell
@@ -98,7 +97,7 @@ docker run -it --rm `
--volume /var/run/docker.sock:/var/run/docker.sock `
--volume ${pwd}/appwrite:/usr/src/code/appwrite:rw `
--entrypoint="install" `
appwrite/appwrite:1.3.1
appwrite/appwrite:1.3.4
```
Once the Docker installation is complete, go to http://localhost to access the Appwrite console from your browser. Please note that on non-Linux native hosts, the server might take a few minutes to start after completing the installation.
+917
View File
@@ -3251,6 +3251,17 @@ $collections = [
'default' => null,
'filters' => [],
],
[
'$id' => ID::custom('bucketInternalId'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => true,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('name'),
'type' => Database::VAR_STRING,
@@ -3580,6 +3591,912 @@ $collections = [
],
],
],
'videos' => [
'$collection' => Database::METADATA,
'$id' => 'videos',
'$name' => 'Videos',
'attributes' => [
[
'$id' => 'bucketId',
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => 'bucketInternalId',
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => 'fileId',
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => 'fileInternalId',
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => 'previewId',
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => 'previewInternalId',
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => 'size',
'type' => Database::VAR_INTEGER,
'format' => '',
'size' => 0,
'signed' => false,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => 'format',
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => 'duration',
'type' => Database::VAR_INTEGER,
'format' => '',
'size' => 0,
'signed' => false,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => 'width',
'type' => Database::VAR_INTEGER,
'format' => '',
'size' => 0,
'signed' => false,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => 'height',
'type' => Database::VAR_INTEGER,
'format' => '',
'size' => 0,
'signed' => false,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => 'aspectRatio',
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => 'videoFormat',
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => 'videoFormatProfile',
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => 'videoBitRate',
'type' => Database::VAR_INTEGER,
'format' => '',
'size' => 0,
'signed' => false,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => 'videoFrameRateMode',
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => 'videoFrameRate',
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => 'audioFormat',
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => 'audioBitRate',
'type' => Database::VAR_INTEGER,
'format' => '',
'size' => 0,
'signed' => false,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => 'audioSampleRate',
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
],
'indexes' => [
[
'$id' => '_key_bucket_file',
'type' => Database::INDEX_KEY,
'attributes' => ['bucketId', 'fileId'],
'lengths' => [Database::LENGTH_KEY],
'orders' => [Database::ORDER_ASC],
],
]
],
'videos_previews' => [
'$collection' => Database::METADATA,
'$id' => 'videos_previews',
'$name' => 'Videos_previews',
'attributes' => [
[
'$id' => 'videoId',
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => 'videoInternalId',
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => 'type',
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => true,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => 'name',
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => 'path',
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => 'second',
'type' => Database::VAR_INTEGER,
'format' => '',
'size' => 0,
'signed' => false,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
],
'indexes' => [
[
'$id' => '_key_video_type',
'type' => Database::INDEX_UNIQUE,
'attributes' => ['videoId', 'type', 'name'],
'lengths' => [],
'orders' => [],
],
]
],
'videos_renditions' => [
'$collection' => Database::METADATA,
'$id' => 'videos_renditions',
'$name' => 'Videos_renditions',
'attributes' => [
[
'$id' => 'videoId',
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => true,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => 'videoInternalId',
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => 'profileId',
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => 0,
'array' => false,
'filters' => [],
],
[
'$id' => 'profileInternalId',
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => 0,
'array' => false,
'filters' => [],
],
[
'$id' => 'name',
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => 'startedAt',
'type' => Database::VAR_DATETIME,
'format' => '',
'size' => 0,
'signed' => false,
'required' => false,
'default' => null,
'array' => false,
'filters' => ['datetime'],
],
[
'$id' => 'endedAt',
'type' => Database::VAR_DATETIME,
'format' => '',
'size' => 0,
'signed' => false,
'required' => false,
'default' => null,
'array' => false,
'filters' => ['datetime'],
],
[
'$id' => 'path',
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => 'targetDuration',
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => 'width',
'type' => Database::VAR_INTEGER,
'format' => '',
'size' => 0,
'signed' => false,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => 'height',
'type' => Database::VAR_INTEGER,
'format' => '',
'size' => 0,
'signed' => false,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => 'videoBitRate',
'type' => Database::VAR_INTEGER,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => 0,
'array' => false,
'filters' => [],
],
[
'$id' => 'audioBitRate',
'type' => Database::VAR_INTEGER,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => 0,
'array' => false,
'filters' => [],
],
[
'$id' => 'metadata',
'type' => Database::VAR_STRING,
'format' => '',
'size' => 4096,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => ['json'],
],
[
'$id' => 'status',
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => 'progress',
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => 'output',
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => true,
'default' => null,
'array' => false,
'filters' => [],
],
],
'indexes' => [
[
'$id' => '_key_video_output_profile',
'type' => Database::INDEX_KEY,
'attributes' => ['videoId', 'output', 'profileId'],
'lengths' => [],
'orders' => [],
],
]
],
'videos_renditions_segments' => [
'$collection' => Database::METADATA,
'$id' => 'videos_rendition_segments',
'$name' => 'Videos_rendition_segments',
'attributes' => [
[
'$id' => 'renditionId',
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => true,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => 'renditionInternalId',
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => true,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => 'fileName',
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => true,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => 'path',
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => true,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => 'duration',
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => 'streamId',
'type' => Database::VAR_INTEGER,
'format' => '',
'size' => 0,
'signed' => true,
'required' => false,
'default' => 0,
'array' => false,
'filters' => [],
],
[
'$id' => 'isInit',
'type' => Database::VAR_INTEGER,
'format' => '',
'size' => 0,
'signed' => true,
'required' => false,
'default' => 0,
'array' => false,
'filters' => [],
],
],
'indexes' => [
[
'$id' => '_key_renditionId',
'type' => Database::INDEX_KEY,
'attributes' => ['renditionId'],
'lengths' => [],
'orders' => [],
],
]
],
'videos_profiles' => [
'$collection' => Database::METADATA,
'$id' => 'videos_profiles',
'$name' => 'Videos_profiles',
'attributes' => [
[
'$id' => 'name',
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => 'videoBitRate',
'type' => Database::VAR_INTEGER,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => 0,
'array' => false,
'filters' => [],
],
[
'$id' => 'audioBitRate',
'type' => Database::VAR_INTEGER,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => 0,
'array' => false,
'filters' => [],
],
[
'$id' => 'width',
'type' => Database::VAR_INTEGER,
'format' => '',
'size' => 0,
'signed' => false,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => 'height',
'type' => Database::VAR_INTEGER,
'format' => '',
'size' => 0,
'signed' => false,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
],
'indexes' => [
[
'$id' => '_key_profile',
'type' => Database::INDEX_KEY,
'attributes' => ['width', 'height', 'videoBitRate', 'audioBitRate'],
'lengths' => [],
'orders' => [],
],
]
],
'videos_subtitles' => [
'$collection' => Database::METADATA,
'$id' => 'videos_subtitles',
'$name' => 'Videos_subtitles',
'attributes' => [
[
'$id' => 'videoId',
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => true,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => 'videoInternalId',
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => true,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => 'bucketId',
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => 0,
'array' => false,
'filters' => [],
],
[
'$id' => 'bucketInternalId',
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => 0,
'array' => false,
'filters' => [],
],
[
'$id' => 'fileId',
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => 0,
'array' => false,
'filters' => [],
],
[
'$id' => 'fileInternalId',
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => 0,
'array' => false,
'filters' => [],
],
[
'$id' => 'path',
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => 'name',
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => 'code',
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => 'default',
'type' => Database::VAR_BOOLEAN,
'format' => '',
'size' => 0,
'signed' => true,
'required' => false,
'default' => false,
'array' => false,
'filters' => [],
],
[
'$id' => 'targetDuration',
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => 'status',
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => '',
'array' => false,
'filters' => [],
],
],
'indexes' => [
[
'$id' => '_key_bucket_video_subtitle',
'type' => Database::INDEX_KEY,
'attributes' => ['videoId'],
'lengths' => [Database::LENGTH_KEY],
'orders' => [Database::ORDER_ASC],
],
]
],
'videos_subtitles_segments' => [
'$collection' => Database::METADATA,
'$id' => 'videos_subtitles_segments',
'$name' => 'Videos_subtitles_segments',
'attributes' => [
[
'$id' => 'subtitleId',
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => true,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => 'subtitleInternalId',
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => true,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => 'fileName',
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => true,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => 'path',
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => true,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => 'duration',
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => true,
'default' => null,
'array' => false,
'filters' => [],
],
],
'indexes' => [
[
'$id' => '_key_subtitleId',
'type' => Database::INDEX_KEY,
'attributes' => ['subtitleId'],
'lengths' => [Database::LENGTH_KEY],
'orders' => [Database::ORDER_ASC],
],
]
],
];
return $collections;
+66 -1
View File
@@ -88,6 +88,11 @@ return [
'description' => 'The request cannot be fulfilled with the current protocol. Please check the value of the _APP_OPTIONS_FORCE_HTTPS environment variable.',
'code' => 500,
],
Exception::GENERAL_USAGE_DISABLED => [
'name' => Exception::GENERAL_USAGE_DISABLED,
'description' => 'Usage stats is not configured. Please check the value of the _APP_USAGE_STATS environment variable of your Appwrite server.',
'code' => 501,
],
/** User Errors */
Exception::USER_COUNT_EXCEEDED => [
@@ -102,7 +107,7 @@ return [
],
Exception::USER_ALREADY_EXISTS => [
'name' => Exception::USER_ALREADY_EXISTS,
'description' => 'A user with the same email already exists in your project.',
'description' => 'A user with the same id, email, or phone already exists in your project.',
'code' => 409,
],
Exception::USER_BLOCKED => [
@@ -564,6 +569,66 @@ return [
'description' => 'Domain verification for the requested domain has failed.',
'code' => 401,
],
Exception::VIDEO_PROFILE_NOT_FOUND => [
'name' => Exception::VIDEO_PROFILE_NOT_FOUND,
'description' => 'Video profile not found.',
'code' => 404,
],
Exception::VIDEO_NOT_VALID => [
'name' => Exception::VIDEO_NOT_VALID,
'description' => 'Video not valid.',
'code' => 400,
],
Exception::VIDEO_RENDITION_NOT_FOUND => [
'name' => Exception::VIDEO_RENDITION_NOT_FOUND,
'description' => 'Video rendition not found.',
'code' => 404,
],
Exception::VIDEO_SUBTITLE_NOT_FOUND => [
'name' => Exception::VIDEO_SUBTITLE_NOT_FOUND,
'description' => 'Video subtitle not found.',
'code' => 404,
],
Exception::VIDEO_LANGUAGE_CODE_NOT_VALID => [
'name' => Exception::VIDEO_LANGUAGE_CODE_NOT_VALID,
'description' => 'Video language code not valid.',
'code' => 400,
],
Exception::VIDEO_SUBTITLE_NOT_VALID => [
'name' => Exception::VIDEO_SUBTITLE_NOT_VALID,
'description' => 'Video subtitle not valid.',
'code' => 400,
],
Exception::VIDEO_SECOND_OUT_OF_RANGE => [
'name' => Exception::VIDEO_SECOND_OUT_OF_RANGE,
'description' => 'Video second out of duration range.',
'code' => 400,
],
Exception::VIDEO_NOT_FOUND => [
'name' => Exception::VIDEO_NOT_FOUND,
'description' => 'Video not found.',
'code' => 404,
],
Exception::VIDEO_PREVIEW_NOT_FOUND => [
'name' => Exception::VIDEO_PREVIEW_NOT_FOUND,
'description' => 'Video preview not found.',
'code' => 404,
],
Exception::VIDEO_TIMELINE_NOT_FOUND => [
'name' => Exception::VIDEO_TIMELINE_NOT_FOUND,
'description' => 'Video timeline not found.',
'code' => 404,
],
Exception::VIDEO_RENDITION_SEGMENT_NOT_FOUND => [
'name' => Exception::VIDEO_RENDITION_SEGMENT_NOT_FOUND,
'description' => 'Video rendition segment not found',
'code' => 404,
],
Exception::VIDEO_SUBTITLE_SEGMENT_NOT_FOUND => [
'name' => Exception::VIDEO_SUBTITLE_SEGMENT_NOT_FOUND,
'description' => 'Video subtitle segment not found',
'code' => 404,
],
Exception::DOMAIN_TARGET_INVALID => [
'name' => Exception::DOMAIN_TARGET_INVALID,
'description' => 'Your Appwrite instance is not publicly accessible. Please check the _APP_DOMAIN_TARGET environment variable of your Appwrite server.',
+29 -1
View File
@@ -236,5 +236,33 @@ return [
'update' => [
'$description' => 'This event triggers when a function is updated.',
]
]
],
'videos' => [
'$model' => Response::MODEL_VIDEO,
'$resource' => true,
'$description' => 'This event triggers on any videos event.',
'renditions' => [
'$model' => Response::MODEL_RENDITION,
'$resource' => true,
'$description' => 'This event triggers on any renditions event.',
'create' => [
'$description' => 'This event triggers when a rendition is created.',
],
'delete' => [
'$description' => 'This event triggers when a rendition is deleted.'
],
'update' => [
'$description' => 'This event triggers when a rendition is updated.'
],
],
'create' => [
'$description' => 'This event triggers when a video is created.'
],
'delete' => [
'$description' => 'This event triggers when a video is deleted.',
],
'update' => [
'$description' => 'This event triggers when a video is updated.',
]
],
];
File diff suppressed because it is too large Load Diff
+19 -19
View File
@@ -28,7 +28,7 @@ return [
'gitUrl' => 'git@github.com:appwrite/sdk-for-web.git',
'gitRepoName' => 'sdk-for-web',
'gitUserName' => 'appwrite',
'gitBranch' => 'master',
'gitBranch' => 'dev',
'demos' => [
[
'icon' => 'react.svg',
@@ -76,7 +76,7 @@ return [
'gitUrl' => 'git@github.com:appwrite/sdk-for-flutter.git',
'gitRepoName' => 'sdk-for-flutter',
'gitUserName' => 'appwrite',
'gitBranch' => 'master',
'gitBranch' => 'dev',
],
[
'key' => 'apple',
@@ -94,7 +94,7 @@ return [
'gitUrl' => 'git@github.com:appwrite/sdk-for-apple.git',
'gitRepoName' => 'sdk-for-apple',
'gitUserName' => 'appwrite',
'gitBranch' => 'master',
'gitBranch' => 'dev',
],
[
'key' => 'objective-c',
@@ -111,7 +111,7 @@ return [
'gitUrl' => 'git@github.com:appwrite/sdk-for-objective-c.git',
'gitRepoName' => 'sdk-for-objective-c',
'gitUserName' => 'appwrite',
'gitBranch' => 'master',
'gitBranch' => 'dev',
],
[
'key' => 'android',
@@ -129,7 +129,7 @@ return [
'gitUrl' => 'git@github.com:appwrite/sdk-for-android.git',
'gitRepoName' => 'sdk-for-android',
'gitUserName' => 'appwrite',
'gitBranch' => 'main',
'gitBranch' => 'dev',
'docDirectories' => [
'Kotlin' => 'kotlin',
'Java' => 'java',
@@ -196,14 +196,14 @@ return [
'prism' => 'javascript',
'source' => \realpath(__DIR__ . '/../sdks/console-web'),
'gitUrl' => 'git@github.com:appwrite/sdk-for-console.git',
'gitBranch' => 'main',
'gitBranch' => 'dev',
'gitRepoName' => 'sdk-for-console',
'gitUserName' => 'appwrite',
],
[
'key' => 'cli',
'name' => 'Command Line',
'version' => '2.0.0',
'version' => '2.0.2',
'url' => 'https://github.com/appwrite/sdk-for-cli',
'package' => 'https://www.npmjs.com/package/appwrite-cli',
'enabled' => true,
@@ -216,7 +216,7 @@ return [
'gitUrl' => 'git@github.com:appwrite/sdk-for-cli.git',
'gitRepoName' => 'sdk-for-cli',
'gitUserName' => 'appwrite',
'gitBranch' => 'master',
'gitBranch' => 'dev',
],
],
],
@@ -244,7 +244,7 @@ return [
'gitUrl' => 'git@github.com:appwrite/sdk-for-node.git',
'gitRepoName' => 'sdk-for-node',
'gitUserName' => 'appwrite',
'gitBranch' => 'master',
'gitBranch' => 'dev',
],
[
'key' => 'deno',
@@ -262,7 +262,7 @@ return [
'gitUrl' => 'git@github.com:appwrite/sdk-for-deno.git',
'gitRepoName' => 'sdk-for-deno',
'gitUserName' => 'appwrite',
'gitBranch' => 'master',
'gitBranch' => 'dev',
],
[
'key' => 'php',
@@ -280,7 +280,7 @@ return [
'gitUrl' => 'git@github.com:appwrite/sdk-for-php.git',
'gitRepoName' => 'sdk-for-php',
'gitUserName' => 'appwrite',
'gitBranch' => 'master',
'gitBranch' => 'dev',
],
[
'key' => 'python',
@@ -298,7 +298,7 @@ return [
'gitUrl' => 'git@github.com:appwrite/sdk-for-python.git',
'gitRepoName' => 'sdk-for-python',
'gitUserName' => 'appwrite',
'gitBranch' => 'master',
'gitBranch' => 'dev',
],
[
'key' => 'ruby',
@@ -316,7 +316,7 @@ return [
'gitUrl' => 'git@github.com:appwrite/sdk-for-ruby.git',
'gitRepoName' => 'sdk-for-ruby',
'gitUserName' => 'appwrite',
'gitBranch' => 'master',
'gitBranch' => 'dev',
],
[
'key' => 'go',
@@ -334,7 +334,7 @@ return [
'gitUrl' => 'git@github.com:appwrite/sdk-for-go.git',
'gitRepoName' => 'sdk-for-go',
'gitUserName' => 'appwrite',
'gitBranch' => 'master',
'gitBranch' => 'dev',
],
[
'key' => 'java',
@@ -352,7 +352,7 @@ return [
'gitUrl' => 'git@github.com:appwrite/sdk-for-java.git',
'gitRepoName' => 'sdk-for-java',
'gitUserName' => 'appwrite',
'gitBranch' => 'master',
'gitBranch' => 'dev',
],
[
'key' => 'dotnet',
@@ -370,7 +370,7 @@ return [
'gitUrl' => 'git@github.com:appwrite/sdk-for-dotnet.git',
'gitRepoName' => 'sdk-for-dotnet',
'gitUserName' => 'appwrite',
'gitBranch' => 'main',
'gitBranch' => 'dev',
],
[
'key' => 'dart',
@@ -388,7 +388,7 @@ return [
'gitUrl' => 'git@github.com:appwrite/sdk-for-dart.git',
'gitRepoName' => 'sdk-for-dart',
'gitUserName' => 'appwrite',
'gitBranch' => 'master',
'gitBranch' => 'dev',
],
[
'key' => 'kotlin',
@@ -406,7 +406,7 @@ return [
'gitUrl' => 'git@github.com:appwrite/sdk-for-kotlin.git',
'gitRepoName' => 'sdk-for-kotlin',
'gitUserName' => 'appwrite',
'gitBranch' => 'main',
'gitBranch' => 'dev',
'docDirectories' => [
'Kotlin' => 'kotlin',
'Java' => 'java',
@@ -428,7 +428,7 @@ return [
'gitUrl' => 'git@github.com:appwrite/sdk-for-swift.git',
'gitRepoName' => 'sdk-for-swift',
'gitUserName' => 'appwrite',
'gitBranch' => 'main',
'gitBranch' => 'dev',
],
[
'key' => 'graphql',
+5
View File
@@ -20,6 +20,8 @@ $member = [
'avatars.read',
'execution.read',
'execution.write',
'videos.read',
'videos.write',
];
$admins = [
@@ -51,6 +53,8 @@ $admins = [
'functions.write',
'execution.read',
'execution.write',
'videos.read',
'videos.write',
];
return [
@@ -68,6 +72,7 @@ return [
'locale.read',
'avatars.read',
'execution.write',
'videos.read',
],
],
Auth::USER_ROLE_USERS => [
+6
View File
@@ -76,4 +76,10 @@ return [ // List of publicly visible scopes
'health.read' => [
'description' => 'Access to read your project\'s health status',
],
'videos.read' => [
'description' => 'Access to read your project\'s video content',
],
'videos.write' => [
'description' => 'Access to create, update, and delete your project\'s video content',
]
];
+13
View File
@@ -199,4 +199,17 @@ return [
'optional' => false,
'icon' => '',
],
'videos' => [
'key' => 'videos',
'name' => 'Videos',
'subtitle' => 'Appwrite\'s Videos Endpoint',
'description' => 'Videos Endpoint',
'controller' => 'api/videos.php',
'sdk' => false,
'docs' => false,
'docsUrl' => '',
'tests' => true,
'optional' => false,
'icon' => '',
],
];
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+48
View File
@@ -0,0 +1,48 @@
<?php
return [
[
'name' => '360p',
'videoBitRate' => 890, //video BitRate in Kbps
'audioBitRate' => 64, //audio BitRate in Kbps
'width' => 640, //width resolution in px
'height' => 360, //height resolution in px
],
[
'name' => '480p',
'videoBitRate' => 2100,
'audioBitRate' => 64,
'width' => 854,
'height' => 480,
],
[
'name' => '576p',
'videoBitRate' => 2538,
'audioBitRate' => 128,
'width' => 1024,
'height' => 576,
],
[
'name' => '720p',
'videoBitRate' => 3551,
'audioBitRate' => 128,
'width' => 1280,
'height' => 720,
],
[
'name' => '1080p',
'videoBitRate' => 4800,
'audioBitRate' => 128,
'width' => 1920,
'height' => 1080,
],
[
'name' => '2160p',
'videoBitRate' => 16000,
'audioBitRate' => 356,
'width' => 4096,
'height' => 2160,
],
];
+19 -85
View File
@@ -10,8 +10,8 @@ use Appwrite\Event\Mail;
use Appwrite\Event\Phone as EventPhone;
use Appwrite\Extend\Exception;
use Appwrite\Network\Validator\Email;
use Utopia\Validator\Host;
use Utopia\Validator\URL;
use Appwrite\Network\Validator\Host;
use Appwrite\Network\Validator\URL;
use Appwrite\OpenSSL\OpenSSL;
use Appwrite\Template\Template;
use Appwrite\URL\URL as URLParser;
@@ -64,7 +64,7 @@ App::post('/v1/account')
->label('sdk.response.type', Response::CONTENT_TYPE_JSON)
->label('sdk.response.model', Response::MODEL_USER)
->label('abuse-limit', 10)
->param('userId', '', new CustomId(), 'Unique Id. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
->param('userId', '', new CustomId(), 'Unique Id. Choose your own unique ID or pass the string `ID.unique()` to auto generate it. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
->param('email', '', new Email(), 'User email.')
->param('password', '', fn ($project, $passwordsDictionary) => new PasswordDictionary($passwordsDictionary, $project->getAttribute('auths', [])['passwordDictionary'] ?? false), 'New user password. Must be at least 8 chars.', false, ['project', 'passwordsDictionary'])
->param('name', '', new Text(128), 'User name. Max length: 128 chars.', true)
@@ -144,7 +144,7 @@ App::post('/v1/account')
App::post('/v1/account/sessions/email')
->alias('/v1/account/sessions')
->desc('Create Email Session')
->groups(['api', 'account', 'auth', 'session'])
->groups(['api', 'account', 'auth'])
->label('event', 'users.[userId].sessions.[sessionId].create')
->label('scope', 'public')
->label('auth.type', 'emailPassword')
@@ -375,7 +375,7 @@ App::post('/v1/account/sessions/oauth2/callback/:provider/:projectId')
App::get('/v1/account/sessions/oauth2/:provider/redirect')
->desc('OAuth2 Redirect')
->groups(['api', 'account', 'session'])
->groups(['api', 'account'])
->label('error', __DIR__ . '/../../views/general/error.phtml')
->label('event', 'users.[userId].sessions.[sessionId].create')
->label('scope', 'public')
@@ -641,7 +641,7 @@ App::post('/v1/account/sessions/magic-url')
->label('sdk.response.model', Response::MODEL_TOKEN)
->label('abuse-limit', 10)
->label('abuse-key', 'url:{url},email:{param-email}')
->param('userId', '', new CustomId(), 'Unique Id. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
->param('userId', '', new CustomId(), 'Unique Id. Choose your own unique ID or pass the string `ID.unique()` to auto generate it. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
->param('email', '', new Email(), 'User email.')
->param('url', '', fn($clients) => new Host($clients), 'URL to redirect the user back to your app from the magic URL login. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', true, ['clients'])
->inject('request')
@@ -733,33 +733,11 @@ App::post('/v1/account/sessions/magic-url')
$url['query'] = Template::mergeQuery(((isset($url['query'])) ? $url['query'] : ''), ['userId' => $user->getId(), 'secret' => $loginSecret, 'expire' => $expire, 'project' => $project->getId()]);
$url = Template::unParseURL($url);
$from = $project->isEmpty() || $project->getId() === 'console' ? '' : \sprintf($locale->getText('emails.sender'), $project->getAttribute('name'));
$body = Template::fromFile(__DIR__ . '/../../config/locale/templates/email-base.tpl');
$subject = $locale->getText("emails.magicSession.subject");
$body
->setParam('{{subject}}', $subject)
->setParam('{{hello}}', $locale->getText("emails.magicSession.hello"))
->setParam('{{name}}', '')
->setParam('{{body}}', $locale->getText("emails.magicSession.body"))
->setParam('{{redirect}}', $url)
->setParam('{{footer}}', $locale->getText("emails.magicSession.footer"))
->setParam('{{thanks}}', $locale->getText("emails.magicSession.thanks"))
->setParam('{{signature}}', $locale->getText("emails.magicSession.signature"))
->setParam('{{project}}', $project->getAttribute('name'))
->setParam('{{direction}}', $locale->getText('settings.direction'))
->setParam('{{bg-body}}', '#f7f7f7')
->setParam('{{bg-content}}', '#ffffff')
->setParam('{{text-content}}', '#000000');
$body = $body->render();
$mails
->setSubject($subject)
->setBody($body)
->setFrom($from)
->setType(MAIL_TYPE_MAGIC_SESSION)
->setRecipient($user->getAttribute('email'))
->setUrl($url)
->setLocale($locale->default)
->trigger()
;
@@ -781,7 +759,7 @@ App::post('/v1/account/sessions/magic-url')
App::put('/v1/account/sessions/magic-url')
->desc('Create Magic URL session (confirmation)')
->groups(['api', 'account', 'session'])
->groups(['api', 'account'])
->label('scope', 'public')
->label('event', 'users.[userId].sessions.[sessionId].create')
->label('audits.event', 'session.update')
@@ -918,7 +896,7 @@ App::post('/v1/account/sessions/phone')
->label('sdk.response.model', Response::MODEL_TOKEN)
->label('abuse-limit', 10)
->label('abuse-key', 'url:{url},email:{param-email}')
->param('userId', '', new CustomId(), 'Unique Id. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
->param('userId', '', new CustomId(), 'Unique Id. Choose your own unique ID or pass the string `ID.unique()` to auto generate it. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
->param('phone', '', new Phone(), 'Phone number. Format this number with a leading \'+\' and a country code, e.g., +16175551212.')
->inject('request')
->inject('response')
@@ -1023,7 +1001,7 @@ App::post('/v1/account/sessions/phone')
App::put('/v1/account/sessions/phone')
->desc('Create Phone Session (confirmation)')
->groups(['api', 'account', 'session'])
->groups(['api', 'account'])
->label('scope', 'public')
->label('event', 'users.[userId].sessions.[sessionId].create')
->label('usage.metric', 'sessions.{scope}.requests.create')
@@ -1138,7 +1116,7 @@ App::put('/v1/account/sessions/phone')
App::post('/v1/account/sessions/anonymous')
->desc('Create Anonymous Session')
->groups(['api', 'account', 'auth', 'session'])
->groups(['api', 'account', 'auth'])
->label('event', 'users.[userId].sessions.[sessionId].create')
->label('scope', 'public')
->label('auth.type', 'anonymous')
@@ -2089,35 +2067,12 @@ App::post('/v1/account/recovery')
$url['query'] = Template::mergeQuery(((isset($url['query'])) ? $url['query'] : ''), ['userId' => $profile->getId(), 'secret' => $secret, 'expire' => $expire]);
$url = Template::unParseURL($url);
$projectName = $project->isEmpty() ? 'Console' : $project->getAttribute('name', '[APP-NAME]');
$from = $project->isEmpty() || $project->getId() === 'console' ? '' : \sprintf($locale->getText('emails.sender'), $projectName);
$body = Template::fromFile(__DIR__ . '/../../config/locale/templates/email-base.tpl');
$subject = $locale->getText("emails.recovery.subject");
$body
->setParam('{{subject}}', $subject)
->setParam('{{hello}}', $locale->getText("emails.recovery.hello"))
->setParam('{{name}}', $profile->getAttribute('name'))
->setParam('{{body}}', $locale->getText("emails.recovery.body"))
->setParam('{{redirect}}', $url)
->setParam('{{footer}}', $locale->getText("emails.recovery.footer"))
->setParam('{{thanks}}', $locale->getText("emails.recovery.thanks"))
->setParam('{{signature}}', $locale->getText("emails.recovery.signature"))
->setParam('{{project}}', $projectName)
->setParam('{{direction}}', $locale->getText('settings.direction'))
->setParam('{{bg-body}}', '#f7f7f7')
->setParam('{{bg-content}}', '#ffffff')
->setParam('{{text-content}}', '#000000');
$body = $body->render();
$mails
->setType(MAIL_TYPE_RECOVERY)
->setRecipient($profile->getAttribute('email', ''))
->setUrl($url)
->setLocale($locale->default)
->setName($profile->getAttribute('name'))
->setBody($body)
->setFrom($from)
->setSubject($subject)
->trigger();
;
@@ -2273,32 +2228,11 @@ App::post('/v1/account/verification')
$url['query'] = Template::mergeQuery(((isset($url['query'])) ? $url['query'] : ''), ['userId' => $user->getId(), 'secret' => $verificationSecret, 'expire' => $expire]);
$url = Template::unParseURL($url);
$projectName = $project->isEmpty() ? 'Console' : $project->getAttribute('name', '[APP-NAME]');
$from = $project->isEmpty() || $project->getId() === 'console' ? '' : \sprintf($locale->getText('emails.sender'), $projectName);
$body = Template::fromFile(__DIR__ . '/../../config/locale/templates/email-base.tpl');
$subject = $locale->getText("emails.verification.subject");
$body
->setParam('{{subject}}', $subject)
->setParam('{{hello}}', $locale->getText("emails.verification.hello"))
->setParam('{{name}}', $user->getAttribute('name'))
->setParam('{{body}}', $locale->getText("emails.verification.body"))
->setParam('{{redirect}}', $url)
->setParam('{{footer}}', $locale->getText("emails.verification.footer"))
->setParam('{{thanks}}', $locale->getText("emails.verification.thanks"))
->setParam('{{signature}}', $locale->getText("emails.verification.signature"))
->setParam('{{project}}', $projectName)
->setParam('{{direction}}', $locale->getText('settings.direction'))
->setParam('{{bg-body}}', '#f7f7f7')
->setParam('{{bg-content}}', '#ffffff')
->setParam('{{text-content}}', '#000000');
$body = $body->render();
$mails
->setSubject($subject)
->setBody($body)
->setFrom($from)
->setType(MAIL_TYPE_VERIFICATION)
->setRecipient($user->getAttribute('email'))
->setUrl($url)
->setLocale($locale->default)
->setName($user->getAttribute('name'))
->trigger()
;
+3 -3
View File
@@ -1,7 +1,7 @@
<?php
use Appwrite\Extend\Exception;
use Utopia\Validator\URL;
use Appwrite\Network\Validator\URL;
use Appwrite\URL\URL as URLParse;
use Appwrite\Utopia\Response;
use chillerlan\QRCode\QRCode;
@@ -242,8 +242,8 @@ App::get('/v1/avatars/favicon')
case 'jpeg':
$size = \explode('x', \strtolower($sizes));
$sizeWidth = (int) ($size[0] ?? 0);
$sizeHeight = (int) ($size[1] ?? 0);
$sizeWidth = (int) $size[0] ?? 0;
$sizeHeight = (int) $size[1] ?? 0;
if (($sizeWidth * $sizeHeight) >= $space) {
$space = $sizeWidth * $sizeHeight;
+73 -62
View File
@@ -743,7 +743,7 @@ App::post('/v1/databases/:databaseId/collections')
]));
$collection = $dbForProject->getDocument('database_' . $database->getInternalId(), $collectionId);
$dbForProject->createCollection('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId());
$dbForProject->createCollection('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), permissions: $permissions ?? [], documentSecurity: $documentSecurity);
} catch (DuplicateException) {
throw new Exception(Exception::COLLECTION_ALREADY_EXISTS);
} catch (LimitException) {
@@ -1001,6 +1001,7 @@ App::put('/v1/databases/:databaseId/collections/:collectionId')
->setAttribute('documentSecurity', $documentSecurity)
->setAttribute('enabled', $enabled)
->setAttribute('search', implode(' ', [$collectionId, $name])));
$dbForProject->updateCollection('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $permissions, $documentSecurity);
} catch (AuthorizationException) {
throw new Exception(Exception::USER_UNAUTHORIZED);
} catch (StructureException $exception) {
@@ -2743,8 +2744,13 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/documents')
if (empty($related)) {
continue;
}
if (!\is_array($related)) {
$related = [$related];
$isList = \is_array($related) && \array_values($related) === $related;
if ($isList) {
$relations = $related;
} else {
$relations = [$related];
}
$relatedCollectionId = $relationship->getAttribute('relatedCollection');
@@ -2752,7 +2758,15 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/documents')
fn() => $dbForProject->getDocument('database_' . $database->getInternalId(), $relatedCollectionId)
);
foreach ($related as $relation) {
foreach ($relations as &$relation) {
if (
\is_array($relation)
&& \array_values($relation) !== $relation
&& !isset($relation['$id'])
) {
$relation['$id'] = ID::unique();
$relation = new Document($relation);
}
if ($relation instanceof Document) {
$current = Authorization::skip(
fn() => $dbForProject->getDocument('database_' . $database->getInternalId() . '_collection_' . $relatedCollection->getInternalId(), $relation->getId())
@@ -2761,7 +2775,7 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/documents')
if ($current->isEmpty()) {
$type = Database::PERMISSION_CREATE;
if (!isset($relation['$id']) || $relation['$id'] === 'unique()') {
if (isset($relation['$id']) && $relation['$id'] === 'unique()') {
$relation['$id'] = ID::unique();
}
} else {
@@ -2774,6 +2788,12 @@ App::post('/v1/databases/:databaseId/collections/:collectionId/documents')
$checkPermissions($relatedCollection, $relation, $type);
}
}
if ($isList) {
$document->setAttribute($relationship->getAttribute('key'), \array_values($relations));
} else {
$document->setAttribute($relationship->getAttribute('key'), \reset($relations));
}
}
};
@@ -2866,12 +2886,19 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents')
$collection = Authorization::skip(fn() => $dbForProject->getDocument('database_' . $database->getInternalId(), $collectionId));
if ($collection->isEmpty() || !$collection->getAttribute('enabled')) {
if (!($mode === APP_MODE_ADMIN && Auth::isPrivilegedUser(Authorization::getRoles()))) {
throw new Exception(Exception::COLLECTION_NOT_FOUND);
if (!($mode === APP_MODE_ADMIN && Auth::isPrivilegedUser(Authorization::getRoles()))) {
if (!$collection->getAttribute('documentSecurity', false)) {
$validator = new Authorization(Database::PERMISSION_READ);
if (!$validator->isValid($collection->getRead())) {
$collection = new Document();
}
}
}
if ($collection->isEmpty() || !$collection->getAttribute('enabled')) {
throw new Exception(Exception::COLLECTION_NOT_FOUND);
}
// Validate queries
$queriesValidator = new Documents($collection->getAttribute('attributes'), $collection->getAttribute('indexes'));
$validQueries = $queriesValidator->isValid($queries);
@@ -2898,34 +2925,16 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents')
$filterQueries = Query::groupByType($queries)['filters'];
$documents = Authorization::skip(fn () => $dbForProject->find('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $queries));
$documentSecurity = $collection->getAttribute('documentSecurity', false);
$validator = new Authorization(Database::PERMISSION_READ);
$valid = $validator->isValid($collection->getRead());
if (!$valid) {
$total = $documentSecurity
? $dbForProject->count('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $filterQueries, APP_LIMIT_COUNT)
: 0;
} else {
$total = Authorization::skip(fn() => $dbForProject->count('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $filterQueries, APP_LIMIT_COUNT));
}
$documents = $dbForProject->find('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $queries);
$total = $dbForProject->count('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $filterQueries, APP_LIMIT_COUNT);
// Add $collectionId and $databaseId for all documents
$processDocument = function (Document $collection, Document $document) use (&$processDocument, $dbForProject, $database): bool {
$documentSecurity = $collection->getAttribute('documentSecurity', false);
$validator = new Authorization(Database::PERMISSION_READ);
$valid = $validator->isValid($collection->getRead());
if (!$documentSecurity && !$valid) {
return false;
}
$valid = $valid || $validator->isValid($document->getRead());
if ($documentSecurity && !$valid) {
if ($document->isEmpty()) {
return false;
}
$document->removeAttribute('$collection');
$document->setAttribute('$databaseId', $database->getId());
$document->setAttribute('$collectionId', $collection->getId());
@@ -2969,18 +2978,10 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents')
};
// The linter is forcing this indentation
foreach ($documents as $index => $document) {
if (!$processDocument($collection, $document)) {
unset($documents[$index]);
if ($valid) {
$total--;
}
}
foreach ($documents as $document) {
$processDocument($collection, $document);
}
$documents = \array_values($documents);
$response->dynamic(new Document([
'total' => $total,
'documents' => $documents,
@@ -3035,7 +3036,7 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents/:documen
$queries = Query::parseQueries($queries);
$document = Authorization::skip(fn () => $dbForProject->getDocument('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $documentId, $queries));
$document = $dbForProject->getDocument('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(), $documentId, $queries);
if ($document->isEmpty()) {
throw new Exception(Exception::DOCUMENT_NOT_FOUND);
@@ -3043,17 +3044,8 @@ App::get('/v1/databases/:databaseId/collections/:collectionId/documents/:documen
// Add $collectionId and $databaseId for all documents
$processDocument = function (Document $collection, Document $document) use (&$processDocument, $dbForProject, $database) {
$documentSecurity = $collection->getAttribute('documentSecurity', false);
$validator = new Authorization(Database::PERMISSION_READ);
$valid = $validator->isValid($collection->getRead());
if (!$documentSecurity && !$valid) {
throw new Exception(Exception::DOCUMENT_NOT_FOUND);
}
$valid = $valid || $validator->isValid($document->getRead());
if ($documentSecurity && !$valid) {
throw new Exception(Exception::DOCUMENT_NOT_FOUND);
if ($document->isEmpty()) {
return;
}
$document->setAttribute('$databaseId', $database->getId());
@@ -3287,7 +3279,7 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:docum
$permissions = $document->getPermissions() ?? [];
}
$data = \array_merge($document->getArrayCopy(), $data);
$data = \array_merge($document->getArrayCopy(), $data); // Merge existing data with new data
$data['$collection'] = $collection->getId(); // Make sure user doesn't switch collectionID
$data['$createdAt'] = $document->getCreatedAt(); // Make sure user doesn't switch createdAt
$data['$id'] = $document->getId(); // Make sure user doesn't switch document unique ID
@@ -3321,8 +3313,13 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:docum
if (empty($related)) {
continue;
}
if (!\is_array($related)) {
$related = [$related];
$isList = \is_array($related) && \array_values($related) === $related;
if ($isList) {
$relations = $related;
} else {
$relations = [$related];
}
$relatedCollectionId = $relationship->getAttribute('relatedCollection');
@@ -3330,7 +3327,15 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:docum
fn() => $dbForProject->getDocument('database_' . $database->getInternalId(), $relatedCollectionId)
);
foreach ($related as $relation) {
foreach ($relations as &$relation) {
if (
\is_array($relation)
&& \array_values($relation) !== $relation
&& !isset($relation['$id'])
) {
$relation['$id'] = ID::unique();
$relation = new Document($relation);
}
if ($relation instanceof Document) {
$oldDocument = Authorization::skip(fn() => $dbForProject->getDocument(
'database_' . $database->getInternalId() . '_collection_' . $relatedCollection->getInternalId(),
@@ -3340,7 +3345,7 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:docum
if ($oldDocument->isEmpty()) {
$type = Database::PERMISSION_CREATE;
if (!isset($relation['$id']) || $relation['$id'] === 'unique()') {
if (isset($relation['$id']) && $relation['$id'] === 'unique()') {
$relation['$id'] = ID::unique();
}
} else {
@@ -3353,20 +3358,26 @@ App::patch('/v1/databases/:databaseId/collections/:collectionId/documents/:docum
$checkPermissions($relatedCollection, $relation, $oldDocument, $type);
}
}
if ($isList) {
$document->setAttribute($relationship->getAttribute('key'), \array_values($relations));
} else {
$document->setAttribute($relationship->getAttribute('key'), \reset($relations));
}
}
};
$checkPermissions($collection, $newDocument, $document, Database::PERMISSION_UPDATE);
try {
$document = Authorization::skip(fn() => $dbForProject->withRequestTimestamp(
$document = $dbForProject->withRequestTimestamp(
$requestTimestamp,
fn () => $dbForProject->updateDocument(
'database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(),
$document->getId(),
$newDocument
)
));
);
} catch (AuthorizationException) {
throw new Exception(Exception::USER_UNAUTHORIZED);
} catch (DuplicateException) {
@@ -3591,7 +3602,7 @@ App::delete('/v1/databases/:databaseId/collections/:collectionId/documents/:docu
App::get('/v1/databases/usage')
->desc('Get usage stats for the database')
->groups(['api', 'database'])
->groups(['api', 'database', 'usage'])
->label('scope', 'collections.read')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'databases')
@@ -3709,7 +3720,7 @@ App::get('/v1/databases/usage')
App::get('/v1/databases/:databaseId/usage')
->desc('Get usage stats for the database')
->groups(['api', 'database'])
->groups(['api', 'database', 'usage'])
->label('scope', 'collections.read')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'databases')
@@ -3819,7 +3830,7 @@ App::get('/v1/databases/:databaseId/usage')
App::get('/v1/databases/:databaseId/collections/:collectionId/usage')
->alias('/v1/database/:collectionId/usage', ['databaseId' => 'default'])
->desc('Get usage stats for a collection')
->groups(['api', 'database'])
->groups(['api', 'database', 'usage'])
->label('scope', 'collections.read')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'databases')
+2 -2
View File
@@ -211,7 +211,7 @@ App::get('/v1/functions/:functionId')
App::get('/v1/functions/:functionId/usage')
->desc('Get Function Usage')
->groups(['api', 'functions'])
->groups(['api', 'functions', 'usage'])
->label('scope', 'functions.read')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'functions')
@@ -321,7 +321,7 @@ App::get('/v1/functions/:functionId/usage')
App::get('/v1/functions/usage')
->desc('Get Functions Usage')
->groups(['api', 'functions'])
->groups(['api', 'functions', 'usage'])
->label('scope', 'functions.read')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'functions')
+18 -5
View File
@@ -169,9 +169,22 @@ App::post('/v1/projects')
$dbForProject->createCollection($key, $attributes, $indexes);
}
$response
->setStatusCode(Response::STATUS_CODE_CREATED)
->dynamic($project, Response::MODEL_PROJECT);
if ($dbForProject->exists($dbForProject->getDefaultDatabase(), 'videos_profiles')) {
foreach (Config::getParam('videos-profiles', []) as $profile) {
Authorization::skip(function () use ($project, $profile, $dbForProject) {
return $dbForProject->createDocument('videos_profiles', new Document([
'name' => $profile['name'],
'videoBitRate' => $profile['videoBitRate'],
'audioBitRate' => $profile['audioBitRate'],
'width' => $profile['width'],
'height' => $profile['height']
]));
});
}
}
$response->setStatusCode(Response::STATUS_CODE_CREATED);
$response->dynamic($project, Response::MODEL_PROJECT);
});
App::get('/v1/projects')
@@ -245,7 +258,7 @@ App::get('/v1/projects/:projectId')
App::get('/v1/projects/:projectId/usage')
->desc('Get usage stats for a project')
->groups(['api', 'projects'])
->groups(['api', 'projects', 'usage'])
->label('scope', 'projects.read')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'projects')
@@ -1237,7 +1250,7 @@ App::get('/v1/projects/:projectId/platforms')
}
$platforms = $dbForConsole->find('platforms', [
Query::equal('projectId', [$project->getId()]),
Query::equal('projectInternalId', [$project->getInternalId()]),
Query::limit(5000),
]);
+6 -3
View File
@@ -128,7 +128,7 @@ App::post('/v1/storage/buckets')
$bucket = $dbForProject->getDocument('buckets', $bucketId);
$dbForProject->createCollection('bucket_' . $bucket->getInternalId(), $attributes, $indexes);
$dbForProject->createCollection('bucket_' . $bucket->getInternalId(), $attributes, $indexes, permissions: $permissions ?? [], documentSecurity: $fileSecurity);
} catch (Duplicate) {
throw new Exception(Exception::STORAGE_BUCKET_ALREADY_EXISTS);
}
@@ -274,6 +274,7 @@ App::put('/v1/storage/buckets/:bucketId')
->setAttribute('encryption', $encryption)
->setAttribute('compression', $compression)
->setAttribute('antivirus', $antivirus));
$dbForProject->updateCollection('bucket_' . $bucket->getInternalId(), $permissions, $fileSecurity);
$events
->setParam('bucketId', $bucket->getId())
@@ -567,6 +568,7 @@ App::post('/v1/storage/buckets/:bucketId/files')
'$id' => $fileId,
'$permissions' => $permissions,
'bucketId' => $bucket->getId(),
'bucketInternalId' => $bucket->getInternalId(),
'name' => $fileName,
'path' => $path,
'signature' => $fileHash,
@@ -616,6 +618,7 @@ App::post('/v1/storage/buckets/:bucketId/files')
'$id' => ID::custom($fileId),
'$permissions' => $permissions,
'bucketId' => $bucket->getId(),
'bucketInternalId' => $bucket->getInternalId(),
'name' => $fileName,
'path' => $path,
'signature' => '',
@@ -1447,7 +1450,7 @@ App::delete('/v1/storage/buckets/:bucketId/files/:fileId')
App::get('/v1/storage/usage')
->desc('Get usage stats for storage')
->groups(['api', 'storage'])
->groups(['api', 'storage', 'usage'])
->label('scope', 'files.read')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'storage')
@@ -1557,7 +1560,7 @@ App::get('/v1/storage/usage')
App::get('/v1/storage/:bucketId/usage')
->desc('Get usage stats for a storage bucket')
->groups(['api', 'storage'])
->groups(['api', 'storage', 'usage'])
->label('scope', 'files.read')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'storage')
+1 -1
View File
@@ -1129,7 +1129,7 @@ App::delete('/v1/users/:userId')
App::get('/v1/users/usage')
->desc('Get usage stats for the users API')
->groups(['api', 'users'])
->groups(['api', 'users', 'usage'])
->label('scope', 'users.read')
->label('sdk.auth', [APP_AUTH_TYPE_ADMIN])
->label('sdk.namespace', 'users')
File diff suppressed because it is too large Load Diff
-1
View File
@@ -262,7 +262,6 @@ App::init()
$role = ($user->isEmpty())
? Role::guests()->toString()
: Role::users()->toString();
// Add user roles
$memberships = $user->find('teamId', $project->getAttribute('teamId', null), 'memberships');
+1 -1
View File
@@ -4,7 +4,7 @@ global $utopia, $request, $response;
use Appwrite\Extend\Exception;
use Utopia\Database\Document;
use Utopia\Validator\Host;
use Appwrite\Network\Validator\Host;
use Appwrite\Utopia\Request;
use Appwrite\Utopia\Response;
use Appwrite\Utopia\Response\Model;
+9 -3
View File
@@ -215,7 +215,6 @@ App::init()
if ($type === 'bucket') {
$bucketId = $parts[1] ?? null;
$bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
if ($bucket->isEmpty() || (!$bucket->getAttribute('enabled') && $mode !== APP_MODE_ADMIN)) {
@@ -242,14 +241,12 @@ App::init()
throw new Exception(Exception::STORAGE_FILE_NOT_FOUND);
}
}
$response
->addHeader('Expires', \date('D, d M Y H:i:s', \time() + $timestamp) . ' GMT')
->addHeader('X-Appwrite-Cache', 'hit')
->setContentType($data['contentType'])
->send(base64_decode($data['payload']))
;
$route->setIsActive(false);
} else {
$response->addHeader('X-Appwrite-Cache', 'miss');
@@ -499,6 +496,7 @@ App::shutdown()
$cacheLog = $dbForProject->getDocument('cache', $key);
$accessedAt = $cacheLog->getAttribute('accessedAt', '');
$now = DateTime::now();
if ($cacheLog->isEmpty()) {
Authorization::skip(fn () => $dbForProject->createDocument('cache', new Document([
'$id' => $key,
@@ -552,3 +550,11 @@ App::shutdown()
->submit();
}
});
App::init()
->groups(['usage'])
->action(function () {
if (App::getEnv('_APP_USAGE_STATS', 'enabled') !== 'enabled') {
throw new Exception(Exception::GENERAL_USAGE_DISABLED);
}
});
-6
View File
@@ -115,12 +115,6 @@ $http->on('start', function (Server $http) use ($payloadSize, $register) {
if (!$dbForConsole->getCollection($key)->isEmpty()) {
continue;
}
/**
* Skip to prevent 0.16 migration issues.
*/
if (in_array($key, ['cache', 'variables']) && $dbForConsole->exists(App::getEnv('_APP_DB_SCHEMA', 'appwrite'), 'bucket_1')) {
continue;
}
Console::success('[Setup] - Creating collection: ' . $collection['$id'] . '...');
+33 -26
View File
@@ -101,7 +101,7 @@ const APP_LIMIT_LIST_DEFAULT = 25; // Default maximum number of items to return
const APP_KEY_ACCCESS = 24 * 60 * 60; // 24 hours
const APP_CACHE_UPDATE = 24 * 60 * 60; // 24 hours
const APP_CACHE_BUSTER = 503;
const APP_VERSION_STABLE = '1.3.1';
const APP_VERSION_STABLE = '1.3.4';
const APP_DATABASE_ATTRIBUTE_EMAIL = 'email';
const APP_DATABASE_ATTRIBUTE_ENUM = 'enum';
const APP_DATABASE_ATTRIBUTE_IP = 'ip';
@@ -114,6 +114,7 @@ const APP_STORAGE_UPLOADS = '/storage/uploads';
const APP_STORAGE_FUNCTIONS = '/storage/functions';
const APP_STORAGE_BUILDS = '/storage/builds';
const APP_STORAGE_CACHE = '/storage/cache';
const APP_STORAGE_VIDEOS = '/storage/videos';
const APP_STORAGE_CERTIFICATES = '/storage/certificates';
const APP_STORAGE_CONFIG = '/storage/config';
const APP_STORAGE_READ_BUFFER = 20 * (1000 * 1000); //20MB other names `APP_STORAGE_MEMORY_LIMIT`, `APP_STORAGE_MEMORY_BUFFER`, `APP_STORAGE_READ_LIMIT`, `APP_STORAGE_BUFFER_LIMIT`
@@ -162,6 +163,7 @@ const DELETE_TYPE_CACHE_BY_RESOURCE = 'cacheByResource';
const COMPRESSION_TYPE_NONE = 'none';
const COMPRESSION_TYPE_GZIP = 'gzip';
const COMPRESSION_TYPE_ZSTD = 'zstd';
const DELETE_TYPE_VIDEOS = 'videos';
// Mail Types
const MAIL_TYPE_VERIFICATION = 'verification';
const MAIL_TYPE_MAGIC_SESSION = 'magicSession';
@@ -175,7 +177,7 @@ const APP_AUTH_TYPE_KEY = 'Key';
const APP_AUTH_TYPE_ADMIN = 'Admin';
// Response related
const MAX_OUTPUT_CHUNK_SIZE = 2 * 1024 * 1024; // 2MB
const TMP_HOST = 'http://127.0.0.1/';
$register = new Registry();
App::setMode(App::getEnv('_APP_ENV', App::MODE_TYPE_PRODUCTION));
@@ -209,6 +211,7 @@ Config::load('storage-logos', __DIR__ . '/config/storage/logos.php');
Config::load('storage-mimes', __DIR__ . '/config/storage/mimes.php');
Config::load('storage-inputs', __DIR__ . '/config/storage/inputs.php');
Config::load('storage-outputs', __DIR__ . '/config/storage/outputs.php');
Config::load('videos-profiles', __DIR__ . '/config/videos-profiles.php');
$user = App::getEnv('_APP_REDIS_USER', '');
$pass = App::getEnv('_APP_REDIS_PASS', '');
@@ -514,7 +517,7 @@ $register->set('logger', function () {
return new Logger($adapter);
});
$register->set('dbPool', function () {
// Register DB connection
// Register DB connection
$dbHost = App::getEnv('_APP_DB_HOST', '');
$dbPort = App::getEnv('_APP_DB_PORT', '');
$dbUser = App::getEnv('_APP_DB_USER', '');
@@ -523,20 +526,20 @@ $register->set('dbPool', function () {
$pool = new PDOPool(
(new PDOConfig())
->withHost($dbHost)
->withPort($dbPort)
->withDbName($dbScheme)
->withCharset('utf8mb4')
->withUsername($dbUser)
->withPassword($dbPass)
->withOptions([
PDO::ATTR_ERRMODE => App::isDevelopment() ? PDO::ERRMODE_WARNING : PDO::ERRMODE_SILENT, // If in production mode, warnings are not displayed
PDO::ATTR_TIMEOUT => 3, // Seconds
PDO::ATTR_PERSISTENT => true,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => true,
PDO::ATTR_STRINGIFY_FETCHES => true,
]),
->withHost($dbHost)
->withPort($dbPort)
->withDbName($dbScheme)
->withCharset('utf8mb4')
->withUsername($dbUser)
->withPassword($dbPass)
->withOptions([
PDO::ATTR_ERRMODE => App::isDevelopment() ? PDO::ERRMODE_WARNING : PDO::ERRMODE_SILENT, // If in production mode, warnings are not displayed
PDO::ATTR_TIMEOUT => 3, // Seconds
PDO::ATTR_PERSISTENT => true,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => true,
PDO::ATTR_STRINGIFY_FETCHES => true,
]),
64
);
@@ -555,17 +558,17 @@ $register->set('redisPool', function () {
$pool = new RedisPool(
(new RedisConfig())
->withHost($redisHost)
->withPort($redisPort)
->withAuth($redisAuth)
->withDbIndex(0),
->withHost($redisHost)
->withPort($redisPort)
->withAuth($redisAuth)
->withDbIndex(0),
64
);
return $pool;
});
$register->set('influxdb', function () {
// Register DB connection
// Register DB connection
$host = App::getEnv('_APP_INFLUXDB_HOST', '');
$port = App::getEnv('_APP_INFLUXDB_PORT', '');
@@ -579,7 +582,7 @@ $register->set('influxdb', function () {
return $client;
});
$register->set('statsd', function () {
// Register DB connection
// Register DB connection
$host = App::getEnv('_APP_STATSD_HOST', 'telegraf');
$port = App::getEnv('_APP_STATSD_PORT', 8125);
@@ -626,7 +629,7 @@ $register->set('passwordsDictionary', function () {
return $content;
});
$register->set('db', function () {
// This is usually for our workers or CLI commands scope
// This is usually for our workers or CLI commands scope
$dbHost = App::getEnv('_APP_DB_HOST', '');
$dbPort = App::getEnv('_APP_DB_PORT', '');
$dbUser = App::getEnv('_APP_DB_USER', '');
@@ -645,7 +648,7 @@ $register->set('db', function () {
return $pdo;
});
$register->set('cache', function () {
// This is usually for our workers or CLI commands scope
// This is usually for our workers or CLI commands scope
$redis = new Redis();
$redis->pconnect(App::getEnv('_APP_REDIS_HOST', ''), App::getEnv('_APP_REDIS_PORT', ''));
$redis->setOption(Redis::OPT_READ_TIMEOUT, -1);
@@ -904,6 +907,7 @@ App::setResource('project', function ($dbForConsole, $request, $console) {
/** @var Utopia\Database\Document $console */
$projectId = $request->getParam('project', $request->getHeader('x-appwrite-project', 'console'));
$projectId = '645905a0594ef29a8680';
if ($projectId === 'console') {
return $console;
@@ -950,7 +954,6 @@ App::setResource('console', function () {
App::setResource('dbForProject', function ($db, $cache, Document $project) {
$cache = new Cache(new RedisCache($cache));
$database = new Database(new MariaDB($db), $cache);
$database->setDefaultDatabase(App::getEnv('_APP_DB_SCHEMA', 'appwrite'));
$database->setNamespace("_{$project->getInternalId()}");
@@ -977,6 +980,10 @@ App::setResource('deviceFiles', function ($project) {
return getDevice(APP_STORAGE_UPLOADS . '/app-' . $project->getId());
}, ['project']);
App::setResource('deviceVideos', function ($project) {
return getDevice(APP_STORAGE_VIDEOS . '/app-' . $project->getId());
}, ['project']);
App::setResource('deviceFunctions', function ($project) {
return getDevice(APP_STORAGE_FUNCTIONS . '/app-' . $project->getId());
}, ['project']);
+5 -2
View File
@@ -34,8 +34,11 @@ foreach (
realpath(__DIR__ . '/../vendor/matomo'),
realpath(__DIR__ . '/../vendor/symfony'),
realpath(__DIR__ . '/../vendor/mongodb'),
realpath(__DIR__ . '/../vendor/utopia-php/websocket'), // TODO: remove workerman autoload
realpath(__DIR__ . '/../vendor/utopia-php/cache'), // TODO: remove memcache autoload
realpath(__DIR__ . '/../vendor/utopia-php/websocket'),
realpath(__DIR__ . '/../vendor/utopia-php/cache'),
realpath(__DIR__ . '/../vendor/alchemy'),
realpath(__DIR__ . '/../vendor/php-ffmpeg'),
realpath(__DIR__ . '/../vendor/utopia-php/storage'),
] as $key => $value
) {
if ($value !== false) {
+5 -5
View File
@@ -95,9 +95,9 @@ $cli
if (is_null($value)) {
continue;
}
foreach ($vars as &$var) {
foreach ($vars as $i => $var) {
if ($var['name'] === $key) {
$var['default'] = $value;
$vars[$i]['default'] = $value;
}
}
}
@@ -114,9 +114,9 @@ $cli
if (is_null($value)) {
continue;
}
foreach ($vars as &$var) {
foreach ($vars as $i => $var) {
if ($var['name'] === $key) {
$var['default'] = $value;
$vars[$i]['default'] = $value;
}
}
}
@@ -146,7 +146,7 @@ $cli
$input = [];
foreach ($vars as $key => $var) {
foreach ($vars as $var) {
if (!empty($var['filter']) && ($interactive !== 'Y' || !Console::isInteractive())) {
if ($data && $var['default'] !== null) {
$input[$var['name']] = $var['default'];
+2 -2
View File
@@ -239,8 +239,8 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
cd ' . $target . ' && \
git init --initial-branch=' . $gitBranch . ' && \
git remote add origin ' . $gitUrl . ' && \
git fetch && \
git pull ' . $gitUrl . ' && \
git fetch origin ' . $gitBranch . ' && \
git pull origin ' . $gitBranch . ' && \
rm -rf ' . $target . '/* && \
cp -r ' . $result . '/* ' . $target . '/ && \
git add . && \
+39
View File
@@ -0,0 +1,39 @@
<?php
$mpd = $this->getParam('mpd', []);
echo '<?xml version="1.0" encoding="utf-8"?>'; ?>
<MPD xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="urn:mpeg:dash:schema:mpd:2011" xmlns:xlink="http://www.w3.org/1999/xlink"
xsi:schemaLocation="urn:mpeg:DASH:schema:MPD:2011 http://standards.iso.org/ittf/PubliclyAvailableStandards/MPEG-DASH_schema_files/DASH-MPD.xsd"
profiles="<?php if (isset($mpd['profiles'])): echo ($mpd['profiles']); endif;?>" type="<?php if (isset($mpd['type'])): echo ($mpd['type']); endif;?>" mediaPresentationDuration="<?php if (isset($mpd['maxSegmentDuration'])): echo ($mpd['mediaPresentationDuration']); endif;?>" maxSegmentDuration="<?php if (isset($mpd['maxSegmentDuration'])): echo ($mpd['maxSegmentDuration']); endif;?>" minBufferTime="<?php if (isset($mpd['minBufferTime'])): echo ($mpd['minBufferTime']); endif;?>">
<ProgramInformation></ProgramInformation>
<ServiceDescription id="0"></ServiceDescription>
<Period id="0" start="PT0.0S">
<?php foreach ($this->getParam('renditions', []) as $adaptation):
$representation = $adaptation['representation'];
?>
<AdaptationSet <?php if (isset($adaptation['id'])):?> id="<?php echo ($adaptation['id']); endif;?>"<?php if (isset($adaptation['attributes']['contentType'])): ?> contentType="<?php echo $adaptation['attributes']['contentType']; ?>"<?php endif;?><?php if (isset($adaptation['attributes']['startWithSAP'])): ?> startWithSAP="<?php echo $adaptation['attributes']['startWithSAP']; ?>"<?php endif;?><?php if (isset($adaptation['attributes']['segmentAlignment'])): ?> segmentAlignment="<?php echo $adaptation['attributes']['segmentAlignment']; ?>"<?php endif;?><?php if (isset($adaptation['attributes']['bitstreamSwitching'])): ?> bitstreamSwitching="<?php echo $adaptation['attributes']['bitstreamSwitching']; ?>"<?php endif;?><?php if (isset($adaptation['attributes']['frameRate'])): ?> frameRate="<?php echo $adaptation['attributes']['frameRate']; ?>"<?php endif;?><?php if (isset($adaptation['attributes']['maxWidth'])): ?> maxWidth="<?php echo $adaptation['attributes']['maxWidth']; ?>"<?php endif;?><?php if (isset($adaptation['attributes']['par'])): ?> par="<?php echo $adaptation['attributes']['par']; ?>"<?php endif;?><?php if (isset($adaptation['attributes']['lang'])): ?> lang="<?php echo $adaptation['attributes']['lang']; ?>"<?php endif;?>>
<Representation <?php if (isset($representation['attributes']['id'])):?> id="<?php echo $representation['attributes']['id']; ?>"<?php endif;?><?php if (isset($representation['attributes']['mimeType'])):?> mimeType="<?php echo $representation['attributes']['mimeType']; ?>"<?php endif;?><?php if (isset($representation['attributes']['codecs'])):?> codecs="<?php echo $representation['attributes']['codecs']; ?>"<?php endif;?><?php if (isset($representation['attributes']['bandwidth'])):?> bandwidth="<?php echo $representation['attributes']['bandwidth']; ?>"<?php endif;?><?php if (isset($representation['attributes']['width'])):?> width="<?php echo $representation['attributes']['width']; ?>"<?php endif;?><?php if (isset($representation['attributes']['height'])):?> height="<?php echo $representation['attributes']['height']; ?>"<?php endif;?><?php if (isset($representation['attributes']['sar'])):?> sar="<?php echo $representation['attributes']['sar']; ?>"<?php endif;?><?php if (isset($representation['attributes']['audioSamplingRate'])):?> audioSamplingRate="<?php echo $representation['attributes']['audioSamplingRate']; ?>"<?php endif;?>>
<BaseURL><?php if (isset($adaptation['baseUrl'])): echo $this->escape($adaptation['baseUrl']); endif;?></BaseURL>
<?php if (isset($adaptation['attributes']['contentType']) && $adaptation['attributes']['contentType'] === 'audio' ):?>
<AudioChannelConfiguration schemeIdUri="urn:mpeg:dash:23003:3:audio_channel_configuration:2011" value="2" />
<?php endif;?>
<SegmentList<?php if (isset($representation['segmentList']['attributes']['timescale'])): ?> timescale="<?php echo $representation['segmentList']['attributes']['timescale']; ?>"<?php endif;?><?php if (isset($representation['segmentList']['attributes']['duration'])): ?> duration="<?php echo $representation['segmentList']['attributes']['duration']; ?>"<?php endif;?><?php if (isset($representation['segmentList']['attributes']['startNumber'])): ?> startNumber="<?php echo $representation['segmentList']['attributes']['startNumber']; ?>"<?php endif;?>>
<Initialization sourceURL="<?php if (isset($representation['segmentList']['init'])):?><?php echo $representation['segmentList']['init']; ?><?php endif;?>"/>
<?php if (isset($representation['segmentList']['media'])):?>
<?php foreach ($representation['segmentList']['media'] as $segment): ?>
<SegmentURL media="<?php if (isset($segment)):?><?php echo $segment; ?><?php endif;?>"/>
<?php endforeach; ?>
<?php endif;?>
</SegmentList>
</Representation>
</AdaptationSet>
<?php endforeach; ?>
<?php foreach ($this->getParam('subtitles', []) as $subtitle):?>
<AdaptationSet id="<?php if (isset($subtitle['id'])): echo $subtitle['id']; endif;?>" lang="<?php if (isset($subtitle['name'])): echo $this->escape($subtitle['name']); endif;?>" mimeType="text/vtt">
<Role schemeIdUri="urn:mpeg:dash:role:2011" value="subtitle" />
<Representation>
<BaseURL><?php if (isset($subtitle['baseUrl'])): echo $this->escape($subtitle['baseUrl']); endif;?></BaseURL>
</Representation>
</AdaptationSet>
<?php endforeach; ?>
</Period>
</MPD>
+12
View File
@@ -0,0 +1,12 @@
#EXTM3U
#EXT-X-VERSION:4
<?php foreach ($this->getParam('audios', []) as $audio):?>
#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="group_audio"<?php if($audio['name'] !== null):?>,NAME="<?php echo $audio['name'];?>"<?php endif;?>,AUTOSELECT=YES<?php if($audio['language'] !== null):?>,LANGUAGE="<?php echo $audio['language'];?>"<?php endif;?><?php if($audio['uri'] !== null):?>,URI="<?php echo $audio['uri'];?>"<?php endif;?><?php echo PHP_EOL?>
<?php endforeach;?><?php echo PHP_EOL?>
<?php foreach ($this->getParam('subtitles', []) as $subtitle):?>
#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="subs",NAME="<?php echo $this->escape($subtitle['name']); ?>",DEFAULT=<?php echo $subtitle['default']; ?>,AUTOSELECT=YES,FORCED=NO,LANGUAGE="<?php echo $this->escape($subtitle['code']); ?>",URI="<?php echo $subtitle['uri']; ?>"<?php echo PHP_EOL?>
<?php endforeach;?><?php echo PHP_EOL?>
<?php foreach ($this->getParam('renditions', []) as $rendition):?>
#EXT-X-STREAM-INF:BANDWIDTH="<?php echo $rendition['bandwidth']; ?>",RESOLUTION="<?php echo $rendition['resolution']; ?>",NAME="<?php echo $rendition['name']; ?>"<?php if($rendition['codecs'] !== null):?>,CODECS="<?php echo $rendition['codecs']; ?>"<?php endif;?><?php if($rendition['subs'] !== null):?>,SUBTITLES="<?php echo $rendition['subs'];?>"<?php endif;?><?php if($rendition['audio'] !== null):?>,AUDIO="<?php echo $rendition['audio'];?>"<?php endif;?> <?php echo PHP_EOL?>
<?php echo $rendition['uri'];?><?php echo PHP_EOL?>
<?php endforeach;?>
+10
View File
@@ -0,0 +1,10 @@
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-MEDIA-SEQUENCE:1
#EXT-X-PLAYLIST-TYPE:VOD
#EXT-X-TARGETDURATION:<?php echo $this->getParam('targetDuration', 0)?><?php echo PHP_EOL?>
#EXT-X-MEDIA-SEQUENCE:0
<?php foreach ($this->getParam('segments', []) as $segment): ?>
#EXTINF:<?php echo $segment['duration']; ?><?php echo PHP_EOL?>
<?php echo $segment['url']; ?><?php echo PHP_EOL?>
<?php endforeach; ?>
+10
View File
@@ -0,0 +1,10 @@
#EXTM3U
#EXT-X-VERSION:4
#EXT-X-ALLOW-CACHE:YES
#EXT-X-TARGETDURATION:<?php echo $this->getParam('targetDuration', 0)?><?php echo PHP_EOL?>
#EXT-X-MEDIA-SEQUENCE:0
<?php foreach ($this->getParam('segments', []) as $segment): ?>
#EXTINF:<?php echo $segment['duration']; ?>,<?php echo PHP_EOL?>
<?php echo $segment['url']; ?><?php echo PHP_EOL?>
<?php endforeach; ?>
#EXT-X-ENDLIST
+77 -5
View File
@@ -9,7 +9,6 @@ use Utopia\Database\Document;
use Utopia\Database\Query;
use Appwrite\Resque\Worker;
use Executor\Executor;
use Utopia\Storage\Device\Local;
use Utopia\Abuse\Abuse;
use Utopia\Abuse\Adapters\TimeLimit;
use Utopia\CLI\Console;
@@ -65,6 +64,9 @@ class DeletesV1 extends Worker
case DELETE_TYPE_BUCKETS:
$this->deleteBucket($document, $project->getId());
break;
case DELETE_TYPE_VIDEOS:
$this->deleteVideo($document, $project->getId());
break;
default:
if (\str_starts_with($document->getCollection(), 'database_')) {
$this->deleteCollection($document, $project->getId());
@@ -291,12 +293,13 @@ class DeletesV1 extends Worker
protected function deleteProject(Document $document): void
{
$projectId = $document->getId();
$projectInternalId = $document->getInternalId();
// Delete project domains and certificates
// Delete project certificates
$dbForConsole = $this->getConsoleDB();
$domains = $dbForConsole->find('domains', [
Query::equal('projectInternalId', [$document->getInternalId()])
Query::equal('projectInternalId', [$projectInternalId])
]);
foreach ($domains as $domain) {
@@ -318,6 +321,26 @@ class DeletesV1 extends Worker
}
}
// Delete Platforms
$this->deleteByGroup('platforms', [
Query::equal('projectInternalId', [$projectInternalId])
], $dbForConsole);
// Delete Domains
$this->deleteByGroup('domains', [
Query::equal('projectInternalId', [$projectInternalId])
], $dbForConsole);
// Delete Keys
$this->deleteByGroup('keys', [
Query::equal('projectInternalId', [$projectInternalId])
], $dbForConsole);
// Delete Webhooks
$this->deleteByGroup('webhooks', [
Query::equal('projectInternalId', [$projectInternalId])
], $dbForConsole);
// Delete metadata tables
try {
$dbForProject->deleteCollection('_metadata');
@@ -756,9 +779,58 @@ class DeletesV1 extends Worker
{
$dbForProject = $this->getProjectDB($projectId);
$dbForProject->deleteCollection('bucket_' . $document->getInternalId());
$device = $this->getFilesDevice($projectId);
$device->deletePath($document->getId());
}
/**
* @param Document $document database document
* @param string $projectId
*/
protected function deleteVideo(Document $document, string $projectId): void
{
$videoId = $document->getId();
$dbForProject = $this->getProjectDB($projectId);
$previews = $dbForProject->find('videos_previews', [
new Query('videoId', Query::TYPE_EQUAL, [$videoId])
]);
foreach ($previews as $preview) {
$dbForProject->deleteDocument('videos_previews', $preview->getId());
}
$renditions = $dbForProject->find('videos_renditions', [
new Query('videoId', Query::TYPE_EQUAL, [$videoId])
]);
foreach ($renditions as $rendition) {
$this->deleteByGroup('videos_renditions_segments', [
new Query('renditionId', Query::TYPE_EQUAL, [$rendition->getId()])
], $dbForProject);
$dbForProject->deleteDocument('videos_renditions', $rendition->getId());
}
$subtitles = $dbForProject->find('videos_subtitles', [
new Query('videoId', Query::TYPE_EQUAL, [$videoId])
]);
foreach ($subtitles as $subtitle) {
$segments = $dbForProject->find('videos_subtitles_segments', [
new Query('subtitleId', Query::TYPE_EQUAL, [$subtitle->getId()])
]);
foreach ($segments as $segment) {
$dbForProject->deleteDocument('videos_subtitles_segments', $segment->getId());
}
$dbForProject->deleteDocument('videos_subtitles', $subtitle->getId());
}
$videosDevice = $this->getVideoDevice($projectId);
$videosPath = $videosDevice->getPath($videoId);
if ($videosDevice->deletePath($videosPath)) {
Console::success('Deleted video directory: ' . $videosPath);
} else {
Console::error('Failed to delete video directory: ' . $videosPath);
}
}
}
+15 -11
View File
@@ -82,18 +82,22 @@ class FunctionsV1 extends Worker
Console::success('Iterating function: ' . $function->getAttribute('name'));
$this->execute(
project: $project,
function: $function,
dbForProject: $database,
trigger: 'event',
// Pass first, most verbose event pattern
event: $events[0],
eventData: $payload,
user: $user
);
try {
$this->execute(
project: $project,
function: $function,
dbForProject: $database,
trigger: 'event',
// Pass first, most verbose event pattern
event: $events[0],
eventData: $payload,
user: $user
);
Console::success('Triggered function: ' . $events[0]);
Console::success('Triggered function: ' . $events[0]);
} catch (\Throwable $th) {
Console::error("Failed to execute " . $function->getId() . " with error: " . $th->getMessage());
}
}
}
+863
View File
@@ -0,0 +1,863 @@
<?php
use Appwrite\Event\Delete;
use Appwrite\Event\Event;
use Appwrite\Extend\Exception;
use Appwrite\Messaging\Adapter\Realtime;
use Appwrite\OpenSSL\OpenSSL;
use Appwrite\Resque\Worker;
use Streaming\FFMpeg;
use FFMpeg\FFProbe;
use Streaming\Format\StreamFormat;
use Streaming\HLSSubtitle;
use Streaming\Media;
use Streaming\Representation;
use Mhor\MediaInfo\MediaInfo;
use Utopia\App;
use Utopia\CLI\Console;
use Utopia\Database\Database;
use Utopia\Database\DateTime;
use Utopia\Database\Document;
use Utopia\Database\Exception\Authorization;
use Utopia\Database\Exception\Structure;
use Utopia\Database\Query;
use Utopia\Storage\Compression\Algorithms\GZIP;
use Utopia\Storage\Compression\Algorithms\Zstd;
use Captioning\Format\SubripFile;
use Utopia\Storage\Device\Local;
require_once __DIR__ . '/../init.php';
Console::title('Video V1 Worker');
Console::success(APP_NAME . ' video worker v1 has started');
class VideosV1 extends Worker
{
/**
* Rendition Status
*/
private const STATUS_START = 'started';
private const STATUS_END = 'ended';
private const STATUS_UPLOADING = 'uploading';
private const STATUS_READY = 'ready';
private const STATUS_ERROR = 'error';
private const OUTPUT_HLS = 'hls';
private const OUTPUT_DASH = 'dash';
private string $basePath = '/tmp/';
//private string $basePath = '/usr/src/code/tests/tmp/';
private string $inDir;
private string $inPath;
private string $outDir;
private string $outPath;
private string $renditionName;
private Database $database;
private Document $video;
private string $action;
private Document $profile;
private Document $project;
private Document $file;
private Document $bucket;
private FFProbe $ffprobe;
private FFMpeg $ffmpeg;
private bool $isVideo = false;
public function getName(): string
{
return "Video v1";
}
public function init(): void
{
$this->video = new Document($this->args['video'] ?? []);
$this->profile = new Document($this->args['profile'] ?? []);
$this->project = new Document($this->args['project'] ?? []);
$this->action = $this->args['action'];
$this->basePath .= uniqid();
$this->inDir = $this->basePath . '/in/';
$this->outDir = $this->basePath . '/out/';
@mkdir($this->inDir, 0755, true);
@mkdir($this->outDir, 0755, true);
$this->outPath = $this->outDir . $this->video->getId();
}
public function run(): void
{
$this->database = $this->getProjectDB($this->project->getId());
$this->bucket = $this->database->getDocument('buckets', $this->video->getAttribute('bucketId'));
$this->file = $this->database->getDocument('bucket_' . $this->bucket->getInternalId(), $this->video->getAttribute('fileId'));
$path = basename($this->file->getAttribute('path'));
$this->inPath = $this->inDir . $path;
/**
* Write original asset to tmp
*/
$result = $this->write($this->project, $this->file);
if (empty($result)) {
console::error('Storage transfer error');
}
$this->renditionName = $this->profile->getAttribute('width')
. 'X' . $this->profile->getAttribute('height')
. '@' . ($this->profile->getAttribute('videoBitRate') + $this->profile->getAttribute('audioBitRate'));
/**
* FFMpeg init
*/
$this->ffprobe = FFProbe::create();
$this->ffmpeg = FFMpeg::create([
'timeout' => 0,
'ffmpeg.threads' => 4
]);
if (!$this->ffprobe->isValid($this->inPath)) {
console::error('Not an valid Video file "' . $this->inPath . '"');
}
if (empty($this->video->getAttribute('duration'))) {
/**
* Original asset metadata
*/
$mediaInfo = new MediaInfo();
$mediaInfoContainer = $mediaInfo->getInfo($this->inPath);
$general = $mediaInfoContainer->getGeneral();
$this->video
->setAttribute('duration', $general->has('duration') ? $general->get('duration')->getMilliseconds() : 0)
->setAttribute('format', $general->has('format') ? $general->get('format')->getShortName() : '');
foreach ($mediaInfoContainer->getVideos() ?? [] as $video) {
$this->isVideo = true;
$this->video
->setAttribute('height', $video->has('height') ? $video->get('height')->getAbsoluteValue() : 0)
->setAttribute('width', $video->has('width') ? $video->get('width')->getAbsoluteValue() : 0)
->setAttribute('aspectRatio', $video->has('display_aspect_ratio') ? $video->get('display_aspect_ratio')->getTextValue() : '')
->setAttribute('videoFormat', $video->has('format') ? $video->get('format')->getShortName() : '')
->setAttribute('videoFormatProfile', $video->has('format_profile') ? $video->get('format_profile') : '')
->setAttribute('videoFrameRate', $video->has('frame_rate') ? strval($video->get('frame_rate')->getAbsoluteValue()) : '')
->setAttribute('videoFrameRateMode', $video->has('frame_rate_mode') ? $video->get('frame_rate_mode')->getFullName() : '')
->setAttribute('videoBitRate', $video->has('bit_rate') ? $video->get('bit_rate')->getAbsoluteValue() : 0);
}
foreach ($mediaInfoContainer->getAudios() ?? [] as $audio) {
$this->video
->setAttribute('audioFormat', $audio->has('format') ? strval($audio->get('format')->getShortName()) : '')
->setAttribute('audioSampleRate', $audio->has('sampling_rate') ? strval($audio->get('sampling_rate')->getAbsoluteValue()) : '')
->setAttribute('audioBitRate', $audio->has('bit_rate') ? $audio->get('bit_rate')->getAbsoluteValue() : 0);
}
console::info('Input video id: ' . $this->video->getId() . PHP_EOL .
'Input name: ' . $this->file->getAttribute('name') . PHP_EOL .
'Input width: ' . $this->video->getAttribute('width') . ' px' . PHP_EOL .
'Input height: ' . $this->video->getAttribute('height') . ' px' . PHP_EOL .
'Input duration: ' . ($this->video->getAttribute('duration') / 1000) . ' Sec' . PHP_EOL .
'Input size: ' . ($this->video->getAttribute('size') / 1024 / 1024) . ' MiB' . PHP_EOL .
'Input videoBitRate: ' . ($this->video->getAttribute('videoBitRate') / 1000) . ' kb/s' . PHP_EOL .
'Input audioBitRate: ' . ($this->video->getAttribute('audioBitRate') / 1000) . ' kb/s' . PHP_EOL);
$this->database->updateDocument(
'videos',
$this->video->getId(),
new document(
array_filter((array)$this->video, fn($value) => !is_null($value))
),
);
}
$this->video->getAttribute('videoBitRate');
$media = $this->ffmpeg->open($this->inPath);
match ($this->action) {
'preview' => $this->createPreview($media),
'timeline' => $this->createTimeLine($media),
default => $this->createOutput($media),
};
unset($media);
}
/**
* @throws Authorization
* @throws Throwable
* @throws Structure
*/
private function createPreview($media): void
{
if (!$this->isVideo) {
return;
}
console::info('Creating preview image from second ' . $this->args['second']);
$path = $this->getVideoDevice($this->project->getId())->getPath($this->video->getId()) . '/preview/';
$name = 'preview.jpg';
$media
->filters()
->resize(new \FFMpeg\Coordinate\Dimension($this->video->getAttribute('width'), $this->video->getAttribute('height')))
->synchronize();
$media
->frame(\FFMpeg\Coordinate\TimeCode::fromSeconds($this->args['second']))
->save($this->outDir . $name);
/**
* Upload preview
*/
console::info('Uploading ' . $name);
$this->getVideoDevice($this->project->getId())->write(
$path . $name,
(new Local('/'))->read($this->outDir . $name),
mime_content_type($this->outDir . $name)
);
$preview = $this->database->findOne('videos_previews', [
Query::equal('videoInternalId', [$this->video->getInternalId()]),
Query::equal('type', [$this->action]),
Query::equal('name', [$name]),
]);
if (empty($preview)) {
$preview = $this->database->createDocument('videos_previews', new Document([
'videoId' => $this->video->getId(),
'videoInternalId' => $this->video->getInternalId(),
'type' => $this->action,
'name' => $name,
'path' => $path,
'second' => $this->args['second'],
]));
$this->video->setAttribute('previewId', $preview->getId());
$this->video->setAttribute('previewInternalId', $preview->getInternalId());
$this->database->updateDocument(
'videos',
$this->video->getId(),
new document(
array_filter((array)$this->video, fn($value) => !is_null($value))
),
);
} else {
$this->database->updateDocument(
'videos_previews',
$preview->getId(),
$preview->setAttribute('second', $this->args['second'])
);
/**
* Clean preview cache
*/
(new Delete())
->setType(DELETE_TYPE_CACHE_BY_RESOURCE)
->setResource('preview/' . $preview->getId())
->trigger();
}
}
/**
* @throws Authorization
* @throws Structure
* @throws Throwable
*/
private function createTimeLine($media): void
{
/*
* Title: PlayerJS Thumbnails & WebVTT Creator
* URI: https://playerjs.com/docs/q=thumbnailsphpwebvtt
* Version: 1.0
* Author: Playerjs.com
* Author URI: https://playerjs.com
* License: GPL-2.0+
* License URI: http://www.gnu.org/licenses/gpl-2.0.txt
* Text Domain: playerjs
*/
if (!$this->isVideo) {
return;
}
$interval = 2;
$ranges = [
['from' => 120, 'to' => 600, 'interval' => 5],
['from' => 600, 'to' => 1800, 'interval' => 10],
['from' => 1800, 'to' => 3600, 'interval' => 20],
['from' => 3600, 'to' => 99999, 'interval' => 30],
];
foreach ($ranges as $range) {
if (
$this->video->getAttribute('duration') > $range['from'] &&
$this->video->getAttribute('duration') <= $range['to']
) {
$interval = $range['interval'];
break;
}
}
$timeline['aspect'] = $this->video->getAttribute('width') / $this->video->getAttribute('height');
$timeline['width'] = 160;
$timeline['height'] = round($timeline['width'] / $timeline['aspect']);
$timeline['size'] = '5x5';
$cmd = [
'/usr/bin/ffmpeg',
'-i ' . $this->inPath,
'-hide_banner',
'-loglevel error',
'-vsync vfr',
'-vf \'select=isnan(prev_selected_t)+gte(t-prev_selected_t\,' . $interval . '),scale=' . $timeline['width'] . ':' . $timeline['height'] . ',tile=' . $timeline['size'] . '\'',
'-qscale:v 3',
$this->outDir . 'sprite%d.jpg',
];
$result = shell_exec(implode(" ", $cmd));
/**
* Vtt creation*
*/
if ($result !== false) {
$size = explode('x', $timeline['size']);
$counter = 0;
$images = ceil((($this->video->getAttribute('duration') / 1000) / $interval) / ($size[0] * $size[1]));
$data = "WEBVTT";
$path = $this->getVideoDevice($this->project->getId())->getPath($this->video->getId()) . '/timeline/';
for ($image = 1; $image <= $images; $image++) {
$sprite = $this->database->createDocument('videos_previews', new Document([
'videoId' => $this->video->getId(),
'videoInternalId' => $this->video->getInternalId(),
'type' => 'sprite',
'name' => 'sprite' . $image . '.jpg',
'path' => $path,
]));
$url = TMP_HOST . 'v1/videos/' . $this->video->getId() . '/preview/' . $sprite->getId() . '/';
for ($col = 0; $col < $size[0]; $col++) {
for ($row = 0; $row < $size[1]; $row++) {
$data .= "\n" . gmdate("H:i:s", $counter * $interval) . " --> " . gmdate("H:i:s", ($counter + 1) * $interval) . "\n" . $url . "#xywh=" . ($row * $timeline['width']) . "," . ($col * $timeline['height']) . "," . $timeline['width'] . "," . $timeline['height'];
$counter++;
}
}
}
if ($counter > 0) {
/**
* Upload vtt
*/
$this->getVideoDevice($this->project->getId())->write(
$this->getVideoDevice($this->project->getId())->getPath($this->video->getId() . '/timeline/timeline.vtt'),
$data,
'text/vtt'
);
console::info('Uploading timeline vtt');
/**
* Upload sprites*
*/
$dir = new DirectoryIterator($this->outDir);
foreach ($dir as $fileinfo) {
if (!$fileinfo->isDot()) {
console::info('Uploading ' . $fileinfo->getFilename());
$this->getVideoDevice($this->project->getId())->write(
$path . $fileinfo->getFilename(),
(new Local('/'))->read($this->outDir . $fileinfo->getFilename()),
mime_content_type($this->outDir . $fileinfo->getFilename())
);
}
}
}
}
}
/**
* @throws Authorization
* @throws Exception
* @throws Throwable
* @throws Structure
*/
private function createOutput($media): void
{
$subs = [];
$subtitles = $this->database->find('videos_subtitles', [
Query::equal('videoInternalId', [$this->video->getInternalId()]),
Query::equal('status', [''])
]);
foreach ($subtitles as $subtitle) {
$subtitle->setAttribute('status', self::STATUS_START);
$this->database->updateDocument('videos_subtitles', $subtitle->getId(), $subtitle);
$bucket = $this->database->getDocument('buckets', $subtitle->getAttribute('bucketId'));
$file = $this->database->getDocument('bucket_' . $bucket->getInternalId(), $subtitle->getAttribute('fileId'));
$path = basename($file->getAttribute('path'));
$this->write($this->project, $file);
console::info('Transferring subtitle from storage to ' . $this->inDir);
$ext = pathinfo($path, PATHINFO_EXTENSION);
$subtitlePath = $this->inDir . $subtitle->getId() . '.vtt';
if ($ext === 'srt') {
$srt = new SubripFile($this->inDir . $path);
$srt->convertTo('webvtt')->save($subtitlePath);
}
$subs[] = [
'name' => $subtitle->getAttribute('name'),
'code' => $subtitle->getAttribute('code'),
'path' => $subtitlePath,
];
}
$query = $this->database->createDocument('videos_renditions', new Document([
'videoId' => $this->video->getId(),
'videoInternalId' => $this->video->getInternalId(),
'profileId' => $this->profile->getId(),
'profileInternalId' => $this->profile->getInternalId(),
'name' => $this->renditionName,
'startedAt' => DateTime::now(),
'status' => self::STATUS_START,
'width' => $this->profile->getAttribute('width'),
'height' => $this->profile->getAttribute('height'),
'videoBitRate' => $this->profile->getAttribute('videoBitRate'),
'audioBitRate' => $this->profile->getAttribute('audioBitRate'),
'output' => $this->args['output'],
]));
$this->send($query);
$renditionRootPath = $this->getVideoDevice($this->project->getId())->getPath($this->video->getId()) . '/';
$renditionPath = $renditionRootPath . $this->renditionName . '-' . $query->getId() . '/';
try {
$representation = (new Representation())
->setKiloBitRate($this->profile->getAttribute('videoBitRate'))
->setAudioKiloBitRate($this->profile->getAttribute('audioBitRate'))
->setResize($this->profile->getAttribute('width'), $this->profile->getAttribute('height'));
console::info('Output video id:' . $this->video->getId() . PHP_EOL .
'Output name: ' . $this->file->getAttribute('name') . PHP_EOL .
'Output width: ' . $this->profile->getAttribute('width') . PHP_EOL .
'Output height: ' . $this->profile->getAttribute('height') . PHP_EOL .
'Output video BitRate:' . $this->profile->getAttribute('videoBitRate') . PHP_EOL .
'Output audio BitRate:' . $this->profile->getAttribute('audioBitRate') . PHP_EOL .
'Output:' . $this->args['output'] . PHP_EOL);
$format = new Streaming\Format\X264();
$format->on('progress', function ($media, $format, $percentage) use ($query) {
if ($percentage % 3 === 0) {
$query->setAttribute('progress', (string)$percentage);
$this->database->updateDocument('videos_renditions', $query->getId(), $query);
$this->send($query, 'update');
}
});
$this->transcode($media, $format, $representation, $subs);
//exec('/usr/bin/ffmpeg -y -i /usr/src/code/tests/tmp/637f59c88f9ff0fe3b1f/637e1b82aeab8980400e/in/637f59ab5bce0e36d05e.mp4 -c:v libx264 -c:a aac -bf 1 -keyint_min 25 -g 250 -sc_threshold 40 -use_timeline 0 -use_template 0 -seg_duration 10 -hls_playlist 0 -f dash -dn -sn -vf scale=iw:-2:force_original_aspect_ratio=increase,setsar=1:1 -b_strategy 1 -bf 3 -force_key_frames "expr:gte(t,n_forced*2)" -map 0 -s:v:0 1024x576 -b:v:0 2538k -b:a:0 128k -strict -2 -threads 12 /usr/src/code/tests/tmp/637f59c88f9ff0fe3b1f/637e1b82aeab8980400e/out/637f59c88f9ff0fe3b1f.mpd2>&1', $o, $v);
//var_dump($o);
//var_dump($v);
if ($this->args['output'] === self::OUTPUT_HLS) {
$streams = $this->getHlsSegmentsUrls($this->outDir . 'master.m3u8');
foreach ($streams as $stream) {
$m3u8 = $this->getSegments($this->outDir . $stream['path']);
if (!empty($m3u8['segments'])) {
foreach ($m3u8['segments'] as $segment) {
$this->database->createDocument('videos_renditions_segments', new Document([
'renditionId' => $query->getId(),
'renditionInternalId' => $query->getInternalId(),
'streamId' => (int)$stream['id'],
'fileName' => $segment['fileName'],
'path' => $renditionPath,
'duration' => $segment['duration'],
]));
}
}
$query->setAttribute('metadata', json_encode(['hls' => $streams]));
$query->setAttribute('targetDuration', $m3u8['targetDuration']);
}
} else {
$mpd = $this->getSegments($this->outPath . '.mpd');
if (!empty($mpd['segments'])) {
foreach ($mpd['segments'] as $segment) {
$this->database->createDocument('videos_renditions_segments', new Document([
'renditionId' => $query->getId(),
'renditionInternalId' => $query->getInternalId(),
'streamId' => $segment['streamId'],
'fileName' => $segment['fileName'],
'path' => $renditionPath,
'isInit' => $segment['isInit'],
]));
}
}
if (!empty($mpd['metadata'])) {
$query->setAttribute('metadata', json_encode(['mpd' => $mpd['metadata']]));
}
}
$query->setAttribute('status', self::STATUS_END);
$query->setAttribute('endedAt', DateTime::now());
$this->database->updateDocument('videos_renditions', $query->getId(), $query);
$this->send($query, 'update');
foreach ($subtitles ?? [] as $subtitle) {
if ($this->args['output'] === self::OUTPUT_HLS) {
$m3u8 = $this->getSegments($this->outPath . '_subtitles_' . $subtitle['code'] . '.m3u8');
foreach ($m3u8['segments'] ?? [] as $segment) {
$this->database->createDocument('videos_subtitles_segments', new Document([
'subtitleId' => $subtitle->getId(),
'subtitleInternalId' => $subtitle->getInternalId(),
'fileName' => $segment['fileName'],
'path' => $renditionRootPath . 'subtitles/',
'duration' => $segment['duration'],
]));
}
$subtitle->setAttribute('targetDuration', $m3u8['targetDuration']);
} else {
$this->getFilesDevice($this->project->getId())->transfer($this->inDir . $subtitle->getId() . '.vtt', $this->outDir . $subtitle->getId() . '.vtt', $this->getFilesDevice($this->project->getId()));
}
$subtitle->setAttribute('status', self::STATUS_READY);
$subtitle->setAttribute('path', $renditionRootPath . 'subtitles/');
$this->database->updateDocument('videos_subtitles', $subtitle->getId(), $subtitle);
}
console::info('Rendition ' . $query->getId() . ' conversion, done');
/** Upload **/
$dir = new DirectoryIterator($this->outDir);
foreach ($dir as $fileinfo) {
if (!$fileinfo->isDot()) {
$data = (new Local('/'))->read($this->outDir . $fileinfo->getFilename());
$to = $renditionPath;
if (str_contains($fileinfo->getFilename(), "_subtitles_") || str_contains($fileinfo->getFilename(), ".vtt")) {
$to = $renditionRootPath . 'subtitles/';
}
console::info('Uploading ' . $fileinfo->getFilename());
$this->getVideoDevice($this->project->getId())->write(
$to . $fileinfo->getFilename(),
$data,
mime_content_type($this->outDir . $fileinfo->getFilename())
);
if ($fileinfo->key() === 0) {
$query->setAttribute('progress', '100');
$query->setAttribute('status', self::STATUS_UPLOADING);
$query->setAttribute('path', $renditionPath);
$this->database->updateDocument('videos_renditions', $query->getId(), $query);
$this->send($query, 'update');
}
}
}
$query->setAttribute('status', self::STATUS_READY);
$this->database->updateDocument('videos_renditions', $query->getId(), $query);
$this->send($query, 'update');
} catch (\Throwable $th) {
$query->setAttribute('metadata', json_encode([
'code' => $th->getCode(),
'message' => substr($th->getMessage(), 0, 255),
]));
$query->setAttribute('status', self::STATUS_ERROR);
$this->database->updateDocument('videos_renditions', $query->getId(), $query);
$this->send($query, 'update');
console::error('Error video id:' . $this->video->getId() . PHP_EOL
. 'Message: ' . $th->getMessage() . PHP_EOL
. 'File: ' . $th->getFile() . PHP_EOL
. 'line: ' . $th->getLine() . PHP_EOL);
}
}
/**
* @param $media Media
* @param $format StreamFormat
* @param $representation Representation
* @param array $subtitles
* @return void
*/
private function transcode(Media $media, StreamFormat $format, Representation $representation, array $subtitles): void
{
$additionalParams = [
'-dn',
'-sn',
'-vf', 'scale=iw:-2:force_original_aspect_ratio=increase,setsar=1:1', //keep frame size
'-crf', '22', // constant rate factor
'-bf', '3', // bframe
'-force_key_frames', 'expr:gte(t,n_forced*2)' //enforce strict key frame
];
$segmentSize = 6;
if ($this->args['output'] === self::OUTPUT_DASH) {
$media->dash()
->setFormat($format)
->setSegDuration($segmentSize)
->addRepresentation($representation)
->setAdditionalParams($additionalParams)
->save($this->outPath);
return;
}
$hls = $media->hls();
foreach ($subtitles as $subtitle) {
$sub = new HLSSubtitle($subtitle['path'], $subtitle['name'], $subtitle['code']);
$sub->default();
$hls->subtitle($sub);
}
$hls->setFormat($format)
->setHlsTime($segmentSize)
->addRepresentation($representation)
->setAdditionalParams($additionalParams)
->save($this->outPath);
}
/**
* @param string $path
* @return array
*/
private function getSegments(string $path): array
{
$segments = [];
if ($this->args['output'] === self::OUTPUT_DASH) {
$metadata = null;
$handle = fopen($path, "r");
if ($handle) {
$streamId = -1;
while (($line = fgets($handle)) !== false) {
$line = str_replace([",", "\r", "\n"], "", $line);
if (str_contains($line, "<AdaptationSet")) {
$streamId++;
}
if (!str_contains($line, "SegmentURL") && !str_contains($line, "Initialization")) {
$metadata .= $line . PHP_EOL;
} else {
$segments[] = [
'isInit' => str_contains($line, "Initialization") ? 1 : 0,
'streamId' => $streamId,
'fileName' => trim(str_replace(["<SegmentURL media=\"", "<Initialization sourceURL=\"", "\"/>", "\" />"], "", $line)),
];
}
}
fclose($handle);
}
return [
'metadata' => $metadata,
'segments' => $segments
];
}
$targetDuration = 0;
$handle = fopen($path, "r");
if ($handle) {
while (($line = fgets($handle)) !== false) {
$line = str_replace([",","\r","\n"], "", $line);
if (str_contains($line, "#EXT-X-TARGETDURATION")) {
$targetDuration = str_replace(["#EXT-X-TARGETDURATION:"], "", $line);
}
if (str_contains($line, "#EXTINF")) {
$duration = str_replace(["#EXTINF:"], "", $line);
}
if (str_contains($line, ".ts") || str_contains($line, ".vtt")) {
if (!empty($duration)) {
$segments[] = [
'fileName' => $line,
'duration' => $duration
];
$duration = null;
}
}
}
fclose($handle);
}
return [
'targetDuration' => $targetDuration,
'segments' => $segments
];
}
/**
* @param string $path
* @return array
*/
private function getHlsSegmentsUrls(string $path): array
{
$files = [];
$handle = fopen($path, "r");
if ($handle) {
while (($line = fgets($handle)) !== false) {
$tmp = [];
$line = str_replace(['"'], '', $line);
$attributes = explode(',', $line);
foreach ($attributes as $key => $attribute) {
$parts = explode('=', $attribute);
switch (true) {
case str_contains($parts[0], 'LANGUAGE'):
$attr['language'] = $parts[1];
break;
case str_contains($parts[0], 'BANDWIDTH'):
$attr['bandwidth'] = $parts[1];
break;
case str_contains($parts[0], 'RESOLUTION'):
$attr['resolution'] = $parts[1];
break;
case str_contains($parts[0], 'CODECS'):
$attr['codecs'] = trim($parts[1], " \n\r\t\v\x00");
if (!empty($attributes[$key + 1])) {
$attr['codecs'] .= ',' . trim($attributes[$key + 1], " \n\r\t\v\x00");
}
break;
}
}
$end = strpos($line, 'm3u8');
if ($end !== false) {
$start = strpos($line, $this->video->getId());
if ($start !== false) {
$path = substr($line, $start, ($end - $start) + 4);
$parts = explode('_', $path);
$tmp = [
'id' => $parts[1],
'path' => $path
];
if (str_contains($line, "TYPE=AUDIO")) {
$tmp['type'] = 'audio';
if (!empty($attr['language'])) {
$tmp['language'] = $attr['language'];
}
} else {
$tmp['type'] = 'video';
if (!empty($attr['resolution'])) {
$tmp['resolution'] = $attr['resolution'];
}
if (!empty($attr['bandwidth'])) {
$tmp['bandwidth'] = $attr['bandwidth'];
}
if (!empty($attr['codecs'])) {
$tmp['codecs'] = $attr['codecs'];
}
}
}
$files[] = $tmp;
}
}
fclose($handle);
}
return $files;
}
/**
* Send realtime updates
*
* @param string $action
* @param Document $payload
* @throws Exception
*/
private function send(Document $payload, string $action = 'create')
{
unset($payload['metadata']);
$allEvents = Event::generateEvents('videos.[videoId].renditions.[renditionId].' . $action, [
'videoId' => $payload['videoId'],
'renditionId' => $payload->getId()
]);
$payload->setAttribute('$permissions', $this->bucket->getAttribute('fileSecurity', false)
? \array_merge($this->bucket->getAttribute('$permissions'), $this->file->getAttribute('$permissions'))
: $this->bucket->getAttribute('$permissions'));
$target = Realtime::fromPayload(
event: $allEvents[0],
payload: $payload
);
Realtime::send(
projectId: 'console',
payload: $payload->getArrayCopy(),
events: $allEvents,
channels: $target['channels'],
roles: $target['roles']
);
}
/**
* Write files
*
* @param $project Document
* @param $file Document
* @return boolean
* @throws \Exception
*/
private function write(Document $project, Document $file): bool
{
console::info('Writing file to ' . $this->inDir);
$fullPath = $file->getAttribute('path');
$path = basename($file->getAttribute('path'));
$local = new Local('/');
if (
!empty($file->getAttribute('openSSLCipher')) ||
$file->getAttribute('algorithm', 'none') !== 'none'
) {
$data = $this->getFilesDevice($project->getId())->read($fullPath);
if (!empty($file->getAttribute('openSSLCipher'))) {
$data = OpenSSL::decrypt(
$data,
$file->getAttribute('openSSLCipher'),
App::getEnv('_APP_OPENSSL_KEY_V' . $file->getAttribute('openSSLVersion')),
0,
\hex2bin($file->getAttribute('openSSLIV')),
\hex2bin($file->getAttribute('openSSLTag'))
);
}
$algorithm = $file->getAttribute('algorithm', 'none');
switch ($algorithm) {
case 'zstd':
$compressor = new Zstd();
$data = $compressor->decompress($data);
break;
case 'gzip':
$compressor = new GZIP();
$data = $compressor->decompress($data);
break;
}
$result = $local
->write($this->inDir . $path, $data, $file->getAttribute('mimeType'));
} else {
$result = $this->getFilesDevice(
$project->getId()
)->transfer($fullPath, $this->inDir . $path, $local);
}
return $result;
}
private function cleanup(): bool
{
$stdout = '';
$stderr = '';
$stdin = '';
return Console::execute("rm -rf {$this->basePath}", $stdin, $stdout, $stderr, 3) === 0;
}
public function shutdown(): void
{
$result = $this->cleanup();
if (!$result) {
Console::error('Failed Removing files from [' . $this->basePath . ']');
}
Console::info('Removing files from [' . $this->basePath . ']');
}
}
+10
View File
@@ -0,0 +1,10 @@
#!/bin/sh
if [ -z "$_APP_REDIS_USER" ] && [ -z "$_APP_REDIS_PASS" ]
then
REDIS_BACKEND="${_APP_REDIS_HOST}:${_APP_REDIS_PORT}"
else
REDIS_BACKEND="redis://${_APP_REDIS_USER}:${_APP_REDIS_PASS}@${_APP_REDIS_HOST}:${_APP_REDIS_PORT}"
fi
INTERVAL=0.1 QUEUE='v1-videos' APP_INCLUDE='/usr/src/code/app/workers/videos.php' php /usr/src/code/vendor/bin/resque -dopcache.preload=opcache.preload=/usr/src/code/app/preload.php
+15 -7
View File
@@ -41,15 +41,16 @@
"ext-openssl": "*",
"ext-zlib": "*",
"ext-sockets": "*",
"ext-fileinfo": "*",
"appwrite/php-clamav": "1.1.*",
"appwrite/php-runtimes": "0.11.*",
"utopia-php/abuse": "0.24.*",
"utopia-php/abuse": "0.25.*",
"utopia-php/analytics": "0.2.*",
"utopia-php/audit": "0.25.*",
"utopia-php/audit": "0.26.*",
"utopia-php/cache": "0.8.*",
"utopia-php/cli": "0.13.*",
"utopia-php/config": "0.2.*",
"utopia-php/database": "0.35.*",
"utopia-php/database": "0.36.*",
"utopia-php/domains": "1.1.*",
"utopia-php/framework": "0.28.*",
"utopia-php/image": "0.5.*",
@@ -59,7 +60,7 @@
"utopia-php/orchestration": "0.6.*",
"utopia-php/preloader": "0.2.*",
"utopia-php/registry": "0.5.*",
"utopia-php/storage": "0.14.*",
"utopia-php/storage": "dev-feat-transfer as 0.13.2",
"utopia-php/swoole": "0.5.*",
"utopia-php/websocket": "0.1.*",
"resque/php-resque": "1.3.6",
@@ -70,17 +71,24 @@
"chillerlan/php-qrcode": "4.3.3",
"adhocore/jwt": "1.1.2",
"slickdeals/statsd": "3.1.0",
"webonyx/graphql-php": "14.11.*"
"aminyazdanpanah/php-ffmpeg-video-streaming": "dev-master",
"captioning/captioning": "2.*",
"ext-simplexml": "*",
"webonyx/graphql-php": "14.11.*",
"mhor/php-mediainfo": "5.4.3"
},
"repositories": [
{
"url": "https://github.com/appwrite/runtimes.git",
"type": "git"
},
{
"url": "https://github.com/shimonewman/PHP-FFmpeg-video-streaming.git",
"type": "git"
}
],
"require-dev": {
"appwrite/sdk-generator": "0.32.*",
"ext-fileinfo": "*",
"appwrite/sdk-generator": "dev-shimon-video-test",
"phpunit/phpunit": "9.5.20",
"squizlabs/php_codesniffer": "^3.6",
"swoole/ide-helper": "4.8.9",
Generated
+1267 -266
View File
File diff suppressed because it is too large Load Diff
+65
View File
@@ -73,6 +73,7 @@ services:
- traefik.http.routers.appwrite_api_https.tls=true
volumes:
- appwrite-uploads:/storage/uploads:rw
- appwrite-videos:/storage/videos:rw
- appwrite-cache:/storage/cache:rw
- appwrite-config:/storage/config:rw
- appwrite-certificates:/storage/certificates:rw
@@ -84,6 +85,9 @@ services:
- ./public:/usr/src/code/public
- ./src:/usr/src/code/src
- ./dev:/usr/src/code/dev
- ./dev:/usr/local/dev
- ./tests/tmp:/tmp
- ./vendor/utopia-php/storage:/usr/src/code/vendor/utopia-php/storage
depends_on:
- mariadb
- redis
@@ -225,6 +229,65 @@ services:
- _APP_LOGGING_PROVIDER
- _APP_LOGGING_CONFIG
appwrite-worker-videos:
entrypoint: worker-videos
<<: *x-logging
container_name: appwrite-worker-videos
build:
context: .
networks:
- appwrite
volumes:
- ./app:/usr/src/code/app
- ./src:/usr/src/code/src
- appwrite-uploads:/storage/uploads:rw
- appwrite-videos:/storage/videos:rw
#- ./tests:/usr/src/code/tests #TODO remove when done!!
- ./vendor/aminyazdanpanah/php-ffmpeg-video-streaming:/usr/src/code/vendor/aminyazdanpanah/php-ffmpeg-video-streaming #TODO remove when done!!
- ./vendor/php-ffmpeg/php-ffmpeg:/usr/src/code/vendor/php-ffmpeg/php-ffmpeg #TODO remove when done!!
- ./vendor/alchemy/binary-driver:/usr/src/code/vendor/alchemy/binary-driver
- ./vendor/utopia-php/storage:/usr/src/code/vendor/utopia-php/storage
#- ./vendor/symfony/process:/usr/src/code/vendor/symfony/process
depends_on:
- redis
- mariadb
environment:
- _APP_ENV
- _APP_OPENSSL_KEY_V1
- _APP_STORAGE_DEVICE
- _APP_STORAGE_S3_ACCESS_KEY
- _APP_STORAGE_S3_SECRET
- _APP_STORAGE_S3_REGION
- _APP_STORAGE_S3_BUCKET
- _APP_STORAGE_DO_SPACES_ACCESS_KEY
- _APP_STORAGE_DO_SPACES_SECRET
- _APP_STORAGE_DO_SPACES_REGION
- _APP_STORAGE_DO_SPACES_BUCKET
- _APP_STORAGE_BACKBLAZE_ACCESS_KEY
- _APP_STORAGE_BACKBLAZE_SECRET
- _APP_STORAGE_BACKBLAZE_REGION
- _APP_STORAGE_BACKBLAZE_BUCKET
- _APP_STORAGE_LINODE_ACCESS_KEY
- _APP_STORAGE_LINODE_SECRET
- _APP_STORAGE_LINODE_REGION
- _APP_STORAGE_LINODE_BUCKET
- _APP_STORAGE_WASABI_ACCESS_KEY
- _APP_STORAGE_WASABI_SECRET
- _APP_STORAGE_WASABI_REGION
- _APP_STORAGE_WASABI_BUCKET
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_LOGGING_PROVIDER
- _APP_LOGGING_CONFIG
appwrite-worker-audits:
entrypoint: worker-audits
<<: *x-logging
@@ -290,6 +353,7 @@ services:
- mariadb
volumes:
- appwrite-uploads:/storage/uploads:rw
- appwrite-videos:/storage/videos:rw
- appwrite-cache:/storage/cache:rw
- appwrite-functions:/storage/functions:rw
- appwrite-builds:/storage/builds:rw
@@ -846,6 +910,7 @@ volumes:
appwrite-redis:
appwrite-cache:
appwrite-uploads:
appwrite-videos:
appwrite-certificates:
appwrite-functions:
appwrite-builds:
@@ -13,7 +13,7 @@ public class MainActivity extends AppCompatActivity {
setContentView(R.layout.activity_main);
Client client = new Client(getApplicationContext())
.setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
.setProject("5df5acd0d48c2"); // Your project ID
Account account = new Account(client);
@@ -13,7 +13,7 @@ public class MainActivity extends AppCompatActivity {
setContentView(R.layout.activity_main);
Client client = new Client(getApplicationContext())
.setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
.setProject("5df5acd0d48c2"); // Your project ID
Account account = new Account(client);
@@ -13,7 +13,7 @@ public class MainActivity extends AppCompatActivity {
setContentView(R.layout.activity_main);
Client client = new Client(getApplicationContext())
.setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
.setProject("5df5acd0d48c2"); // Your project ID
Account account = new Account(client);
@@ -13,7 +13,7 @@ public class MainActivity extends AppCompatActivity {
setContentView(R.layout.activity_main);
Client client = new Client(getApplicationContext())
.setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
.setProject("5df5acd0d48c2"); // Your project ID
Account account = new Account(client);
@@ -13,7 +13,7 @@ public class MainActivity extends AppCompatActivity {
setContentView(R.layout.activity_main);
Client client = new Client(getApplicationContext())
.setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
.setProject("5df5acd0d48c2"); // Your project ID
Account account = new Account(client);
@@ -13,7 +13,7 @@ public class MainActivity extends AppCompatActivity {
setContentView(R.layout.activity_main);
Client client = new Client(getApplicationContext())
.setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
.setProject("5df5acd0d48c2"); // Your project ID
Account account = new Account(client);
@@ -13,7 +13,7 @@ public class MainActivity extends AppCompatActivity {
setContentView(R.layout.activity_main);
Client client = new Client(getApplicationContext())
.setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
.setProject("5df5acd0d48c2"); // Your project ID
Account account = new Account(client);
@@ -13,7 +13,7 @@ public class MainActivity extends AppCompatActivity {
setContentView(R.layout.activity_main);
Client client = new Client(getApplicationContext())
.setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
.setProject("5df5acd0d48c2"); // Your project ID
Account account = new Account(client);
@@ -13,7 +13,7 @@ public class MainActivity extends AppCompatActivity {
setContentView(R.layout.activity_main);
Client client = new Client(getApplicationContext())
.setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
.setProject("5df5acd0d48c2"); // Your project ID
Account account = new Account(client);
@@ -13,7 +13,7 @@ public class MainActivity extends AppCompatActivity {
setContentView(R.layout.activity_main);
Client client = new Client(getApplicationContext())
.setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
.setProject("5df5acd0d48c2"); // Your project ID
Account account = new Account(client);
@@ -13,7 +13,7 @@ public class MainActivity extends AppCompatActivity {
setContentView(R.layout.activity_main);
Client client = new Client(getApplicationContext())
.setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
.setProject("5df5acd0d48c2"); // Your project ID
Account account = new Account(client);
@@ -13,7 +13,7 @@ public class MainActivity extends AppCompatActivity {
setContentView(R.layout.activity_main);
Client client = new Client(getApplicationContext())
.setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
.setProject("5df5acd0d48c2"); // Your project ID
Account account = new Account(client);
@@ -13,7 +13,7 @@ public class MainActivity extends AppCompatActivity {
setContentView(R.layout.activity_main);
Client client = new Client(getApplicationContext())
.setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
.setProject("5df5acd0d48c2"); // Your project ID
Account account = new Account(client);
@@ -13,7 +13,7 @@ public class MainActivity extends AppCompatActivity {
setContentView(R.layout.activity_main);
Client client = new Client(getApplicationContext())
.setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
.setProject("5df5acd0d48c2"); // Your project ID
Account account = new Account(client);
@@ -13,7 +13,7 @@ public class MainActivity extends AppCompatActivity {
setContentView(R.layout.activity_main);
Client client = new Client(getApplicationContext())
.setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
.setProject("5df5acd0d48c2"); // Your project ID
Account account = new Account(client);
@@ -13,7 +13,7 @@ public class MainActivity extends AppCompatActivity {
setContentView(R.layout.activity_main);
Client client = new Client(getApplicationContext())
.setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
.setProject("5df5acd0d48c2"); // Your project ID
Account account = new Account(client);
@@ -13,7 +13,7 @@ public class MainActivity extends AppCompatActivity {
setContentView(R.layout.activity_main);
Client client = new Client(getApplicationContext())
.setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
.setProject("5df5acd0d48c2"); // Your project ID
Account account = new Account(client);
@@ -13,7 +13,7 @@ public class MainActivity extends AppCompatActivity {
setContentView(R.layout.activity_main);
Client client = new Client(getApplicationContext())
.setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
.setProject("5df5acd0d48c2"); // Your project ID
Account account = new Account(client);
@@ -13,7 +13,7 @@ public class MainActivity extends AppCompatActivity {
setContentView(R.layout.activity_main);
Client client = new Client(getApplicationContext())
.setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
.setProject("5df5acd0d48c2"); // Your project ID
Account account = new Account(client);
@@ -13,7 +13,7 @@ public class MainActivity extends AppCompatActivity {
setContentView(R.layout.activity_main);
Client client = new Client(getApplicationContext())
.setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
.setProject("5df5acd0d48c2"); // Your project ID
Account account = new Account(client);
@@ -13,7 +13,7 @@ public class MainActivity extends AppCompatActivity {
setContentView(R.layout.activity_main);
Client client = new Client(getApplicationContext())
.setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
.setProject("5df5acd0d48c2"); // Your project ID
Account account = new Account(client);
@@ -13,7 +13,7 @@ public class MainActivity extends AppCompatActivity {
setContentView(R.layout.activity_main);
Client client = new Client(getApplicationContext())
.setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
.setProject("5df5acd0d48c2"); // Your project ID
Account account = new Account(client);
@@ -13,7 +13,7 @@ public class MainActivity extends AppCompatActivity {
setContentView(R.layout.activity_main);
Client client = new Client(getApplicationContext())
.setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
.setProject("5df5acd0d48c2"); // Your project ID
Account account = new Account(client);
@@ -13,7 +13,7 @@ public class MainActivity extends AppCompatActivity {
setContentView(R.layout.activity_main);
Client client = new Client(getApplicationContext())
.setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
.setProject("5df5acd0d48c2"); // Your project ID
Account account = new Account(client);
@@ -13,7 +13,7 @@ public class MainActivity extends AppCompatActivity {
setContentView(R.layout.activity_main);
Client client = new Client(getApplicationContext())
.setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
.setProject("5df5acd0d48c2"); // Your project ID
Account account = new Account(client);
@@ -13,7 +13,7 @@ public class MainActivity extends AppCompatActivity {
setContentView(R.layout.activity_main);
Client client = new Client(getApplicationContext())
.setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
.setProject("5df5acd0d48c2"); // Your project ID
Account account = new Account(client);
@@ -13,7 +13,7 @@ public class MainActivity extends AppCompatActivity {
setContentView(R.layout.activity_main);
Client client = new Client(getApplicationContext())
.setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
.setProject("5df5acd0d48c2"); // Your project ID
Account account = new Account(client);
@@ -13,7 +13,7 @@ public class MainActivity extends AppCompatActivity {
setContentView(R.layout.activity_main);
Client client = new Client(getApplicationContext())
.setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
.setProject("5df5acd0d48c2"); // Your project ID
Account account = new Account(client);
@@ -13,7 +13,7 @@ public class MainActivity extends AppCompatActivity {
setContentView(R.layout.activity_main);
Client client = new Client(getApplicationContext())
.setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
.setProject("5df5acd0d48c2"); // Your project ID
Account account = new Account(client);
@@ -13,7 +13,7 @@ public class MainActivity extends AppCompatActivity {
setContentView(R.layout.activity_main);
Client client = new Client(getApplicationContext())
.setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
.setProject("5df5acd0d48c2"); // Your project ID
Avatars avatars = new Avatars(client);
@@ -13,7 +13,7 @@ public class MainActivity extends AppCompatActivity {
setContentView(R.layout.activity_main);
Client client = new Client(getApplicationContext())
.setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
.setProject("5df5acd0d48c2"); // Your project ID
Avatars avatars = new Avatars(client);
@@ -13,7 +13,7 @@ public class MainActivity extends AppCompatActivity {
setContentView(R.layout.activity_main);
Client client = new Client(getApplicationContext())
.setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
.setProject("5df5acd0d48c2"); // Your project ID
Avatars avatars = new Avatars(client);
@@ -13,7 +13,7 @@ public class MainActivity extends AppCompatActivity {
setContentView(R.layout.activity_main);
Client client = new Client(getApplicationContext())
.setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
.setProject("5df5acd0d48c2"); // Your project ID
Avatars avatars = new Avatars(client);
@@ -13,7 +13,7 @@ public class MainActivity extends AppCompatActivity {
setContentView(R.layout.activity_main);
Client client = new Client(getApplicationContext())
.setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
.setProject("5df5acd0d48c2"); // Your project ID
Avatars avatars = new Avatars(client);
@@ -13,7 +13,7 @@ public class MainActivity extends AppCompatActivity {
setContentView(R.layout.activity_main);
Client client = new Client(getApplicationContext())
.setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
.setProject("5df5acd0d48c2"); // Your project ID
Avatars avatars = new Avatars(client);
@@ -13,7 +13,7 @@ public class MainActivity extends AppCompatActivity {
setContentView(R.layout.activity_main);
Client client = new Client(getApplicationContext())
.setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
.setProject("5df5acd0d48c2"); // Your project ID
Avatars avatars = new Avatars(client);
@@ -13,7 +13,7 @@ public class MainActivity extends AppCompatActivity {
setContentView(R.layout.activity_main);
Client client = new Client(getApplicationContext())
.setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
.setProject("5df5acd0d48c2"); // Your project ID
Databases databases = new Databases(client);
@@ -13,7 +13,7 @@ public class MainActivity extends AppCompatActivity {
setContentView(R.layout.activity_main);
Client client = new Client(getApplicationContext())
.setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
.setProject("5df5acd0d48c2"); // Your project ID
Databases databases = new Databases(client);
@@ -13,7 +13,7 @@ public class MainActivity extends AppCompatActivity {
setContentView(R.layout.activity_main);
Client client = new Client(getApplicationContext())
.setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
.setProject("5df5acd0d48c2"); // Your project ID
Databases databases = new Databases(client);
@@ -13,7 +13,7 @@ public class MainActivity extends AppCompatActivity {
setContentView(R.layout.activity_main);
Client client = new Client(getApplicationContext())
.setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
.setProject("5df5acd0d48c2"); // Your project ID
Databases databases = new Databases(client);
@@ -13,7 +13,7 @@ public class MainActivity extends AppCompatActivity {
setContentView(R.layout.activity_main);
Client client = new Client(getApplicationContext())
.setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
.setProject("5df5acd0d48c2"); // Your project ID
Databases databases = new Databases(client);
@@ -13,7 +13,7 @@ public class MainActivity extends AppCompatActivity {
setContentView(R.layout.activity_main);
Client client = new Client(getApplicationContext())
.setEndpoint("https://[HOSTNAME_OR_IP]/v1") // Your API Endpoint
.setEndpoint("https://cloud.appwrite.io/v1") // Your API Endpoint
.setProject("5df5acd0d48c2"); // Your project ID
Functions functions = new Functions(client);

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