Compare commits

..
Author SHA1 Message Date
Jake Barnby 0880fa23a5 fix: retain mails worker action 2026-05-26 21:15:41 +12:00
Jake Barnby 16bfbce2e9 fix: scope notification listing to user 2026-05-26 20:17:59 +12:00
Jake Barnby afb956a18c fix: purge project cache after updates 2026-05-26 19:46:13 +12:00
Jake Barnby 76411aeca8 Merge remote-tracking branch 'origin/1.9.x' into feat-notifications-worker 2026-05-26 18:47:13 +12:00
Chirag AggarwalandGitHub 4efe1e6890 Merge pull request #12392 from appwrite/fix/preserve-sdk-enum-metadata
Update SDK spec metadata generation
2026-05-26 11:53:00 +05:30
Chirag Aggarwal c7caf8d23d fix: map numeric spec enum keys 2026-05-26 11:33:25 +05:30
Chirag Aggarwal a4d889145b test: expect client web version 2026-05-26 11:28:30 +05:30
Chirag Aggarwal fe1a2f6364 fix: preserve organization project region enum 2026-05-26 11:19:48 +05:30
Chirag Aggarwal 3f0d1d2a27 fix: preserve remaining spec enum metadata 2026-05-26 11:14:52 +05:30
Chirag Aggarwal 08b18f0774 fix: name preserved enum metadata 2026-05-26 11:06:34 +05:30
Chirag Aggarwal 6f1841681f fix: preserve runtime and migration enum metadata 2026-05-26 11:02:52 +05:30
Chirag Aggarwal 29d2a99e92 fix: remove weight from spec metadata 2026-05-26 10:53:50 +05:30
Chirag Aggarwal 17bae22b24 Revert "Merge pull request #12356 from appwrite/fix-account-server-specs-auth"
This reverts commit ff76f3efa0, reversing
changes made to ed14acfc75.
2026-05-26 10:51:52 +05:30
Chirag Aggarwal 6bc5d0dd06 fix: remove endpoint docs appwrite metadata 2026-05-26 10:51:10 +05:30
Chirag Aggarwal 7f97d91f66 fix: order spec defaults before examples 2026-05-26 10:48:13 +05:30
Chirag Aggarwal 6266dfb7ab fix: preserve remaining SDK enum metadata 2026-05-26 10:45:48 +05:30
Chirag Aggarwal 88648b78f0 fix: preserve SDK enum metadata 2026-05-26 10:30:50 +05:30
Chirag AggarwalandGitHub e7f720b8e7 Merge pull request #12358 from appwrite/feat/platform-param-enums
Use platform enum metadata for SDK specs
2026-05-26 10:10:43 +05:30
Jake Barnby d69844c08d fix: address notification review feedback 2026-05-26 16:12:39 +12:00
Jake Barnby 5b9e2eaca7 fix: remove unreachable alert helper return 2026-05-26 15:26:57 +12:00
Jake Barnby d260dd8f6f Merge remote-tracking branch 'origin/1.9.x' into feat-notifications-worker
# Conflicts:
#	app/init/constants.php
#	app/init/resources.php
#	app/init/worker/message.php
#	src/Appwrite/Platform/Appwrite.php
2026-05-26 15:22:11 +12:00
Jake Barnby a4b719a666 feat: track notification logo views 2026-05-26 15:16:08 +12:00
Chirag Aggarwal e19ca24d5d Update enum metadata tests 2026-05-25 20:36:14 +05:30
Chirag Aggarwal ee97d4ef4d Use compatible HTTP release for platform 2026-05-25 19:27:04 +05:30
Chirag Aggarwal 764fb0d156 Merge remote-tracking branch 'origin/1.9.x' into feat/platform-param-enums
# Conflicts:
#	composer.lock
2026-05-25 19:23:54 +05:30
Chirag Aggarwal a659b0b187 Use stable Utopia dependency releases 2026-05-25 19:21:35 +05:30
Chirag Aggarwal 91227249b8 chore: use enum metadata server branch 2026-05-20 17:47:45 +05:30
Chirag Aggarwal 83dc606c14 Use platform enum metadata for SDK specs 2026-05-20 17:19:35 +05:30
Jake Barnby a8772a53b8 fix: drain legacy mail queue 2026-05-17 12:04:51 +12:00
Jake Barnby 7479cf495a fix: personalize webhook pause emails 2026-05-17 11:44:38 +12:00
Jake Barnby 41e4483c3f test: update compose worker count 2026-05-17 11:21:28 +12:00
Jake Barnby 3924afb2e0 refactor: replace mails worker 2026-05-17 11:15:49 +12:00
Jake Barnby a5593c0e30 test: match webhook pause alerts by content 2026-05-17 01:08:52 +12:00
Jake Barnby 6c571aa5c7 fix: refresh webhook pause dedup keys 2026-05-17 00:47:48 +12:00
Jake Barnby 32aea1f863 fix: preserve legacy schedule access 2026-05-17 00:33:03 +12:00
Jake Barnby fde18b7c39 refactor: publish notification messages 2026-05-16 23:49:26 +12:00
Jake Barnby 90b7d6ac26 fix: trim alert indexes 2026-05-16 23:14:34 +12:00
Jake Barnby db7210eb69 docs: update alert reference 2026-05-16 22:47:17 +12:00
Jake Barnby c3642804e6 fix: repair notifications e2e fixture 2026-05-16 22:45:08 +12:00
5a2c5d6b55 refactor(account): patch alerts via read boolean
Agent-Logs-Url: https://github.com/appwrite/appwrite/sessions/d60871ef-6caf-48b7-b18a-4db70377eb20

Co-authored-by: abnegate <5857008+abnegate@users.noreply.github.com>
2026-05-16 10:44:19 +00:00
Jake Barnby d76ef18b57 fix: provision and clean up alert records 2026-05-16 22:35:28 +12:00
Jake Barnby 730b1cb723 fix: use resource scoped alert records 2026-05-16 22:29:53 +12:00
c5738b6a8e fix(alerts): use VAR_ID for projectInternalId
Agent-Logs-Url: https://github.com/appwrite/appwrite/sessions/e5a13d2c-8eba-4a7e-b98a-68faa7d2cdf3

Co-authored-by: abnegate <5857008+abnegate@users.noreply.github.com>
2026-05-16 10:09:36 +00:00
21d73af3e1 fix(platform): use VAR_ID for schedules projectInternalId
Agent-Logs-Url: https://github.com/appwrite/appwrite/sessions/c0a8700e-3763-4cf0-b19e-cc83b2e4318e

Co-authored-by: abnegate <5857008+abnegate@users.noreply.github.com>
2026-05-16 10:06:48 +00:00
Jake Barnby 62b0314bcb fix: scope notification alerts by project 2026-05-16 21:58:45 +12:00
Jake Barnby 70df121e6f fix: address notifications review comments 2026-05-16 19:37:21 +12:00
Jake Barnby bc9e67dc5b chore: merge 1.9.x into notifications worker 2026-05-16 18:39:08 +12:00
Jake BarnbyandClaude Opus 4.7 1f1a02f026 fix(ci): add missing alert endpoint docs and update compose test for notifications worker service
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 18:29:32 +12:00
Jake BarnbyandClaude Opus 4.7 ac87c0e2d6 test(notifications): add regression and happy-path coverage for review-fix critical gaps
Locks the bug-fix invariants from PR #12195's last review pass and rounds out
worker channel coverage:

C1 testEmailSendFailureDoesNotPersistAlert — SMTP throw must NOT leave a dedup
   row behind, retry must deliver and persist exactly once.
C2 testNotificationEventResetClearsAllState — reset() drops every state-bearing
   field including preview (regression: missed in original reset body).
C3 testWebhookSendAlertResetsBetweenCalls — Webhooks::sendAlert must reset()
   the DI-shared Notification event so two paused-webhook alerts in one worker
   pass do not bleed recipients/subject/body into each other.
C4 testConsoleAdapterTreatsDuplicateAsDelivered — Duplicate on createDocument
   must surface as a successful idempotent send, not a per-recipient error.
M7 testTrackingPixelRejectsJwtWithoutPurposeClaim — Track endpoint silently
   ignores JWTs missing or with the wrong purpose claim (defends against
   replaying session/reset JWTs to mark alerts read).

Worker happy-path tests: testEmailChannelHappyPath, testConsoleChannelHappyPath,
testWebhookChannelHappyPath cover the full per-channel dispatch contract end
to end, including HMAC signing for webhooks and the tracking pixel injection
+ post-send persistence for email.

Also extracts CapturingWebhook into its own PSR-4 file so reused across tests.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 18:02:12 +12:00
Jake Barnby f83993d51e merge: major review fixes (M2 drop SDK auth, M4 dedup unique-index test, M9 e2e secret assertion) 2026-05-06 16:15:27 +12:00
Jake Barnby a0ec2827fa merge: critical review fixes (C1 deferred persist, C2 reset preview, C3 webhook reset, C4 dup catch, M3 status, M5 id helper, M7 jwt purpose) 2026-05-06 16:15:23 +12:00
Jake BarnbyandClaude Opus 4.7 1aa6a05ad8 fix(notifications): drop misleading auth metadata, exercise unique-index dedup, fail loudly on missing tracking secret in tests
- Account/Alerts/Track endpoint is scope:public (email clients have no
  session); SDK Method now declares auth: [] so generators do not require
  an auth header for an unauthenticated endpoint.
- NotificationsTest setUp now creates the production `_key_recipient`
  UNIQUE composite index on (messageId, channel, userId, teamId), and
  testPersistAlertReturnsExistingAlertIdOnDuplicate exercises the
  DuplicateException → return-existing-alertId branch end-to-end (both
  primary-key collision and unique-index collision via a sibling $id).
- Tracking-pixel e2e now asserts _APP_OPENSSL_KEY_V1 is set instead of
  silently falling back to the .env.example placeholder, so a missing
  CI secret fails loudly rather than passing against the wrong key.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 16:13:52 +12:00
Jake BarnbyandClaude Opus 4.7 5f8cc27c9f fix(notifications): defer persistence until after send, reset event state, distinguish duplicate writes, scope tracking jwt purpose
- C1: dispatchEmail now writes the alert row only after a successful
  adapter send. The deterministic alertId is pre-computed via the new
  buildAlertId() helper so the tracking pixel URL stays stable, but the
  database write is deferred. Previously an SMTP failure left an orphan
  dedup row that permanently swallowed the email on retry.
- C2: Notification::reset() now clears $preview alongside the other
  per-trigger fields so state cannot leak across triggers.
- C3: Webhooks::sendAlert() resets the DI-shared notification queue at
  the top, preventing recipients/subject/body accumulation across
  multiple webhook failures in one worker invocation.
- C4: Console adapter catches DuplicateException separately and treats
  it as a successful (already-delivered) result, so an idempotent retry
  no longer surfaces as a delivery failure when combined with
  dispatchConsole's zero-delivery throw.
- M3: dispatchEmail throws plain 500 for any SMTP delivery failure;
  401 mis-signalled auth errors for timeouts/DNS/connection refused.
- M5: buildAlertId() is the single source of truth shared by
  persistAlert() and dispatchEmail(); no more drift between the
  tracking-pixel id and the persisted row's id.
- M7: Tracking JWT now carries a purpose='alert_track' claim and the
  /v1/account/alerts/:alertId/track decoder rejects tokens missing or
  mismatching that purpose, isolating the shared signing key from
  cross-endpoint replay.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 16:06:49 +12:00
Jake Barnby e125bf6aec merge: cross-db alerts persistence fix 2026-05-06 15:26:51 +12:00
Jake BarnbyandClaude Opus 4.7 aa9b45b5a1 fix(notifications): persist alerts to platform db regardless of dispatching project
The `alerts` collection lives in the platform database, but the Notifications
worker was injecting `dbForProject` and writing alerts there. Webhooks
dispatches with the user's project context, so alerts were being written to
the user's project DB (which has no `alerts` collection), and `/v1/account/alerts`
(which reads from `dbForPlatform`) returned nothing.

Switch the worker to inject `dbForPlatform` instead. All alert reads (dedup
lookup) and writes (persistAlert + ConsoleAdapter) now target the platform DB,
independent of which project the dispatching event belongs to.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 15:25:45 +12:00
Jake Barnby 5357e7dd35 fix(notifications): quote $id key in PHPDoc array shape
PHPStan rejects unquoted $id as a variable token in array shape syntax.
Quoting the key satisfies the parser without changing the runtime contract.
2026-05-06 15:24:25 +12:00
Jake Barnby a8ce7c3736 merge: ST17 e2e tests 2026-05-06 15:11:57 +12:00
Jake Barnby 540a7823cd merge: ST16 unit tests 2026-05-06 15:11:54 +12:00
Jake BarnbyandClaude Opus 4.7 534123cd87 test(notifications): add unit tests for dedup, fanout, console skip, zero-delivery, recipient struct, and tracking pixel
Cover the new worker behaviors introduced in waves 1-3:

- testDedupQueriesByAttributeNotById: prove alreadyDelivered() queries the
  messageId attribute, not getDocument($messageId), by seeding a row with
  a non-matching $id but matching messageId.
- testConsoleChannelSkipsPersistAlert: confirm the action loop does NOT
  call persistAlert for console recipients (the adapter persists).
- testConsoleZeroDeliveryThrows: surface adapter failures via the worker.
- testMultiRecipientFanoutNoCollision: same messageId across recipients
  must produce distinct $id values.
- testRecipientStructRoundtripsUserIdAndTeamId: persisted alert carries
  recipient userId/teamId.
- testTrackingPixelInjectedIntoEmailHtml: verify the tracking pixel is
  spliced before the last </body> tag with the expected URL shape.
- testPersistAlertReturnsAlertIdAndStoresUserId: dispatchEmail's
  persistAlert returns a resolvable id and stores userId + read=false.

ConsoleTest: add testMultiRecipientWithSameMessageIdGeneratesDistinctIds
and update existing tests to use the post-ST2 compound `$id` form
(messageId + 8-hex md5 suffix).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 15:10:38 +12:00
Jake Barnby 3e0df70f5c test(notifications): e2e tests for list, mark-read, tracking pixel, and webhook-failure fanout
Add 6 new test methods to NotificationsBase that exercise the wave3
account-alerts surface end-to-end:

- testListAccountAlertsEmpty: GET /v1/account/alerts shape check
- testWebhookFailureCreatesConsoleAlert: drives a webhook past
  _APP_WEBHOOK_MAX_FAILED_ATTEMPTS via user-create events and polls
  /account/alerts until the worker fans the paused alert out to the
  project owner on the console channel
- testMarkAlertReadTogglesFlag: PATCH /:alertId/read happy path
- testMarkAlertReadUnauthorized: stranger console user cannot mark
  someone else's alert as read; alert remains unread for the owner
- testTrackingPixelTogglesRead: GET /:alertId/track with a valid
  HS256 JWT signed with _APP_OPENSSL_KEY_V1 returns the canonical 1x1
  PNG and atomically marks the alert as read
- testTrackingPixelInvalidTokenReturnsPng: tampered JWT still gets a
  PNG (no information disclosure) but performs no DB write

Helpers:

