mirror of
https://github.com/appwrite/appwrite
synced 2026-05-05 22:38:37 +00:00
Merge remote-tracking branch 'origin/1.8.x' into feat-operators
# Conflicts: # composer.lock
This commit is contained in:
commit
1efe9fa693
47 changed files with 17247 additions and 3664 deletions
2
.gitattributes
vendored
2
.gitattributes
vendored
|
|
@ -5,3 +5,5 @@ src/** linguist-detectable=false
|
|||
tests/** linguist-detectable=false
|
||||
public/scripts/** linguist-detectable=false
|
||||
public/dist/scripts/** linguist-detectable=false
|
||||
|
||||
.github/workflows/*.lock.yml linguist-generated=true merge=ours
|
||||
83
.github/labeler.yml
vendored
Normal file
83
.github/labeler.yml
vendored
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
# Fixes and upgrades for the Appwrite Auth / Users / Teams services.
|
||||
"product / auth":
|
||||
- "(auth|session|login|logout|register|2fa|mfa|users|teams|memberships|invite|oauth|oauth2|sso|jwt)"
|
||||
|
||||
# Fixes and upgrades for the Appwrite Realtime API.
|
||||
"api / realtime":
|
||||
- "(realtime|subscribe|websockets)"
|
||||
|
||||
# Console, UI and UX issues
|
||||
"product / console":
|
||||
- "(console)"
|
||||
|
||||
# Fixes and upgrades for the Appwrite Storage.
|
||||
"product / storage":
|
||||
- "(storage|bucket|file|image|preview|download)"
|
||||
|
||||
# Fixes and upgrades for the Appwrite Database.
|
||||
"product / databases":
|
||||
- "(database|collection|tables|attribute|column|document|row|query|queries|indexes|search|filter|sort|pagination)"
|
||||
|
||||
# Fixes and upgrades for the Appwrite Functions.
|
||||
"product / functions":
|
||||
- "(function|runtime|deployment|execution|trigger|cron|schedule)"
|
||||
|
||||
# Fixes and upgrades for the Appwrite Docs.
|
||||
# "product / docs":
|
||||
# -
|
||||
|
||||
# Fixes and upgrades for the Appwrite Migrations.
|
||||
"product / migrations":
|
||||
- "(migrate|migration)"
|
||||
|
||||
# Fixes and upgrades for the Appwrite Messaging.
|
||||
"product / messaging":
|
||||
- "(messaging|email|sms|push|provider|topic|target|notification)"
|
||||
|
||||
# Fixes and upgrades for the Appwrite Platform.
|
||||
# "product / platform":
|
||||
# -
|
||||
|
||||
# Fixes and upgrades for database relationships
|
||||
"feature / relationships":
|
||||
- "(relationship)"
|
||||
|
||||
# Issues found only on Appwrite Cloud
|
||||
# "product / cloud":
|
||||
# -
|
||||
|
||||
# Fixes and upgrades for the Appwrite VCS.
|
||||
"product / vcs":
|
||||
- "(repo|push|vcs|repository)"
|
||||
|
||||
# Fixes and upgrades for the Appwrite GraphQL API.
|
||||
"api / graphql":
|
||||
- "(graphql|gql|mutation)"
|
||||
|
||||
# Fixes and upgrades for the Appwrite Assistant.
|
||||
"product / assistant":
|
||||
- "(assistant)"
|
||||
|
||||
# Fixes and upgrades for the Appwrite Domains.
|
||||
"product / domains":
|
||||
- "(domain|dns|ssl|certificate)"
|
||||
|
||||
# Fixes and upgrades for the Appwrite Locale.
|
||||
"product / locale":
|
||||
- "(locale|i18n|internationalization|localization|l10n|translation|timezone|country)"
|
||||
|
||||
# Fixes and upgrades for the Appwrite Avatars.
|
||||
"product / avatars":
|
||||
- "(avatar|initial|flag|icon)"
|
||||
|
||||
# Fixes and upgrades for Appwrite Sites.
|
||||
"product / sites":
|
||||
- "(site|web|hosting|domain|ssl|certificate|nextjs|nuxt|react|angular|vue|svelte|astro)"
|
||||
|
||||
# Fixes and upgrades for the Appwrite CLI.
|
||||
"sdk / cli":
|
||||
- "(cli|command line)"
|
||||
|
||||
# Issues only found when self-hosting Appwrite
|
||||
"product / self-hosted":
|
||||
- "(self-host|self host)"
|
||||
32
.github/workflows/ai-moderator.yml
vendored
Normal file
32
.github/workflows/ai-moderator.yml
vendored
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
name: AI Moderator
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [opened, edited]
|
||||
issue_comment:
|
||||
types: [created, edited]
|
||||
pull_request:
|
||||
types: [opened, edited]
|
||||
pull_request_review:
|
||||
types: [submitted, edited]
|
||||
pull_request_review_comment:
|
||||
types: [created, edited]
|
||||
discussion:
|
||||
types: [created, edited]
|
||||
discussion_comment:
|
||||
types: [created, edited]
|
||||
|
||||
permissions:
|
||||
models: read
|
||||
issues: write
|
||||
pull-requests: write
|
||||
discussions: write
|
||||
|
||||
jobs:
|
||||
moderate:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: AI Moderator
|
||||
uses: github/ai-moderator@v1
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
22
.github/workflows/auto-label-issue.yml
vendored
Normal file
22
.github/workflows/auto-label-issue.yml
vendored
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
name: Auto Label Issue
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [opened]
|
||||
|
||||
permissions:
|
||||
issues: write
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
labeler:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Issue Labeler
|
||||
uses: github/issue-labeler@v3.4
|
||||
with:
|
||||
configuration-path: .github/labeler.yml
|
||||
enable-versioned-regex: false
|
||||
include-title: 1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
4992
.github/workflows/issue-triage.lock.yml
generated
vendored
Normal file
4992
.github/workflows/issue-triage.lock.yml
generated
vendored
Normal file
File diff suppressed because it is too large
Load diff
85
.github/workflows/issue-triage.md
vendored
Normal file
85
.github/workflows/issue-triage.md
vendored
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
---
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 0 * * *' # Run daily at midnight UTC
|
||||
workflow_dispatch: # Enable manual trigger
|
||||
stop-after: +30d # workflow will no longer trigger after 30 days. Remove this and recompile to run indefinitely
|
||||
reaction: eyes
|
||||
|
||||
permissions: read-all
|
||||
|
||||
network: defaults
|
||||
|
||||
safe-outputs:
|
||||
add-labels:
|
||||
max: 5
|
||||
add-comment:
|
||||
|
||||
tools:
|
||||
web-fetch:
|
||||
web-search:
|
||||
|
||||
timeout_minutes: 10
|
||||
source: githubnext/agentics/workflows/issue-triage.md@0837fb7b24c3b84ee77fb7c8cfa8735c48be347a
|
||||
---
|
||||
# Agentic Triage
|
||||
|
||||
<!-- Note - this file can be customized to your needs. Replace this section directly, or add further instructions here. After editing run 'gh aw compile' -->
|
||||
|
||||
You're a triage assistant for GitHub issues. Your task is to analyze issues created in the last 24 hours and perform initial triage tasks for each of them.
|
||||
|
||||
1. First, use the `list_issues` tool to retrieve all issues created in the last 24 hours. Filter issues by using the `since` parameter with a timestamp from 24 hours ago (calculate: current time minus 24 hours in ISO 8601 format).
|
||||
|
||||
2. For each issue found, perform the following triage tasks:
|
||||
|
||||
3. Select appropriate labels for the issue from the provided list.
|
||||
|
||||
4. Retrieve the issue content using the `get_issue` tool. If the issue is obviously spam, or generated by bot, or something else that is not an actual issue to be worked on, then add an issue comment to the issue with a one sentence analysis and move to the next issue.
|
||||
|
||||
5. Next, use the GitHub tools to gather additional context about the issue:
|
||||
|
||||
- Fetch the list of labels available in this repository. Use 'gh label list' bash command to fetch the labels. This will give you the labels you can use for triaging issues.
|
||||
- Fetch any comments on the issue using the `get_issue_comments` tool
|
||||
- **Search for duplicate and related issues**: Use the `search_issues` tool to find similar issues by searching for key terms from the issue title and description. Look for both open and closed issues that might be related or duplicates.
|
||||
|
||||
6. Analyze the issue content, considering:
|
||||
|
||||
- The issue title and description
|
||||
- The type of issue (bug report, feature request, question, etc.)
|
||||
- Technical areas mentioned
|
||||
- Severity or priority indicators
|
||||
- User impact
|
||||
- Components affected
|
||||
|
||||
7. Write notes, ideas, nudges, resource links, debugging strategies and/or reproduction steps for the team to consider relevant to the issue.
|
||||
|
||||
8. Select appropriate labels from the available labels list provided above:
|
||||
|
||||
- Choose labels that accurately reflect the issue's nature
|
||||
- Be specific but comprehensive
|
||||
- Select priority labels if you can determine urgency (high-priority, med-priority, or low-priority)
|
||||
- Consider platform labels (android, ios) if applicable
|
||||
- Search for similar issues, and if you find similar issues consider using a "duplicate" label if appropriate. Only do so if the issue is a duplicate of another OPEN issue.
|
||||
- Only select labels from the provided list above
|
||||
- It's okay to not add any labels if none are clearly applicable
|
||||
|
||||
9. Apply the selected labels:
|
||||
|
||||
- Use the `update_issue` tool to apply the labels to the issue
|
||||
- DO NOT communicate directly with users
|
||||
- If no labels are clearly applicable, do not apply any labels
|
||||
|
||||
10. Add an issue comment to the issue with your analysis:
|
||||
- Start with "🎯 Agentic Issue Triage"
|
||||
- Provide a brief summary of the issue
|
||||
- **If duplicate or related issues were found**, add a section listing them with links (e.g., "### 🔗 Potentially Related Issues" followed by a bullet list of related issues with their titles and links)
|
||||
- Mention any relevant details that might help the team understand the issue better
|
||||
- Include any debugging strategies or reproduction steps if applicable
|
||||
- Suggest resources or links that might be helpful for resolving the issue or learning skills related to the issue or the particular area of the codebase affected by it
|
||||
- Mention any nudges or ideas that could help the team in addressing the issue
|
||||
- If you have possible reproduction steps, include them in the comment
|
||||
- If you have any debugging strategies, include them in the comment
|
||||
- If appropriate break the issue down to sub-tasks and write a checklist of things to do.
|
||||
- Use collapsed-by-default sections in the GitHub markdown to keep the comment tidy. Collapse all sections except the short main summary at the top.
|
||||
|
||||
11. After processing all issues, provide a summary of how many issues were triaged. If no issues were created in the last 24 hours, simply note that no new issues needed triage.
|
||||
|
|
@ -98,6 +98,7 @@ RUN mkdir -p /etc/letsencrypt/live/ && chmod -Rf 755 /etc/letsencrypt/live/
|
|||
# Enable Extensions
|
||||
RUN if [ "$DEBUG" = "true" ]; then cp /usr/src/code/dev/xdebug.ini /usr/local/etc/php/conf.d/xdebug.ini; fi
|
||||
RUN if [ "$DEBUG" = "true" ]; then mkdir -p /tmp/xdebug; fi
|
||||
RUN if [ "$DEBUG" = "true" ]; then apk add --update --no-cache openssh-client github-cli; fi
|
||||
RUN if [ "$DEBUG" = "false" ]; then rm -rf /usr/src/code/dev; fi
|
||||
RUN if [ "$DEBUG" = "false" ]; then rm -f /usr/local/lib/php/extensions/no-debug-non-zts-20230831/xdebug.so; fi
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
> We just announced Transactions API for Appwrite Databases - [Learn more](https://appwrite.io/blog/post/announcing-transactions-api)
|
||||
> We just announced DB operators for Appwrite Databases - [Learn more](https://appwrite.io/blog/post/announcing-db-operators)
|
||||
|
||||
> Appwrite Cloud is now Generally Available - [Learn more](https://appwrite.io/cloud-ga)
|
||||
|
||||
|
|
|
|||
|
|
@ -129,6 +129,7 @@
|
|||
color: #ffffff;
|
||||
border-radius: 8px;
|
||||
height: 48px;
|
||||
line-height: 24px;
|
||||
padding: 12px 20px;
|
||||
box-sizing: border-box;
|
||||
cursor: pointer;
|
||||
|
|
@ -184,7 +185,7 @@
|
|||
<tr>
|
||||
<td>
|
||||
<img
|
||||
height="32px"
|
||||
height="26px"
|
||||
src="{{logoUrl}}"
|
||||
alt="Appwrite logo"
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -60,11 +60,11 @@
|
|||
"emails.csvExport.success.subject": "Your CSV export is ready",
|
||||
"emails.csvExport.success.preview": "Your data export has been completed successfully.",
|
||||
"emails.csvExport.success.hello": "Hello {{user}},",
|
||||
"emails.csvExport.success.body": "Your CSV export is ready for download. Click the link below to download your data export.",
|
||||
"emails.csvExport.success.body": "Your CSV export is ready to download. Click the button below to download your data export.",
|
||||
"emails.csvExport.success.footer": "This download link will expire in 1 hour.",
|
||||
"emails.csvExport.success.thanks": "Thanks,",
|
||||
"emails.csvExport.success.buttonText": "Download CSV",
|
||||
"emails.csvExport.success.signature": "{{project}} team",
|
||||
"emails.csvExport.success.signature": "Appwrite team",
|
||||
"emails.csvExport.failure.subject": "Your CSV export failed - file too large",
|
||||
"emails.csvExport.failure.preview": "Your data export failed because the file size exceeds your plan limit.",
|
||||
"emails.csvExport.failure.hello": "Hello {{user}},",
|
||||
|
|
|
|||
|
|
@ -300,7 +300,7 @@ return [
|
|||
[
|
||||
'key' => 'python',
|
||||
'name' => 'Python',
|
||||
'version' => '13.6.0',
|
||||
'version' => '13.6.1',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-python',
|
||||
'package' => 'https://pypi.org/project/appwrite/',
|
||||
'enabled' => true,
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -24,6 +24,7 @@ class UseCases
|
|||
public const ECOMMERCE = 'ecommerce';
|
||||
public const DOCUMENTATION = 'documentation';
|
||||
public const BLOG = 'blog';
|
||||
public const AI = 'artificial intelligence';
|
||||
}
|
||||
|
||||
const TEMPLATE_FRAMEWORKS = [
|
||||
|
|
@ -970,7 +971,7 @@ return [
|
|||
'name' => 'TanStack Start starter',
|
||||
'useCases' => [UseCases::STARTER],
|
||||
'tagline' => 'Simple TanStack Start application integrated with Appwrite SDK.',
|
||||
'score' => 6, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
|
||||
'score' => 9, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
|
||||
'screenshotDark' => $url . '/images/sites/templates/starter-for-tanstack-start-dark.png',
|
||||
'screenshotLight' => $url . '/images/sites/templates/starter-for-tanstack-start-light.png',
|
||||
'frameworks' => [
|
||||
|
|
@ -1443,4 +1444,32 @@ return [
|
|||
'providerVersion' => '0.3.*',
|
||||
'variables' => []
|
||||
],
|
||||
[
|
||||
'key' => 'text-to-speech',
|
||||
'name' => 'Text-to-speech with ElevenLabs',
|
||||
'tagline' => 'Next.js app that transforms text into natural, human-like speech using ElevenLabs',
|
||||
'score' => 10, // 0 to 10 based on looks of screenshot (avoid 1,2,3,8,9,10 if possible)
|
||||
'useCases' => [UseCases::AI],
|
||||
'screenshotDark' => $url . '/images/sites/templates/text-to-speech-dark.png',
|
||||
'screenshotLight' => $url . '/images/sites/templates/text-to-speech-light.png',
|
||||
'frameworks' => [
|
||||
getFramework('NEXTJS', [
|
||||
'providerRootDirectory' => './nextjs/text-to-speech',
|
||||
]),
|
||||
],
|
||||
'vcsProvider' => 'github',
|
||||
'providerRepositoryId' => 'templates-for-sites',
|
||||
'providerOwner' => 'appwrite',
|
||||
'providerVersion' => '0.6.*',
|
||||
'variables' => [
|
||||
[
|
||||
'name' => 'ELEVENLABS_API_KEY',
|
||||
'description' => 'Your ElevenLabs API key',
|
||||
'value' => '',
|
||||
'placeholder' => 'sk_.....',
|
||||
'required' => true,
|
||||
'type' => 'password'
|
||||
],
|
||||
]
|
||||
],
|
||||
];
|
||||
|
|
|
|||
|
|
@ -37,7 +37,6 @@ App::get('/v1/locale')
|
|||
$currencies = Config::getParam('locale-currencies');
|
||||
$output = [];
|
||||
$ip = $request->getIP();
|
||||
$time = (60 * 60 * 24 * 45); // 45 days cache
|
||||
|
||||
$output['ip'] = $ip;
|
||||
|
||||
|
|
@ -68,10 +67,6 @@ App::get('/v1/locale')
|
|||
$output['currency'] = $currency;
|
||||
}
|
||||
|
||||
$response
|
||||
->addHeader('Cache-Control', 'public, max-age=' . $time)
|
||||
->addHeader('Cache-Control', 'private, max-age=3888000') // 45 days
|
||||
;
|
||||
$response->dynamic(new Document($output), Response::MODEL_LOCALE);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -271,6 +271,8 @@ const METRIC_SITES_ID_REQUESTS = 'sites.{siteInternalId}.requests';
|
|||
const METRIC_SITES_ID_INBOUND = 'sites.{siteInternalId}.inbound';
|
||||
const METRIC_SITES_ID_OUTBOUND = 'sites.{siteInternalId}.outbound';
|
||||
const METRIC_AVATARS_SCREENSHOTS_GENERATED = 'avatars.screenshotsGenerated';
|
||||
const METRIC_FUNCTIONS_RUNTIME = 'functions.runtimes.{runtime}';
|
||||
const METRIC_SITES_FRAMEWORK = 'sites.frameworks.{framework}';
|
||||
|
||||
// Resource types
|
||||
const RESOURCE_TYPE_PROJECTS = 'projects';
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@
|
|||
"utopia-php/detector": "0.2.*",
|
||||
"utopia-php/domains": "0.9.*",
|
||||
"utopia-php/emails": "0.6.*",
|
||||
"utopia-php/dns": "0.3.*",
|
||||
"utopia-php/dns": "1.1.*",
|
||||
"utopia-php/dsn": "0.2.1",
|
||||
"utopia-php/framework": "0.33.*",
|
||||
"utopia-php/fetch": "0.4.*",
|
||||
|
|
@ -107,23 +107,5 @@
|
|||
"php-http/discovery": true,
|
||||
"tbachert/spi": true
|
||||
}
|
||||
},
|
||||
"repositories": [
|
||||
{
|
||||
"type": "vcs",
|
||||
"url": "https://github.com/utopia-php/migration"
|
||||
},
|
||||
{
|
||||
"type": "vcs",
|
||||
"url": "https://github.com/utopia-php/emails"
|
||||
},
|
||||
{
|
||||
"type": "vcs",
|
||||
"url": "https://github.com/utopia-php/validators"
|
||||
},
|
||||
{
|
||||
"type": "vcs",
|
||||
"url": "https://github.com/utopia-php/database"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
141
composer.lock
generated
141
composer.lock
generated
|
|
@ -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": "a184716dd568cd37c015e1e929dd3c24",
|
||||
"content-hash": "ad28b7155175986191bd19bbcd13d623",
|
||||
"packages": [
|
||||
{
|
||||
"name": "adhocore/jwt",
|
||||
|
|
@ -3882,38 +3882,7 @@
|
|||
"Utopia\\Database\\": "src/Database"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"Tests\\E2E\\": "tests/e2e",
|
||||
"Tests\\Unit\\": "tests/unit"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"build": [
|
||||
"Composer\\Config::disableProcessTimeout",
|
||||
"docker compose build"
|
||||
],
|
||||
"start": [
|
||||
"Composer\\Config::disableProcessTimeout",
|
||||
"docker compose up -d"
|
||||
],
|
||||
"test": [
|
||||
"Composer\\Config::disableProcessTimeout",
|
||||
"docker compose exec tests vendor/bin/phpunit --configuration phpunit.xml"
|
||||
],
|
||||
"lint": [
|
||||
"php -d memory_limit=2G ./vendor/bin/pint --test"
|
||||
],
|
||||
"format": [
|
||||
"php -d memory_limit=2G ./vendor/bin/pint"
|
||||
],
|
||||
"check": [
|
||||
"./vendor/bin/phpstan analyse --level 7 src tests --memory-limit 2G"
|
||||
],
|
||||
"coverage": [
|
||||
"./vendor/bin/coverage-check ./tmp/clover.xml 90"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
|
|
@ -3978,29 +3947,28 @@
|
|||
},
|
||||
{
|
||||
"name": "utopia-php/dns",
|
||||
"version": "0.3.0",
|
||||
"version": "1.1.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/utopia-php/dns.git",
|
||||
"reference": "8fd4161bc3a8021a670c1101b40f6b09a97f1a54"
|
||||
"reference": "d6eca184883262bdcb4261e57491c91b16079b9a"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/utopia-php/dns/zipball/8fd4161bc3a8021a670c1101b40f6b09a97f1a54",
|
||||
"reference": "8fd4161bc3a8021a670c1101b40f6b09a97f1a54",
|
||||
"url": "https://api.github.com/repos/utopia-php/dns/zipball/d6eca184883262bdcb4261e57491c91b16079b9a",
|
||||
"reference": "d6eca184883262bdcb4261e57491c91b16079b9a",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=8.0",
|
||||
"utopia-php/cli": "0.15.*",
|
||||
"utopia-php/telemetry": "^0.1.1"
|
||||
"php": ">=8.3",
|
||||
"utopia-php/console": "0.0.*",
|
||||
"utopia-php/telemetry": "0.1.*"
|
||||
},
|
||||
"require-dev": {
|
||||
"laravel/pint": "1.2.*",
|
||||
"phpstan/phpstan": "1.8.*",
|
||||
"phpunit/phpunit": "^9.3",
|
||||
"rregeer/phpunit-coverage-check": "^0.3.1",
|
||||
"swoole/ide-helper": "4.6.6"
|
||||
"laravel/pint": "1.25.*",
|
||||
"phpstan/phpstan": "2.0.*",
|
||||
"phpunit/phpunit": "12.4.*",
|
||||
"swoole/ide-helper": "5.1.8"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
|
|
@ -4028,9 +3996,9 @@
|
|||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/utopia-php/dns/issues",
|
||||
"source": "https://github.com/utopia-php/dns/tree/0.3.0"
|
||||
"source": "https://github.com/utopia-php/dns/tree/1.1.0"
|
||||
},
|
||||
"time": "2025-08-04T11:05:53+00:00"
|
||||
"time": "2025-11-03T22:49:02+00:00"
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/domains",
|
||||
|
|
@ -4173,35 +4141,7 @@
|
|||
"Utopia\\Emails\\": "src/Emails"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"test": [
|
||||
"vendor/bin/phpunit"
|
||||
],
|
||||
"lint": [
|
||||
"./vendor/bin/pint --test"
|
||||
],
|
||||
"format": [
|
||||
"./vendor/bin/pint"
|
||||
],
|
||||
"check": [
|
||||
"./vendor/bin/phpstan analyse"
|
||||
],
|
||||
"import": [
|
||||
"php import.php"
|
||||
],
|
||||
"import:all": [
|
||||
"php import.php all --commit=true"
|
||||
],
|
||||
"import:disposable": [
|
||||
"php import.php disposable --commit=true"
|
||||
],
|
||||
"import:free": [
|
||||
"php import.php free --commit=true"
|
||||
],
|
||||
"import:stats": [
|
||||
"php import.php stats"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
|
|
@ -4213,19 +4153,19 @@
|
|||
],
|
||||
"description": "Utopia Emails library is simple and lite library for parsing and validating email addresses. This library is aiming to be as simple and easy to learn and use.",
|
||||
"keywords": [
|
||||
"RFC5322",
|
||||
"email",
|
||||
"emails",
|
||||
"framework",
|
||||
"parsing",
|
||||
"php",
|
||||
"rfc5322",
|
||||
"upf",
|
||||
"utopia",
|
||||
"validation"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/utopia-php/emails/tree/0.6.2",
|
||||
"issues": "https://github.com/utopia-php/emails/issues"
|
||||
"issues": "https://github.com/utopia-php/emails/issues",
|
||||
"source": "https://github.com/utopia-php/emails/tree/0.6.2"
|
||||
},
|
||||
"time": "2025-10-28T16:08:17+00:00"
|
||||
},
|
||||
|
|
@ -4553,25 +4493,7 @@
|
|||
"Utopia\\Migration\\": "src/Migration"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"Utopia\\Tests\\": "tests/Migration"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"test": [
|
||||
"./vendor/bin/phpunit"
|
||||
],
|
||||
"lint": [
|
||||
"./vendor/bin/pint --test"
|
||||
],
|
||||
"format": [
|
||||
"./vendor/bin/pint"
|
||||
],
|
||||
"check": [
|
||||
"./vendor/bin/phpstan analyse --level 3 src tests --memory-limit 2G"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
|
|
@ -4584,8 +4506,8 @@
|
|||
"utopia"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/utopia-php/migration/tree/1.3.3",
|
||||
"issues": "https://github.com/utopia-php/migration/issues"
|
||||
"issues": "https://github.com/utopia-php/migration/issues",
|
||||
"source": "https://github.com/utopia-php/migration/tree/1.3.3"
|
||||
},
|
||||
"time": "2025-10-28T04:02:08+00:00"
|
||||
},
|
||||
|
|
@ -5217,20 +5139,7 @@
|
|||
"Utopia\\": "src/"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"lint": [
|
||||
"vendor/bin/pint --test"
|
||||
],
|
||||
"format": [
|
||||
"vendor/bin/pint"
|
||||
],
|
||||
"check": [
|
||||
"vendor/bin/phpstan analyse -c phpstan.neon --memory-limit 512M"
|
||||
],
|
||||
"test": [
|
||||
"vendor/bin/phpunit --configuration phpunit.xml"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
|
|
@ -5242,8 +5151,8 @@
|
|||
"validator"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/utopia-php/validators/tree/0.0.2",
|
||||
"issues": "https://github.com/utopia-php/validators/issues"
|
||||
"issues": "https://github.com/utopia-php/validators/issues",
|
||||
"source": "https://github.com/utopia-php/validators/tree/0.0.2"
|
||||
},
|
||||
"time": "2025-10-20T21:52:28+00:00"
|
||||
},
|
||||
|
|
|
|||
7
docker-compose.override.yml
Normal file
7
docker-compose.override.yml
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
services:
|
||||
appwrite:
|
||||
# volumes:
|
||||
# - ~/.ssh:/root/.ssh
|
||||
environment:
|
||||
- GH_TOKEN=
|
||||
- GIT_EMAIL=
|
||||
|
|
@ -1,5 +1,9 @@
|
|||
# Change Log
|
||||
|
||||
## 13.6.1
|
||||
|
||||
* Fix passing of `None` to nullable parameters
|
||||
|
||||
## 13.6.0
|
||||
|
||||
* Add `total` parameter to list queries allowing skipping counting rows in a table for improved performance
|
||||
|
|
|
|||
|
|
@ -26,34 +26,27 @@ Before releasing SDKs, you need to:
|
|||
|
||||
To enable SDK releases via GitHub, you need to mount SSH keys and configure GitHub authentication in your Docker environment.
|
||||
|
||||
#### Update Dockerfile
|
||||
#### Update docker-compose.override.yml
|
||||
|
||||
Add the following configuration to your `Dockerfile`:
|
||||
|
||||
```dockerfile
|
||||
ARG GH_TOKEN
|
||||
ENV GH_TOKEN=your_github_token_here
|
||||
RUN git config --global user.email "your-email@example.com"
|
||||
RUN apk add --update --no-cache openssh-client github-cli
|
||||
```
|
||||
|
||||
Replace:
|
||||
- `your_github_token_here` with your GitHub personal access token (with appropriate permissions)
|
||||
- `your-email@example.com` with your Git email address
|
||||
|
||||
#### Update docker-compose.yml
|
||||
|
||||
Add the SSH key volume mount to the `appwrite` service in `docker-compose.yml`:
|
||||
Update `docker-compose.override.yml` to mount SSH keys and set environment variables for the `appwrite` service:
|
||||
|
||||
```yaml
|
||||
services:
|
||||
appwrite:
|
||||
volumes:
|
||||
- ~/.ssh:/root/.ssh
|
||||
# ... other volumes
|
||||
environment:
|
||||
- GH_TOKEN=your_github_token_here
|
||||
- GIT_EMAIL=your-email@example.com
|
||||
```
|
||||
|
||||
This mounts your SSH keys from the host machine, allowing the container to authenticate with GitHub.
|
||||
Uncomment the volumes section.
|
||||
|
||||
Replace:
|
||||
- `your_github_token_here` with your GitHub personal access token (with appropriate permissions)
|
||||
- `your-email@example.com` with your Git email address
|
||||
|
||||
This mounts your SSH keys from the host machine and sets the GitHub token and email as environment variables, allowing the container to authenticate with GitHub. The git configuration is handled automatically at runtime.
|
||||
|
||||
### Updating Specs
|
||||
|
||||
|
|
|
|||
BIN
public/images/sites/templates/text-to-speech-dark.png
Normal file
BIN
public/images/sites/templates/text-to-speech-dark.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 288 KiB |
BIN
public/images/sites/templates/text-to-speech-light.png
Normal file
BIN
public/images/sites/templates/text-to-speech-light.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 225 KiB |
|
|
@ -53,7 +53,9 @@ class Google extends OAuth2
|
|||
'redirect_uri' => $this->callback,
|
||||
'scope' => \implode(' ', $this->getScopes()),
|
||||
'state' => \json_encode($this->state),
|
||||
'response_type' => 'code'
|
||||
'response_type' => 'code',
|
||||
'access_type' => 'offline',
|
||||
'prompt' => 'consent'
|
||||
]);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,118 +3,65 @@
|
|||
namespace Appwrite\Network\Validator;
|
||||
|
||||
use Utopia\DNS\Client;
|
||||
use Utopia\DNS\Message;
|
||||
use Utopia\DNS\Message\Question;
|
||||
use Utopia\DNS\Message\Record;
|
||||
use Utopia\Domains\Domain;
|
||||
use Utopia\System\System;
|
||||
use Utopia\Validator;
|
||||
|
||||
class DNS extends Validator
|
||||
{
|
||||
public const RECORD_A = 'A';
|
||||
public const RECORD_AAAA = 'AAAA';
|
||||
public const RECORD_CNAME = 'CNAME';
|
||||
public const RECORD_CAA = 'CAA'; // You can provide domain only (as $target) for CAA validation
|
||||
|
||||
/**
|
||||
* @var mixed
|
||||
*/
|
||||
protected mixed $logs;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected string $dnsServer;
|
||||
|
||||
/**
|
||||
* @param string $target
|
||||
*/
|
||||
public function __construct(protected string $target, protected string $type = self::RECORD_CNAME, string $dnsServer = '')
|
||||
{
|
||||
if (empty($dnsServer)) {
|
||||
$dnsServer = System::getEnv('_APP_DNS', '8.8.8.8');
|
||||
}
|
||||
|
||||
$this->dnsServer = $dnsServer;
|
||||
public function __construct(
|
||||
protected string $target,
|
||||
protected int $type = Record::TYPE_CNAME,
|
||||
protected string $server = ''
|
||||
) {
|
||||
$this->server = $server ?: System::getEnv('_APP_DNS', '8.8.8.8');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Invalid DNS record';
|
||||
return 'Invalid DNS record.';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getLogs(): mixed
|
||||
{
|
||||
return $this->logs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if DNS record value matches specific value
|
||||
*
|
||||
* @param mixed $domain
|
||||
* @return bool
|
||||
*/
|
||||
public function isValid($value): bool
|
||||
{
|
||||
if (!is_string($value)) {
|
||||
if (!is_string($value) || trim($value) === '') {
|
||||
return false;
|
||||
}
|
||||
|
||||
$dns = new Client($this->dnsServer);
|
||||
|
||||
$client = new Client($this->server);
|
||||
try {
|
||||
$rawQuery = $dns->query($value, $this->type);
|
||||
|
||||
// Some DNS servers return all records, not only type that's asked for
|
||||
// Likely occurs when no records of specific type are found
|
||||
$query = array_filter($rawQuery, function ($record) {
|
||||
return $record->getTypeName() === $this->type;
|
||||
});
|
||||
|
||||
$this->logs = $query;
|
||||
} catch (\Exception $e) {
|
||||
$this->logs = ['error' => $e->getMessage()];
|
||||
$response = $client->query(Message::query(
|
||||
new Question($value, $this->type)
|
||||
));
|
||||
} catch (\Throwable) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (empty($query)) {
|
||||
// CAA records inherit from parent (custom CAA behaviour)
|
||||
if ($this->type === self::RECORD_CAA) {
|
||||
$domain = new Domain($value);
|
||||
if ($domain->get() === $domain->getApex()) {
|
||||
return true; // No CAA on apex domain means anyone can issue certificate
|
||||
}
|
||||
$typeMatches = array_filter(
|
||||
$response->answers,
|
||||
fn (Record $record) => $record->type === $this->type
|
||||
);
|
||||
|
||||
// Recursive validation by parent domain
|
||||
$parts = \explode('.', $value);
|
||||
\array_shift($parts);
|
||||
$parentDomain = \implode('.', $parts);
|
||||
$validator = new DNS($this->target, DNS::RECORD_CAA, $this->dnsServer);
|
||||
return $validator->isValid($parentDomain);
|
||||
if (empty($typeMatches)) {
|
||||
if ($this->type === Record::TYPE_CAA) {
|
||||
return $this->validateParentCAA($value);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach ($query as $record) {
|
||||
// CAA validation only needs to ensure domain
|
||||
if ($this->type === self::RECORD_CAA) {
|
||||
// Extract domain; comments showcase extraction steps in most complex scenario
|
||||
$rdata = $record->getRdata(); // 255 issuewild "certainly.com;validationmethods=tls-alpn-01;retrytimeout=3600"
|
||||
$rdata = \explode(' ', $rdata, 3)[2] ?? ''; // "certainly.com;validationmethods=tls-alpn-01;retrytimeout=3600"
|
||||
$rdata = \trim($rdata, '"'); // certainly.com;validationmethods=tls-alpn-01;retrytimeout=3600
|
||||
$rdata = \explode(';', $rdata, 2)[0] ?? ''; // certainly.com
|
||||
|
||||
if ($rdata === $this->target) {
|
||||
foreach ($typeMatches as $record) {
|
||||
if ($this->type === Record::TYPE_CAA) {
|
||||
$valuePart = $this->extractCAAValue($record->rdata);
|
||||
if ($valuePart !== '' && $valuePart === $this->target) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if ($record->getRdata() === $this->target) {
|
||||
if ($record->rdata === $this->target) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -122,25 +69,46 @@ class DNS extends Validator
|
|||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is array
|
||||
*
|
||||
* Function will return true if object is array.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function validateParentCAA(string $domain): bool
|
||||
{
|
||||
try {
|
||||
$domainInfo = new Domain($domain);
|
||||
} catch (\Throwable) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($domainInfo->get() === $domainInfo->getApex()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$parts = explode('.', $domainInfo->get());
|
||||
array_shift($parts);
|
||||
$parent = implode('.', $parts);
|
||||
|
||||
if ($parent === '') {
|
||||
return false;
|
||||
}
|
||||
|
||||
$validator = new self($this->target, Record::TYPE_CAA, $this->server);
|
||||
return $validator->isValid($parent);
|
||||
}
|
||||
|
||||
private function extractCAAValue(string $rdata): string
|
||||
{
|
||||
$parts = explode(' ', $rdata, 3);
|
||||
if (count($parts) < 3) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$value = trim($parts[2], '"');
|
||||
return explode(';', $value)[0] ?? '';
|
||||
}
|
||||
|
||||
public function isArray(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Type
|
||||
*
|
||||
* Returns validator type.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getType(): string
|
||||
{
|
||||
return self::TYPE_STRING;
|
||||
|
|
|
|||
|
|
@ -589,7 +589,10 @@ class Builds extends Action
|
|||
// Some runtimes/frameworks can't compile with less memory than this
|
||||
$minMemory = $resource->getCollection() === 'sites' ? 2048 : 1024;
|
||||
|
||||
if ($resource->getAttribute('framework', '') === 'analog') {
|
||||
if (
|
||||
$resource->getAttribute('framework', '') === 'analog' ||
|
||||
$resource->getAttribute('framework', '') === 'tanstack-start'
|
||||
) {
|
||||
$minMemory = 4096;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ use Utopia\Database\Database;
|
|||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Exception\Duplicate;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\DNS\Message\Record;
|
||||
use Utopia\Domains\Domain;
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
|
|
@ -135,13 +136,13 @@ class Create extends Action
|
|||
$validators = [];
|
||||
$targetCNAME = new Domain(System::getEnv('_APP_DOMAIN_TARGET_CNAME', ''));
|
||||
if ($targetCNAME->isKnown() && !$targetCNAME->isTest()) {
|
||||
$validators[] = new DNS($targetCNAME->get(), DNS::RECORD_CNAME);
|
||||
$validators[] = new DNS($targetCNAME->get(), Record::TYPE_CNAME);
|
||||
}
|
||||
if ((new IP(IP::V4))->isValid(System::getEnv('_APP_DOMAIN_TARGET_A', ''))) {
|
||||
$validators[] = new DNS(System::getEnv('_APP_DOMAIN_TARGET_A', ''), DNS::RECORD_A);
|
||||
$validators[] = new DNS(System::getEnv('_APP_DOMAIN_TARGET_A', ''), Record::TYPE_A);
|
||||
}
|
||||
if ((new IP(IP::V6))->isValid(System::getEnv('_APP_DOMAIN_TARGET_AAAA', ''))) {
|
||||
$validators[] = new DNS(System::getEnv('_APP_DOMAIN_TARGET_AAAA', ''), DNS::RECORD_AAAA);
|
||||
$validators[] = new DNS(System::getEnv('_APP_DOMAIN_TARGET_AAAA', ''), Record::TYPE_AAAA);
|
||||
}
|
||||
|
||||
if (empty($validators)) {
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ use Utopia\Database\Document;
|
|||
use Utopia\Database\Exception\Duplicate;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\DNS\Message\Record;
|
||||
use Utopia\Domains\Domain;
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
|
|
@ -147,13 +148,13 @@ class Create extends Action
|
|||
$validators = [];
|
||||
$targetCNAME = new Domain(System::getEnv('_APP_DOMAIN_TARGET_CNAME', ''));
|
||||
if ($targetCNAME->isKnown() && !$targetCNAME->isTest()) {
|
||||
$validators[] = new DNS($targetCNAME->get(), DNS::RECORD_CNAME);
|
||||
$validators[] = new DNS($targetCNAME->get(), Record::TYPE_CNAME);
|
||||
}
|
||||
if ((new IP(IP::V4))->isValid(System::getEnv('_APP_DOMAIN_TARGET_A', ''))) {
|
||||
$validators[] = new DNS(System::getEnv('_APP_DOMAIN_TARGET_A', ''), DNS::RECORD_A);
|
||||
$validators[] = new DNS(System::getEnv('_APP_DOMAIN_TARGET_A', ''), Record::TYPE_A);
|
||||
}
|
||||
if ((new IP(IP::V6))->isValid(System::getEnv('_APP_DOMAIN_TARGET_AAAA', ''))) {
|
||||
$validators[] = new DNS(System::getEnv('_APP_DOMAIN_TARGET_AAAA', ''), DNS::RECORD_AAAA);
|
||||
$validators[] = new DNS(System::getEnv('_APP_DOMAIN_TARGET_AAAA', ''), Record::TYPE_AAAA);
|
||||
}
|
||||
|
||||
if (empty($validators)) {
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ use Utopia\Database\Document;
|
|||
use Utopia\Database\Exception\Duplicate;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\DNS\Message\Record;
|
||||
use Utopia\Domains\Domain;
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
|
|
@ -152,13 +153,13 @@ class Create extends Action
|
|||
$validators = [];
|
||||
$targetCNAME = new Domain(System::getEnv('_APP_DOMAIN_TARGET_CNAME', ''));
|
||||
if ($targetCNAME->isKnown() && !$targetCNAME->isTest()) {
|
||||
$validators[] = new DNS($targetCNAME->get(), DNS::RECORD_CNAME);
|
||||
$validators[] = new DNS($targetCNAME->get(), Record::TYPE_CNAME);
|
||||
}
|
||||
if ((new IP(IP::V4))->isValid(System::getEnv('_APP_DOMAIN_TARGET_A', ''))) {
|
||||
$validators[] = new DNS(System::getEnv('_APP_DOMAIN_TARGET_A', ''), DNS::RECORD_A);
|
||||
$validators[] = new DNS(System::getEnv('_APP_DOMAIN_TARGET_A', ''), Record::TYPE_A);
|
||||
}
|
||||
if ((new IP(IP::V6))->isValid(System::getEnv('_APP_DOMAIN_TARGET_AAAA', ''))) {
|
||||
$validators[] = new DNS(System::getEnv('_APP_DOMAIN_TARGET_AAAA', ''), DNS::RECORD_AAAA);
|
||||
$validators[] = new DNS(System::getEnv('_APP_DOMAIN_TARGET_AAAA', ''), Record::TYPE_AAAA);
|
||||
}
|
||||
|
||||
if (empty($validators)) {
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ use Utopia\Database\Document;
|
|||
use Utopia\Database\Exception\Duplicate;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\DNS\Message\Record;
|
||||
use Utopia\Domains\Domain;
|
||||
use Utopia\Platform\Action;
|
||||
use Utopia\Platform\Scope\HTTP;
|
||||
|
|
@ -147,13 +148,13 @@ class Create extends Action
|
|||
$validators = [];
|
||||
$targetCNAME = new Domain(System::getEnv('_APP_DOMAIN_TARGET_CNAME', ''));
|
||||
if ($targetCNAME->isKnown() && !$targetCNAME->isTest()) {
|
||||
$validators[] = new DNS($targetCNAME->get(), DNS::RECORD_CNAME);
|
||||
$validators[] = new DNS($targetCNAME->get(), Record::TYPE_CNAME);
|
||||
}
|
||||
if ((new IP(IP::V4))->isValid(System::getEnv('_APP_DOMAIN_TARGET_A', ''))) {
|
||||
$validators[] = new DNS(System::getEnv('_APP_DOMAIN_TARGET_A', ''), DNS::RECORD_A);
|
||||
$validators[] = new DNS(System::getEnv('_APP_DOMAIN_TARGET_A', ''), Record::TYPE_A);
|
||||
}
|
||||
if ((new IP(IP::V6))->isValid(System::getEnv('_APP_DOMAIN_TARGET_AAAA', ''))) {
|
||||
$validators[] = new DNS(System::getEnv('_APP_DOMAIN_TARGET_AAAA', ''), DNS::RECORD_AAAA);
|
||||
$validators[] = new DNS(System::getEnv('_APP_DOMAIN_TARGET_AAAA', ''), Record::TYPE_AAAA);
|
||||
}
|
||||
|
||||
if (empty($validators)) {
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ use Appwrite\Utopia\Response;
|
|||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\DNS\Message\Record;
|
||||
use Utopia\Domains\Domain;
|
||||
use Utopia\Logger\Log;
|
||||
use Utopia\Platform\Action;
|
||||
|
|
@ -113,15 +114,15 @@ class Update extends Action
|
|||
|
||||
if (!is_null($targetCNAME)) {
|
||||
if ($targetCNAME->isKnown() && !$targetCNAME->isTest()) {
|
||||
$validators[] = new DNS($targetCNAME->get(), DNS::RECORD_CNAME);
|
||||
$validators[] = new DNS($targetCNAME->get(), Record::TYPE_CNAME);
|
||||
}
|
||||
}
|
||||
|
||||
if ((new IP(IP::V4))->isValid(System::getEnv('_APP_DOMAIN_TARGET_A', ''))) {
|
||||
$validators[] = new DNS(System::getEnv('_APP_DOMAIN_TARGET_A', ''), DNS::RECORD_A);
|
||||
$validators[] = new DNS(System::getEnv('_APP_DOMAIN_TARGET_A', ''), Record::TYPE_A);
|
||||
}
|
||||
if ((new IP(IP::V6))->isValid(System::getEnv('_APP_DOMAIN_TARGET_AAAA', ''))) {
|
||||
$validators[] = new DNS(System::getEnv('_APP_DOMAIN_TARGET_AAAA', ''), DNS::RECORD_AAAA);
|
||||
$validators[] = new DNS(System::getEnv('_APP_DOMAIN_TARGET_AAAA', ''), Record::TYPE_AAAA);
|
||||
}
|
||||
|
||||
if (empty($validators)) {
|
||||
|
|
@ -139,24 +140,13 @@ class Update extends Action
|
|||
if (!$validator->isValid($domain->get())) {
|
||||
$log->addExtra('dnsTiming', \strval(\microtime(true) - $validationStart));
|
||||
$log->addTag('dnsDomain', $domain->get());
|
||||
|
||||
$errors = [];
|
||||
foreach ($validators as $validator) {
|
||||
if (!empty($validator->getLogs())) {
|
||||
$errors[] = $validator->getLogs();
|
||||
}
|
||||
}
|
||||
|
||||
$error = \implode("\n", $errors);
|
||||
$log->addExtra('dnsResponse', \is_array($error) ? \json_encode($error) : \strval($error));
|
||||
|
||||
throw new Exception(Exception::RULE_VERIFICATION_FAILED);
|
||||
}
|
||||
|
||||
// Ensure CAA won't block certificate issuance
|
||||
if (!empty(System::getEnv('_APP_DOMAIN_TARGET_CAA', ''))) {
|
||||
$validationStart = \microtime(true);
|
||||
$validator = new DNS(System::getEnv('_APP_DOMAIN_TARGET_CAA', ''), DNS::RECORD_CAA);
|
||||
$validator = new DNS(System::getEnv('_APP_DOMAIN_TARGET_CAA', ''), Record::TYPE_CAA);
|
||||
if (!$validator->isValid($domain->get())) {
|
||||
$log->addExtra('dnsTimingCaa', \strval(\microtime(true) - $validationStart));
|
||||
$log->addTag('dnsDomain', $domain->get());
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ class Create extends Action
|
|||
group: 'deployments',
|
||||
name: 'createDeployment',
|
||||
description: <<<EOT
|
||||
Create a new site code deployment. Use this endpoint to upload a new version of your site code. To activate your newly uploaded code, you'll need to update the function's deployment to use your new deployment ID.
|
||||
Create a new site code deployment. Use this endpoint to upload a new version of your site code. To activate your newly uploaded code, you'll need to update the site's deployment to use your new deployment ID.
|
||||
EOT,
|
||||
auth: [AuthType::KEY],
|
||||
responses: [
|
||||
|
|
|
|||
|
|
@ -259,6 +259,8 @@ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|||
}
|
||||
|
||||
if ($createRelease) {
|
||||
Console::execute('git config --global user.email "$GIT_EMAIL"', stdin: '', stdout: '', stderr: '');
|
||||
|
||||
$releaseVersion = $language['version'];
|
||||
|
||||
$repoName = $language['gitUserName'] . '/' . $language['gitRepoName'];
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ use Utopia\Database\Exception\Conflict;
|
|||
use Utopia\Database\Exception\Structure;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\DNS\Message\Record;
|
||||
use Utopia\Domains\Domain;
|
||||
use Utopia\Locale\Locale;
|
||||
use Utopia\Logger\Log;
|
||||
|
|
@ -313,13 +314,13 @@ class Certificates extends Action
|
|||
$validators = [];
|
||||
$targetCNAME = new Domain(System::getEnv('_APP_DOMAIN_TARGET_CNAME', ''));
|
||||
if ($targetCNAME->isKnown() && !$targetCNAME->isTest()) {
|
||||
$validators[] = new DNS($targetCNAME->get(), DNS::RECORD_CNAME);
|
||||
$validators[] = new DNS($targetCNAME->get(), Record::TYPE_CNAME);
|
||||
}
|
||||
if ((new IP(IP::V4))->isValid(System::getEnv('_APP_DOMAIN_TARGET_A', ''))) {
|
||||
$validators[] = new DNS(System::getEnv('_APP_DOMAIN_TARGET_A', ''), DNS::RECORD_A);
|
||||
$validators[] = new DNS(System::getEnv('_APP_DOMAIN_TARGET_A', ''), Record::TYPE_A);
|
||||
}
|
||||
if ((new IP(IP::V6))->isValid(System::getEnv('_APP_DOMAIN_TARGET_AAAA', ''))) {
|
||||
$validators[] = new DNS(System::getEnv('_APP_DOMAIN_TARGET_AAAA', ''), DNS::RECORD_AAAA);
|
||||
$validators[] = new DNS(System::getEnv('_APP_DOMAIN_TARGET_AAAA', ''), Record::TYPE_AAAA);
|
||||
}
|
||||
|
||||
// Validate if domain target is properly configured
|
||||
|
|
@ -332,24 +333,13 @@ class Certificates extends Action
|
|||
if (!$validator->isValid($domain->get())) {
|
||||
$log->addExtra('dnsTiming', \strval(\microtime(true) - $validationStart));
|
||||
$log->addTag('dnsDomain', $domain->get());
|
||||
|
||||
$errors = [];
|
||||
foreach ($validators as $validator) {
|
||||
if (!empty($validator->getLogs())) {
|
||||
$errors[] = $validator->getLogs();
|
||||
}
|
||||
}
|
||||
|
||||
$error = \implode("\n", $errors);
|
||||
$log->addExtra('dnsResponse', \is_array($error) ? \json_encode($error) : \strval($error));
|
||||
|
||||
throw new Exception('Failed to verify domain DNS records.');
|
||||
}
|
||||
|
||||
// Ensure CAA won't block certificate issuance
|
||||
if (!empty(System::getEnv('_APP_DOMAIN_TARGET_CAA', ''))) {
|
||||
$validationStart = \microtime(true);
|
||||
$validator = new DNS(System::getEnv('_APP_DOMAIN_TARGET_CAA', ''), DNS::RECORD_CAA);
|
||||
$validator = new DNS(System::getEnv('_APP_DOMAIN_TARGET_CAA', ''), Record::TYPE_CAA);
|
||||
if (!$validator->isValid($domain->get())) {
|
||||
$log->addExtra('dnsTimingCaa', \strval(\microtime(true) - $validationStart));
|
||||
$log->addTag('dnsDomain', $domain->get());
|
||||
|
|
|
|||
|
|
@ -335,7 +335,11 @@ class StatsResources extends Action
|
|||
$this->createStatsDocuments($region, str_replace("{resourceType}", RESOURCE_TYPE_FUNCTIONS, METRIC_RESOURCE_TYPE_DEPLOYMENTS), $deployments);
|
||||
$this->createStatsDocuments($region, str_replace("{resourceType}", RESOURCE_TYPE_FUNCTIONS, METRIC_RESOURCE_TYPE_BUILDS), $deployments);
|
||||
|
||||
$this->foreachDocument($dbForProject, 'functions', [], function (Document $function) use ($dbForProject, $region) {
|
||||
|
||||
// Count runtimes
|
||||
$runtimes = [];
|
||||
|
||||
$this->foreachDocument($dbForProject, 'functions', [], function (Document $function) use ($dbForProject, $region, &$runtimes) {
|
||||
$functionDeploymentsStorage = $dbForProject->sum('deployments', 'sourceSize', [
|
||||
Query::equal('resourceInternalId', [$function->getSequence()]),
|
||||
Query::equal('resourceType', [RESOURCE_TYPE_FUNCTIONS]),
|
||||
|
|
@ -364,7 +368,19 @@ class StatsResources extends Action
|
|||
});
|
||||
|
||||
$this->createStatsDocuments($region, str_replace(['{resourceType}','{resourceInternalId}'], [RESOURCE_TYPE_FUNCTIONS,$function->getSequence()], METRIC_RESOURCE_TYPE_ID_BUILDS_STORAGE), $functionBuildsStorage);
|
||||
|
||||
// Runtimes count
|
||||
$runtime = $function->getAttribute('runtime');
|
||||
if (!empty($runtime)) {
|
||||
$runtimes[$runtime] = ($runtimes[$runtime] ?? 0) + 1;
|
||||
}
|
||||
});
|
||||
|
||||
// Write runtimes counts
|
||||
foreach ($runtimes as $runtime => $count) {
|
||||
$this->createStatsDocuments($region, str_replace('{runtime}', $runtime, METRIC_FUNCTIONS_RUNTIME), $count);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected function countForSites(Database $dbForProject, string $region)
|
||||
|
|
@ -385,7 +401,10 @@ class StatsResources extends Action
|
|||
$this->createStatsDocuments($region, str_replace("{resourceType}", RESOURCE_TYPE_SITES, METRIC_RESOURCE_TYPE_DEPLOYMENTS), $deployments);
|
||||
$this->createStatsDocuments($region, str_replace("{resourceType}", RESOURCE_TYPE_SITES, METRIC_RESOURCE_TYPE_BUILDS), $deployments);
|
||||
|
||||
$this->foreachDocument($dbForProject, 'sites', [], function (Document $site) use ($dbForProject, $region) {
|
||||
// Count frameworks
|
||||
$frameworks = [];
|
||||
|
||||
$this->foreachDocument($dbForProject, 'sites', [], function (Document $site) use ($dbForProject, $region, &$frameworks) {
|
||||
$siteDeploymentsStorage = $dbForProject->sum('deployments', 'sourceSize', [
|
||||
Query::equal('resourceInternalId', [$site->getSequence()]),
|
||||
Query::equal('resourceType', [RESOURCE_TYPE_SITES]),
|
||||
|
|
@ -410,7 +429,18 @@ class StatsResources extends Action
|
|||
]);
|
||||
|
||||
$this->createStatsDocuments($region, str_replace(['{resourceType}','{resourceInternalId}'], [RESOURCE_TYPE_SITES,$site->getSequence()], METRIC_RESOURCE_TYPE_ID_BUILDS_STORAGE), $siteBuildsStorage);
|
||||
|
||||
// Frameworks count
|
||||
$framework = $site->getAttribute('framework');
|
||||
if (!empty($framework)) {
|
||||
$frameworks[$framework] = ($frameworks[$framework] ?? 0) + 1;
|
||||
}
|
||||
});
|
||||
|
||||
// Write frameworks counts
|
||||
foreach ($frameworks as $framework => $count) {
|
||||
$this->createStatsDocuments($region, str_replace('{framework}', $framework, METRIC_SITES_FRAMEWORK), $count);
|
||||
}
|
||||
}
|
||||
|
||||
protected function createStatsDocuments(string $region, string $metric, int $value)
|
||||
|
|
|
|||
|
|
@ -11,9 +11,36 @@ class Comment
|
|||
{
|
||||
// TODO: Add more tips
|
||||
protected array $tips = [
|
||||
'Appwrite has a Discord community with over 16 000 members.',
|
||||
'You can use Avatars API to generate QR code for any text or URLs.',
|
||||
'Cursor pagination performs better than offset pagination when loading further pages.',
|
||||
'Appwrite has crossed the 50K GitHub stars milestone with hundreds of active contributors',
|
||||
'Our Discord community has grown to 24K developers, and counting',
|
||||
'Sites auto-generate unique domains with the pattern https://randomstring.appwrite.network',
|
||||
'Every Git commit and branch gets its own deployment URL automatically',
|
||||
'Custom domains work with both CNAME for subdomains and NS records for apex domains',
|
||||
'HTTPS and SSL certificates are handled automatically for all your Sites',
|
||||
'Functions can run for up to 15 minutes before timing out',
|
||||
'Schedule functions to run as often as every minute with cron expressions',
|
||||
'Environment variables can be scoped per function or shared across your project',
|
||||
'Function scopes give you fine-grained control over API permissions',
|
||||
'Sites support three domain rule types: Active deployment, Git branch, and Redirect',
|
||||
'Preview deployments create instant URLs for every branch and commit',
|
||||
'Trigger functions via HTTP, SDKs, events, webhooks, or scheduled cron jobs',
|
||||
'Each function runs in its own isolated container with custom environment variables',
|
||||
'Build commands execute in runtime containers during deployment',
|
||||
'Dynamic API keys are generated automatically for each function execution',
|
||||
'JWT tokens let functions act on behalf of users while preserving their permissions',
|
||||
'Storage files get ClamAV malware scanning and encryption by default',
|
||||
'Roll back Sites deployments instantly by switching between versions',
|
||||
'Git integration provides automatic deployments with optional PR comments',
|
||||
'Silent mode disables those chatty PR comments if you prefer peace and quiet',
|
||||
'Environment variable changes require redeployment to take effect',
|
||||
'SSR frameworks are fully supported with configurable build runtimes',
|
||||
'Global CDN and DDoS protection come free with every Sites deployment',
|
||||
'Deploy functions via zip upload or connect directly to your Git repo',
|
||||
'Realtime gives you live updates for users, storage, functions, and databases',
|
||||
'GraphQL API works alongside REST and WebSocket protocols',
|
||||
'Messaging handles push notifications, emails, and SMS through one unified API',
|
||||
'Teams feature lets you group users with membership management and role permissions',
|
||||
'MCP server integration brings LLM superpowers to Claude Desktop and Cursor IDE',
|
||||
];
|
||||
|
||||
protected string $statePrefix = '[appwrite]: #';
|
||||
|
|
|
|||
|
|
@ -5,30 +5,20 @@ namespace Tests\Unit\Network\Validators;
|
|||
use Appwrite\Network\Validator\DNS;
|
||||
use Appwrite\Tests\Retry;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Utopia\DNS\Message\Record;
|
||||
|
||||
/*
|
||||
DNS Setup (on Appwrite Labs digital ocean team, network tab):
|
||||
|
||||
certainly.caa.appwrite.org: CAA 0 issue "certainly.com"
|
||||
certainly-full.caa.appwrite.org: CAA 128 issuewild "certainly.com;account=123456;validationmethods=dns-01"
|
||||
letsencrypt.certainly.caa.appwrite.org: CAA 0 issue "letsencrypt.org"
|
||||
|
||||
*/
|
||||
|
||||
/**
|
||||
* DNS Setup (on Appwrite Labs digital ocean team, network tab):
|
||||
*
|
||||
* certainly.caa.appwrite.org: CAA 0 issue "certainly.com"
|
||||
* certainly-full.caa.appwrite.org: CAA 128 issuewild "certainly.com;account=123456;validationmethods=dns-01"
|
||||
* letsencrypt.certainly.caa.appwrite.org: CAA 0 issue "letsencrypt.org"
|
||||
*/
|
||||
class DNSTest extends TestCase
|
||||
{
|
||||
public function setUp(): void
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public function tearDown(): void
|
||||
{
|
||||
}
|
||||
|
||||
public function testCNAME(): void
|
||||
{
|
||||
$validator = new DNS('appwrite.io', DNS::RECORD_CNAME);
|
||||
$validator = new DNS('appwrite.io', Record::TYPE_CNAME);
|
||||
$this->assertEquals($validator->isValid(''), false);
|
||||
$this->assertEquals($validator->isValid(null), false);
|
||||
$this->assertEquals($validator->isValid(false), false);
|
||||
|
|
@ -39,7 +29,7 @@ class DNSTest extends TestCase
|
|||
public function testA(): void
|
||||
{
|
||||
// IPv4 for documentation purposes
|
||||
$validator = new DNS('203.0.113.1', DNS::RECORD_A);
|
||||
$validator = new DNS('203.0.113.1', Record::TYPE_A);
|
||||
$this->assertEquals($validator->isValid(''), false);
|
||||
$this->assertEquals($validator->isValid(null), false);
|
||||
$this->assertEquals($validator->isValid(false), false);
|
||||
|
|
@ -50,7 +40,7 @@ class DNSTest extends TestCase
|
|||
public function testAAAA(): void
|
||||
{
|
||||
// IPv6 for documentation purposes
|
||||
$validator = new DNS('2001:db8::1', DNS::RECORD_AAAA);
|
||||
$validator = new DNS('2001:db8::1', Record::TYPE_AAAA);
|
||||
$this->assertEquals($validator->isValid(''), false);
|
||||
$this->assertEquals($validator->isValid(null), false);
|
||||
$this->assertEquals($validator->isValid(false), false);
|
||||
|
|
@ -61,8 +51,8 @@ class DNSTest extends TestCase
|
|||
#[Retry(count: 5)]
|
||||
public function testCAA(): void
|
||||
{
|
||||
$certainly = new DNS('certainly.com', DNS::RECORD_CAA, 'ns1.digitalocean.com');
|
||||
$letsencrypt = new DNS('letsencrypt.org', DNS::RECORD_CAA, 'ns1.digitalocean.com');
|
||||
$certainly = new DNS('certainly.com', Record::TYPE_CAA, 'ns1.digitalocean.com');
|
||||
$letsencrypt = new DNS('letsencrypt.org', Record::TYPE_CAA, 'ns1.digitalocean.com');
|
||||
|
||||
// No CAA record succeeds on main domain & subdomains for any issuer
|
||||
$this->assertEquals($certainly->isValid('caa.appwrite.org'), true);
|
||||
|
|
@ -78,11 +68,11 @@ class DNSTest extends TestCase
|
|||
$this->assertEquals($letsencrypt->isValid('certainly-full.caa.appwrite.org'), false);
|
||||
|
||||
// Custom flags&tag are not allowed if validator includes specific flags&tag
|
||||
$certainlyFull = new DNS('0 issue "certainly.com"', DNS::RECORD_CAA);
|
||||
$certainlyFull = new DNS('0 issue "certainly.com"', Record::TYPE_CAA);
|
||||
$this->assertEquals($certainlyFull->isValid('certainly-full.caa.appwrite.org'), false);
|
||||
|
||||
// Custom flags&tag still allows if they match exactly
|
||||
$certainlyFull = new DNS('128 issuewild "certainly.com;account=123456;validationmethods=dns-01"', DNS::RECORD_CAA);
|
||||
$certainlyFull = new DNS('128 issuewild "certainly.com;account=123456;validationmethods=dns-01"', Record::TYPE_CAA);
|
||||
$this->assertEquals($certainlyFull->isValid('certainly-full.caa.appwrite.org'), true);
|
||||
|
||||
// Certainly CAA allows Certainly, but not LetsEncrypt; Same for subdomains
|
||||
|
|
|
|||
Loading…
Reference in a new issue