- seedWebhookFailureAlert: registers a webhook pointing at an
  unroutable address (http://127.0.0.1:1/), drives max+2 user-create
  events through the project, polls assertEventually with a 60s budget
  for the paused alert keyed by the deterministic md5 of
  'webhook:<id>:paused:<attempts>'
- createConsoleUser: spins up a fresh, unrelated console user with its
  own session for the unauthorized assertion
- getConsoleAlertHeaders: console-session auth bundle reused across
  every alerts call, so the trait works identically under SideServer
  and SideConsole hosts
2026-05-06 15:09:28 +12:00
Jake Barnby 080b6dbeb2 merge: ST14 alerts Track body 2026-05-06 14:48:39 +12:00
Jake Barnby 678a6bd079 merge: ST13 alerts XList body 2026-05-06 14:48:36 +12:00
Jake Barnby 3b0c46c85e merge: ST12 PATCH /v1/account/alerts/:alertId/read 2026-05-06 14:48:33 +12:00
Jake Barnby c419257755 merge: ST11 Webhooks worker switch to Notification 2026-05-06 14:48:29 +12:00
Jake BarnbyandClaude Opus 4.7 067bf582dc feat(webhooks): emit pausing alerts via notifications worker to owners on email and console
When a webhook is permanently paused after exceeding the max failure
threshold, fan out the alert through the new Notifications worker
instead of the legacy Mails worker. Recipients are filtered to project
owners only and receive both an email and a console notification, with
a single deduplication key per webhook + attempts so duplicate triggers
collapse downstream.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 14:46:57 +12:00
Jake BarnbyandClaude Opus 4.7 dae98bf240 feat(notifications): track email opens by decoding pixel JWT and marking alert read
Replace placeholder body with JWT decode, alertId/userId match validation,
sparse update of read=true via injected Authorization::skip. Always returns
1x1 PNG regardless of decode outcome to avoid leaking JWT validity through
response status.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 14:46:33 +12:00
Jake BarnbyandClaude Opus 4.7 5dd987711b feat(notifications): implement GET /v1/account/alerts list endpoint body
Replace the placeholder body of the listAlerts action with real
implementation: scope alerts to the current user via userId equality,
parse incoming queries, support cursor pagination over the alerts
collection, run filters through count for total, and return
MODEL_ALERT_LIST.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 14:46:22 +12:00
Jake BarnbyandClaude Opus 4.7 e54fbf3b3f feat(notifications): mark alert read via PATCH /v1/account/alerts/:alertId/read
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 14:35:53 +12:00
Jake Barnby 0a9b54543d merge: ST10 queueForNotifications DI registration 2026-05-06 14:20:10 +12:00
Jake Barnby 93bdebb7fa merge: ST9 notifications worker fixes 2026-05-06 14:20:07 +12:00
Jake BarnbyandClaude Opus 4.7 6f01c1492f fix(notifications): worker dedup by attribute, throw on console-zero-delivery, thread recipient userId, inject email tracking pixel
Apply the four Greptile P1 fixes to the Notifications worker and
extend it for C3 email read tracking and ST4's stripped SMTP plumbing.

P1 #1: alreadyDelivered() now queries the indexed messageId attribute
instead of getDocument($messageId). The action loop and Console
adapter both write compound `$id`s (messageId + recipient hash), so
the previous direct-id lookup always missed.

P1 #3: action() no longer calls persistAlert after dispatchConsole;
ConsoleAdapter persists internally. Email persists inside
dispatchEmail BEFORE the adapter send so the alertId is available for
the tracking pixel; webhook persists in the action loop after a
successful HTTP send.

P1 #4: dispatchConsole now throws when the adapter reports
`deliveredTo === 0`, surfacing the per-recipient error.

Recipient threading: dispatch() now takes the full recipient map and
returns the alertId (or null when persistence is the caller's
responsibility). persistAlert() reads userId/teamId from the
recipient and grants per-user / per-team-owner CRUD permissions,
falling back to payload permissions only when neither is set. The
returned alertId lets dispatchEmail splice a 1x1 tracking pixel
before the last `</body>` tag, signed with a 30-day HS256 JWT
(_APP_OPENSSL_KEY_V1) carrying {alertId, userId}.

SMTP resolution: ST4 stripped `smtp` and `customMailOptions` from
the Notification event payload, so the worker now resolves SMTP
from the injected project Document (mirroring Mails.php /
Memberships/Create.php), falling back to the env-driven cloud SMTP
adapter when the project has no enabled override.

Tests updated: SpyNotifications.dispatch() matches the new
signature and emulates per-channel persistence so existing routing
assertions keep their semantics. Memory `alerts` collection adds
the `read` boolean attribute to mirror platform.php.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 14:16:25 +12:00
Jake BarnbyandClaude Opus 4.7 4e7236a366 feat(notifications): register queueForNotifications resource in worker and request containers
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 14:00:45 +12:00
Jake BarnbyandClaude Opus 4.7 8c7ecd6cdb fix(notifications): add SDK Method group to alerts skeleton actions
PHPStan flagged missing required `group` parameter on the SDK Method
constructors for the alerts XList and Track\Get skeleton actions.
Group set to 'alerts' to align with the resource grouping convention
used elsewhere in the account namespace.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 13:57:07 +12:00
Jake Barnby 0f1a2b5097 merge: ST4 event recipient + SMTP removal
# Conflicts:
#	src/Appwrite/Event/Notification.php
2026-05-06 13:48:47 +12:00
Jake Barnby a02eb3f41c merge: ST1 NOTIFICATION_TYPE_* rename 2026-05-06 13:48:16 +12:00
Jake Barnby ddffc69914 merge: ST8 alerts track skeleton
# Conflicts:
#	src/Appwrite/Platform/Modules/Account/Services/Http.php
2026-05-06 13:48:10 +12:00
Jake Barnby 439b3db2bf merge: ST7 alerts XList skeleton 2026-05-06 13:47:05 +12:00
Jake Barnby 134a8710da merge: ST6 alerts queries validator 2026-05-06 13:46:59 +12:00
Jake Barnby b9a928d3f8 merge: ST5 alert response model 2026-05-06 13:46:53 +12:00
Jake Barnby d07292ef1f merge: ST3 alerts schema 2026-05-06 13:46:49 +12:00
Jake Barnby 8cafc8c542 merge: ST2 console $id collision fix 2026-05-06 13:46:46 +12:00
Jake BarnbyandClaude Opus 4.7 a0400d2e93 feat(notifications): scaffold GET /v1/account/alerts/:alertId/track endpoint
Adds a public-scope tracking-pixel endpoint skeleton that always returns a
1x1 transparent PNG. The body is intentionally minimal so the email-tracking
read-flag flip can be wired up cleanly in Wave 3 (ST14) once the JWT decode
and DB write paths are designed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 13:44:58 +12:00
Jake BarnbyandClaude Opus 4.7 d79be3a6e3 feat(notifications): add alert and alertList response models
Adds Alert response model and MODEL_ALERT/MODEL_ALERT_LIST constants
so future GET /v1/account/alerts endpoint can return MODEL_ALERT_LIST.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 13:44:27 +12:00
Jake BarnbyandClaude Opus 4.7 55275259d9 feat(notifications): scaffold GET /v1/account/alerts endpoint
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 13:44:09 +12:00
Jake BarnbyandClaude Opus 4.7 2c2e920a1a feat(notifications): add alerts queries validator
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 13:42:23 +12:00
Jake BarnbyandClaude Opus 4.7 4351dffb88 refactor(notifications): drop SMTP plumbing from event and extend recipient struct with userId/teamId
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 13:42:22 +12:00
Jake BarnbyandClaude Opus 4.7 a5587ff096 feat(notifications): track read state and enable per-recipient alert idempotency
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 13:42:10 +12:00
Jake BarnbyandClaude Opus 4.7 3df219df66 refactor(notifications): consolidate channel constants under NOTIFICATION_TYPE_* prefix
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 13:41:59 +12:00
Jake BarnbyandClaude Opus 4.7 36e1615498 fix(notifications): suffix Console adapter $id with recipient hash to avoid multi-recipient collision
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 13:41:11 +12:00
Jake BarnbyandClaude Opus 4.7 0ce3978f2c feat(notifications): add worker entrypoint and registration alongside Mails
Adds the bin/worker-notifications entrypoint, Dockerfile chmod, and
docker-compose service definitions for the new Notifications worker
without disturbing the existing Mails worker, and re-adds the Mails
queue/class constants on Event so the legacy callers still resolve.
The full mail->notification swap (caller migration, Mails removal,
worker rename) will land as a follow-up.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 15:56:43 +12:00
Jake Barnby d6be6e165b test(notifications): split e2e into base + server/console overlays
Mirrors the dominant e2e pattern (FunctionsBase/MigrationsBase): the
shared health-queue assertions live in a NotificationsBase trait,
with thin per-side overlays for ProjectCustom + SideServer and
ProjectCustom + SideConsole.
2026-05-01 15:51:25 +12:00
Jake Barnby 7839b3bb3f refactor(notifications): align webhook signing with per-recipient signatureKey
Drop the global _APP_NOTIFICATIONS_WEBHOOK_SECRET env var. There's no
analogous global webhook secret in Appwrite; the existing Webhooks
worker carries a per-webhook signatureKey on the webhook document.

Move the same pattern into the Notification event: each webhook
recipient may carry an optional signatureKey, which the worker
forwards to the Webhook adapter for HMAC-SHA256 signing. Recipients
without a key are delivered unsigned and a tag is logged for audit.
2026-05-01 15:51:25 +12:00
Jake Barnby 5320c9441b refactor(notifications): rename dedupKey to deduplicationKey
No abbreviations in identifier names.
2026-05-01 15:51:25 +12:00
Jake Barnby 8ad73aa6bb refactor(notifications): use Utopia Fetch client in Webhook adapter
Replace raw curl with Utopia\Fetch\Client to align with the rest of
the codebase (Github OAuth adapter, Install task). The dispatch seam
is preserved so existing tests can still substitute responses.
2026-05-01 15:51:25 +12:00
Jake Barnby 5cbbacaae5 refactor(notifications): move adapters under Appwrite\Utopia\Messaging
Mirror the upstream Utopia\Messaging package namespace under the
Appwrite\Utopia\Messaging prefix, matching the convention used by
Appwrite\Utopia\Database and Appwrite\Utopia\Response.
2026-05-01 15:51:25 +12:00
Jake Barnby 5ee92e1844 fix(notifications): move alerts collection to platform DB
Alerts is a platform-DB-only concern. Move from common.php to
platform.php where it belongs alongside other platform collections.
2026-05-01 15:51:25 +12:00
Jake BarnbyandClaude Opus 4.7 a546c41662 docs(notifications): document _APP_NOTIFICATIONS_WEBHOOK_SECRET
Registers the webhook signing secret in app/config/variables.php under a
new Notifications category, threads it through the worker container in
docker-compose.yml and tests/resources/docker/docker-compose.yml, and
adds an empty default to .env so operators see the knob alongside the
existing SMTP block.

When set, outbound webhook deliveries from the notifications worker
include an X-Appwrite-Webhook-Signature header carrying
sha256=<hex(hmac_sha256(timestamp.body, secret))>. When unset, the
worker delivers payloads unsigned — receivers must decide whether to
accept them.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 15:51:25 +12:00
Jake BarnbyandClaude Opus 4.7 df16c84b51 test(notifications): e2e coverage for notifications queue health
Adds the Notifications e2e suite under tests/e2e/Services/Notifications.
Asserts that the live notifications queue depth is reported via
GET /v1/health/queue/notifications, that the threshold guard is honoured,
and that the failed-jobs endpoint accepts the v1-notifications queue name.

Dispatch routing, dedup, and webhook signing are covered by the unit
suite — the worker cannot be deterministically driven through the live
queue from a test client without an admin enqueue endpoint, so the e2e
file pins the public health contract that ops dashboards and KEDA scale
on.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 15:51:24 +12:00
Jake BarnbyandClaude Opus 4.7 6bde54675e test(notifications): unit tests for worker and adapters
Covers the dedup short-circuit, per-channel dispatch routing, alert
persistence, error tagging, and legacy single-recipient fallback in the
Notifications worker, plus the Console adapter's permission shape and the
Webhook adapter's HMAC-SHA256 signing contract, header layout, response
handling, and unsigned-when-secret-missing behaviour.

Worker dispatch helpers move from private to protected so a test spy can
override them without monkey-patching. The Swoole runtime hook flag
mutation is now guarded by class_exists so the action can run under bare
PHPUnit (no Swoole extension on the test host).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-01 15:51:24 +12:00
Jake Barnby 90ef2d7487 feat(notifications): add Notifications worker and register it
Replaces the Mails worker. Resolves recipients into email, console, and
webhook channels, deduplicates deliveries via a hashed dedupKey, and persists
alerts for the console channel.
2026-05-01 12:07:42 +12:00
Jake Barnby 725ba86239 feat(notifications): add notifications schema and channel constants
Adds the alerts collection used by the console channel, plus channel and
provider type constants shared by the worker and adapters.
2026-05-01 12:07:40 +12:00
Jake Barnby 99641f24a5 feat(notifications): add Console and Webhook provider adapters
Console adapter persists alerts directly to the project database. Webhook
adapter dispatches signed JSON payloads (HMAC-SHA256) to subscriber URLs.
2026-05-01 12:07:38 +12:00
Jake Barnby 34935b8eed feat(notifications): add Notification event class
Replaces the Mail event with a multi-channel Notification event capable of
dispatching to email, console, and webhook recipients in a single payload.
Maintains the existing Mail surface (setSubject, setRecipient, setBody, etc.)
so existing call sites can be migrated mechanically.
2026-05-01 12:07:32 +12:00
196 changed files with 6798 additions and 1573 deletions
+1
View File
@@ -24,6 +24,7 @@ _APP_OPTIONS_ROUTER_PROTECTION=disabled
_APP_OPTIONS_FORCE_HTTPS=disabled
_APP_OPTIONS_ROUTER_FORCE_HTTPS=disabled
_APP_OPENSSL_KEY_V1=your-secret-key
_APP_NOTIFICATIONS_TRACKING_SECRET=your-secret-key
_APP_DNS=172.16.238.100 # CoreDNS
_APP_DOMAIN=appwrite.test
_APP_CONSOLE_DOMAIN=localhost
+1
View File
@@ -441,6 +441,7 @@ jobs:
Webhooks,
VCS,
Messaging,
Notifications,
Migrations,
Project,
Presences
+1
View File
@@ -93,6 +93,7 @@ RUN chmod +x /usr/local/bin/doctor && \
chmod +x /usr/local/bin/worker-functions && \
chmod +x /usr/local/bin/worker-mails && \
chmod +x /usr/local/bin/worker-messaging && \
chmod +x /usr/local/bin/worker-notifications && \
chmod +x /usr/local/bin/worker-migrations && \
chmod +x /usr/local/bin/worker-webhooks && \
chmod +x /usr/local/bin/worker-stats-usage && \
+257
View File
@@ -474,6 +474,17 @@ $platformCollections = [
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('projectInternalId'),
'type' => Database::VAR_ID,
'format' => '',
'size' => 0,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('schedule'),
'type' => Database::VAR_STRING,
@@ -534,6 +545,13 @@ $platformCollections = [
'lengths' => [],
'orders' => [],
],
[
'$id' => ID::custom('_key_region_resourceType_projectInternalId_resourceId'),
'type' => Database::INDEX_KEY,
'attributes' => ['region', 'resourceType', 'projectInternalId', 'resourceId'],
'lengths' => [],
'orders' => [],
],
[
'$id' => ID::custom('_key_project_id_region'),
'type' => Database::INDEX_KEY,
@@ -541,6 +559,13 @@ $platformCollections = [
'lengths' => [],
'orders' => [],
],
[
'$id' => ID::custom('_key_project_internal_id_region'),
'type' => Database::INDEX_KEY,
'attributes' => ['projectInternalId', 'region'],
'lengths' => [],
'orders' => [],
],
[
'$id' => ID::custom('_key_region_rt_active'),
'type' => Database::INDEX_KEY,
@@ -1032,6 +1057,238 @@ $platformCollections = [
],
],
'alerts' => [
'$collection' => ID::custom(Database::METADATA),
'$id' => ID::custom('alerts'),
'name' => 'Alerts',
'attributes' => [
[
'$id' => ID::custom('messageId'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => false,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('recipientHash'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 64,
'signed' => true,
'required' => true,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('type'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 64,
'signed' => true,
'required' => false,
'default' => 'info',
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('channel'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 64,
'signed' => true,
'required' => true,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('resourceType'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 64,
'signed' => true,
'required' => true,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('resourceId'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => true,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('projectId'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => true,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('projectInternalId'),
'type' => Database::VAR_ID,
'format' => '',
'size' => 0,
'signed' => true,
'required' => true,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('resourceInternalId'),
'type' => Database::VAR_ID,
'format' => '',
'size' => 0,
'signed' => true,
'required' => true,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('parentResourceType'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 64,
'signed' => true,
'required' => true,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('parentResourceId'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => Database::LENGTH_KEY,
'signed' => true,
'required' => true,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('parentResourceInternalId'),
'type' => Database::VAR_ID,
'format' => '',
'size' => 0,
'signed' => true,
'required' => true,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('title'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 256,
'signed' => true,
'required' => true,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('body'),
'type' => Database::VAR_STRING,
'format' => '',
'size' => 16384,
'signed' => true,
'required' => true,
'default' => null,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('read'),
'type' => Database::VAR_BOOLEAN,
'format' => '',
'size' => 0,
'signed' => true,
'required' => false,
'default' => false,
'array' => false,
'filters' => [],
],
[
'$id' => ID::custom('firstSeen'),
'type' => Database::VAR_DATETIME,
'format' => '',
'size' => 0,
'signed' => false,
'required' => false,
'default' => null,
'array' => false,
'filters' => ['datetime'],
],
[
'$id' => ID::custom('lastSeen'),
'type' => Database::VAR_DATETIME,
'format' => '',
'size' => 0,
'signed' => false,
'required' => false,
'default' => null,
'array' => false,
'filters' => ['datetime'],
],
],
'indexes' => [
[
'$id' => ID::custom('_key_messageId'),
'type' => Database::INDEX_KEY,
'attributes' => ['messageId'],
'lengths' => [Database::LENGTH_KEY],
'orders' => [Database::ORDER_ASC],
],
[
'$id' => ID::custom('_key_recipient'),
'type' => Database::INDEX_UNIQUE,
'attributes' => ['messageId', 'channel', 'recipientHash'],
'lengths' => [Database::LENGTH_KEY, 64, 64],
'orders' => [Database::ORDER_ASC, Database::ORDER_ASC, Database::ORDER_ASC],
],
[
'$id' => ID::custom('_key_project'),
'type' => Database::INDEX_KEY,
'attributes' => ['projectId', 'projectInternalId'],
'lengths' => [Database::LENGTH_KEY, 0],
'orders' => [Database::ORDER_ASC, Database::ORDER_ASC],
],
[
'$id' => ID::custom('_key_project_resource'),
'type' => Database::INDEX_KEY,
'attributes' => ['projectId', 'projectInternalId', 'resourceType', 'resourceId', 'resourceInternalId'],
'lengths' => [Database::LENGTH_KEY, 0, 64, Database::LENGTH_KEY, 0],
'orders' => [Database::ORDER_ASC, Database::ORDER_ASC, Database::ORDER_ASC, Database::ORDER_ASC, Database::ORDER_ASC],
],
[
'$id' => ID::custom('_key_project_parent_resource'),
'type' => Database::INDEX_KEY,
'attributes' => ['projectId', 'projectInternalId', 'parentResourceType', 'parentResourceId', 'parentResourceInternalId'],
'lengths' => [Database::LENGTH_KEY, 0, 64, Database::LENGTH_KEY, 0],
'orders' => [Database::ORDER_ASC, Database::ORDER_ASC, Database::ORDER_ASC, Database::ORDER_ASC, Database::ORDER_ASC],
],
],
],
'certificates' => [
'$collection' => ID::custom(Database::METADATA),
'$id' => ID::custom('certificates'),
+49 -49
View File
@@ -8,6 +8,55 @@ return [
'enabled' => true,
'beta' => false,
'sdks' => [
[
'key' => 'web',
'name' => 'Web',
'version' => '22.4.0',
'url' => 'https://github.com/appwrite/sdk-for-web',
'package' => 'https://www.npmjs.com/package/appwrite',
'enabled' => true,
'beta' => false,
'dev' => false,
'hidden' => false,
'family' => APP_SDK_PLATFORM_CLIENT,
'prism' => 'javascript',
'source' => \realpath(__DIR__ . '/../sdks/client-web'),
'gitUrl' => 'git@github.com:appwrite/sdk-for-web.git',
'gitRepoName' => 'sdk-for-web',
'gitUserName' => 'appwrite',
'gitBranch' => 'dev',
'changelog' => \realpath(__DIR__ . '/../../docs/sdks/web/CHANGELOG.md'),
'demos' => [
[
'icon' => 'react.svg',
'name' => 'Todo App with React JS',
'description' => 'A simple Todo app that uses both the Appwrite account and database APIs.',
'source' => 'https://github.com/appwrite/todo-with-react',
'url' => 'https://appwrite-todo-with-react.vercel.app/',
],
[
'icon' => 'vue.svg',
'name' => 'Todo App with Vue JS',
'description' => 'A simple Todo app that uses both the Appwrite account and database APIs.',
'source' => 'https://github.com/appwrite/todo-with-vue',
'url' => 'https://appwrite-todo-with-vue.vercel.app/',
],
[
'icon' => 'angular.svg',
'name' => 'Todo App with Angular',
'description' => 'A simple Todo app that uses both the Appwrite account and database APIs.',
'source' => 'https://github.com/appwrite/todo-with-angular',
'url' => 'https://appwrite-todo-with-angular.vercel.app/',
],
[
'icon' => 'svelte.svg',
'name' => 'Todo App with Svelte',
'description' => 'A simple Todo app that uses both the Appwrite account and database APIs.',
'source' => 'https://github.com/appwrite/todo-with-svelte',
'url' => 'https://appwrite-todo-with-svelte.vercel.app/',
],
]
],
[
'key' => 'flutter',
'name' => 'Flutter',
@@ -301,55 +350,6 @@ return [
'enabled' => true,
'beta' => false,
'sdks' => [
[
'key' => 'web',
'name' => 'Web',
'version' => '22.4.0',
'url' => 'https://github.com/appwrite/sdk-for-web',
'package' => 'https://www.npmjs.com/package/appwrite',
'enabled' => true,
'beta' => false,
'dev' => false,
'hidden' => false,
'family' => APP_SDK_PLATFORM_SERVER,
'prism' => 'javascript',
'source' => \realpath(__DIR__ . '/../sdks/client-web'),
'gitUrl' => 'git@github.com:appwrite/sdk-for-web.git',
'gitRepoName' => 'sdk-for-web',
'gitUserName' => 'appwrite',
'gitBranch' => 'dev',
'changelog' => \realpath(__DIR__ . '/../../docs/sdks/web/CHANGELOG.md'),
'demos' => [
[
'icon' => 'react.svg',
'name' => 'Todo App with React JS',
'description' => 'A simple Todo app that uses both the Appwrite account and database APIs.',
'source' => 'https://github.com/appwrite/todo-with-react',
'url' => 'https://appwrite-todo-with-react.vercel.app/',
],
[
'icon' => 'vue.svg',
'name' => 'Todo App with Vue JS',
'description' => 'A simple Todo app that uses both the Appwrite account and database APIs.',
'source' => 'https://github.com/appwrite/todo-with-vue',
'url' => 'https://appwrite-todo-with-vue.vercel.app/',
],
[
'icon' => 'angular.svg',
'name' => 'Todo App with Angular',
'description' => 'A simple Todo app that uses both the Appwrite account and database APIs.',
'source' => 'https://github.com/appwrite/todo-with-angular',
'url' => 'https://appwrite-todo-with-angular.vercel.app/',
],
[
'icon' => 'svelte.svg',
'name' => 'Todo App with Svelte',
'description' => 'A simple Todo app that uses both the Appwrite account and database APIs.',
'source' => 'https://github.com/appwrite/todo-with-svelte',
'url' => 'https://appwrite-todo-with-svelte.vercel.app/',
],
]
],
[
'key' => 'nodejs',
'name' => 'Node.js',
+9
View File
@@ -79,6 +79,15 @@ return [
'question' => 'Choose a secret API key, make sure to make a backup of your key in a secure location',
'filter' => 'token'
],
[
'name' => '_APP_NOTIFICATIONS_TRACKING_SECRET',
'description' => 'Secret key used to sign notification logo tracking tokens. This must be the same value for API containers and notification workers. Change it from the default value before running in production.',
'introduction' => '1.9.0',
'default' => 'your-secret-key',
'required' => true,
'question' => '',
'filter' => 'token'
],
[
'name' => '_APP_DOMAIN',
'description' => 'Your Appwrite domain address. When setting a public suffix domain, Appwrite will attempt to issue a valid SSL certificate automatically. When used with a dev domain, Appwrite will assign a self-signed SSL certificate. The default value is \'localhost\'. Multiple domains can be separated by commas.',
+7 -5
View File
@@ -67,6 +67,7 @@ use Utopia\Emails\Email;
use Utopia\Emails\Validator\Email as EmailValidator;
use Utopia\Http\Http;
use Utopia\Locale\Locale;
use Utopia\Platform\Enum;
use Utopia\Storage\Validator\FileName;
use Utopia\System\System;
use Utopia\Validator;
@@ -1239,10 +1240,11 @@ Http::get('/v1/account/sessions/oauth2/:provider')
)
],
contentType: ContentType::HTML,
hide: [APP_SDK_PLATFORM_SERVER],
))
->label('abuse-limit', 50)
->label('abuse-key', 'ip:{ip}')
->param('provider', '', new WhiteList(\array_keys(Config::getParam('oAuthProviders')), true), 'OAuth2 Provider. Currently, supported providers are: ' . \implode(', ', \array_keys(\array_filter(Config::getParam('oAuthProviders'), fn ($node) => (!$node['mock'])))) . '.')
->param('provider', '', new WhiteList(\array_keys(Config::getParam('oAuthProviders')), true), 'OAuth2 Provider. Currently, supported providers are: ' . \implode(', ', \array_keys(\array_filter(Config::getParam('oAuthProviders'), fn ($node) => (!$node['mock'])))) . '.', enum: new Enum(name: 'OAuthProvider', exclude: ['mock', 'mock-unverified']))
->param('success', '', fn ($redirectValidator) => $redirectValidator, 'URL to redirect back to your app after a successful login attempt. Only URLs from hostnames in your project\'s 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, ['redirectValidator'])
->param('failure', '', fn ($redirectValidator) => $redirectValidator, 'URL to redirect back to your app after a failed login attempt. Only URLs from hostnames in your project\'s 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, ['redirectValidator'])
->param('scopes', [], new ArrayList(new Text(APP_LIMIT_ARRAY_ELEMENT_SIZE), APP_LIMIT_ARRAY_PARAMS_SIZE), 'A list of custom OAuth2 scopes. Check each provider internal docs for a list of supported scopes. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' scopes are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long.', true)
@@ -2008,7 +2010,7 @@ Http::get('/v1/account/tokens/oauth2/:provider')
))
->label('abuse-limit', 50)
->label('abuse-key', 'ip:{ip}')
->param('provider', '', new WhiteList(\array_keys(Config::getParam('oAuthProviders')), true), 'OAuth2 Provider. Currently, supported providers are: ' . \implode(', ', \array_keys(\array_filter(Config::getParam('oAuthProviders'), fn ($node) => (!$node['mock'])))) . '.')
->param('provider', '', new WhiteList(\array_keys(Config::getParam('oAuthProviders')), true), 'OAuth2 Provider. Currently, supported providers are: ' . \implode(', ', \array_keys(\array_filter(Config::getParam('oAuthProviders'), fn ($node) => (!$node['mock'])))) . '.', enum: new Enum(name: 'OAuthProvider', exclude: ['mock', 'mock-unverified']))
->param('success', '', fn ($redirectValidator) => $redirectValidator, 'URL to redirect back to your app after a successful login attempt. Only URLs from hostnames in your project\'s 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, ['redirectValidator'])
->param('failure', '', fn ($redirectValidator) => $redirectValidator, 'URL to redirect back to your app after a failed login attempt. Only URLs from hostnames in your project\'s 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, ['redirectValidator'])
->param('scopes', [], new ArrayList(new Text(APP_LIMIT_ARRAY_ELEMENT_SIZE), APP_LIMIT_ARRAY_PARAMS_SIZE), 'A list of custom OAuth2 scopes. Check each provider internal docs for a list of supported scopes. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' scopes are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long.', true)
@@ -4514,7 +4516,7 @@ Http::post('/v1/account/targets/push')
group: 'pushTargets',
name: 'createPushTarget',
description: '/docs/references/account/create-push-target.md',
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::JWT],
auth: [AuthType::ADMIN, AuthType::SESSION],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_CREATED,
@@ -4598,7 +4600,7 @@ Http::put('/v1/account/targets/:targetId/push')
group: 'pushTargets',
name: 'updatePushTarget',
description: '/docs/references/account/update-push-target.md',
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::JWT],
auth: [AuthType::ADMIN, AuthType::SESSION],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
@@ -4668,7 +4670,7 @@ Http::delete('/v1/account/targets/:targetId/push')
group: 'pushTargets',
name: 'deletePushTarget',
description: '/docs/references/account/delete-push-target.md',
auth: [AuthType::ADMIN, AuthType::SESSION, AuthType::JWT],
auth: [AuthType::ADMIN, AuthType::SESSION],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_NOCONTENT,
+14 -4
View File
@@ -47,6 +47,7 @@ use Utopia\Database\Validator\UID;
use Utopia\Emails\Validator\Email;
use Utopia\Http\Http;
use Utopia\Locale\Locale;
use Utopia\Platform\Enum;
use Utopia\System\System;
use Utopia\Validator\ArrayList;
use Utopia\Validator\Boolean;
@@ -364,7 +365,7 @@ Http::post('/v1/messaging/providers/smtp')
->param('port', 587, new Range(1, 65535), 'The default SMTP server port.', true)
->param('username', '', new Text(0), 'Authentication username.', true)
->param('password', '', new Text(0), 'Authentication password.', true)
->param('encryption', '', new WhiteList(['none', 'ssl', 'tls']), 'Encryption type. Can be omitted, \'ssl\', or \'tls\'', true)
->param('encryption', '', new WhiteList(['none', 'ssl', 'tls']), 'Encryption type. Can be omitted, \'ssl\', or \'tls\'', true, enum: new Enum(name: 'SmtpEncryption'))
->param('autoTLS', true, new Boolean(), 'Enable SMTP AutoTLS feature.', true)
->param('mailer', '', new Text(0), 'The value to use for the X-Mailer header.', true)
->param('fromName', '', new Text(128, 0), 'Sender Name.', true)
@@ -1602,7 +1603,7 @@ Http::patch('/v1/messaging/providers/smtp/:providerId')
->param('port', null, new Nullable(new Range(1, 65535)), 'SMTP port.', true)
->param('username', '', new Text(0), 'Authentication username.', true)
->param('password', '', new Text(0), 'Authentication password.', true)
->param('encryption', '', new WhiteList(['none', 'ssl', 'tls']), 'Encryption type. Can be \'ssl\' or \'tls\'', true)
->param('encryption', '', new WhiteList(['none', 'ssl', 'tls']), 'Encryption type. Can be \'ssl\' or \'tls\'', true, enum: new Enum(name: 'SmtpEncryption'))
->param('autoTLS', null, new Nullable(new Boolean()), 'Enable SMTP AutoTLS feature.', true)
->param('mailer', '', new Text(0), 'The value to use for the X-Mailer header.', true)
->param('fromName', '', new Text(128), 'Sender Name.', true)
@@ -3292,6 +3293,7 @@ Http::post('/v1/messaging/messages/email')
'resourceInternalId' => $message->getSequence(),
'resourceUpdatedAt' => DateTime::now(),
'projectId' => $project->getId(),
'projectInternalId' => $project->getSequence(),
'schedule' => $scheduledAt,
'active' => true,
]));
@@ -3438,6 +3440,7 @@ Http::post('/v1/messaging/messages/sms')
'resourceInternalId' => $message->getSequence(),
'resourceUpdatedAt' => DateTime::now(),
'projectId' => $project->getId(),
'projectInternalId' => $project->getSequence(),
'schedule' => $scheduledAt,
'active' => true,
]));
@@ -3501,7 +3504,7 @@ Http::post('/v1/messaging/messages/push')
->param('scheduledAt', null, new Nullable(new DatetimeValidator(requireDateInFuture: true)), 'Scheduled delivery time for message in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. DateTime value must be in future.', true)
->param('contentAvailable', false, new Boolean(), 'If set to true, the notification will be delivered in the background. Available only for iOS Platform.', true)
->param('critical', false, new Boolean(), 'If set to true, the notification will be marked as critical. This requires the app to have the critical notification entitlement. Available only for iOS Platform.', true)
->param('priority', 'high', new WhiteList(['normal', 'high']), 'Set the notification priority. "normal" will consider device state and may not deliver notifications immediately. "high" will always attempt to immediately deliver the notification.', true)
->param('priority', 'high', new WhiteList(['normal', 'high']), 'Set the notification priority. "normal" will consider device state and may not deliver notifications immediately. "high" will always attempt to immediately deliver the notification.', true, enum: new Enum(name: 'MessagePriority'))
->inject('queueForEvents')
->inject('dbForProject')
->inject('dbForPlatform')
@@ -3660,6 +3663,7 @@ Http::post('/v1/messaging/messages/push')
'resourceInternalId' => $message->getSequence(),
'resourceUpdatedAt' => DateTime::now(),
'projectId' => $project->getId(),
'projectInternalId' => $project->getSequence(),
'schedule' => $scheduledAt,
'active' => true,
]));
@@ -4054,6 +4058,7 @@ Http::patch('/v1/messaging/messages/email/:messageId')
'resourceInternalId' => $message->getSequence(),
'resourceUpdatedAt' => DateTime::now(),
'projectId' => $project->getId(),
'projectInternalId' => $project->getSequence(),
'schedule' => $scheduledAt,
'active' => $status === MessageStatus::SCHEDULED,
]));
@@ -4070,6 +4075,7 @@ Http::patch('/v1/messaging/messages/email/:messageId')
}
$schedule
->setAttribute('projectInternalId', $project->getSequence())
->setAttribute('resourceUpdatedAt', DateTime::now())
->setAttribute('active', $scheduledStatus);
@@ -4278,6 +4284,7 @@ Http::patch('/v1/messaging/messages/sms/:messageId')
'resourceInternalId' => $message->getSequence(),
'resourceUpdatedAt' => DateTime::now(),
'projectId' => $project->getId(),
'projectInternalId' => $project->getSequence(),
'schedule' => $scheduledAt,
'active' => $status === MessageStatus::SCHEDULED,
]));
@@ -4294,6 +4301,7 @@ Http::patch('/v1/messaging/messages/sms/:messageId')
}
$schedule
->setAttribute('projectInternalId', $project->getSequence())
->setAttribute('resourceUpdatedAt', DateTime::now())
->setAttribute('active', $scheduledStatus);
@@ -4388,7 +4396,7 @@ Http::patch('/v1/messaging/messages/push/:messageId')
->param('scheduledAt', null, new Nullable(new DatetimeValidator(requireDateInFuture: true)), 'Scheduled delivery time for message in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. DateTime value must be in future.', true)
->param('contentAvailable', null, new Nullable(new Boolean()), 'If set to true, the notification will be delivered in the background. Available only for iOS Platform.', true)
->param('critical', null, new Nullable(new Boolean()), 'If set to true, the notification will be marked as critical. This requires the app to have the critical notification entitlement. Available only for iOS Platform.', true)
->param('priority', null, new Nullable(new WhiteList(['normal', 'high'])), 'Set the notification priority. "normal" will consider device battery state and may send notifications later. "high" will always attempt to immediately deliver the notification.', true)
->param('priority', null, new Nullable(new WhiteList(['normal', 'high'])), 'Set the notification priority. "normal" will consider device battery state and may send notifications later. "high" will always attempt to immediately deliver the notification.', true, enum: new Enum(name: 'MessagePriority'))
->inject('queueForEvents')
->inject('dbForProject')
->inject('dbForPlatform')
@@ -4455,6 +4463,7 @@ Http::patch('/v1/messaging/messages/push/:messageId')
'resourceInternalId' => $message->getSequence(),
'resourceUpdatedAt' => DateTime::now(),
'projectId' => $project->getId(),
'projectInternalId' => $project->getSequence(),
'schedule' => $scheduledAt,
'active' => $status === MessageStatus::SCHEDULED,
]));
@@ -4471,6 +4480,7 @@ Http::patch('/v1/messaging/messages/push/:messageId')
}
$schedule
->setAttribute('projectInternalId', $project->getSequence())
->setAttribute('resourceUpdatedAt', DateTime::now())
->setAttribute('active', $scheduledStatus);
+8 -1
View File
@@ -10,6 +10,7 @@ use Utopia\Database\Query;
use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\Datetime as DateTimeValidator;
use Utopia\Http\Http;
use Utopia\Platform\Enum;
use Utopia\Validator\WhiteList;
Http::get('/v1/project/usage')
@@ -31,7 +32,13 @@ Http::get('/v1/project/usage')
))
->param('startDate', '', new DateTimeValidator(), 'Starting date for the usage')
->param('endDate', '', new DateTimeValidator(), 'End date for the usage')
->param('period', '1d', new WhiteList(['1h', '1d']), 'Period used', true)
->param('period', '1d', new WhiteList(['1h', '1d']), 'Period used', true, enum: new Enum(
name: 'ProjectUsageRange',
map: [
'1h' => 'OneHour',
'1d' => 'OneDay',
]
))
->inject('response')
->inject('project')
->inject('dbForProject')
+5 -4
View File
@@ -63,6 +63,7 @@ use Utopia\Emails\Email;
use Utopia\Emails\Validator\Email as EmailValidator;
use Utopia\Http\Http;
use Utopia\Locale\Locale;
use Utopia\Platform\Enum;
use Utopia\System\System;
use Utopia\Validator\ArrayList;
use Utopia\Validator\Assoc;
@@ -432,7 +433,7 @@ Http::post('/v1/users/sha')
->param('userId', '', fn (Database $dbForProject) => new CustomId(false, $dbForProject->getAdapter()->getMaxUIDLength()), 'User 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.', false, ['dbForProject'])
->param('email', '', new EmailValidator(), 'User email.')
->param('password', '', new Password(), 'User password hashed using SHA.')
->param('passwordVersion', '', new WhiteList(['sha1', 'sha224', 'sha256', 'sha384', 'sha512/224', 'sha512/256', 'sha512', 'sha3-224', 'sha3-256', 'sha3-384', 'sha3-512']), "Optional SHA version used to hash password. Allowed values are: 'sha1', 'sha224', 'sha256', 'sha384', 'sha512/224', 'sha512/256', 'sha512', 'sha3-224', 'sha3-256', 'sha3-384', 'sha3-512'", true)
->param('passwordVersion', '', new WhiteList(['sha1', 'sha224', 'sha256', 'sha384', 'sha512/224', 'sha512/256', 'sha512', 'sha3-224', 'sha3-256', 'sha3-384', 'sha3-512']), "Optional SHA version used to hash password. Allowed values are: 'sha1', 'sha224', 'sha256', 'sha384', 'sha512/224', 'sha512/256', 'sha512', 'sha3-224', 'sha3-256', 'sha3-384', 'sha3-512'", true, enum: new Enum(name: 'PasswordHash'))
->param('name', '', new Text(128), 'User name. Max length: 128 chars.', true)
->inject('response')
->inject('project')
@@ -606,7 +607,7 @@ Http::post('/v1/users/:userId/targets')
))
->param('targetId', '', fn (Database $dbForProject) => new CustomId(false, $dbForProject->getAdapter()->getMaxUIDLength()), 'Target 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.', false, ['dbForProject'])
->param('userId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'User ID.', false, ['dbForProject'])
->param('providerType', '', new WhiteList([MESSAGE_TYPE_EMAIL, MESSAGE_TYPE_SMS, MESSAGE_TYPE_PUSH]), 'The target provider type. Can be one of the following: `email`, `sms` or `push`.')
->param('providerType', '', new WhiteList([MESSAGE_TYPE_EMAIL, MESSAGE_TYPE_SMS, MESSAGE_TYPE_PUSH]), 'The target provider type. Can be one of the following: `email`, `sms` or `push`.', enum: new Enum(name: 'MessagingProviderType'))
->param('identifier', '', new Text(Database::LENGTH_KEY), 'The target identifier (token, email, phone etc.)')
->param('providerId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Provider ID. Message will be sent to this target from the specified provider ID. If no provider ID is set the first setup provider will be used.', true, ['dbForProject'])
->param('name', '', new Text(128), 'Target name. Max length: 128 chars. For example: My Awesome App Galaxy S23.', true)
@@ -2318,7 +2319,7 @@ Http::delete('/v1/users/:userId/mfa/authenticators/:type')
)
])
->param('userId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'User ID.', false, ['dbForProject'])
->param('type', null, new WhiteList([Type::TOTP]), 'Type of authenticator.')
->param('type', null, new WhiteList([Type::TOTP]), 'Type of authenticator.', enum: new Enum(name: 'AuthenticatorType'))
->inject('response')
->inject('dbForProject')
->inject('queueForEvents')
@@ -2831,7 +2832,7 @@ Http::get('/v1/users/usage')
)
]
))
->param('range', '30d', new WhiteList(['24h', '30d', '90d'], true), 'Date range.', true)
->param('range', '30d', new WhiteList(['24h', '30d', '90d'], true), 'Date range.', true, enum: new Enum(name: 'UsageRange'))
->inject('response')
->inject('dbForProject')
->inject('authorization')
+10
View File
@@ -164,6 +164,7 @@ const SESSION_PROVIDER_SERVER = 'server';
const ACTOR_TYPE_USER = 'user';
const ACTOR_TYPE_ADMIN = 'admin';
const ACTOR_TYPE_GUEST = 'guest';
const ACTOR_TYPE_HIDDEN = 'hidden';
const ACTOR_TYPE_KEY_PROJECT = 'keyProject';
const ACTOR_TYPE_KEY_ACCOUNT = 'keyAccount';
const ACTOR_TYPE_KEY_ORGANIZATION = 'keyOrganization';
@@ -262,6 +263,13 @@ const FUNCTION_ALLOWLIST_HEADERS_RESPONSE = ['content-type', 'content-length'];
const MESSAGE_TYPE_EMAIL = 'email';
const MESSAGE_TYPE_SMS = 'sms';
const MESSAGE_TYPE_PUSH = 'push';
// Notification types
const NOTIFICATION_TYPE_EMAIL = MESSAGE_TYPE_EMAIL;
const NOTIFICATION_TYPE_SMS = MESSAGE_TYPE_SMS;
const NOTIFICATION_TYPE_PUSH = MESSAGE_TYPE_PUSH;
const NOTIFICATION_TYPE_CONSOLE = 'console';
const NOTIFICATION_TYPE_WEBHOOK = 'webhook';
const NOTIFICATION_TRACKING_JWT_TTL = 60 * 60 * 24 * 7;
// API key types
const API_KEY_STANDARD = 'standard';
const API_KEY_EPHEMERAL = 'ephemeral';
@@ -431,6 +439,8 @@ const RESOURCE_TYPE_SUBSCRIBERS = 'subscribers';
const RESOURCE_TYPE_MESSAGES = 'messages';
const RESOURCE_TYPE_EXECUTIONS = 'executions';
const RESOURCE_TYPE_VCS = 'vcs';
const RESOURCE_TYPE_USERS = 'users';
const RESOURCE_TYPE_TEAMS = 'teams';
const RESOURCE_TYPE_EMBEDDINGS_TEXT = 'embeddingsText';
const RESOURCE_TYPE_INSIGHTS = 'insights';
const RESOURCE_TYPE_REPORTS = 'reports';
+3
View File
@@ -115,6 +115,7 @@ use Appwrite\Utopia\Response\Model\MigrationReport;
use Appwrite\Utopia\Response\Model\Mock;
use Appwrite\Utopia\Response\Model\MockNumber;
use Appwrite\Utopia\Response\Model\None;
use Appwrite\Utopia\Response\Model\Notification;
use Appwrite\Utopia\Response\Model\OAuth2Amazon;
use Appwrite\Utopia\Response\Model\OAuth2Apple;
use Appwrite\Utopia\Response\Model\OAuth2Auth0;
@@ -248,6 +249,7 @@ Response::setModel(new BaseList('Column Indexes List', Response::MODEL_COLUMN_IN
Response::setModel(new BaseList('Users List', Response::MODEL_USER_LIST, 'users', Response::MODEL_USER));
Response::setModel(new BaseList('Sessions List', Response::MODEL_SESSION_LIST, 'sessions', Response::MODEL_SESSION));
Response::setModel(new BaseList('Identities List', Response::MODEL_IDENTITY_LIST, 'identities', Response::MODEL_IDENTITY));
Response::setModel(new BaseList('Notifications List', Response::MODEL_NOTIFICATION_LIST, 'notifications', Response::MODEL_NOTIFICATION));
Response::setModel(new BaseList('Logs List', Response::MODEL_LOG_LIST, 'logs', Response::MODEL_LOG));
Response::setModel(new BaseList('Files List', Response::MODEL_FILE_LIST, 'files', Response::MODEL_FILE));
Response::setModel(new BaseList('Buckets List', Response::MODEL_BUCKET_LIST, 'buckets', Response::MODEL_BUCKET));
@@ -378,6 +380,7 @@ Response::setModel(new Account());
Response::setModel(new Preferences());
Response::setModel(new Session());
Response::setModel(new Identity());
Response::setModel(new Notification());
Response::setModel(new Token());
Response::setModel(new JWT());
Response::setModel(new Locale());
+6 -1
View File
@@ -11,6 +11,7 @@ use Appwrite\Event\Publisher\Func as FunctionPublisher;
use Appwrite\Event\Publisher\Mail as MailPublisher;
use Appwrite\Event\Publisher\Messaging as MessagingPublisher;
use Appwrite\Event\Publisher\Migration as MigrationPublisher;
use Appwrite\Event\Publisher\Notification as NotificationPublisher;
use Appwrite\Event\Publisher\Screenshot as ScreenshotPublisher;
use Appwrite\Event\Publisher\StatsResources as StatsResourcesPublisher;
use Appwrite\Event\Publisher\Usage as UsagePublisher;
@@ -140,6 +141,11 @@ $container->set('publisherForMessaging', fn (Publisher $publisher) => new Messag
new Queue(System::getEnv('_APP_MESSAGING_QUEUE_NAME', Event::MESSAGING_QUEUE_NAME))
), ['publisher']);
$container->set('publisherForNotifications', fn (Publisher $publisher) => new NotificationPublisher(
$publisher,
new Queue(System::getEnv('_APP_NOTIFICATIONS_QUEUE_NAME', Event::NOTIFICATIONS_QUEUE_NAME))
), ['publisher']);
$container->set('logger', function ($register) {
return $register->get('logger');
}, ['register']);
@@ -154,7 +160,6 @@ $container->set('localeCodes', function () {
return array_map(fn ($locale) => $locale['code'], Config::getParam('locale-codes', []));
});
/**
* Platform configuration
*/
+13
View File
@@ -1,6 +1,8 @@
<?php
use Appwrite\Event\Event;
use Appwrite\Event\Publisher\Func as FunctionPublisher;
use Appwrite\Event\Publisher\Notification as NotificationPublisher;
use Appwrite\Event\Realtime;
use Appwrite\Event\Webhook;
use Appwrite\Usage\Context;
@@ -20,6 +22,7 @@ use Utopia\DSN\DSN;
use Utopia\Logger\Log;
use Utopia\Pools\Group;
use Utopia\Queue\Publisher;
use Utopia\Queue\Queue;
use Utopia\Registry\Registry;
use Utopia\Storage\Device\Telemetry as TelemetryDevice;
use Utopia\System\System;
@@ -332,6 +335,16 @@ return function (Container $container): void {
return new Webhook($publisher);
}, ['publisher']);
$container->set('publisherForNotifications', fn (Publisher $publisher) => new NotificationPublisher(
$publisher,
new Queue(System::getEnv('_APP_NOTIFICATIONS_QUEUE_NAME', Event::NOTIFICATIONS_QUEUE_NAME))
), ['publisher']);
$container->set('publisherForFunctions', fn (Publisher $publisher) => new FunctionPublisher(
$publisher,
new Queue(System::getEnv('_APP_FUNCTIONS_QUEUE_NAME', Event::FUNCTIONS_QUEUE_NAME), 'utopia-queue', Event::FUNCTIONS_QUEUE_TTL)
), ['publisher']);
$container->set('queueForRealtime', function () {
return new Realtime();
}, []);
+41
View File
@@ -103,6 +103,7 @@ $hostPath = rtrim($this->getParam('hostPath', ''), '/');
- _APP_OPTIONS_FORCE_HTTPS
- _APP_OPTIONS_ROUTER_FORCE_HTTPS
- _APP_OPENSSL_KEY_V1
- _APP_NOTIFICATIONS_TRACKING_SECRET
- _APP_DOMAIN
- _APP_DOMAIN_TARGET_CNAME
- _APP_DOMAIN_TARGET_AAAA
@@ -613,6 +614,46 @@ $hostPath = rtrim($this->getParam('hostPath', ''), '/');
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_OPENSSL_KEY_V1
- _APP_NOTIFICATIONS_TRACKING_SECRET
- _APP_SYSTEM_EMAIL_NAME
- _APP_SYSTEM_EMAIL_ADDRESS
- _APP_DB_ADAPTER
- _APP_DB_HOST
- _APP_DB_PORT
- _APP_DB_SCHEMA
- _APP_DB_USER
- _APP_DB_PASS
- _APP_REDIS_HOST
- _APP_REDIS_PORT
- _APP_REDIS_USER
- _APP_REDIS_PASS
- _APP_SMTP_HOST
- _APP_SMTP_PORT
- _APP_SMTP_SECURE
- _APP_SMTP_USERNAME
- _APP_SMTP_PASSWORD
- _APP_LOGGING_CONFIG
- _APP_DOMAIN
- _APP_OPTIONS_FORCE_HTTPS
appwrite-worker-notifications:
image: <?php echo $organization; ?>/<?php echo $image; ?>:<?php echo $version."\n"; ?>
entrypoint: worker-notifications
<<: *x-logging
container_name: appwrite-worker-notifications
restart: unless-stopped
networks:
- appwrite
depends_on:
redis:
condition: service_healthy
<?= $dbService ?>:
condition: service_healthy
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_OPENSSL_KEY_V1
- _APP_NOTIFICATIONS_TRACKING_SECRET
- _APP_SYSTEM_EMAIL_NAME
- _APP_SYSTEM_EMAIL_ADDRESS
- _APP_DB_ADAPTER
+1 -1
View File
@@ -1,3 +1,3 @@
#!/bin/sh
exec php /usr/src/code/app/worker.php mails "$@"
exec php /usr/src/code/app/worker.php mails "$@"
+3
View File
@@ -0,0 +1,3 @@
#!/bin/sh
exec php /usr/src/code/app/worker.php notifications "$@"
+4 -4
View File
@@ -60,14 +60,14 @@
"utopia-php/cli": "0.23.*",
"utopia-php/compression": "0.1.*",
"utopia-php/config": "1.*",
"utopia-php/console": "0.2.*",
"utopia-php/database": "dev-chore-bump-console as 5.9.0",
"utopia-php/console": "0.1.*",
"utopia-php/database": "5.*",
"utopia-php/detector": "0.2.*",
"utopia-php/domains": "^2.1",
"utopia-php/emails": "0.7.*",
"utopia-php/dns": "1.7.*",
"utopia-php/dsn": "0.2.1",
"utopia-php/http": "2.0.0-rc2",
"utopia-php/http": "2.0.0-rc3",
"utopia-php/fetch": "^1.1",
"utopia-php/validators": "0.2.*",
"utopia-php/image": "0.8.*",
@@ -76,7 +76,7 @@
"utopia-php/logger": "0.8.*",
"utopia-php/messaging": "0.22.*",
"utopia-php/migration": "1.*",
"utopia-php/platform": "1.0.0-rc2",
"utopia-php/platform": "1.0.0-rc3",
"utopia-php/pools": "1.*",
"utopia-php/span": "1.1.*",
"utopia-php/preloader": "0.2.*",
Generated
+50 -60
View File
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "dde65586a67db696208f0fe7db8d7df5",
"content-hash": "0f60e85b461aa1a57d624e97be0614bd",
"packages": [
{
"name": "adhocore/jwt",
@@ -3731,21 +3731,21 @@
},
{
"name": "utopia-php/cli",
"version": "0.23.3",
"version": "0.23.4",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/cli.git",
"reference": "3c45ae5bcdcd3c7916e1909d74c60b8e771610db"
"reference": "59f66e72ec1f8350968d8b95c0039faaac7a6d6d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/cli/zipball/3c45ae5bcdcd3c7916e1909d74c60b8e771610db",
"reference": "3c45ae5bcdcd3c7916e1909d74c60b8e771610db",
"url": "https://api.github.com/repos/utopia-php/cli/zipball/59f66e72ec1f8350968d8b95c0039faaac7a6d6d",
"reference": "59f66e72ec1f8350968d8b95c0039faaac7a6d6d",
"shasum": ""
},
"require": {
"php": ">=7.4",
"utopia-php/servers": "0.4.0"
"utopia-php/servers": "0.4.*"
},
"require-dev": {
"laravel/pint": "1.2.*",
@@ -3776,9 +3776,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/cli/issues",
"source": "https://github.com/utopia-php/cli/tree/0.23.3"
"source": "https://github.com/utopia-php/cli/tree/0.23.4"
},
"time": "2026-05-05T04:38:59+00:00"
"time": "2026-05-21T14:42:15+00:00"
},
{
"name": "utopia-php/compression",
@@ -3875,21 +3875,20 @@
},
{
"name": "utopia-php/console",
"version": "0.2.1",
"version": "0.1.1",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/console.git",
"reference": "97e3de44424ee9ea207c3129dfcc82f8df37c5b5"
"reference": "d298e43960780e6d76e66de1228c75dc81220e3e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/console/zipball/97e3de44424ee9ea207c3129dfcc82f8df37c5b5",
"reference": "97e3de44424ee9ea207c3129dfcc82f8df37c5b5",
"url": "https://api.github.com/repos/utopia-php/console/zipball/d298e43960780e6d76e66de1228c75dc81220e3e",
"reference": "d298e43960780e6d76e66de1228c75dc81220e3e",
"shasum": ""
},
"require": {
"php": ">=8.0",
"utopia-php/validators": "^0.2.0"
"php": ">=8.0"
},
"require-dev": {
"laravel/pint": "1.2.*",
@@ -3918,22 +3917,22 @@
],
"support": {
"issues": "https://github.com/utopia-php/console/issues",
"source": "https://github.com/utopia-php/console/tree/0.2.1"
"source": "https://github.com/utopia-php/console/tree/0.1.1"
},
"time": "2026-04-20T10:53:53+00:00"
"time": "2026-02-10T10:20:29+00:00"
},
{
"name": "utopia-php/database",
"version": "dev-chore-bump-console",
"version": "5.9.0",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/database.git",
"reference": "1abb810d78901aab61a28d38b3eaaf7fc22a7591"
"reference": "477bae83e27631f78c159f45b0441c0c7dc69050"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/database/zipball/1abb810d78901aab61a28d38b3eaaf7fc22a7591",
"reference": "1abb810d78901aab61a28d38b3eaaf7fc22a7591",
"url": "https://api.github.com/repos/utopia-php/database/zipball/477bae83e27631f78c159f45b0441c0c7dc69050",
"reference": "477bae83e27631f78c159f45b0441c0c7dc69050",
"shasum": ""
},
"require": {
@@ -3943,7 +3942,7 @@
"ext-redis": "*",
"php": ">=8.4",
"utopia-php/cache": "^3.0",
"utopia-php/console": "0.2.*",
"utopia-php/console": "0.1.*",
"utopia-php/mongo": "1.*",
"utopia-php/pools": "1.*",
"utopia-php/validators": "0.2.*"
@@ -3978,9 +3977,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/database/issues",
"source": "https://github.com/utopia-php/database/tree/chore-bump-console"
"source": "https://github.com/utopia-php/database/tree/5.9.0"
},
"time": "2026-05-25T21:06:24+00:00"
"time": "2026-05-17T15:57:21+00:00"
},
{
"name": "utopia-php/detector",
@@ -4347,23 +4346,23 @@
},
{
"name": "utopia-php/http",
"version": "2.0.0-rc2",
"version": "2.0.0-rc3",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/http.git",
"reference": "17f3d5e966ada8a5c041717436f069f269aef2b3"
"reference": "e1a51a2eb906a3d461378fb551a647ef01714f1c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/http/zipball/17f3d5e966ada8a5c041717436f069f269aef2b3",
"reference": "17f3d5e966ada8a5c041717436f069f269aef2b3",
"url": "https://api.github.com/repos/utopia-php/http/zipball/e1a51a2eb906a3d461378fb551a647ef01714f1c",
"reference": "e1a51a2eb906a3d461378fb551a647ef01714f1c",
"shasum": ""
},
"require": {
"php": ">=8.3",
"utopia-php/compression": "0.1.*",
"utopia-php/di": "0.3.*",
"utopia-php/servers": "0.4.0",
"utopia-php/servers": "0.4.*",
"utopia-php/telemetry": "0.2.*",
"utopia-php/validators": "0.2.*"
},
@@ -4397,9 +4396,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/http/issues",
"source": "https://github.com/utopia-php/http/tree/2.0.0-rc2"
"source": "https://github.com/utopia-php/http/tree/2.0.0-rc3"
},
"time": "2026-05-20T11:13:49+00:00"
"time": "2026-05-21T14:42:41+00:00"
},
{
"name": "utopia-php/image",
@@ -4774,16 +4773,16 @@
},
{
"name": "utopia-php/platform",
"version": "1.0.0-rc2",
"version": "1.0.0-rc3",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/platform.git",
"reference": "a67e5037007ee7fdca5359ab4577b82917e55452"
"reference": "674fd3cea6d1f8db660b01c5be89e8333dfb6de9"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/platform/zipball/a67e5037007ee7fdca5359ab4577b82917e55452",
"reference": "a67e5037007ee7fdca5359ab4577b82917e55452",
"url": "https://api.github.com/repos/utopia-php/platform/zipball/674fd3cea6d1f8db660b01c5be89e8333dfb6de9",
"reference": "674fd3cea6d1f8db660b01c5be89e8333dfb6de9",
"shasum": ""
},
"require": {
@@ -4819,9 +4818,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/platform/issues",
"source": "https://github.com/utopia-php/platform/tree/1.0.0-rc2"
"source": "https://github.com/utopia-php/platform/tree/1.0.0-rc3"
},
"time": "2026-05-15T06:19:20+00:00"
"time": "2026-05-25T11:48:18+00:00"
},
{
"name": "utopia-php/pools",
@@ -4977,16 +4976,16 @@
},
{
"name": "utopia-php/queue",
"version": "0.18.3",
"version": "0.18.4",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/queue.git",
"reference": "141aad162b90728353f3aa834684b1f2affed045"
"reference": "1323b52f39b899c580dbb7af8562fc53d4e13e62"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/queue/zipball/141aad162b90728353f3aa834684b1f2affed045",
"reference": "141aad162b90728353f3aa834684b1f2affed045",
"url": "https://api.github.com/repos/utopia-php/queue/zipball/1323b52f39b899c580dbb7af8562fc53d4e13e62",
"reference": "1323b52f39b899c580dbb7af8562fc53d4e13e62",
"shasum": ""
},
"require": {
@@ -4994,7 +4993,7 @@
"php-amqplib/php-amqplib": "^3.7",
"utopia-php/di": "0.3.*",
"utopia-php/pools": "1.*",
"utopia-php/servers": "0.4.0",
"utopia-php/servers": "0.4.*",
"utopia-php/telemetry": "0.2.*",
"utopia-php/validators": "0.2.*"
},
@@ -5037,9 +5036,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/queue/issues",
"source": "https://github.com/utopia-php/queue/tree/0.18.3"
"source": "https://github.com/utopia-php/queue/tree/0.18.4"
},
"time": "2026-05-14T08:53:35+00:00"
"time": "2026-05-21T14:43:24+00:00"
},
{
"name": "utopia-php/registry",
@@ -5095,16 +5094,16 @@
},
{
"name": "utopia-php/servers",
"version": "0.4.0",
"version": "0.4.1",
"source": {
"type": "git",
"url": "https://github.com/utopia-php/servers.git",
"reference": "7db346ef377503efe0acafe0791085270cd9ed70"
"reference": "6e9241f587a19c0e9e7a3587fe3b15ffb2d4c2a4"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/utopia-php/servers/zipball/7db346ef377503efe0acafe0791085270cd9ed70",
"reference": "7db346ef377503efe0acafe0791085270cd9ed70",
"url": "https://api.github.com/repos/utopia-php/servers/zipball/6e9241f587a19c0e9e7a3587fe3b15ffb2d4c2a4",
"reference": "6e9241f587a19c0e9e7a3587fe3b15ffb2d4c2a4",
"shasum": ""
},
"require": {
@@ -5143,9 +5142,9 @@
],
"support": {
"issues": "https://github.com/utopia-php/servers/issues",
"source": "https://github.com/utopia-php/servers/tree/0.4.0"
"source": "https://github.com/utopia-php/servers/tree/0.4.1"
},
"time": "2026-05-05T04:08:30+00:00"
"time": "2026-05-21T13:08:45+00:00"
},
{
"name": "utopia-php/span",
@@ -8549,18 +8548,9 @@
"time": "2026-05-20T07:31:59+00:00"
}
],
"aliases": [
{
"package": "utopia-php/database",
"version": "dev-chore-bump-console",
"alias": "5.9.0",
"alias_normalized": "5.9.0.0"
}
],
"aliases": [],
"minimum-stability": "dev",
"stability-flags": {
"utopia-php/database": 20
},
"stability-flags": {},
"prefer-stable": true,
"prefer-lowest": false,
"platform": {
+44
View File
@@ -142,6 +142,7 @@ services:
- _APP_OPTIONS_FORCE_HTTPS
- _APP_OPTIONS_ROUTER_FORCE_HTTPS
- _APP_OPENSSL_KEY_V1
- _APP_NOTIFICATIONS_TRACKING_SECRET
- _APP_DOMAIN
- _APP_CONSOLE_DOMAIN
- _APP_CONSOLE_TRUSTED_PROJECTS
@@ -784,6 +785,49 @@ services:
- _APP_WORKER_PER_CORE
- _APP_POOL_ADAPTER
- _APP_OPENSSL_KEY_V1
- _APP_NOTIFICATIONS_TRACKING_SECRET
- _APP_SYSTEM_EMAIL_NAME
- _APP_SYSTEM_EMAIL_ADDRESS
- _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_DB_ADAPTER
- _APP_SMTP_HOST
- _APP_SMTP_PORT
- _APP_SMTP_SECURE
- _APP_SMTP_USERNAME
- _APP_SMTP_PASSWORD
- _APP_LOGGING_CONFIG
- _APP_DOMAIN
- _APP_OPTIONS_FORCE_HTTPS
- _APP_DATABASE_SHARED_TABLES
appwrite-worker-notifications:
entrypoint: worker-notifications
<<: [*x-logging, *x-build]
container_name: appwrite-worker-notifications
image: appwrite-dev
networks:
- appwrite
volumes:
- ./app:/usr/src/code/app
- ./src:/usr/src/code/src
depends_on:
- redis
- maildev
- ${_APP_DB_HOST:-mariadb}
environment:
- _APP_ENV
- _APP_WORKER_PER_CORE
- _APP_POOL_ADAPTER
- _APP_OPENSSL_KEY_V1
- _APP_NOTIFICATIONS_TRACKING_SECRET
- _APP_SYSTEM_EMAIL_NAME
- _APP_SYSTEM_EMAIL_ADDRESS
- _APP_REDIS_HOST
@@ -0,0 +1 @@
Get the number of jobs in the notifications queue.
@@ -0,0 +1 @@
Get the Appwrite logo SVG used in notification emails. When loaded with a valid tracking token, the related notification is marked as read and its view timestamps are updated.
@@ -0,0 +1 @@
Get the list of notifications for the currently logged in console user. Use queries to filter the results by attributes such as read status, view timestamps, or creation date.
@@ -0,0 +1 @@
Update a notification by its unique ID. Use the `read` parameter to mark the notification as read or unread.
+1
View File
@@ -37,6 +37,7 @@
<directory>./tests/e2e/Services/ProjectWebhooks</directory>
<directory>./tests/e2e/Services/Messaging</directory>
<directory>./tests/e2e/Services/Migrations</directory>
<directory>./tests/e2e/Services/Notifications</directory>
<directory>./tests/e2e/Services/Project</directory>
<directory>./tests/e2e/Services/Advisor</directory>
<file>./tests/e2e/Services/Functions/FunctionsBase.php</file>
+11 -17
View File
@@ -4,7 +4,6 @@ namespace Appwrite\Certificates;
use Appwrite\Certificates\Exception\CertificateStatus as CertificateStatusException;
use Exception;
use Utopia\Command;
use Utopia\Console;
use Utopia\Database\DateTime;
use Utopia\Http\Http;
@@ -25,22 +24,17 @@ class LetsEncrypt implements Adapter
$stdout = '';
$stderr = '';
$issueCommand = (new Command('certbot'))
->argument('certonly')
->flag('-v')
->flag('--webroot')
->flag('--noninteractive')
->flag('--agree-tos')
->option('--email', $this->email)
->option('--cert-name', $certName)
->option('-w', APP_STORAGE_CERTIFICATES)
->option('-d', $domain);
if (! Http::isProduction()) {
$issueCommand->flag('--dry-run');
}
$exit = Console::execute($issueCommand, '', $stdout, $stderr);
$staging = (Http::isProduction()) ? '' : ' --dry-run';
$exit = Console::execute(
"certbot certonly -v --webroot --noninteractive --agree-tos{$staging}"
. " --email " . $this->email
. " --cert-name " . $certName
. " -w " . APP_STORAGE_CERTIFICATES
. " -d {$domain}",
'',
$stdout,
$stderr
);
// Unexpected error, usually 5XX, API limits, ...
if ($exit !== 0) {
+3 -1
View File
@@ -4,6 +4,7 @@ namespace Appwrite\Docker;
use Appwrite\Docker\Compose\Service;
use Exception;
use Spyc;
class Compose
{
@@ -14,7 +15,8 @@ class Compose
public function __construct(string $data)
{
$this->compose = yaml_parse($data);
$compose = \function_exists('yaml_parse') ? \yaml_parse($data) : Spyc::YAMLLoadString($data);
$this->compose = \is_array($compose) ? $compose : [];
$this->compose['services'] = (isset($this->compose['services']) && is_array($this->compose['services']))
? $this->compose['services'] : [];
+5
View File
@@ -54,6 +54,11 @@ class Service
return substr($image, ((int)strpos($image, ':')) + 1);
}
public function getEntrypoint(): string
{
return (isset($this->service['entrypoint'])) ? $this->service['entrypoint'] : '';
}
/**
* @return Env
*/
+3
View File
@@ -21,6 +21,9 @@ class Event
public const MAILS_QUEUE_NAME = 'v1-mails';
public const MAILS_CLASS_NAME = 'MailsV1';
public const NOTIFICATIONS_QUEUE_NAME = 'v1-notifications';
public const NOTIFICATIONS_CLASS_NAME = 'NotificationsV1';
public const FUNCTIONS_QUEUE_NAME = 'v1-functions';
public const FUNCTIONS_CLASS_NAME = 'FunctionsV1';
public const FUNCTIONS_QUEUE_TTL = 60 * 60 * 24 * 7; // 7 days
+116
View File
@@ -0,0 +1,116 @@
<?php
namespace Appwrite\Event\Message;
use Appwrite\Event\Event;
use Utopia\Config\Config;
use Utopia\Database\Document;
final class Notification extends Base
{
/**
* @param array<int, array{address: string, channel: string, signatureKey?: string, resourceType?: string, resourceId?: string, resourceInternalId?: string, parentResourceType?: string, parentResourceId?: string, parentResourceInternalId?: string}> $recipients
* @param array<string> $channels
* @param array<string, mixed> $templateParams
* @param array<string> $permissions
* @param array<string, mixed> $variables
* @param array<string, mixed> $attachment
* @param array<string> $events
* @param array<string, mixed> $params
* @param array<string, mixed> $platform
*/
public function __construct(
public readonly ?Document $project = null,
public readonly string $recipient = '',
public readonly array $recipients = [],
public readonly array $channels = [],
public readonly string $template = '',
public readonly array $templateParams = [],
public readonly string $deduplicationKey = '',
public readonly array $permissions = [],
public readonly string $name = '',
public readonly string $subject = '',
public readonly string $bodyTemplate = '',
public readonly string $body = '',
public readonly string $preview = '',
public readonly array $variables = [],
public readonly array $attachment = [],
public readonly array $events = [],
public readonly string $event = '',
public readonly array $params = [],
public readonly array $platform = [],
) {
}
public function toArray(): array
{
$recipients = $this->recipients;
if (empty($recipients) && $this->recipient !== '') {
$recipients = [[
'address' => $this->recipient,
'channel' => NOTIFICATION_TYPE_EMAIL,
]];
}
$platform = !empty($this->platform) ? $this->platform : Config::getParam('platform', []);
return [
'project' => $this->project === null ? null : [
'$id' => $this->project->getId(),
'$sequence' => $this->project->getSequence(),
'database' => $this->project->getAttribute('database'),
],
'recipient' => $this->recipient,
'recipients' => $recipients,
'channels' => $this->channels,
'template' => $this->template,
'templateParams' => $this->templateParams,
'deduplicationKey' => $this->deduplicationKey,
'permissions' => $this->permissions,
'name' => $this->name,
'subject' => $this->subject,
'bodyTemplate' => $this->bodyTemplate,
'body' => $this->body,
'preview' => $this->preview,
'variables' => $this->variables,
'attachment' => $this->attachment,
'events' => !empty($this->events) ? $this->events : $this->generateEvents(),
'platform' => $platform,
];
}
public static function fromArray(array $data): static
{
return new self(
project: !empty($data['project']) ? new Document($data['project']) : null,
recipient: $data['recipient'] ?? '',
recipients: $data['recipients'] ?? [],
channels: $data['channels'] ?? [],
template: $data['template'] ?? '',
templateParams: $data['templateParams'] ?? [],
deduplicationKey: $data['deduplicationKey'] ?? '',
permissions: $data['permissions'] ?? [],
name: $data['name'] ?? '',
subject: $data['subject'] ?? '',
bodyTemplate: $data['bodyTemplate'] ?? '',
body: $data['body'] ?? '',
preview: $data['preview'] ?? '',
variables: $data['variables'] ?? [],
attachment: $data['attachment'] ?? [],
events: $data['events'] ?? [],
platform: $data['platform'] ?? [],
);
}
/**
* @return array<string>
*/
private function generateEvents(): array
{
if ($this->event === '') {
return [];
}
return Event::generateEvents($this->event, $this->params);
}
}
@@ -0,0 +1,27 @@
<?php
namespace Appwrite\Event\Publisher;
use Appwrite\Event\Message\Notification as NotificationMessage;
use Utopia\Queue\Publisher;
use Utopia\Queue\Queue;
readonly class Notification extends Base
{
public function __construct(
Publisher $publisher,
protected Queue $queue,
) {
parent::__construct($publisher);
}
public function enqueue(NotificationMessage $message, ?Queue $queue = null): string|bool
{
return $this->publish($queue ?? $this->queue, $message);
}
public function getSize(bool $failed = false, ?Queue $queue = null): int
{
return $this->getQueueSize($queue ?? $this->queue, $failed);
}
}
+1
View File
@@ -736,6 +736,7 @@ class V19 extends Migration
'resourceInternalId' => $document->getSequence(),
'resourceUpdatedAt' => DateTime::now(),
'projectId' => $this->project->getId(),
'projectInternalId' => $this->project->getSequence(),
'schedule' => $document->getAttribute('schedule'),
'active' => !empty($document->getAttribute('schedule')) && !empty($document->getAttribute('deployment')),
]));
+53
View File
@@ -90,6 +90,12 @@ class V24 extends Migration
};
$collections = $this->collections[$collectionType];
$alertsCollectionExists = false;
if ($collectionType === 'console') {
$alertsCollectionExists = !$this->dbForProject->getCollection('alerts')->isEmpty();
$this->createCollection('alerts');
}
foreach ($collections as $collection) {
$id = $collection['$id'];
@@ -147,6 +153,42 @@ class V24 extends Migration
$this->dbForProject->purgeCachedCollection($id);
break;
case 'schedules':
if ($collectionType === 'console') {
try {
$this->createAttributeFromCollection($this->dbForProject, $id, 'projectInternalId');
} catch (Throwable $th) {
Console::warning("Failed to create attribute \"projectInternalId\" in collection {$id}: {$th->getMessage()}");
}
$indexes = [
'_key_region_resourceType_projectInternalId_resourceId',
'_key_project_internal_id_region',
];
foreach ($indexes as $index) {
try {
$this->createIndexFromCollection($this->dbForProject, $id, $index);
} catch (Throwable $th) {
Console::warning("Failed to create index \"{$index}\" from {$id}: {$th->getMessage()}");
}
}
}
$this->dbForProject->purgeCachedCollection($id);
break;
case 'alerts':
if ($collectionType === 'console' && $alertsCollectionExists) {
foreach (['firstSeen', 'lastSeen'] as $attribute) {
try {
$this->createAttributeFromCollection($this->dbForProject, $id, $attribute);
} catch (Throwable $th) {
Console::warning("Failed to create attribute \"{$attribute}\" in collection {$id}: {$th->getMessage()}");
}
}
}
$this->dbForProject->purgeCachedCollection($id);
break;
case 'rules':
if ($collectionType === 'console') {
try {
@@ -399,6 +441,17 @@ class V24 extends Migration
$document->setAttribute('resourceInternalId', $projectInternalId);
}
break;
case 'schedules':
if (empty($document->getAttribute('projectInternalId'))) {
$projectId = $document->getAttribute('projectId', '');
if ($projectId !== '') {
$project = $this->dbForProject->getDocument('projects', $projectId);
if (!$project->isEmpty()) {
$document->setAttribute('projectInternalId', $project->getSequence());
}
}
}
break;
default:
break;
}
+2
View File
@@ -11,6 +11,7 @@ use Appwrite\Platform\Modules\Databases;
use Appwrite\Platform\Modules\Functions;
use Appwrite\Platform\Modules\Health;
use Appwrite\Platform\Modules\Migrations;
use Appwrite\Platform\Modules\Notifications;
use Appwrite\Platform\Modules\Organization;
use Appwrite\Platform\Modules\Presences;
use Appwrite\Platform\Modules\Project;
@@ -36,6 +37,7 @@ class Appwrite extends Platform
$this->addModule(new Presences\Module());
$this->addModule(new Functions\Module());
$this->addModule(new Health\Module());
$this->addModule(new Notifications\Module());
$this->addModule(new Sites\Module());
$this->addModule(new Console\Module());
$this->addModule(new Proxy\Module());
@@ -6,6 +6,7 @@ use Appwrite\Auth\MFA\Type;
use Appwrite\Auth\MFA\Type\TOTP;
use Appwrite\Event\Event;
use Appwrite\Extend\Exception;
use Appwrite\Platform\Action;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\ContentType;
use Appwrite\SDK\Deprecated;
@@ -17,7 +18,7 @@ use Utopia\Database\Document;
use Utopia\Database\Helpers\ID;
use Utopia\Database\Helpers\Permission;
use Utopia\Database\Helpers\Role;
use Utopia\Platform\Action;
use Utopia\Platform\Enum;
use Utopia\Platform\Scope\HTTP;
use Utopia\Validator\WhiteList;
@@ -77,7 +78,7 @@ class Create extends Action
contentType: ContentType::JSON
)
])
->param('type', null, new WhiteList([Type::TOTP]), 'Type of authenticator. Must be `' . Type::TOTP . '`')
->param('type', null, new WhiteList([Type::TOTP]), 'Type of authenticator. Must be `' . Type::TOTP . '`', enum: new Enum(name: 'AuthenticatorType'))
->inject('response')
->inject('project')
->inject('user')
@@ -6,6 +6,7 @@ use Appwrite\Auth\MFA\Type;
use Appwrite\Auth\MFA\Type\TOTP;
use Appwrite\Event\Event;
use Appwrite\Extend\Exception;
use Appwrite\Platform\Action;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\ContentType;
use Appwrite\SDK\Deprecated;
@@ -14,7 +15,7 @@ use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Response;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Platform\Action;
use Utopia\Platform\Enum;
use Utopia\Platform\Scope\HTTP;
use Utopia\Validator\WhiteList;
@@ -74,7 +75,7 @@ class Delete extends Action
contentType: ContentType::NONE
)
])
->param('type', null, new WhiteList([Type::TOTP]), 'Type of authenticator.')
->param('type', null, new WhiteList([Type::TOTP]), 'Type of authenticator.', enum: new Enum(name: 'AuthenticatorType'))
->inject('response')
->inject('user')
->inject('dbForProject')
@@ -7,6 +7,7 @@ use Appwrite\Auth\MFA\Type;
use Appwrite\Auth\MFA\Type\TOTP;
use Appwrite\Event\Event;
use Appwrite\Extend\Exception;
use Appwrite\Platform\Action;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\ContentType;
use Appwrite\SDK\Deprecated;
@@ -15,7 +16,7 @@ use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Response;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Platform\Action;
use Utopia\Platform\Enum;
use Utopia\Platform\Scope\HTTP;
use Utopia\Validator\Text;
use Utopia\Validator\WhiteList;
@@ -76,7 +77,7 @@ class Update extends Action
contentType: ContentType::JSON
)
])
->param('type', null, new WhiteList([Type::TOTP]), 'Type of authenticator.')
->param('type', null, new WhiteList([Type::TOTP]), 'Type of authenticator.', enum: new Enum(name: 'AuthenticatorType'))
->param('otp', '', new Text(256), 'Valid verification token.')
->inject('response')
->inject('user')
@@ -10,6 +10,7 @@ use Appwrite\Event\Message\Messaging as MessagingMessage;
use Appwrite\Event\Publisher\Mail as MailPublisher;
use Appwrite\Event\Publisher\Messaging as MessagingPublisher;
use Appwrite\Extend\Exception;
use Appwrite\Platform\Action;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\ContentType;
use Appwrite\SDK\Deprecated;
@@ -29,7 +30,7 @@ use Utopia\Database\Document;
use Utopia\Database\Helpers\Permission;
use Utopia\Database\Helpers\Role;
use Utopia\Locale\Locale;
use Utopia\Platform\Action;
use Utopia\Platform\Enum;
use Utopia\Platform\Scope\HTTP;
use Utopia\Storage\Validator\FileName;
use Utopia\System\System;
@@ -94,7 +95,7 @@ class Create extends Action
])
->label('abuse-limit', 10)
->label('abuse-key', 'url:{url},userId:{userId}')
->param('factor', '', new WhiteList([Type::EMAIL, Type::PHONE, Type::TOTP, Type::RECOVERY_CODE]), 'Factor used for verification. Must be one of following: `' . Type::EMAIL . '`, `' . Type::PHONE . '`, `' . Type::TOTP . '`, `' . Type::RECOVERY_CODE . '`.')
->param('factor', '', new WhiteList([Type::EMAIL, Type::PHONE, Type::TOTP, Type::RECOVERY_CODE]), 'Factor used for verification. Must be one of following: `' . Type::EMAIL . '`, `' . Type::PHONE . '`, `' . Type::TOTP . '`, `' . Type::RECOVERY_CODE . '`.', enum: new Enum(name: 'AuthenticationFactor'))
->inject('response')
->inject('dbForProject')
->inject('user')
@@ -11,6 +11,7 @@ use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Response;
use Utopia\Config\Config;
use Utopia\Platform\Action as UtopiaAction;
use Utopia\Platform\Enum;
use Utopia\Platform\Scope\HTTP;
use Utopia\Validator\Range;
use Utopia\Validator\WhiteList;
@@ -49,7 +50,10 @@ class Get extends Action
],
contentType: ContentType::IMAGE_PNG
))
->param('code', '', new WhiteList(\array_keys(Config::getParam('avatar-browsers'))), 'Browser Code.')
->param('code', '', new WhiteList(\array_keys(Config::getParam('avatar-browsers'))), 'Browser Code.', enum: new Enum(
name: 'Browser',
map: \array_map(fn (array $browser) => $browser['name'], Config::getParam('avatar-browsers')),
))
->param('width', 100, new Range(0, 2000), 'Image width. Pass an integer between 0 to 2000. Defaults to 100.', true)
->param('height', 100, new Range(0, 2000), 'Image height. Pass an integer between 0 to 2000. Defaults to 100.', true)
->param('quality', -1, new Range(-1, 100), 'Image quality. Pass an integer between 0 to 100. Defaults to keep existing image quality.', true)
@@ -11,6 +11,7 @@ use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Response;
use Utopia\Config\Config;
use Utopia\Platform\Action as UtopiaAction;
use Utopia\Platform\Enum;
use Utopia\Platform\Scope\HTTP;
use Utopia\Validator\Range;
use Utopia\Validator\WhiteList;
@@ -49,7 +50,10 @@ class Get extends Action
],
contentType: ContentType::IMAGE_PNG
))
->param('code', '', new WhiteList(\array_keys(Config::getParam('avatar-credit-cards'))), 'Credit Card Code. Possible values: ' . \implode(', ', \array_keys(Config::getParam('avatar-credit-cards'))) . '.')
->param('code', '', new WhiteList(\array_keys(Config::getParam('avatar-credit-cards'))), 'Credit Card Code. Possible values: ' . \implode(', ', \array_keys(Config::getParam('avatar-credit-cards'))) . '.', enum: new Enum(
name: 'CreditCard',
map: \array_map(fn (array $creditCard) => $creditCard['name'], Config::getParam('avatar-credit-cards')),
))
->param('width', 100, new Range(0, 2000), 'Image width. Pass an integer between 0 to 2000. Defaults to 100.', true)
->param('height', 100, new Range(0, 2000), 'Image height. Pass an integer between 0 to 2000. Defaults to 100.', true)
->param('quality', -1, new Range(-1, 100), 'Image quality. Pass an integer between 0 to 100. Defaults to keep existing image quality.', true)
@@ -11,6 +11,7 @@ use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Response;
use Utopia\Config\Config;
use Utopia\Platform\Action as UtopiaAction;
use Utopia\Platform\Enum;
use Utopia\Platform\Scope\HTTP;
use Utopia\Validator\Range;
use Utopia\Validator\WhiteList;
@@ -49,7 +50,10 @@ class Get extends Action
],
contentType: ContentType::IMAGE_PNG
))
->param('code', '', new WhiteList(\array_keys(Config::getParam('avatar-flags'))), 'Country Code. ISO Alpha-2 country code format.')
->param('code', '', new WhiteList(\array_keys(Config::getParam('avatar-flags'))), 'Country Code. ISO Alpha-2 country code format.', enum: new Enum(
name: 'Flag',
map: \array_map(fn (array $flag) => $flag['name'], Config::getParam('avatar-flags')),
))
->param('width', 100, new Range(0, 2000), 'Image width. Pass an integer between 0 to 2000. Defaults to 100.', true)
->param('height', 100, new Range(0, 2000), 'Image height. Pass an integer between 0 to 2000. Defaults to 100.', true)
->param('quality', -1, new Range(-1, 100), 'Image quality. Pass an integer between 0 to 100. Defaults to keep existing image quality.', true)
@@ -16,6 +16,7 @@ use Utopia\Domains\Domain;
use Utopia\Fetch\Client;
use Utopia\Image\Image;
use Utopia\Platform\Action as UtopiaAction;
use Utopia\Platform\Enum;
use Utopia\Platform\Scope\HTTP;
use Utopia\System\System;
use Utopia\Validator\ArrayList;
@@ -68,21 +69,21 @@ class Get extends Action
->param('viewportWidth', 1280, new Range(1, 1920), 'Browser viewport width. Pass an integer between 1 to 1920. Defaults to 1280.', true, example: '1920')
->param('viewportHeight', 720, new Range(1, 1080), 'Browser viewport height. Pass an integer between 1 to 1080. Defaults to 720.', true, example: '1080')
->param('scale', 1, new Range(0.1, 3, Range::TYPE_FLOAT), 'Browser scale factor. Pass a number between 0.1 to 3. Defaults to 1.', true, example: '2')
->param('theme', 'light', new WhiteList(['light', 'dark']), 'Browser theme. Pass "light" or "dark". Defaults to "light".', true, example: 'dark')
->param('theme', 'light', new WhiteList(['light', 'dark']), 'Browser theme. Pass "light" or "dark". Defaults to "light".', true, example: 'dark', enum: new Enum(name: 'BrowserTheme'))
->param('userAgent', '', new Text(512), 'Custom user agent string. Defaults to browser default.', true, example: 'Mozilla/5.0 (iPhone; CPU iPhone OS 14_0 like Mac OS X) AppleWebKit/605.1.15')
->param('fullpage', false, new Boolean(true), 'Capture full page scroll. Pass 0 for viewport only, or 1 for full page. Defaults to 0.', true, example: 'true')
->param('locale', '', new Text(10), 'Browser locale (e.g., "en-US", "fr-FR"). Defaults to browser default.', true, example: 'en-US')
->param('timezone', '', new WhiteList(timezone_identifiers_list()), 'IANA timezone identifier (e.g., "America/New_York", "Europe/London"). Defaults to browser default.', true, example: 'America/New_York')
->param('timezone', '', new WhiteList(timezone_identifiers_list()), 'IANA timezone identifier (e.g., "America/New_York", "Europe/London"). Defaults to browser default.', true, example: 'America/New_York', enum: new Enum(name: 'Timezone'))
->param('latitude', 0, new Range(-90, 90, Range::TYPE_FLOAT), 'Geolocation latitude. Pass a number between -90 to 90. Defaults to 0.', true, example: '37.7749')
->param('longitude', 0, new Range(-180, 180, Range::TYPE_FLOAT), 'Geolocation longitude. Pass a number between -180 to 180. Defaults to 0.', true, example: '-122.4194')
->param('accuracy', 0, new Range(0, 100000, Range::TYPE_FLOAT), 'Geolocation accuracy in meters. Pass a number between 0 to 100000. Defaults to 0.', true, example: '100')
->param('touch', false, new Boolean(true), 'Enable touch support. Pass 0 for no touch, or 1 for touch enabled. Defaults to 0.', true, example: 'true')
->param('permissions', [], new ArrayList(new WhiteList(['geolocation', 'camera', 'microphone', 'notifications', 'midi', 'push', 'clipboard-read', 'clipboard-write', 'payment-handler', 'usb', 'bluetooth', 'accelerometer', 'gyroscope', 'magnetometer', 'ambient-light-sensor', 'background-sync', 'persistent-storage', 'screen-wake-lock', 'web-share', 'xr-spatial-tracking'])), 'Browser permissions to grant. Pass an array of permission names like ["geolocation", "camera", "microphone"]. Defaults to empty.', true, example: '["geolocation","notifications"]')
->param('permissions', [], new ArrayList(new WhiteList(['geolocation', 'camera', 'microphone', 'notifications', 'midi', 'push', 'clipboard-read', 'clipboard-write', 'payment-handler', 'usb', 'bluetooth', 'accelerometer', 'gyroscope', 'magnetometer', 'ambient-light-sensor', 'background-sync', 'persistent-storage', 'screen-wake-lock', 'web-share', 'xr-spatial-tracking'])), 'Browser permissions to grant. Pass an array of permission names like ["geolocation", "camera", "microphone"]. Defaults to empty.', true, example: '["geolocation","notifications"]', enum: new Enum(name: 'BrowserPermission'))
->param('sleep', 0, new Range(0, 10), 'Wait time in seconds before taking the screenshot. Pass an integer between 0 to 10. Defaults to 0.', true, example: '3')
->param('width', 0, new Range(0, 2000), 'Output image width. Pass 0 to use original width, or an integer between 1 to 2000. Defaults to 0 (original width).', true, example: '800')
->param('height', 0, new Range(0, 2000), 'Output image height. Pass 0 to use original height, or an integer between 1 to 2000. Defaults to 0 (original height).', true, example: '600')
->param('quality', -1, new Range(-1, 100), 'Screenshot quality. Pass an integer between 0 to 100. Defaults to keep existing image quality.', true, example: '85')
->param('output', '', new WhiteList(\array_keys(Config::getParam('storage-outputs')), true), 'Output format type (jpeg, jpg, png, gif and webp).', true, example: 'jpeg')
->param('output', '', new WhiteList(\array_keys(Config::getParam('storage-outputs')), true), 'Output format type (jpeg, jpg, png, gif and webp).', true, example: 'jpeg', enum: new Enum(name: 'ImageFormat'))
->inject('response')
->inject('usage')
->callback($this->action(...));
@@ -3,6 +3,7 @@
namespace Appwrite\Platform\Modules\Console\Http\Resources;
use Appwrite\Extend\Exception;
use Appwrite\Platform\Action;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\ContentType;
use Appwrite\SDK\Method;
@@ -12,7 +13,7 @@ use Utopia\Database\Database;
use Utopia\Database\Query;
use Utopia\Database\Validator\Authorization;
use Utopia\Domains\Domain as Domain;
use Utopia\Platform\Action;
use Utopia\Platform\Enum;
use Utopia\Platform\Scope\HTTP;
use Utopia\System\System;
use Utopia\Validator\Domain as DomainValidator;
@@ -56,7 +57,7 @@ class Get extends Action
->label('abuse-key', 'userId:{userId}, url:{url}')
->label('abuse-time', 60)
->param('value', '', new Text(256), 'Resource value.')
->param('type', '', new WhiteList(['rules']), 'Resource type.')
->param('type', '', new WhiteList(['rules']), 'Resource type.', enum: new Enum(name: 'ConsoleResourceType'))
->inject('response')
->inject('dbForPlatform')
->inject('platform')
@@ -11,6 +11,7 @@ use Utopia\Config\Config;
use Utopia\Database\Document;
use Utopia\Locale\Locale;
use Utopia\Platform\Action;
use Utopia\Platform\Enum;
use Utopia\Platform\Scope\HTTP;
use Utopia\System\System;
use Utopia\Validator\WhiteList;
@@ -46,8 +47,8 @@ class Get extends Action
)
]
))
->param('templateId', '', new WhiteList(Config::getParam('locale-templates')['email'] ?? [], true), 'Email template type. Can be one of: ' . \implode(', ', Config::getParam('locale-templates')['email'] ?? []))
->param('locale', '', fn ($localeCodes) => new WhiteList($localeCodes), 'Template locale. If left empty, the fallback locale (en) will be used.', optional: true, injections: ['localeCodes'])
->param('templateId', '', new WhiteList(Config::getParam('locale-templates')['email'] ?? [], true), 'Email template type. Can be one of: ' . \implode(', ', Config::getParam('locale-templates')['email'] ?? []), enum: new Enum(name: 'ProjectEmailTemplateId'))
->param('locale', '', fn ($localeCodes) => new WhiteList($localeCodes), 'Template locale. If left empty, the fallback locale (en) will be used.', optional: true, injections: ['localeCodes'], enum: new Enum(name: 'ProjectEmailTemplateLocale'))
->inject('response')
->callback($this->action(...));
}
@@ -17,6 +17,7 @@ use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\Key;
use Utopia\Database\Validator\UID;
use Utopia\Http\Adapter\Swoole\Response as SwooleResponse;
use Utopia\Platform\Enum;
use Utopia\Validator\Boolean;
use Utopia\Validator\Nullable;
use Utopia\Validator\WhiteList;
@@ -70,7 +71,7 @@ class Create extends Action
Database::RELATION_MANY_TO_ONE,
Database::RELATION_MANY_TO_MANY,
Database::RELATION_ONE_TO_MANY
], true), 'Relation type')
], true), 'Relation type', enum: new Enum(name: 'RelationshipType'))
->param('twoWay', false, new Boolean(), 'Is Two Way?', true)
->param('key', null, fn (Database $dbForProject) => new Nullable(new Key(false, $dbForProject->getAdapter()->getMaxUIDLength())), 'Attribute Key.', true, ['dbForProject'])
->param('twoWayKey', null, fn (Database $dbForProject) => new Nullable(new Key(false, $dbForProject->getAdapter()->getMaxUIDLength())), 'Two Way Attribute Key.', true, ['dbForProject'])
@@ -78,7 +79,7 @@ class Create extends Action
Database::RELATION_MUTATE_CASCADE,
Database::RELATION_MUTATE_RESTRICT,
Database::RELATION_MUTATE_SET_NULL
], true), 'Constraints option', true)
], true), 'Constraints option', true, enum: new Enum(name: 'RelationMutate'))
->inject('response')
->inject('dbForProject')
->inject('publisherForDatabase')
@@ -16,6 +16,7 @@ use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\Key;
use Utopia\Database\Validator\UID;
use Utopia\Http\Adapter\Swoole\Response as SwooleResponse;
use Utopia\Platform\Enum;
use Utopia\Validator\Nullable;
use Utopia\Validator\WhiteList;
@@ -69,7 +70,7 @@ class Update extends Action
Database::RELATION_MUTATE_CASCADE,
Database::RELATION_MUTATE_RESTRICT,
Database::RELATION_MUTATE_SET_NULL
], true), 'Constraints option', true)
], true), 'Constraints option', true, enum: new Enum(name: 'RelationMutate'))
->param('newKey', null, fn (Database $dbForProject) => new Nullable(new Key(false, $dbForProject->getAdapter()->getMaxUIDLength())), 'New Attribute Key.', true, ['dbForProject'])
->inject('response')
->inject('dbForProject')
@@ -22,6 +22,7 @@ use Utopia\Database\Validator\Index as IndexValidator;
use Utopia\Database\Validator\Key;
use Utopia\Database\Validator\UID;
use Utopia\Http\Adapter\Swoole\Response as SwooleResponse;
use Utopia\Platform\Enum;
use Utopia\Validator\ArrayList;
use Utopia\Validator\Integer;
use Utopia\Validator\Nullable;
@@ -72,9 +73,9 @@ class Create extends Action
->param('databaseId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Database ID.', false, ['dbForProject'])
->param('collectionId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).', false, ['dbForProject'])
->param('key', null, fn (Database $dbForProject) => new Key(false, $dbForProject->getAdapter()->getMaxUIDLength()), 'Index Key.', false, ['dbForProject'])
->param('type', null, new WhiteList([Database::INDEX_KEY, Database::INDEX_FULLTEXT, Database::INDEX_UNIQUE, Database::INDEX_SPATIAL]), 'Index type.')
->param('type', null, new WhiteList([Database::INDEX_KEY, Database::INDEX_FULLTEXT, Database::INDEX_UNIQUE, Database::INDEX_SPATIAL]), 'Index type.', enum: new Enum(name: 'DatabasesIndexType'))
->param('attributes', null, fn (Database $dbForProject) => new ArrayList(new Key(true, $dbForProject->getAdapter()->getMaxUIDLength()), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of attributes to index. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' attributes are allowed, each 32 characters long.', false, ['dbForProject'])
->param('orders', [], new ArrayList(new WhiteList(['ASC', 'DESC'], false, Database::VAR_STRING), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of index orders. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' orders are allowed.', true)
->param('orders', [], new ArrayList(new WhiteList(['ASC', 'DESC'], false, Database::VAR_STRING), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of index orders. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' orders are allowed.', true, enum: new Enum(name: 'OrderBy'))
->param('lengths', [], new ArrayList(new Nullable(new Integer()), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Length of index. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE, optional: true)
->inject('response')
->inject('dbForProject')
@@ -17,6 +17,7 @@ use Utopia\Database\Query;
use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\UID;
use Utopia\Http\Adapter\Swoole\Response as SwooleResponse;
use Utopia\Platform\Enum;
use Utopia\Validator\WhiteList;
class Get extends Action
@@ -64,7 +65,14 @@ class Get extends Action
),
))
->param('databaseId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Database ID.', false, ['dbForProject'])
->param('range', '30d', new WhiteList(['24h', '30d', '90d'], true), 'Date range.', true)
->param('range', '30d', new WhiteList(['24h', '30d', '90d'], true), 'Date range.', true, enum: new Enum(
name: 'UsageRange',
map: [
'24h' => 'Twenty Four Hours',
'30d' => 'Thirty Days',
'90d' => 'Ninety Days',
]
))
->param('collectionId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Collection ID.', false, ['dbForProject'])
->inject('response')
->inject('dbForProject')
@@ -3,6 +3,7 @@
namespace Appwrite\Platform\Modules\Databases\Http\Databases\Usage;
use Appwrite\Extend\Exception;
use Appwrite\Platform\Action;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\ContentType;
use Appwrite\SDK\Deprecated;
@@ -16,7 +17,7 @@ use Utopia\Database\Query;
use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\UID;
use Utopia\Http\Adapter\Swoole\Response as SwooleResponse;
use Utopia\Platform\Action;
use Utopia\Platform\Enum;
use Utopia\Validator\WhiteList;
class Get extends Action
@@ -36,7 +37,9 @@ class Get extends Action
default => DATABASE_TYPE_LEGACY,
};
return parent::setHttpPath($path);
parent::setHttpPath($path);
return $this;
}
protected function getMetrics(): array
@@ -93,7 +96,14 @@ class Get extends Action
)
])
->param('databaseId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Database ID.', false, ['dbForProject'])
->param('range', '30d', new WhiteList(['24h', '30d', '90d'], true), 'Date range.', true)
->param('range', '30d', new WhiteList(['24h', '30d', '90d'], true), 'Date range.', true, enum: new Enum(
name: 'UsageRange',
map: [
'24h' => 'Twenty Four Hours',
'30d' => 'Thirty Days',
'90d' => 'Ninety Days',
]
))
->inject('response')
->inject('dbForProject')
->inject('authorization')
@@ -2,6 +2,7 @@
namespace Appwrite\Platform\Modules\Databases\Http\Databases\Usage;
use Appwrite\Platform\Action;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\ContentType;
use Appwrite\SDK\Deprecated;
@@ -14,7 +15,7 @@ use Utopia\Database\Document;
use Utopia\Database\Query;
use Utopia\Database\Validator\Authorization;
use Utopia\Http\Adapter\Swoole\Response as SwooleResponse;
use Utopia\Platform\Action;
use Utopia\Platform\Enum;
use Utopia\Validator\WhiteList;
class XList extends Action
@@ -34,7 +35,9 @@ class XList extends Action
default => DATABASE_TYPE_LEGACY,
};
return parent::setHttpPath($path);
parent::setHttpPath($path);
return $this;
}
protected function getMetrics(): array
@@ -90,7 +93,14 @@ class XList extends Action
)
),
])
->param('range', '30d', new WhiteList(['24h', '30d', '90d'], true), 'Date range.', true)
->param('range', '30d', new WhiteList(['24h', '30d', '90d'], true), 'Date range.', true, enum: new Enum(
name: 'UsageRange',
map: [
'24h' => 'Twenty Four Hours',
'30d' => 'Thirty Days',
'90d' => 'Ninety Days',
]
))
->inject('response')
->inject('dbForProject')
->inject('authorization')
@@ -12,6 +12,7 @@ use Utopia\Database\Database;
use Utopia\Database\Validator\Key;
use Utopia\Database\Validator\UID;
use Utopia\Http\Adapter\Swoole\Response as SwooleResponse;
use Utopia\Platform\Enum;
use Utopia\Validator\ArrayList;
use Utopia\Validator\Integer;
use Utopia\Validator\Nullable;
@@ -58,9 +59,9 @@ class Create extends IndexCreate
->param('databaseId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Database ID.', false, ['dbForProject'])
->param('collectionId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).', false, ['dbForProject'])
->param('key', null, fn (Database $dbForProject) => new Key(false, $dbForProject->getAdapter()->getMaxUIDLength()), 'Index Key.', false, ['dbForProject'])
->param('type', null, new WhiteList([Database::INDEX_KEY, Database::INDEX_FULLTEXT, Database::INDEX_UNIQUE]), 'Index type.')
->param('type', null, new WhiteList([Database::INDEX_KEY, Database::INDEX_FULLTEXT, Database::INDEX_UNIQUE]), 'Index type.', enum: new Enum(name: 'DocumentsDBIndexType'))
->param('attributes', null, fn (Database $dbForProject) => new ArrayList(new Key(true, $dbForProject->getAdapter()->getMaxUIDLength()), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of attributes to index. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' attributes are allowed, each 32 characters long.', false, ['dbForProject'])
->param('orders', [], new ArrayList(new WhiteList(['ASC', 'DESC'], false, Database::VAR_STRING), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of index orders. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' orders are allowed.', true)
->param('orders', [], new ArrayList(new WhiteList(['ASC', 'DESC'], false, Database::VAR_STRING), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of index orders. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' orders are allowed.', true, enum: new Enum(name: 'OrderBy'))
->param('lengths', [], new ArrayList(new Nullable(new Integer()), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Length of index. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE, optional: true)
->inject('response')
->inject('dbForProject')
@@ -11,6 +11,7 @@ use Appwrite\Utopia\Response as UtopiaResponse;
use Utopia\Database\Database;
use Utopia\Database\Validator\UID;
use Utopia\Http\Adapter\Swoole\Response as SwooleResponse;
use Utopia\Platform\Enum;
use Utopia\Validator\WhiteList;
class Get extends CollectionUsageGet
@@ -54,7 +55,14 @@ class Get extends CollectionUsageGet
contentType: ContentType::JSON,
))
->param('databaseId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Database ID.', false, ['dbForProject'])
->param('range', '30d', new WhiteList(['24h', '30d', '90d'], true), 'Date range.', true)
->param('range', '30d', new WhiteList(['24h', '30d', '90d'], true), 'Date range.', true, enum: new Enum(
name: 'UsageRange',
map: [
'24h' => 'Twenty Four Hours',
'30d' => 'Thirty Days',
'90d' => 'Ninety Days',
]
))
->param('collectionId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Collection ID.', false, ['dbForProject'])
->inject('response')
->inject('dbForProject')
@@ -11,6 +11,7 @@ use Appwrite\Utopia\Response as UtopiaResponse;
use Utopia\Database\Database;
use Utopia\Database\Validator\UID;
use Utopia\Http\Adapter\Swoole\Response as SwooleResponse;
use Utopia\Platform\Enum;
use Utopia\Validator\WhiteList;
class Get extends DatabaseUsageGet
@@ -51,7 +52,14 @@ class Get extends DatabaseUsageGet
),
])
->param('databaseId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Database ID.', false, ['dbForProject'])
->param('range', '30d', new WhiteList(['24h', '30d', '90d'], true), 'Date range.', true)
->param('range', '30d', new WhiteList(['24h', '30d', '90d'], true), 'Date range.', true, enum: new Enum(
name: 'UsageRange',
map: [
'24h' => 'Twenty Four Hours',
'30d' => 'Thirty Days',
'90d' => 'Ninety Days',
]
))
->inject('response')
->inject('dbForProject')
->inject('authorization')
@@ -9,6 +9,7 @@ use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Response as UtopiaResponse;
use Utopia\Http\Adapter\Swoole\Response as SwooleResponse;
use Utopia\Platform\Enum;
use Utopia\Validator\WhiteList;
class XList extends DatabaseUsageXList
@@ -48,7 +49,14 @@ class XList extends DatabaseUsageXList
contentType: ContentType::JSON
),
])
->param('range', '30d', new WhiteList(['24h', '30d', '90d'], true), 'Date range.', true)
->param('range', '30d', new WhiteList(['24h', '30d', '90d'], true), 'Date range.', true, enum: new Enum(
name: 'UsageRange',
map: [
'24h' => 'Twenty Four Hours',
'30d' => 'Thirty Days',
'90d' => 'Ninety Days',
]
))
->inject('response')
->inject('dbForProject')
->callback($this->action(...));
@@ -11,6 +11,7 @@ use Utopia\Database\Database;
use Utopia\Database\Validator\Key;
use Utopia\Database\Validator\UID;
use Utopia\Http\Adapter\Swoole\Response as SwooleResponse;
use Utopia\Platform\Enum;
use Utopia\Validator\Boolean;
use Utopia\Validator\Nullable;
use Utopia\Validator\WhiteList;
@@ -60,7 +61,7 @@ class Create extends RelationshipCreate
Database::RELATION_MANY_TO_ONE,
Database::RELATION_MANY_TO_MANY,
Database::RELATION_ONE_TO_MANY
], true), 'Relation type')
], true), 'Relation type', enum: new Enum(name: 'RelationshipType'))
->param('twoWay', false, new Boolean(), 'Is Two Way?', true)
->param('key', null, fn (Database $dbForProject) => new Nullable(new Key(false, $dbForProject->getAdapter()->getMaxUIDLength())), 'Column Key.', true, ['dbForProject'])
->param('twoWayKey', null, fn (Database $dbForProject) => new Nullable(new Key(false, $dbForProject->getAdapter()->getMaxUIDLength())), 'Two Way Column Key.', true, ['dbForProject'])
@@ -68,7 +69,7 @@ class Create extends RelationshipCreate
Database::RELATION_MUTATE_CASCADE,
Database::RELATION_MUTATE_RESTRICT,
Database::RELATION_MUTATE_SET_NULL
], true), 'Constraints option', true)
], true), 'Constraints option', true, enum: new Enum(name: 'RelationMutate'))
->inject('response')
->inject('dbForProject')
->inject('publisherForDatabase')
@@ -12,6 +12,7 @@ use Utopia\Database\Database;
use Utopia\Database\Validator\Key;
use Utopia\Database\Validator\UID;
use Utopia\Http\Adapter\Swoole\Response as SwooleResponse;
use Utopia\Platform\Enum;
use Utopia\Validator\Nullable;
use Utopia\Validator\WhiteList;
@@ -60,7 +61,7 @@ class Update extends RelationshipUpdate
Database::RELATION_MUTATE_CASCADE,
Database::RELATION_MUTATE_RESTRICT,
Database::RELATION_MUTATE_SET_NULL
], true)), 'Constraints option', true)
], true)), 'Constraints option', true, enum: new Enum(name: 'RelationMutate'))
->param('newKey', null, fn (Database $dbForProject) => new Nullable(new Key(false, $dbForProject->getAdapter()->getMaxUIDLength())), 'New Column Key.', true, ['dbForProject'])
->inject('response')
->inject('dbForProject')
@@ -12,6 +12,7 @@ use Utopia\Database\Database;
use Utopia\Database\Validator\Key;
use Utopia\Database\Validator\UID;
use Utopia\Http\Adapter\Swoole\Response as SwooleResponse;
use Utopia\Platform\Enum;
use Utopia\Validator\ArrayList;
use Utopia\Validator\Integer;
use Utopia\Validator\Nullable;
@@ -58,9 +59,9 @@ class Create extends IndexCreate
->param('databaseId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Database ID.', false, ['dbForProject'])
->param('tableId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Table ID. You can create a new table using the Database service [server integration](https://appwrite.io/docs/references/cloud/server-dart/tablesDB#createTable).', false, ['dbForProject'])
->param('key', null, fn (Database $dbForProject) => new Key(false, $dbForProject->getAdapter()->getMaxUIDLength()), 'Index Key.', false, ['dbForProject'])
->param('type', null, new WhiteList([Database::INDEX_KEY, Database::INDEX_FULLTEXT, Database::INDEX_UNIQUE, Database::INDEX_SPATIAL]), 'Index type.')
->param('type', null, new WhiteList([Database::INDEX_KEY, Database::INDEX_FULLTEXT, Database::INDEX_UNIQUE, Database::INDEX_SPATIAL]), 'Index type.', enum: new Enum(name: 'TablesDBIndexType'))
->param('columns', null, fn (Database $dbForProject) => new ArrayList(new Key(true, $dbForProject->getAdapter()->getMaxUIDLength()), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of columns to index. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' columns are allowed, each 32 characters long.', false, ['dbForProject'])
->param('orders', [], new ArrayList(new WhiteList(['ASC', 'DESC'], false, Database::VAR_STRING), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of index orders. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' orders are allowed.', true)
->param('orders', [], new ArrayList(new WhiteList(['ASC', 'DESC'], false, Database::VAR_STRING), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of index orders. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' orders are allowed.', true, enum: new Enum(name: 'OrderBy'))
->param('lengths', [], new ArrayList(new Nullable(new Integer()), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Length of index. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE, optional: true)
->inject('response')
->inject('dbForProject')
@@ -11,6 +11,7 @@ use Appwrite\Utopia\Response as UtopiaResponse;
use Utopia\Database\Database;
use Utopia\Database\Validator\UID;
use Utopia\Http\Adapter\Swoole\Response as SwooleResponse;
use Utopia\Platform\Enum;
use Utopia\Validator\WhiteList;
class Get extends CollectionUsageGet
@@ -49,7 +50,14 @@ class Get extends CollectionUsageGet
contentType: ContentType::JSON,
))
->param('databaseId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Database ID.', false, ['dbForProject'])
->param('range', '30d', new WhiteList(['24h', '30d', '90d'], true), 'Date range.', true)
->param('range', '30d', new WhiteList(['24h', '30d', '90d'], true), 'Date range.', true, enum: new Enum(
name: 'UsageRange',
map: [
'24h' => 'Twenty Four Hours',
'30d' => 'Thirty Days',
'90d' => 'Ninety Days',
]
))
->param('tableId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Table ID.', false, ['dbForProject'])
->inject('response')
->inject('dbForProject')
@@ -11,6 +11,7 @@ use Appwrite\Utopia\Response as UtopiaResponse;
use Utopia\Database\Database;
use Utopia\Database\Validator\UID;
use Utopia\Http\Adapter\Swoole\Response as SwooleResponse;
use Utopia\Platform\Enum;
use Utopia\Validator\WhiteList;
class Get extends DatabaseUsageGet
@@ -46,7 +47,14 @@ class Get extends DatabaseUsageGet
),
])
->param('databaseId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Database ID.', false, ['dbForProject'])
->param('range', '30d', new WhiteList(['24h', '30d', '90d'], true), 'Date range.', true)
->param('range', '30d', new WhiteList(['24h', '30d', '90d'], true), 'Date range.', true, enum: new Enum(
name: 'UsageRange',
map: [
'24h' => 'Twenty Four Hours',
'30d' => 'Thirty Days',
'90d' => 'Ninety Days',
]
))
->inject('response')
->inject('dbForProject')
->inject('authorization')
@@ -9,6 +9,7 @@ use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Response as UtopiaResponse;
use Utopia\Http\Adapter\Swoole\Response as SwooleResponse;
use Utopia\Platform\Enum;
use Utopia\Validator\WhiteList;
class XList extends DatabaseUsageXList
@@ -43,7 +44,14 @@ class XList extends DatabaseUsageXList
contentType: ContentType::JSON
),
])
->param('range', '30d', new WhiteList(['24h', '30d', '90d'], true), 'Date range.', true)
->param('range', '30d', new WhiteList(['24h', '30d', '90d'], true), 'Date range.', true, enum: new Enum(
name: 'UsageRange',
map: [
'24h' => 'Twenty Four Hours',
'30d' => 'Thirty Days',
'90d' => 'Ninety Days',
]
))
->inject('response')
->inject('dbForProject')
->inject('authorization')
@@ -12,6 +12,7 @@ use Utopia\Database\Database;
use Utopia\Database\Validator\Key;
use Utopia\Database\Validator\UID;
use Utopia\Http\Adapter\Swoole\Response as SwooleResponse;
use Utopia\Platform\Enum;
use Utopia\Validator\ArrayList;
use Utopia\Validator\Integer;
use Utopia\Validator\Nullable;
@@ -58,9 +59,9 @@ class Create extends IndexCreate
->param('databaseId', '', new UID(), 'Database ID.')
->param('collectionId', '', new UID(), 'Collection ID. You can create a new collection using the Database service [server integration](https://appwrite.io/docs/server/databases#databasesCreateCollection).')
->param('key', null, new Key(), 'Index Key.')
->param('type', null, new WhiteList([Database::INDEX_HNSW_EUCLIDEAN,Database::INDEX_HNSW_DOT, Database::INDEX_HNSW_COSINE, Database::INDEX_OBJECT, Database::INDEX_KEY, Database::INDEX_UNIQUE]), 'Index type.')
->param('type', null, new WhiteList([Database::INDEX_HNSW_EUCLIDEAN,Database::INDEX_HNSW_DOT, Database::INDEX_HNSW_COSINE, Database::INDEX_OBJECT, Database::INDEX_KEY, Database::INDEX_UNIQUE]), 'Index type.', enum: new Enum(name: 'VectorsDBIndexType'))
->param('attributes', null, new ArrayList(new Key(true), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of attributes to index. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' attributes are allowed, each 32 characters long.')
->param('orders', [], new ArrayList(new WhiteList(['ASC', 'DESC'], false, Database::VAR_STRING), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of index orders. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' orders are allowed.', true)
->param('orders', [], new ArrayList(new WhiteList(['ASC', 'DESC'], false, Database::VAR_STRING), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Array of index orders. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' orders are allowed.', true, enum: new Enum(name: 'OrderBy'))
->param('lengths', [], new ArrayList(new Nullable(new Integer()), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Length of index. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE, optional: true)
->inject('response')
->inject('dbForProject')
@@ -10,6 +10,7 @@ use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Response as UtopiaResponse;
use Utopia\Database\Validator\UID;
use Utopia\Http\Adapter\Swoole\Response as SwooleResponse;
use Utopia\Platform\Enum;
use Utopia\Validator\WhiteList;
class Get extends CollectionUsageGet
@@ -53,7 +54,14 @@ class Get extends CollectionUsageGet
contentType: ContentType::JSON,
))
->param('databaseId', '', new UID(), 'Database ID.')
->param('range', '30d', new WhiteList(['24h', '30d', '90d'], true), 'Date range.', true)
->param('range', '30d', new WhiteList(['24h', '30d', '90d'], true), 'Date range.', true, enum: new Enum(
name: 'UsageRange',
map: [
'24h' => 'Twenty Four Hours',
'30d' => 'Thirty Days',
'90d' => 'Ninety Days',
]
))
->param('collectionId', '', new UID(), 'Collection ID.')
->inject('response')
->inject('dbForProject')
@@ -16,6 +16,7 @@ use Utopia\Database\Document;
use Utopia\Http\Adapter\Swoole\Response as SwooleResponse;
use Utopia\Logger\Log;
use Utopia\Logger\Logger;
use Utopia\Platform\Enum;
use Utopia\System\System;
use Utopia\Validator\ArrayList;
use Utopia\Validator\Text;
@@ -74,7 +75,7 @@ class Create extends CreateDocumentAction
)
])
->param('texts', [], fn (array $plan) => new ArrayList(new Text(0), $plan['databasesMaxEmbeddingTexts'] ?? APP_LIMIT_DATABASE_BATCH), 'Array of text to generate embeddings.', false, ['plan'])
->param('model', Ollama::MODEL_EMBEDDING_GEMMA, new WhiteList(Ollama::MODELS), 'The embedding model to use for generating vector embeddings.', true)
->param('model', Ollama::MODEL_EMBEDDING_GEMMA, new WhiteList(Ollama::MODELS), 'The embedding model to use for generating vector embeddings.', true, enum: new Enum(name: 'EmbeddingModel'))
->inject('response')
->inject('project')
->inject('embeddingAgent')
@@ -10,6 +10,7 @@ use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Response as UtopiaResponse;
use Utopia\Database\Validator\UID;
use Utopia\Http\Adapter\Swoole\Response as SwooleResponse;
use Utopia\Platform\Enum;
use Utopia\Validator\WhiteList;
class Get extends DatabaseUsageGet
@@ -50,7 +51,14 @@ class Get extends DatabaseUsageGet
),
])
->param('databaseId', '', new UID(), 'Database ID.')
->param('range', '30d', new WhiteList(['24h', '30d', '90d'], true), 'Date range.', true)
->param('range', '30d', new WhiteList(['24h', '30d', '90d'], true), 'Date range.', true, enum: new Enum(
name: 'UsageRange',
map: [
'24h' => 'Twenty Four Hours',
'30d' => 'Thirty Days',
'90d' => 'Ninety Days',
]
))
->inject('response')
->inject('dbForProject')
->inject('authorization')
@@ -9,6 +9,7 @@ use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Response as UtopiaResponse;
use Utopia\Http\Adapter\Swoole\Response as SwooleResponse;
use Utopia\Platform\Enum;
use Utopia\Validator\WhiteList;
class XList extends DatabaseUsageXList
@@ -48,7 +49,14 @@ class XList extends DatabaseUsageXList
contentType: ContentType::JSON
),
])
->param('range', '30d', new WhiteList(['24h', '30d', '90d'], true), 'Date range.', true)
->param('range', '30d', new WhiteList(['24h', '30d', '90d'], true), 'Date range.', true, enum: new Enum(
name: 'UsageRange',
map: [
'24h' => 'Twenty Four Hours',
'30d' => 'Thirty Days',
'90d' => 'Ninety Days',
]
))
->inject('response')
->inject('dbForProject')
->callback($this->action(...));
@@ -3,6 +3,7 @@
namespace Appwrite\Platform\Modules\Functions\Http\Deployments\Download;
use Appwrite\Extend\Exception;
use Appwrite\Platform\Action;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\ContentType;
use Appwrite\SDK\Method;
@@ -12,7 +13,7 @@ use Appwrite\Utopia\Response;
use Utopia\Database\Database;
use Utopia\Database\Validator\UID;
use Utopia\Http\Adapter\Swoole\Request;
use Utopia\Platform\Action;
use Utopia\Platform\Enum;
use Utopia\Platform\Scope\HTTP;
use Utopia\Storage\Device;
use Utopia\Validator\WhiteList;
@@ -55,7 +56,7 @@ class Get extends Action
))
->param('functionId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Function ID.', false, ['dbForProject'])
->param('deploymentId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Deployment ID.', false, ['dbForProject'])
->param('type', 'source', new WhiteList(['source', 'output']), 'Deployment file to download. Can be: "source", "output".', true)
->param('type', 'source', new WhiteList(['source', 'output']), 'Deployment file to download. Can be: "source", "output".', true, enum: new Enum(name: 'DeploymentDownloadType'))
->inject('response')
->inject('request')
->inject('dbForProject')
@@ -6,6 +6,7 @@ use Appwrite\Event\Event;
use Appwrite\Event\Message\Build as BuildMessage;
use Appwrite\Event\Publisher\Build as BuildPublisher;
use Appwrite\Extend\Exception;
use Appwrite\Platform\Action;
use Appwrite\Platform\Modules\Compute\Base;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\Method;
@@ -19,7 +20,7 @@ use Utopia\Database\Helpers\Role;
use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\UID;
use Utopia\Http\Adapter\Swoole\Request;
use Utopia\Platform\Action;
use Utopia\Platform\Enum;
use Utopia\Platform\Scope\HTTP;
use Utopia\Validator\Boolean;
use Utopia\Validator\Text;
@@ -68,7 +69,7 @@ class Create extends Base
->param('repository', '', new Text(128, 0), 'Repository name of the template.')
->param('owner', '', new Text(128, 0), 'The name of the owner of the template.')
->param('rootDirectory', '', new Text(128, 0), 'Path to function code in the template repo.')
->param('type', '', new WhiteList(['commit', 'branch', 'tag']), 'Type for the reference provided. Can be commit, branch, or tag')
->param('type', '', new WhiteList(['commit', 'branch', 'tag']), 'Type for the reference provided. Can be commit, branch, or tag', enum: new Enum(name: 'TemplateReferenceType'))
->param('reference', '', new Text(128, 0), 'Reference value, can be a commit hash, branch name, or release tag')
->param('activate', false, new Boolean(), 'Automatically activate the deployment when it is finished building.', true)
->inject('request')
@@ -5,6 +5,7 @@ namespace Appwrite\Platform\Modules\Functions\Http\Deployments\Vcs;
use Appwrite\Event\Event;
use Appwrite\Event\Publisher\Build as BuildPublisher;
use Appwrite\Extend\Exception;
use Appwrite\Platform\Action;
use Appwrite\Platform\Modules\Compute\Base;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\Method;
@@ -14,7 +15,7 @@ use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Validator\UID;
use Utopia\Http\Adapter\Swoole\Request;
use Utopia\Platform\Action;
use Utopia\Platform\Enum;
use Utopia\Platform\Scope\HTTP;
use Utopia\Validator\Boolean;
use Utopia\Validator\Text;
@@ -61,7 +62,7 @@ class Create extends Base
))
->param('functionId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Function ID.', false, ['dbForProject'])
// TODO: Support tag in future
->param('type', '', new WhiteList(['branch', 'commit']), 'Type of reference passed. Allowed values are: branch, commit')
->param('type', '', new WhiteList(['branch', 'commit']), 'Type of reference passed. Allowed values are: branch, commit', enum: new Enum(name: 'VCSReferenceType'))
->param('reference', '', new Text(255), 'VCS reference to create deployment from. Depending on type this can be: branch name, commit hash')
->param('activate', false, new Boolean(), 'Automatically activate the deployment when it is finished building.', true)
->inject('request')
@@ -38,6 +38,7 @@ use Utopia\Database\Validator\Datetime as DatetimeValidator;
use Utopia\Database\Validator\UID;
use Utopia\Http\Adapter\Swoole\Request;
use Utopia\Platform\Action;
use Utopia\Platform\Enum;
use Utopia\Platform\Scope\HTTP;
use Utopia\System\System;
use Utopia\Validator\AnyOf;
@@ -86,7 +87,7 @@ class Create extends Base
->param('body', '', new Text(10485760, 0), 'HTTP body of execution. Default value is empty string.', true)
->param('async', false, new Boolean(true), 'Execute code in the background. Default value is false.', true)
->param('path', '/', new Text(2048), 'HTTP path of execution. Path can include query params. Default value is /', true)
->param('method', 'POST', new Whitelist(['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS', 'HEAD'], true), 'HTTP method of execution. Default value is POST.', true)
->param('method', 'POST', new Whitelist(['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS', 'HEAD'], true), 'HTTP method of execution. Default value is POST.', true, enum: new Enum(name: 'ExecutionMethod'))
->param('headers', [], new AnyOf([new Assoc(), new Text(65535)], AnyOf::TYPE_MIXED), 'HTTP headers of execution. Defaults to empty.', true)
->param('scheduledAt', null, new Nullable(new Text(100)), 'Scheduled execution time in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. DateTime value must be in future with precision in minutes.', true)
->inject('response')
@@ -325,6 +326,7 @@ class Create extends Base
'resourceInternalId' => $execution->getSequence(),
'resourceUpdatedAt' => DateTime::now(),
'projectId' => $project->getId(),
'projectInternalId' => $project->getSequence(),
'schedule' => $scheduledAt,
'data' => $data,
'active' => true,
@@ -33,6 +33,7 @@ use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\Roles;
use Utopia\Http\Request;
use Utopia\Platform\Action;
use Utopia\Platform\Enum;
use Utopia\Platform\Scope\HTTP;
use Utopia\System\System;
use Utopia\Validator\ArrayList;
@@ -80,7 +81,7 @@ class Create extends Base
))
->param('functionId', '', fn (Database $dbForProject) => new CustomId(false, $dbForProject->getAdapter()->getMaxUIDLength()), 'Function 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.', false, ['dbForProject'])
->param('name', '', new Text(128), 'Function name. Max length: 128 chars.')
->param('runtime', '', new WhiteList(array_keys(Config::getParam('runtimes')), true), 'Execution runtime.')
->param('runtime', '', new WhiteList(array_keys(Config::getParam('runtimes')), true), 'Execution runtime.', enum: new Enum(name: 'FunctionRuntime'))
->param('execute', [], new Roles(APP_LIMIT_ARRAY_PARAMS_SIZE), 'An array of role strings with execution permissions. By default no user is granted with any execute permissions. [learn more about roles](https://appwrite.io/docs/permissions#permission-roles). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' roles are allowed, each 64 characters long.', true)
->param('events', [], new ArrayList(new FunctionEvent(), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Events list. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' events are allowed.', true)
->param('schedule', '', new Cron(), 'Schedule CRON syntax.', true)
@@ -89,7 +90,7 @@ class Create extends Base
->param('logging', true, new Boolean(), 'When disabled, executions will exclude logs and errors, and will be slightly faster.', true)
->param('entrypoint', '', new Text(1028, 0), 'Entrypoint File. This path is relative to the "providerRootDirectory".', true)
->param('commands', '', new Text(8192, 0), 'Build Commands.', true)
->param('scopes', [], new ArrayList(new WhiteList(array_keys(Config::getParam('projectScopes')), true), APP_LIMIT_ARRAY_PARAMS_SIZE), 'List of scopes allowed for API key auto-generated for every execution. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' scopes are allowed.', true)
->param('scopes', [], new ArrayList(new WhiteList(array_keys(Config::getParam('projectScopes')), true), APP_LIMIT_ARRAY_PARAMS_SIZE), 'List of scopes allowed for API key auto-generated for every execution. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' scopes are allowed.', true, enum: new Enum(name: 'ProjectKeyScopes'))
->param('installationId', '', new Text(128, 0), 'Appwrite Installation ID for VCS (Version Control System) deployment.', true)
->param('providerRepositoryId', '', new Text(128, 0), 'Repository ID of the repo linked to the function.', true)
->param('providerBranch', '', new Text(128, 0), 'Production branch for the repo linked to the function.', true)
@@ -269,6 +270,7 @@ class Create extends Base
'resourceInternalId' => $function->getSequence(),
'resourceUpdatedAt' => DateTime::now(),
'projectId' => $project->getId(),
'projectInternalId' => $project->getSequence(),
'schedule' => $function->getAttribute('schedule'),
'active' => false,
]))
@@ -25,6 +25,7 @@ use Utopia\Database\Validator\Roles;
use Utopia\Database\Validator\UID;
use Utopia\Http\Adapter\Swoole\Request;
use Utopia\Platform\Action;
use Utopia\Platform\Enum;
use Utopia\Platform\Scope\HTTP;
use Utopia\System\System;
use Utopia\Validator\ArrayList;
@@ -72,7 +73,7 @@ class Update extends Base
))
->param('functionId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Function ID.', false, ['dbForProject'])
->param('name', '', new Text(128), 'Function name. Max length: 128 chars.')
->param('runtime', '', new WhiteList(array_keys(Config::getParam('runtimes')), true), 'Execution runtime.', true)
->param('runtime', '', new WhiteList(array_keys(Config::getParam('runtimes')), true), 'Execution runtime.', true, enum: new Enum(name: 'FunctionRuntime'))
->param('execute', [], new Roles(APP_LIMIT_ARRAY_PARAMS_SIZE), 'An array of role strings with execution permissions. By default no user is granted with any execute permissions. [learn more about roles](https://appwrite.io/docs/permissions#permission-roles). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' roles are allowed, each 64 characters long.', true)
->param('events', [], new ArrayList(new FunctionEvent(), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Events list. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' events are allowed.', true)
->param('schedule', '', new Cron(), 'Schedule CRON syntax.', true)
@@ -81,7 +82,7 @@ class Update extends Base
->param('logging', true, new Boolean(), 'When disabled, executions will exclude logs and errors, and will be slightly faster.', true)
->param('entrypoint', '', new Text(1028, 0), 'Entrypoint File. This path is relative to the "providerRootDirectory".', true)
->param('commands', '', new Text(8192, 0), 'Build Commands.', true)
->param('scopes', [], new ArrayList(new WhiteList(array_keys(Config::getParam('projectScopes')), true), APP_LIMIT_ARRAY_PARAMS_SIZE), 'List of scopes allowed for API Key auto-generated for every execution. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' scopes are allowed.', true)
->param('scopes', [], new ArrayList(new WhiteList(array_keys(Config::getParam('projectScopes')), true), APP_LIMIT_ARRAY_PARAMS_SIZE), 'List of scopes allowed for API Key auto-generated for every execution. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' scopes are allowed.', true, enum: new Enum(name: 'ProjectKeyScopes'))
->param('installationId', '', new Text(128, 0), 'Appwrite Installation ID for VCS (Version Controle System) deployment.', true)
->param('providerRepositoryId', null, new Nullable(new Text(128, 0)), 'Repository ID of the repo linked to the function', true)
->param('providerBranch', '', new Text(128, 0), 'Production branch for the repo linked to the function', true)
@@ -305,6 +306,7 @@ class Update extends Base
'resourceInternalId' => $function->getSequence(),
'resourceUpdatedAt' => DateTime::now(),
'projectId' => $project->getId(),
'projectInternalId' => $project->getSequence(),
'schedule' => $function->getAttribute('schedule'),
'active' => false,
]))
@@ -317,6 +319,7 @@ class Update extends Base
}
$schedule
->setAttribute('projectInternalId', $project->getSequence())
->setAttribute('resourceUpdatedAt', DateTime::now())
->setAttribute('schedule', $function->getAttribute('schedule'))
->setAttribute('active', !empty($function->getAttribute('schedule')) && !empty($function->getAttribute('deploymentId')));
@@ -11,6 +11,7 @@ use FunctionUseCases;
use Utopia\Config\Config;
use Utopia\Database\Document;
use Utopia\Platform\Action;
use Utopia\Platform\Enum;
use Utopia\Platform\Scope\HTTP;
use Utopia\Validator\ArrayList;
use Utopia\Validator\Boolean;
@@ -50,8 +51,8 @@ class XList extends Base
)
]
))
->param('runtimes', [], new ArrayList(new WhiteList(array_keys(Config::getParam('runtimes')), true), APP_LIMIT_ARRAY_PARAMS_SIZE), 'List of runtimes allowed for filtering function templates. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' runtimes are allowed.', true)
->param('useCases', [], new ArrayList(new WhiteList(FunctionUseCases::getAll()), APP_LIMIT_ARRAY_PARAMS_SIZE), 'List of use cases allowed for filtering function templates. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' use cases are allowed.', true)
->param('runtimes', [], new ArrayList(new WhiteList(array_keys(Config::getParam('runtimes')), true), APP_LIMIT_ARRAY_PARAMS_SIZE), 'List of runtimes allowed for filtering function templates. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' runtimes are allowed.', true, enum: new Enum(name: 'FunctionRuntime'))
->param('useCases', [], new ArrayList(new WhiteList(FunctionUseCases::getAll()), APP_LIMIT_ARRAY_PARAMS_SIZE), 'List of use cases allowed for filtering function templates. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' use cases are allowed.', true, enum: new Enum(name: 'FunctionTemplateUseCase'))
->param('limit', 25, new Range(1, 5000), 'Limit the number of templates returned in the response. Default limit is 25, and maximum limit is 5000.', true)
->param('offset', 0, new Range(0, 5000), 'Offset the list of returned templates. Maximum offset is 5000.', true)
->param('total', true, new Boolean(true), 'When set to false, the total count returned will be 0 and will not be calculated.', true)
@@ -3,6 +3,7 @@
namespace Appwrite\Platform\Modules\Functions\Http\Usage;
use Appwrite\Extend\Exception;
use Appwrite\Platform\Action;
use Appwrite\Platform\Modules\Compute\Base;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\Method;
@@ -14,7 +15,7 @@ use Utopia\Database\Document;
use Utopia\Database\Query;
use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\UID;
use Utopia\Platform\Action;
use Utopia\Platform\Enum;
use Utopia\Platform\Scope\HTTP;
use Utopia\Validator\WhiteList;
@@ -52,7 +53,14 @@ class Get extends Base
]
))
->param('functionId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Function ID.', false, ['dbForProject'])
->param('range', '30d', new WhiteList(['24h', '30d', '90d']), 'Date range.', true)
->param('range', '30d', new WhiteList(['24h', '30d', '90d']), 'Date range.', true, enum: new Enum(
name: 'UsageRange',
map: [
'24h' => 'Twenty Four Hours',
'30d' => 'Thirty Days',
'90d' => 'Ninety Days',
]
))
->inject('response')
->inject('dbForProject')
->inject('authorization')
@@ -3,6 +3,7 @@
namespace Appwrite\Platform\Modules\Functions\Http\Usage;
use Appwrite\Extend\Exception;
use Appwrite\Platform\Action;
use Appwrite\Platform\Modules\Compute\Base;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\Method;
@@ -13,7 +14,7 @@ use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Query;
use Utopia\Database\Validator\Authorization;
use Utopia\Platform\Action;
use Utopia\Platform\Enum;
use Utopia\Platform\Scope\HTTP;
use Utopia\Validator\WhiteList;
@@ -50,7 +51,14 @@ class XList extends Base
)
]
))
->param('range', '30d', new WhiteList(['24h', '30d', '90d']), 'Date range.', true)
->param('range', '30d', new WhiteList(['24h', '30d', '90d']), 'Date range.', true, enum: new Enum(
name: 'UsageRange',
map: [
'24h' => 'Twenty Four Hours',
'30d' => 'Thirty Days',
'90d' => 'Ninety Days',
]
))
->inject('response')
->inject('dbForProject')
->inject('authorization')
@@ -21,7 +21,6 @@ use Executor\Exception\Timeout as ExecutorTimeout;
use Executor\Executor;
use Swoole\Coroutine as Co;
use Utopia\Cache\Cache;
use Utopia\Command;
use Utopia\Config\Config;
use Utopia\Console;
use Utopia\Database\Database;
@@ -317,24 +316,10 @@ class Builds extends Action
throw new \Exception('Unable to clone code repository: ' . $stderr);
}
$cleanupGitDirectoryCommand = (new Command('find'))
->argument($tmpTemplateDirectory)
->argument('-type')
->argument('d')
->argument('-name')
->argument('.git')
->argument('-exec')
->argument('rm')
->argument('-rf')
->argument('{}')
->argument('+');
Console::execute($cleanupGitDirectoryCommand, '', $stdout, $stderr);
Console::execute('find ' . \escapeshellarg($tmpTemplateDirectory) . ' -type d -name ".git" -exec rm -rf {} +', '', $stdout, $stderr);
// Ensure directories
$ensureTemplateDirectoryCommand = (new Command('mkdir'))
->flag('-p')
->argument($tmpTemplateDirectory . '/' . $templateRootDirectory);
Console::execute($ensureTemplateDirectoryCommand, '', $stdout, $stderr);
Console::execute('mkdir -p ' . \escapeshellarg($tmpTemplateDirectory . '/' . $templateRootDirectory), '', $stdout, $stderr);
$tmpPathFile = $tmpTemplateDirectory . '/code.tar.gz';
@@ -344,14 +329,8 @@ class Builds extends Action
$tmpTemplateDirectory .= '/';
}
$tarParamDirectory = $tmpTemplateDirectory . (empty($templateRootDirectory) ? '' : '/' . $templateRootDirectory);
$archiveCommand = (new Command('tar'))
->option('--exclude', 'code.tar.gz')
->flag('-czf')
->argument($tmpPathFile)
->option('-C', $tarParamDirectory)
->argument('.');
Console::execute($archiveCommand, '', $stdout, $stderr);
$tarParamDirectory = \escapeshellarg($tmpTemplateDirectory . (empty($templateRootDirectory) ? '' : '/' . $templateRootDirectory));
Console::execute('tar --exclude code.tar.gz -czf ' . \escapeshellarg($tmpPathFile) . ' -C ' . \escapeshellcmd($tarParamDirectory) . ' .', '', $stdout, $stderr); // TODO: Replace escapeshellcmd with escapeshellarg if we find a way that doesnt break syntax
$source = $device->getPath($deployment->getId() . '.' . \pathinfo('code.tar.gz', PATHINFO_EXTENSION));
$result = $localDevice->transfer($tmpPathFile, $source, $device);
@@ -360,10 +339,7 @@ class Builds extends Action
throw new \Exception('Unable to move file');
}
$removeTemplateDirectoryCommand = (new Command('rm'))
->flag('-rf')
->argument($tmpTemplateDirectory);
Console::execute($removeTemplateDirectoryCommand, '', $stdout, $stderr);
Console::execute('rm -rf ' . \escapeshellarg($tmpTemplateDirectory), '', $stdout, $stderr);
$directorySize = $device->getFileSize($source);
$deployment
@@ -410,10 +386,7 @@ class Builds extends Action
$stdout = '';
$stderr = '';
$ensureBuildDirectoryCommand = (new Command('mkdir'))
->flag('-p')
->argument('/tmp/builds/' . $deploymentId);
Console::execute($ensureBuildDirectoryCommand, '', $stdout, $stderr);
Console::execute('mkdir -p ' . \escapeshellarg('/tmp/builds/' . $deploymentId), '', $stdout, $stderr);
if ($dbForProject->getDocument('deployments', $deploymentId)->getAttribute('status') === 'canceled') {
$this->cancelDeployment($deployment->getId(), $dbForProject, $queueForRealtime);
@@ -432,10 +405,7 @@ class Builds extends Action
$rootDirectoryWithoutSpaces = str_replace(' ', '', $rootDirectory);
$from = $tmpDirectory . '/' . $rootDirectory;
$to = $tmpDirectory . '/' . $rootDirectoryWithoutSpaces;
$moveRootDirectoryCommand = (new Command('mv'))
->argument($from)
->argument($to);
$exit = Console::execute($moveRootDirectoryCommand, '', $stdout, $stderr);
$exit = Console::execute('mv ' . \escapeshellarg($from) . ' ' . \escapeshellarg($to), '', $stdout, $stderr);
if ($exit !== 0) {
throw new \Exception('Unable to move function with spaces' . $stderr);
@@ -466,67 +436,20 @@ class Builds extends Action
}
// Ensure directories
$ensureTemplateDirectoryCommand = (new Command('mkdir'))
->flag('-p')
->argument($tmpTemplateDirectory . '/' . $templateRootDirectory);
Console::execute($ensureTemplateDirectoryCommand, '', $stdout, $stderr);
$ensureRootDirectoryCommand = (new Command('mkdir'))
->flag('-p')
->argument($tmpDirectory . '/' . $rootDirectory);
Console::execute($ensureRootDirectoryCommand, '', $stdout, $stderr);
Console::execute('mkdir -p ' . \escapeshellarg($tmpTemplateDirectory . '/' . $templateRootDirectory), '', $stdout, $stderr);
Console::execute('mkdir -p ' . \escapeshellarg($tmpDirectory . '/' . $rootDirectory), '', $stdout, $stderr);
// Merge template into user repo
$syncTemplateCommand = (new Command('rsync'))
->flag('-av')
->option('--exclude', '.git')
->argument($tmpTemplateDirectory . '/' . $templateRootDirectory . '/')
->argument($tmpDirectory . '/' . $rootDirectory);
Console::execute($syncTemplateCommand, '', $stdout, $stderr);
Console::execute('rsync -av --exclude \'.git\' ' . \escapeshellarg($tmpTemplateDirectory . '/' . $templateRootDirectory . '/') . ' ' . \escapeshellarg($tmpDirectory . '/' . $rootDirectory), '', $stdout, $stderr);
// Commit and push
$commitMessage = "Create '" . $resource->getAttribute('name', '') . "' function";
$pushTemplateCommand = Command::and(
(new Command('git'))
->argument('config')
->flag('--global')
->argument('user.email')
->argument(APP_VCS_GITHUB_EMAIL),
(new Command('git'))
->argument('config')
->flag('--global')
->argument('user.name')
->argument(APP_VCS_GITHUB_USERNAME),
(new Command('git'))
->option('-C', $tmpDirectory)
->argument('checkout')
->flag('-b')
->argument($branchName),
(new Command('git'))
->option('-C', $tmpDirectory)
->argument('add')
->argument('.'),
(new Command('git'))
->option('-C', $tmpDirectory)
->argument('commit')
->option('-m', $commitMessage),
(new Command('git'))
->option('-C', $tmpDirectory)
->argument('push')
->argument('origin')
->argument($branchName)
);
$exit = Console::execute($pushTemplateCommand, '', $stdout, $stderr);
$exit = Console::execute('git config --global user.email ' . \escapeshellarg(APP_VCS_GITHUB_EMAIL) . ' && git config --global user.name ' . \escapeshellarg(APP_VCS_GITHUB_USERNAME) . ' && cd ' . \escapeshellarg($tmpDirectory) . ' && git checkout -b ' . \escapeshellarg($branchName) . ' && git add . && git commit -m "Create ' . \escapeshellarg($resource->getAttribute('name', '')) . ' function" && git push origin ' . \escapeshellarg($branchName), '', $stdout, $stderr);
if ($exit !== 0) {
throw new \Exception('Unable to push code repository: ' . $stderr);
}
$providerCommitHashCommand = (new Command('git'))
->option('-C', $tmpDirectory)
->argument('rev-parse')
->argument('HEAD');
$exit = Console::execute($providerCommitHashCommand, '', $stdout, $stderr);
$exit = Console::execute('cd ' . \escapeshellarg($tmpDirectory) . ' && git rev-parse HEAD', '', $stdout, $stderr);
if ($exit !== 0) {
throw new \Exception('Unable to get vcs commit SHA: ' . $stderr);
@@ -571,27 +494,10 @@ class Builds extends Action
throw new \Exception('Repository directory size should be less than ' . number_format($sizeLimit / (1000 * 1000), 2) . ' MBs.');
}
$cleanupGitDirectoryCommand = (new Command('find'))
->argument($tmpDirectory)
->argument('-type')
->argument('d')
->argument('-name')
->argument('.git')
->argument('-exec')
->argument('rm')
->argument('-rf')
->argument('{}')
->argument('+');
Console::execute($cleanupGitDirectoryCommand, '', $stdout, $stderr);
Console::execute('find ' . \escapeshellarg($tmpDirectory) . ' -type d -name ".git" -exec rm -rf {} +', '', $stdout, $stderr);
$tarParamDirectory = '/tmp/builds/' . $deploymentId . '/code' . (empty($rootDirectory) ? '' : '/' . $rootDirectory);
$archiveCommand = (new Command('tar'))
->option('--exclude', 'code.tar.gz')
->flag('-czf')
->argument($tmpPathFile)
->option('-C', $tarParamDirectory)
->argument('.');
Console::execute($archiveCommand, '', $stdout, $stderr);
Console::execute('tar --exclude code.tar.gz -czf ' . \escapeshellarg($tmpPathFile) . ' -C ' . \escapeshellcmd($tarParamDirectory) . ' .', '', $stdout, $stderr); // TODO: Replace escapeshellcmd with escapeshellarg if we find a way that doesnt break syntax
$source = $device->getPath($deployment->getId() . '.' . \pathinfo('code.tar.gz', PATHINFO_EXTENSION));
$result = $localDevice->transfer($tmpPathFile, $source, $device);
@@ -600,10 +506,7 @@ class Builds extends Action
throw new \Exception('Unable to move file');
}
$removeBuildDirectoryCommand = (new Command('rm'))
->flag('-rf')
->argument($tmpPath);
Console::execute($removeBuildDirectoryCommand, '', $stdout, $stderr);
Console::execute('rm -rf ' . \escapeshellarg($tmpPath), '', $stdout, $stderr);
$directorySize = $device->getFileSize($source);
@@ -12,6 +12,7 @@ use Appwrite\Event\Publisher\Func as FunctionPublisher;
use Appwrite\Event\Publisher\Mail as MailPublisher;
use Appwrite\Event\Publisher\Messaging as MessagingPublisher;
use Appwrite\Event\Publisher\Migration as MigrationPublisher;
use Appwrite\Event\Publisher\Notification as NotificationPublisher;
use Appwrite\Event\Publisher\Screenshot;
use Appwrite\Event\Publisher\StatsResources as StatsResourcesPublisher;
use Appwrite\Event\Publisher\Usage as UsagePublisher;
@@ -24,6 +25,7 @@ use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Response;
use Utopia\Database\Document;
use Utopia\Platform\Enum;
use Utopia\System\System;
use Utopia\Validator\Integer;
use Utopia\Validator\WhiteList;
@@ -71,7 +73,8 @@ class Get extends Base
System::getEnv('_APP_SCREENSHOTS_QUEUE_NAME', Event::SCREENSHOTS_QUEUE_NAME),
System::getEnv('_APP_MESSAGING_QUEUE_NAME', Event::MESSAGING_QUEUE_NAME),
System::getEnv('_APP_MIGRATIONS_QUEUE_NAME', Event::MIGRATIONS_QUEUE_NAME),
]), 'The name of the queue')
System::getEnv('_APP_NOTIFICATIONS_QUEUE_NAME', Event::NOTIFICATIONS_QUEUE_NAME),
]), 'The name of the queue', enum: new Enum(name: 'HealthQueueName'))
->param('threshold', 5000, new Integer(true), 'Queue size threshold. When hit (equal or higher), endpoint returns server error. Default value is 5000.', true)
->inject('response')
->inject('publisherForDatabase')
@@ -87,6 +90,7 @@ class Get extends Base
->inject('publisherForMessaging')
->inject('publisherForMigrations')
->inject('publisherForScreenshots')
->inject('publisherForNotifications')
->callback($this->action(...));
}
@@ -107,6 +111,7 @@ class Get extends Base
MessagingPublisher $publisherForMessaging,
MigrationPublisher $publisherForMigrations,
Screenshot $publisherForScreenshots,
NotificationPublisher $publisherForNotifications,
): void {
$threshold = (int) $threshold;
@@ -124,6 +129,7 @@ class Get extends Base
System::getEnv('_APP_SCREENSHOTS_QUEUE_NAME', Event::SCREENSHOTS_QUEUE_NAME) => $publisherForScreenshots,
System::getEnv('_APP_MESSAGING_QUEUE_NAME', Event::MESSAGING_QUEUE_NAME) => $publisherForMessaging,
System::getEnv('_APP_MIGRATIONS_QUEUE_NAME', Event::MIGRATIONS_QUEUE_NAME) => $publisherForMigrations,
System::getEnv('_APP_NOTIFICATIONS_QUEUE_NAME', Event::NOTIFICATIONS_QUEUE_NAME) => $publisherForNotifications,
default => throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Unknown queue name: ' . $name),
};
$failed = $queue->getSize(failed: true);
@@ -0,0 +1,60 @@
<?php
namespace Appwrite\Platform\Modules\Health\Http\Health\Queue\Notifications;
use Appwrite\Event\Publisher\Notification as NotificationPublisher;
use Appwrite\Platform\Modules\Health\Http\Health\Queue\Base;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\ContentType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Response;
use Utopia\Database\Document;
use Utopia\Validator\Integer;
class Get extends Base
{
public static function getName(): string
{
return 'getQueueNotifications';
}
public function __construct()
{
$this
->setHttpMethod(Base::HTTP_REQUEST_METHOD_GET)
->setHttpPath('/v1/health/queue/notifications')
->desc('Get notifications queue')
->groups(['api', 'health'])
->label('scope', 'health.read')
->label('sdk', new Method(
namespace: 'health',
group: 'queue',
name: 'getQueueNotifications',
description: '/docs/references/health/get-queue-notifications.md',
auth: [AuthType::ADMIN, AuthType::KEY],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_HEALTH_QUEUE,
)
],
contentType: ContentType::JSON
))
->param('threshold', 5000, new Integer(true), 'Queue size threshold. When hit (equal or higher), endpoint returns server error. Default value is 5000.', true)
->inject('publisherForNotifications')
->inject('response')
->callback($this->action(...));
}
public function action(int|string $threshold, NotificationPublisher $publisherForNotifications, Response $response): void
{
$threshold = (int) $threshold;
$size = $publisherForNotifications->getSize();
$this->assertQueueThreshold($size, $threshold);
$response->dynamic(new Document(['size' => $size]), Response::MODEL_HEALTH_QUEUE);
}
}
@@ -19,6 +19,7 @@ use Appwrite\Platform\Modules\Health\Http\Health\Queue\Logs\Get as GetQueueLogs;
use Appwrite\Platform\Modules\Health\Http\Health\Queue\Mails\Get as GetQueueMails;
use Appwrite\Platform\Modules\Health\Http\Health\Queue\Messaging\Get as GetQueueMessaging;
use Appwrite\Platform\Modules\Health\Http\Health\Queue\Migrations\Get as GetQueueMigrations;
use Appwrite\Platform\Modules\Health\Http\Health\Queue\Notifications\Get as GetQueueNotifications;
use Appwrite\Platform\Modules\Health\Http\Health\Queue\StatsResources\Get as GetQueueStatsResources;
use Appwrite\Platform\Modules\Health\Http\Health\Queue\StatsUsage\Get as GetQueueUsage;
use Appwrite\Platform\Modules\Health\Http\Health\Queue\Webhooks\Get as GetQueueWebhooks;
@@ -56,6 +57,7 @@ class Http extends Service
$this->addAction(GetQueueMails::getName(), new GetQueueMails());
$this->addAction(GetQueueMessaging::getName(), new GetQueueMessaging());
$this->addAction(GetQueueMigrations::getName(), new GetQueueMigrations());
$this->addAction(GetQueueNotifications::getName(), new GetQueueNotifications());
$this->addAction(GetQueueFunctions::getName(), new GetQueueFunctions());
$this->addAction(GetQueueStatsResources::getName(), new GetQueueStatsResources());
$this->addAction(GetQueueUsage::getName(), new GetQueueUsage());
@@ -5,6 +5,7 @@ namespace Appwrite\Platform\Modules\Migrations\Http\Migrations\Appwrite;
use Appwrite\Event\Event;
use Appwrite\Event\Message\Migration as MigrationMessage;
use Appwrite\Event\Publisher\Migration as MigrationPublisher;
use Appwrite\Platform\Action;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
@@ -15,7 +16,7 @@ use Utopia\Database\Helpers\ID;
use Utopia\Database\Validator\UID;
use Utopia\Migration\Destinations\OnDuplicate;
use Utopia\Migration\Sources\Appwrite as AppwriteSource;
use Utopia\Platform\Action;
use Utopia\Platform\Enum;
use Utopia\Platform\Scope\HTTP;
use Utopia\Validator\ArrayList;
use Utopia\Validator\Text;
@@ -54,11 +55,11 @@ class Create extends Action
)
]
))
->param('resources', [], new ArrayList(new WhiteList(AppwriteSource::getSupportedResources())), 'List of resources to migrate')
->param('resources', [], new ArrayList(new WhiteList(AppwriteSource::getSupportedResources())), 'List of resources to migrate', enum: new Enum(name: 'AppwriteMigrationResource'))
->param('endpoint', '', new URL(), 'Source Appwrite endpoint')
->param('projectId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'Source Project ID', false, ['dbForProject'])
->param('apiKey', '', new Text(512), 'Source API Key')
->param('onDuplicate', OnDuplicate::Fail->value, new WhiteList(OnDuplicate::values()), 'Behavior when a row with an existing $id is encountered. "fail" (default): abort on first conflict. "skip": silently ignore. "overwrite": replace existing row.', true)
->param('onDuplicate', OnDuplicate::Fail->value, new WhiteList(OnDuplicate::values()), 'Behavior when a row with an existing $id is encountered. "fail" (default): abort on first conflict. "skip": silently ignore. "overwrite": replace existing row.', true, enum: new Enum(name: 'MigrationOnDuplicate'))
->inject('response')
->inject('dbForProject')
->inject('project')
@@ -3,13 +3,14 @@
namespace Appwrite\Platform\Modules\Migrations\Http\Migrations\Appwrite\Report;
use Appwrite\Extend\Exception;
use Appwrite\Platform\Action;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Response;
use Utopia\Database\Document;
use Utopia\Migration\Sources\Appwrite as AppwriteSource;
use Utopia\Platform\Action;
use Utopia\Platform\Enum;
use Utopia\Platform\Scope\HTTP;
use Utopia\Validator\ArrayList;
use Utopia\Validator\Text;
@@ -46,7 +47,7 @@ class Get extends Action
)
]
))
->param('resources', [], new ArrayList(new WhiteList(AppwriteSource::getSupportedResources())), 'List of resources to migrate')
->param('resources', [], new ArrayList(new WhiteList(AppwriteSource::getSupportedResources())), 'List of resources to migrate', enum: new Enum(name: 'AppwriteMigrationResource'))
->param('endpoint', '', new URL(), "Source's Appwrite Endpoint")
->param('projectID', '', new Text(512), "Source's Project ID")
->param('key', '', new Text(512), "Source's API Key")
@@ -26,6 +26,7 @@ use Utopia\Migration\Sources\Appwrite as AppwriteSource;
use Utopia\Migration\Sources\CSV;
use Utopia\Migration\Transfer;
use Utopia\Platform\Action;
use Utopia\Platform\Enum;
use Utopia\Platform\Scope\HTTP;
use Utopia\Storage\Device;
use Utopia\System\System;
@@ -69,7 +70,7 @@ class Create extends Action
->param('fileId', '', fn (Database $dbForProject) => new UID($dbForProject->getAdapter()->getMaxUIDLength()), 'File ID.', false, ['dbForProject'])
->param('resourceId', null, new CompoundUID(), 'Composite ID in the format {databaseId:collectionId}, identifying a collection within a database.')
->param('internalFile', false, new Boolean(), 'Is the file stored in an internal bucket?', true)
->param('onDuplicate', OnDuplicate::Fail->value, new WhiteList(OnDuplicate::values()), 'Behavior when a row with an existing $id is encountered. "fail" (default): abort on first conflict. "skip": silently ignore. "overwrite": replace existing row.', true)
->param('onDuplicate', OnDuplicate::Fail->value, new WhiteList(OnDuplicate::values()), 'Behavior when a row with an existing $id is encountered. "fail" (default): abort on first conflict. "skip": silently ignore. "overwrite": replace existing row.', true, enum: new Enum(name: 'MigrationOnDuplicate'))
->inject('response')
->inject('dbForProject')
->inject('dbForPlatform')
@@ -6,6 +6,7 @@ use Appwrite\Event\Event;
use Appwrite\Event\Message\Migration as MigrationMessage;
use Appwrite\Event\Publisher\Migration as MigrationPublisher;
use Appwrite\Extend\Exception;
use Appwrite\Platform\Action;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
@@ -15,7 +16,7 @@ use Utopia\Database\Document;
use Utopia\Database\Helpers\ID;
use Utopia\Migration\Sources\Appwrite as AppwriteSource;
use Utopia\Migration\Sources\Firebase;
use Utopia\Platform\Action;
use Utopia\Platform\Enum;
use Utopia\Platform\Scope\HTTP;
use Utopia\Validator\ArrayList;
use Utopia\Validator\Text;
@@ -53,7 +54,7 @@ class Create extends Action
)
]
))
->param('resources', [], new ArrayList(new WhiteList(Firebase::getSupportedResources())), 'List of resources to migrate')
->param('resources', [], new ArrayList(new WhiteList(Firebase::getSupportedResources())), 'List of resources to migrate', enum: new Enum(name: 'FirebaseMigrationResource'))
->param('serviceAccount', '', new Text(65536), 'JSON of the Firebase service account credentials')
->inject('response')
->inject('dbForProject')
@@ -3,13 +3,14 @@
namespace Appwrite\Platform\Modules\Migrations\Http\Migrations\Firebase\Report;
use Appwrite\Extend\Exception;
use Appwrite\Platform\Action;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Response;
use Utopia\Database\Document;
use Utopia\Migration\Sources\Firebase;
use Utopia\Platform\Action;
use Utopia\Platform\Enum;
use Utopia\Platform\Scope\HTTP;
use Utopia\Validator\ArrayList;
use Utopia\Validator\Text;
@@ -45,7 +46,7 @@ class Get extends Action
)
]
))
->param('resources', [], new ArrayList(new WhiteList(Firebase::getSupportedResources())), 'List of resources to migrate')
->param('resources', [], new ArrayList(new WhiteList(Firebase::getSupportedResources())), 'List of resources to migrate', enum: new Enum(name: 'FirebaseMigrationResource'))
->param('serviceAccount', '', new Text(65536), 'JSON of the Firebase service account credentials')
->inject('response')
->callback($this->action(...));
@@ -26,6 +26,7 @@ use Utopia\Migration\Sources\Appwrite as AppwriteSource;
use Utopia\Migration\Sources\JSON as JSONSource;
use Utopia\Migration\Transfer;
use Utopia\Platform\Action;
use Utopia\Platform\Enum;
use Utopia\Platform\Scope\HTTP;
use Utopia\Storage\Device;
use Utopia\System\System;
@@ -68,7 +69,7 @@ class Create extends Action
->param('fileId', '', new UID(), 'File ID.')
->param('resourceId', null, new CompoundUID(), 'Composite ID in the format {databaseId:collectionId}, identifying a collection within a database.')
->param('internalFile', false, new Boolean(), 'Is the file stored in an internal bucket?', true)
->param('onDuplicate', OnDuplicate::Fail->value, new WhiteList(OnDuplicate::values()), 'Behavior when a row with an existing $id is encountered. "fail" (default): abort on first conflict. "skip": silently ignore. "overwrite": replace existing row.', true)
->param('onDuplicate', OnDuplicate::Fail->value, new WhiteList(OnDuplicate::values()), 'Behavior when a row with an existing $id is encountered. "fail" (default): abort on first conflict. "skip": silently ignore. "overwrite": replace existing row.', true, enum: new Enum(name: 'MigrationOnDuplicate'))
->inject('response')
->inject('dbForProject')
->inject('dbForPlatform')
@@ -5,6 +5,7 @@ namespace Appwrite\Platform\Modules\Migrations\Http\Migrations\NHost;
use Appwrite\Event\Event;
use Appwrite\Event\Message\Migration as MigrationMessage;
use Appwrite\Event\Publisher\Migration as MigrationPublisher;
use Appwrite\Platform\Action;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
@@ -14,7 +15,7 @@ use Utopia\Database\Document;
use Utopia\Database\Helpers\ID;
use Utopia\Migration\Sources\Appwrite as AppwriteSource;
use Utopia\Migration\Sources\NHost;
use Utopia\Platform\Action;
use Utopia\Platform\Enum;
use Utopia\Platform\Scope\HTTP;
use Utopia\Validator\ArrayList;
use Utopia\Validator\Integer;
@@ -53,7 +54,7 @@ class Create extends Action
)
]
))
->param('resources', [], new ArrayList(new WhiteList(NHost::getSupportedResources())), 'List of resources to migrate')
->param('resources', [], new ArrayList(new WhiteList(NHost::getSupportedResources())), 'List of resources to migrate', enum: new Enum(name: 'NHostMigrationResource'))
->param('subdomain', '', new Text(512), 'Source\'s Subdomain')
->param('region', '', new Text(512), 'Source\'s Region')
->param('adminSecret', '', new Text(512), 'Source\'s Admin Secret')
@@ -3,13 +3,14 @@
namespace Appwrite\Platform\Modules\Migrations\Http\Migrations\NHost\Report;
use Appwrite\Extend\Exception;
use Appwrite\Platform\Action;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Response;
use Utopia\Database\Document;
use Utopia\Migration\Sources\NHost;
use Utopia\Platform\Action;
use Utopia\Platform\Enum;
use Utopia\Platform\Scope\HTTP;
use Utopia\Validator\ArrayList;
use Utopia\Validator\Integer;
@@ -46,7 +47,7 @@ class Get extends Action
)
]
))
->param('resources', [], new ArrayList(new WhiteList(NHost::getSupportedResources())), 'List of resources to migrate.')
->param('resources', [], new ArrayList(new WhiteList(NHost::getSupportedResources())), 'List of resources to migrate.', enum: new Enum(name: 'NHostMigrationResource'))
->param('subdomain', '', new Text(512), 'Source\'s Subdomain.')
->param('region', '', new Text(512), 'Source\'s Region.')
->param('adminSecret', '', new Text(512), 'Source\'s Admin Secret.')
@@ -5,6 +5,7 @@ namespace Appwrite\Platform\Modules\Migrations\Http\Migrations\Supabase;
use Appwrite\Event\Event;
use Appwrite\Event\Message\Migration as MigrationMessage;
use Appwrite\Event\Publisher\Migration as MigrationPublisher;
use Appwrite\Platform\Action;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
@@ -14,7 +15,7 @@ use Utopia\Database\Document;
use Utopia\Database\Helpers\ID;
use Utopia\Migration\Sources\Appwrite as AppwriteSource;
use Utopia\Migration\Sources\Supabase;
use Utopia\Platform\Action;
use Utopia\Platform\Enum;
use Utopia\Platform\Scope\HTTP;
use Utopia\Validator\ArrayList;
use Utopia\Validator\Integer;
@@ -54,7 +55,7 @@ class Create extends Action
)
]
))
->param('resources', [], new ArrayList(new WhiteList(Supabase::getSupportedResources(), true)), 'List of resources to migrate')
->param('resources', [], new ArrayList(new WhiteList(Supabase::getSupportedResources(), true)), 'List of resources to migrate', enum: new Enum(name: 'SupabaseMigrationResource'))
->param('endpoint', '', new URL(), 'Source\'s Supabase Endpoint')
->param('apiKey', '', new Text(512), 'Source\'s API Key')
->param('databaseHost', '', new Text(512), 'Source\'s Database Host')
@@ -3,13 +3,14 @@
namespace Appwrite\Platform\Modules\Migrations\Http\Migrations\Supabase\Report;
use Appwrite\Extend\Exception;
use Appwrite\Platform\Action;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Response;
use Utopia\Database\Document;
use Utopia\Migration\Sources\Supabase;
use Utopia\Platform\Action;
use Utopia\Platform\Enum;
use Utopia\Platform\Scope\HTTP;
use Utopia\Validator\ArrayList;
use Utopia\Validator\Integer;
@@ -47,7 +48,7 @@ class Get extends Action
)
]
))
->param('resources', [], new ArrayList(new WhiteList(Supabase::getSupportedResources(), true)), 'List of resources to migrate')
->param('resources', [], new ArrayList(new WhiteList(Supabase::getSupportedResources(), true)), 'List of resources to migrate', enum: new Enum(name: 'SupabaseMigrationResource'))
->param('endpoint', '', new URL(), 'Source\'s Supabase Endpoint.')
->param('apiKey', '', new Text(512), 'Source\'s API Key.')
->param('databaseHost', '', new Text(512), 'Source\'s Database Host.')
@@ -0,0 +1,209 @@
<?php
namespace Appwrite\Platform\Modules\Notifications\Http\Notifications\Logos\Appwrite;
use Ahc\Jwt\JWT;
use Appwrite\Event\Message\Audit as AuditMessage;
use Appwrite\Event\Publisher\Audit as AuditPublisher;
use Appwrite\SDK\ContentType;
use Appwrite\SDK\Method;
use Appwrite\SDK\MethodType;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Request;
use Appwrite\Utopia\Response;
use Utopia\Database\Database;
use Utopia\Database\DateTime;
use Utopia\Database\Document;
use Utopia\Database\Query;
use Utopia\Database\Validator\Authorization;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
use Utopia\System\System;
use Utopia\Validator\Text;
use Utopia\Validator\WhiteList;
class Get extends Action
{
use HTTP;
public static function getName(): string
{
return 'getNotificationLogo';
}
public function __construct()
{
$this
->setHttpMethod(Action::HTTP_REQUEST_METHOD_GET)
->setHttpPath('/v1/notifications/logos/appwrite')
->desc('Get notification logo')
->groups(['api', 'notifications'])
->label('scope', 'public')
->label('sdk', new Method(
namespace: 'notifications',
group: 'logos',
name: 'getLogo',
description: '/docs/references/notifications/get-logo.md',
auth: [],
type: MethodType::LOCATION,
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_NONE,
),
],
contentType: ContentType::IMAGE_SVG,
))
->param('jwt', '', new Text(2048, 0), 'Tracking token.', true)
->param('theme', 'system', new WhiteList(['system', 'light', 'dark']), 'Logo color theme.', true)
->inject('request')
->inject('response')
->inject('dbForPlatform')
->inject('authorization')
->inject('publisherForAudits')
->callback($this->action(...));
}
public function action(
string $jwt,
string $theme,
Request $request,
Response $response,
Database $dbForPlatform,
Authorization $authorization,
AuditPublisher $publisherForAudits,
): void {
$secret = System::getEnv('_APP_NOTIFICATIONS_TRACKING_SECRET');
if (!empty($secret) && $jwt !== '') {
try {
$decoder = new JWT($secret, 'HS256', NOTIFICATION_TRACKING_JWT_TTL, 0);
$decoded = $decoder->decode($jwt);
$result = $authorization->skip(fn () => $this->trackView($decoded, $dbForPlatform));
if ($result !== null) {
$this->logView(
$result['notification'],
$result['project'],
$result['seenAt'],
$request,
$publisherForAudits,
);
}
} catch (\Throwable) {
// Always return the logo and avoid exposing token validity.
}
}
$response
->setContentType('image/svg+xml')
->addHeader('Cache-Control', 'no-store')
->send($this->renderLogo($theme));
}
/**
* @return array{notification: Document, project: Document, seenAt: string}|null
*/
private function trackView(array $decoded, Database $dbForPlatform): ?array
{
if (
!isset($decoded['messageId'], $decoded['recipientHash'], $decoded['channel'], $decoded['projectId'], $decoded['purpose'])
|| $decoded['purpose'] !== 'notification_track'
) {
return null;
}
$notifications = $dbForPlatform->find('alerts', [
Query::equal('messageId', [(string) $decoded['messageId']]),
Query::equal('channel', [(string) $decoded['channel']]),
Query::equal('recipientHash', [(string) $decoded['recipientHash']]),
Query::limit(1),
]);
if (empty($notifications)) {
return null;
}
$notification = $notifications[0];
if (
$notification->getAttribute('projectId') !== (string) $decoded['projectId']
|| (isset($decoded['projectInternalId']) && $notification->getAttribute('projectInternalId') !== (string) $decoded['projectInternalId'])
) {
return null;
}
$seenAt = DateTime::now();
$updates = [
'read' => true,
'lastSeen' => $seenAt,
];
if (empty($notification->getAttribute('firstSeen'))) {
$updates['firstSeen'] = $seenAt;
}
$updated = $dbForPlatform->updateDocument('alerts', $notification->getId(), new Document($updates));
$project = $dbForPlatform->getDocument('projects', $notification->getAttribute('projectId'));
if ($project->isEmpty()) {
$project = new Document([
'$id' => 'console',
'$sequence' => 'console',
]);
}
return [
'notification' => $updated,
'project' => $project,
'seenAt' => $seenAt,
];
}
private function logView(Document $notification, Document $project, string $seenAt, Request $request, AuditPublisher $publisherForAudits): void
{
$publisherForAudits->enqueue(new AuditMessage(
event: 'notification.view',
payload: [
'notificationId' => $notification->getId(),
'messageId' => $notification->getAttribute('messageId', ''),
'channel' => $notification->getAttribute('channel', ''),
'projectId' => $notification->getAttribute('projectId', ''),
'firstSeen' => $notification->getAttribute('firstSeen'),
'lastSeen' => $notification->getAttribute('lastSeen'),
'seenAt' => $seenAt,
],
project: $project,
user: new Document([
'$id' => $notification->getAttribute('resourceId', ''),
'$sequence' => $notification->getAttribute('resourceInternalId', ''),
'name' => '',
'email' => '',
'type' => ACTOR_TYPE_HIDDEN,
]),
resource: 'notification/' . $notification->getId(),
mode: APP_MODE_DEFAULT,
ip: $request->getIP(),
userAgent: $request->getUserAgent(''),
hostname: $request->getHostname(),
));
}
private function renderLogo(string $theme): string
{
$style = match ($theme) {
'light' => '.wordmark { fill: #19191C; }',
'dark' => '.wordmark { fill: #F5F5F7; }',
default => '.wordmark { fill: #19191C; } @media (prefers-color-scheme: dark) { .wordmark { fill: #F5F5F7; } }',
};
return <<<SVG
<svg width="120" height="28" viewBox="0 0 120 28" fill="none" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Appwrite">
<style>{$style}</style>
<path d="M24.4429 17.4322V22.9096H10.7519C6.76318 22.9096 3.28044 20.7067 1.4171 17.4322C1.14622 16.9561 0.909137 16.4567 0.710264 15.9383C0.319864 14.9225 0.0744552 13.8325 0 12.6952V11.2143C0.0161646 10.9609 0.0416361 10.7094 0.0749451 10.4609C0.143032 9.95105 0.245898 9.45211 0.381093 8.96711C1.66006 4.36909 5.81877 1 10.7519 1C15.6851 1 19.8433 4.36909 21.1223 8.96711H15.2682C14.3072 7.4683 12.6437 6.4774 10.7519 6.4774C8.86017 6.4774 7.19668 7.4683 6.23562 8.96711C5.9427 9.42274 5.71542 9.92516 5.56651 10.4609C5.43425 10.936 5.36371 11.4369 5.36371 11.9548C5.36371 13.5248 6.01324 14.94 7.05463 15.9383C8.01961 16.865 9.32061 17.4322 10.7519 17.4322H24.4429Z" fill="#FD366E"/>
<path d="M24.4429 10.4609V15.9383H14.4492C15.4906 14.94 16.1401 13.5248 16.1401 11.9548C16.1401 11.4369 16.0696 10.936 15.9373 10.4609H24.4429Z" fill="#FD366E"/>
<text class="wordmark" x="34" y="19" font-family="Arial, Helvetica, sans-serif" font-size="16" font-weight="700">Appwrite</text>
</svg>
SVG;
}
}
@@ -0,0 +1,87 @@
<?php
namespace Appwrite\Platform\Modules\Notifications\Http\Notifications;
use Appwrite\Extend\Exception;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Response;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\UID;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
use Utopia\Validator\Boolean;
class Update extends Action
{
use HTTP;
public static function getName(): string
{
return 'updateNotification';
}
public function __construct()
{
$this
->setHttpMethod(Action::HTTP_REQUEST_METHOD_PATCH)
->setHttpPath('/v1/notifications/:notificationId')
->desc('Update notification')
->groups(['api', 'notifications'])
->label('scope', 'account')
->label('sdk', new Method(
namespace: 'notifications',
group: null,
name: 'update',
description: '/docs/references/notifications/update-notification.md',
auth: [AuthType::SESSION, AuthType::JWT],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_NOTIFICATION,
)
]
))
->param('notificationId', '', new UID(), 'Notification ID.')
->param('read', null, new Boolean(), 'Notification read status.')
->inject('response')
->inject('dbForPlatform')
->inject('authorization')
->inject('project')
->inject('user')
->callback($this->action(...));
}
public function action(
string $notificationId,
bool $read,
Response $response,
Database $dbForPlatform,
Authorization $authorization,
Document $project,
Document $user,
): void {
if ($project->getId() !== 'console') {
throw new Exception(Exception::USER_UNAUTHORIZED);
}
$notification = $dbForPlatform->getDocument('alerts', $notificationId);
if ($notification->isEmpty()) {
$exists = $authorization->skip(fn () => !$dbForPlatform->getDocument('alerts', $notificationId)->isEmpty());
if ($exists) {
throw new Exception(Exception::USER_UNAUTHORIZED);
}
throw new Exception(Exception::DOCUMENT_NOT_FOUND);
}
$updated = $dbForPlatform->updateDocument('alerts', $notificationId, new Document([
'read' => $read,
]));
$response->dynamic($updated, Response::MODEL_NOTIFICATION);
}
}
@@ -0,0 +1,117 @@
<?php
namespace Appwrite\Platform\Modules\Notifications\Http\Notifications;
use Appwrite\Extend\Exception;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\Method;
use Appwrite\SDK\Response as SDKResponse;
use Appwrite\Utopia\Database\Validator\Queries\Notifications as NotificationQueries;
use Appwrite\Utopia\Response;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Exception\Order as OrderException;
use Utopia\Database\Exception\Query as QueryException;
use Utopia\Database\Query;
use Utopia\Database\Validator\Query\Cursor;
use Utopia\Platform\Action;
use Utopia\Platform\Scope\HTTP;
class XList extends Action
{
use HTTP;
public static function getName(): string
{
return 'listNotifications';
}
public function __construct()
{
$this
->setHttpMethod(Action::HTTP_REQUEST_METHOD_GET)
->setHttpPath('/v1/notifications')
->desc('List notifications')
->groups(['api', 'notifications'])
->label('scope', 'account')
->label('sdk', new Method(
namespace: 'notifications',
group: null,
name: 'list',
description: '/docs/references/notifications/list-notifications.md',
auth: [AuthType::SESSION, AuthType::JWT],
responses: [
new SDKResponse(
code: Response::STATUS_CODE_OK,
model: Response::MODEL_NOTIFICATION_LIST,
)
]
))
->param('queries', [], new NotificationQueries(), 'Array of query strings generated using the Query class provided by the SDK. [Learn more about queries](https://appwrite.io/docs/queries). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long. You may filter on the following attributes: ' . implode(', ', NotificationQueries::ALLOWED_ATTRIBUTES), true)
->inject('response')
->inject('dbForPlatform')
->inject('project')
->inject('user')
->callback($this->action(...));
}
/**
* @param array<string> $queries
*/
public function action(
array $queries,
Response $response,
Database $dbForPlatform,
Document $project,
Document $user
): void {
if ($project->getId() !== 'console') {
throw new Exception(Exception::USER_UNAUTHORIZED);
}
try {
$queries = Query::parseQueries($queries);
} catch (QueryException $e) {
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
}
$cursor = \array_filter($queries, function ($query) {
return \in_array($query->getMethod(), [Query::TYPE_CURSOR_AFTER, Query::TYPE_CURSOR_BEFORE]);
});
$cursor = reset($cursor);
if ($cursor) {
/** @var Query $cursor */
$validator = new Cursor();
if (!$validator->isValid($cursor)) {
throw new Exception(Exception::GENERAL_QUERY_INVALID, $validator->getDescription());
}
$notificationId = $cursor->getValue();
$cursorDocument = $dbForPlatform->getDocument('alerts', $notificationId);
if ($cursorDocument->isEmpty()) {
throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Notification '{$notificationId}' for the 'cursor' value not found.");
}
$cursor->setValue($cursorDocument);
}
$queries[] = Query::equal('resourceType', [RESOURCE_TYPE_USERS]);
$queries[] = Query::equal('resourceId', [$user->getId()]);
$filterQueries = Query::groupByType($queries)['filters'];
try {
$results = $dbForPlatform->find('alerts', $queries);
$total = $dbForPlatform->count('alerts', $filterQueries, APP_LIMIT_COUNT);
} catch (OrderException $e) {
throw new Exception(Exception::DATABASE_QUERY_ORDER_NULL, "The order attribute '{$e->getAttribute()}' had a null value. Cursor pagination requires all documents order attribute values are non-null.");
}
$response->dynamic(new Document([
'notifications' => $results,
'total' => $total,
]), Response::MODEL_NOTIFICATION_LIST);
}
}
@@ -0,0 +1,14 @@
<?php
namespace Appwrite\Platform\Modules\Notifications;
use Appwrite\Platform\Modules\Notifications\Services\Http;
use Utopia\Platform;
class Module extends Platform\Module
{
public function __construct()
{
$this->addService('http', new Http());
}
}
@@ -0,0 +1,21 @@
<?php
namespace Appwrite\Platform\Modules\Notifications\Services;
use Appwrite\Platform\Modules\Notifications\Http\Notifications\Logos\Appwrite\Get as GetAppwriteLogo;
use Appwrite\Platform\Modules\Notifications\Http\Notifications\Update as UpdateNotification;
use Appwrite\Platform\Modules\Notifications\Http\Notifications\XList as ListNotifications;
use Utopia\Platform\Service;
class Http extends Service
{
public function __construct()
{
$this->type = Service::TYPE_HTTP;
$this
->addAction(ListNotifications::getName(), new ListNotifications())
->addAction(UpdateNotification::getName(), new UpdateNotification())
->addAction(GetAppwriteLogo::getName(), new GetAppwriteLogo());
}
}
@@ -21,6 +21,7 @@ use Utopia\Database\Document;
use Utopia\Database\Exception\Duplicate;
use Utopia\Database\Helpers\ID;
use Utopia\DSN\DSN;
use Utopia\Platform\Enum;
use Utopia\Platform\Scope\HTTP;
use Utopia\Pools\Group;
use Utopia\System\System;
@@ -64,7 +65,7 @@ class Create extends Action
))
->param('projectId', '', new ProjectId(), 'Unique Id. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, and hyphen. Can\'t start with a special char. Max length is 36 chars.')
->param('name', null, new Text(128), 'Project name. Max length: 128 chars.')
->param('region', System::getEnv('_APP_REGION', 'default'), new WhiteList(array_keys(array_filter(Config::getParam('regions'), fn ($config) => !$config['disabled']))), 'Project Region.', true)
->param('region', System::getEnv('_APP_REGION', 'default'), new WhiteList(array_keys(array_filter(Config::getParam('regions'), fn ($config) => !$config['disabled']))), 'Project Region.', true, enum: new Enum(name: 'Region'))
->inject('response')
->inject('dbForPlatform')
->inject('cache')
@@ -52,7 +52,7 @@ class XList extends Action
group: 'projects',
name: 'listProjects',
description: <<<EOT
Get a list of all projects. You can use the query params to filter your results.
Get a list of all projects. You can use the query params to filter your results.
EOT,
auth: [AuthType::ADMIN, AuthType::KEY],
responses: [
@@ -3,6 +3,7 @@
namespace Appwrite\Platform\Modules\Presences\HTTP\Usage;
use Appwrite\Extend\Exception;
use Appwrite\Platform\Action;
use Appwrite\Platform\Action as PlatformAction;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\Method;
@@ -13,7 +14,7 @@ use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Query;
use Utopia\Database\Validator\Authorization;
use Utopia\Platform\Action;
use Utopia\Platform\Enum;
use Utopia\Validator\WhiteList;
class Get extends PlatformAction
@@ -45,7 +46,14 @@ class Get extends PlatformAction
),
],
))
->param('range', '30d', new WhiteList(['24h', '30d', '90d']), 'Date range.', true)
->param('range', '30d', new WhiteList(['24h', '30d', '90d']), 'Date range.', true, enum: new Enum(
name: 'UsageRange',
map: [
'24h' => 'Twenty Four Hours',
'30d' => 'Thirty Days',
'90d' => 'Ninety Days',
]
))
->inject('response')
->inject('dbForProject')
->inject('authorization')
@@ -12,6 +12,7 @@ use Utopia\Config\Config;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Validator\Authorization;
use Utopia\Platform\Enum;
use Utopia\Platform\Scope\HTTP;
use Utopia\Validator\Boolean;
use Utopia\Validator\WhiteList;
@@ -53,7 +54,7 @@ class Update extends Action
],
))
->param('methodId', '', new WhiteList(\array_keys(Config::getParam('auth')), true), 'Auth Method ID. Possible values: ' . implode(',', \array_keys(Config::getParam('auth'))), false)
->param('methodId', '', new WhiteList(\array_keys(Config::getParam('auth')), true), 'Auth Method ID. Possible values: ' . implode(',', \array_keys(Config::getParam('auth'))), false, enum: new Enum(name: 'ProjectAuthMethodId'))
->param('enabled', null, new Boolean(), 'Auth method status.')
->inject('response')
->inject('dbForPlatform')
@@ -81,6 +82,7 @@ class Update extends Action
$project = $authorization->skip(fn () => $dbForPlatform->updateDocument('projects', $project->getId(), new Document([
'auths' => $auths,
])));
$authorization->skip(fn () => $dbForPlatform->purgeCachedDocument('projects', $project->getId()));
$queueForEvents->setParam('methodId', $methodId);
@@ -4,6 +4,7 @@ namespace Appwrite\Platform\Modules\Project\Http\Project\Keys;
use Appwrite\Event\Event as QueueEvent;
use Appwrite\Extend\Exception;
use Appwrite\Platform\Action;
use Appwrite\Platform\Modules\Compute\Base;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\Method;
@@ -17,7 +18,7 @@ use Utopia\Database\Exception\Duplicate as DuplicateException;
use Utopia\Database\Helpers\ID;
use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\Datetime;
use Utopia\Platform\Action;
use Utopia\Platform\Enum;
use Utopia\Platform\Scope\HTTP;
use Utopia\Validator\ArrayList;
use Utopia\Validator\Nullable;
@@ -64,7 +65,7 @@ class Create extends Base
))
->param('keyId', '', fn (Database $dbForPlatform) => new CustomId(false, $dbForPlatform->getAdapter()->getMaxUIDLength()), 'Key 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.', false, ['dbForPlatform'])
->param('name', null, new Text(128), 'Key name. Max length: 128 chars.')
->param('scopes', [], new ArrayList(new WhiteList(array_keys(Config::getParam('projectScopes')), true), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Key scopes list. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' scopes are allowed.', optional: false)
->param('scopes', [], new ArrayList(new WhiteList(array_keys(Config::getParam('projectScopes')), true), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Key scopes list. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' scopes are allowed.', optional: false, enum: new Enum(name: 'ProjectKeyScopes'))
->param('expire', null, new Nullable(new Datetime()), 'Expiration time in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. Use null for unlimited expiration.', true)
->inject('response')
->inject('queueForEvents')
@@ -4,6 +4,7 @@ namespace Appwrite\Platform\Modules\Project\Http\Project\Keys\Ephemeral;
use Ahc\Jwt\JWT;
use Appwrite\Event\Event as QueueEvent;
use Appwrite\Platform\Action;
use Appwrite\Platform\Modules\Compute\Base;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\Method;
@@ -13,7 +14,7 @@ use Utopia\Config\Config;
use Utopia\Database\DateTime as DatabaseDateTime;
use Utopia\Database\Document;
use Utopia\Database\Helpers\ID;
use Utopia\Platform\Action;
use Utopia\Platform\Enum;
use Utopia\Platform\Scope\HTTP;
use Utopia\System\System;
use Utopia\Validator\ArrayList;
@@ -58,7 +59,7 @@ class Create extends Base
)
],
))
->param('scopes', [], new ArrayList(new WhiteList(array_keys(Config::getParam('projectScopes')), true), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Key scopes list. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' scopes are allowed.', optional: false)
->param('scopes', [], new ArrayList(new WhiteList(array_keys(Config::getParam('projectScopes')), true), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Key scopes list. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' scopes are allowed.', optional: false, enum: new Enum(name: 'ProjectKeyScopes'))
->param('duration', null, new Range(1, 3600), 'Time in seconds before ephemeral key expires. Maximum duration is 3600 seconds.', optional: false, example: 600)
->inject('response')
->inject('queueForEvents')
@@ -4,6 +4,7 @@ namespace Appwrite\Platform\Modules\Project\Http\Project\Keys;
use Appwrite\Event\Event as QueueEvent;
use Appwrite\Extend\Exception;
use Appwrite\Platform\Action;
use Appwrite\Platform\Modules\Compute\Base;
use Appwrite\SDK\AuthType;
use Appwrite\SDK\Method;
@@ -16,7 +17,7 @@ use Utopia\Database\Exception\Duplicate;
use Utopia\Database\Validator\Authorization;
use Utopia\Database\Validator\Datetime;
use Utopia\Database\Validator\UID;
use Utopia\Platform\Action;
use Utopia\Platform\Enum;
use Utopia\Platform\Scope\HTTP;
use Utopia\Validator\ArrayList;
use Utopia\Validator\Nullable;
@@ -60,7 +61,7 @@ class Update extends Base
))
->param('keyId', '', fn (Database $dbForPlatform) => new UID($dbForPlatform->getAdapter()->getMaxUIDLength()), 'Key ID.', false, ['dbForPlatform'])
->param('name', null, new Text(128), 'Key name. Max length: 128 chars.')
->param('scopes', [], new ArrayList(new WhiteList(array_keys(Config::getParam('projectScopes')), true), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Key scopes list. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' scopes are allowed.', optional: false)
->param('scopes', [], new ArrayList(new WhiteList(array_keys(Config::getParam('projectScopes')), true), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Key scopes list. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' scopes are allowed.', optional: false, enum: new Enum(name: 'ProjectKeyScopes'))
->param('expire', null, new Nullable(new Datetime()), 'Expiration time in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. Use null for unlimited expiration.', true)
->inject('response')
->inject('queueForEvents')

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