mirror of
https://github.com/appwrite/appwrite.git
synced 2026-05-26 13:51:13 +00:00
Compare commits
261
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7b3bceeb7d | ||
|
|
90ef76ad82 | ||
|
|
5a8a900b18 | ||
|
|
c2645be4e1 | ||
|
|
20221cd6b6 | ||
|
|
275ff3dbf2 | ||
|
|
b6fbddad81 | ||
|
|
39c5b6c4d0 | ||
|
|
f9274dad3f | ||
|
|
3568af54b3 | ||
|
|
71b6ac0364 | ||
|
|
919b01a1b1 | ||
|
|
deb5039352 | ||
|
|
793057c823 | ||
|
|
4a86b2d5b1 | ||
|
|
db1b87ecbb | ||
|
|
99d16dfc60 | ||
|
|
725870b1b1 | ||
|
|
cc7b5b7dcf | ||
|
|
38ef0ad399 | ||
|
|
a1dff370a4 | ||
|
|
c961316f46 | ||
|
|
2349401a86 | ||
|
|
6274d5a3e4 | ||
|
|
d0057ec404 | ||
|
|
ab90563989 | ||
|
|
e783f4c234 | ||
|
|
83b9815644 | ||
|
|
afb0fcbc6f | ||
|
|
082afcec19 | ||
|
|
2ab36f4cd6 | ||
|
|
72c71785a6 | ||
|
|
463eb0e66a | ||
|
|
0588fb405d | ||
|
|
b9211d7141 | ||
|
|
a11cf6b002 | ||
|
|
a2799b16f8 | ||
|
|
298e6f9260 | ||
|
|
3df2efb7ae | ||
|
|
b0a0454c55 | ||
|
|
b2d228e102 | ||
|
|
b695ddc7e3 | ||
|
|
3bbd87ecb2 | ||
|
|
fe8c4cb479 | ||
|
|
5e4c74d501 | ||
|
|
1da4736d5d | ||
|
|
c78da61304 | ||
|
|
5722241ff8 | ||
|
|
fe8593fecc | ||
|
|
b07674ccd3 | ||
|
|
aa5e3bf9a9 | ||
|
|
c1b50e9941 | ||
|
|
3a76249d80 | ||
|
|
b2991709dc | ||
|
|
fcc07533e1 | ||
|
|
3a6d907b42 | ||
|
|
d411b741b3 | ||
|
|
bc19e66775 | ||
|
|
08c9db7c60 | ||
|
|
e8f2e78ac4 | ||
|
|
37f16ef0bb | ||
|
|
46d785bbb8 | ||
|
|
a9c9f602ec | ||
|
|
d90b4c2f19 | ||
|
|
79d733486c | ||
|
|
d862245090 | ||
|
|
aacaadcce0 | ||
|
|
69ee6e0659 | ||
|
|
e564793089 | ||
|
|
37910a4c5c | ||
|
|
0c7551b0c1 | ||
|
|
c9582978ed | ||
|
|
ae21fadf0d | ||
|
|
cfd62cf0a1 | ||
|
|
279682941a | ||
|
|
eae26d90af | ||
|
|
b46ebb95a3 | ||
|
|
ffcb6a4677 | ||
|
|
142e359dba | ||
|
|
4130983210 | ||
|
|
e01ac65ce8 | ||
|
|
97454c0768 | ||
|
|
8b4a56cd9b | ||
|
|
da0a6b167f | ||
|
|
27d0c511ac | ||
|
|
8a58f7b6d7 | ||
|
|
cc97d78b5a | ||
|
|
91c24b671b | ||
|
|
b28e536967 | ||
|
|
ab5aac00c4 | ||
|
|
653ac184e1 | ||
|
|
353e84bb74 | ||
|
|
94614d32d0 | ||
|
|
1efe9fa693 | ||
|
|
74ff9a955b | ||
|
|
ce7f40dd34 | ||
|
|
49318b5dac | ||
|
|
da736631b2 | ||
|
|
9db6f9cb75 | ||
|
|
6096c42ff9 | ||
|
|
831a261c04 | ||
|
|
c784cf950b | ||
|
|
b06354b99a | ||
|
|
a797951bec | ||
|
|
bb0007a1e8 | ||
|
|
507717fdf7 | ||
|
|
5afae248ae | ||
|
|
98255fe628 | ||
|
|
057397197b | ||
|
|
c398d5fba9 | ||
|
|
4dc700927e | ||
|
|
4a148e6ef9 | ||
|
|
2ff0ae61c8 | ||
|
|
b06460b562 | ||
|
|
74492ded44 | ||
|
|
9ecffbf4e9 | ||
|
|
0ed4b5dbe7 | ||
|
|
eb17ffd5d2 | ||
|
|
0bf8cead51 | ||
|
|
add1142d1f | ||
|
|
86896a6d3c | ||
|
|
4fd4af312d | ||
|
|
3ef6154ebe | ||
|
|
a96ba5499e | ||
|
|
599aa1dcca | ||
|
|
4b7a7496ff | ||
|
|
eb0e42b79c | ||
|
|
0c93e7d545 | ||
|
|
469d0859fc | ||
|
|
7c408d675e | ||
|
|
4a96d89ee6 | ||
|
|
4d47bc2ebf | ||
|
|
119d6c1789 | ||
|
|
2aa2b8df91 | ||
|
|
4f1940ac0c | ||
|
|
ef938a1c39 | ||
|
|
ebee879855 | ||
|
|
ae2ffd259c | ||
|
|
3d471bae56 | ||
|
|
d0f2293755 | ||
|
|
d6bd26da43 | ||
|
|
16d6c9e5e3 | ||
|
|
e342009c9e | ||
|
|
41d89f1b7a | ||
|
|
ad6aff6af2 | ||
|
|
9b52f6f8f6 | ||
|
|
ad46d057d5 | ||
|
|
8a9d0a3f33 | ||
|
|
1f0e3e0f51 | ||
|
|
26d3cef528 | ||
|
|
fb7691be3b | ||
|
|
501cba216b | ||
|
|
c910c98b32 | ||
|
|
fe9d49c346 | ||
|
|
f3fd2fd191 | ||
|
|
a970969c4d | ||
|
|
6c1e4434a6 | ||
|
|
f0e4c9184d | ||
|
|
ba6e389f81 | ||
|
|
decad9ff9d | ||
|
|
2d332264c6 | ||
|
|
2f843153da | ||
|
|
df8fb23e75 | ||
|
|
60e57eb9c5 | ||
|
|
10a9845898 | ||
|
|
dc43b5977f | ||
|
|
e1fbaf14eb | ||
|
|
d721425cfe | ||
|
|
02dd275be4 | ||
|
|
3391563829 | ||
|
|
5709265f6c | ||
|
|
89d5a28c28 | ||
|
|
f613a06d19 | ||
|
|
30f4b846d2 | ||
|
|
a3742bc65e | ||
|
|
cd1b2b6c05 | ||
|
|
c13aed651a | ||
|
|
dbccb16784 | ||
|
|
c0be333c0f | ||
|
|
d96c7eb3e9 | ||
|
|
4fca374fe9 | ||
|
|
9b82dd0411 | ||
|
|
0904881de8 | ||
|
|
6a41f8b2b9 | ||
|
|
503145734b | ||
|
|
371cab0658 | ||
|
|
ca8cb6f260 | ||
|
|
1f9afc8cab | ||
|
|
c5d4551cab | ||
|
|
d1511d5ba1 | ||
|
|
4a8651ab39 | ||
|
|
de4f473da1 | ||
|
|
ce539972a5 | ||
|
|
458100cd69 | ||
|
|
fea1630909 | ||
|
|
e40e88c322 | ||
|
|
627adb43e0 | ||
|
|
9ad08694ed | ||
|
|
d38399dcc0 | ||
|
|
68b0fe8c70 | ||
|
|
7c2a1ade1f | ||
|
|
5c2828bc78 | ||
|
|
111b2abeeb | ||
|
|
9503507011 | ||
|
|
a6d1f46133 | ||
|
|
145b884628 | ||
|
|
0e22b06f0a | ||
|
|
9e79d883a0 | ||
|
|
02e8a7b429 | ||
|
|
ca861119f9 | ||
|
|
0bfbae4cdd | ||
|
|
61de3b9e08 | ||
|
|
7b1e163c77 | ||
|
|
3f710ddc70 | ||
|
|
1546add5d5 | ||
|
|
734c44c1da | ||
|
|
68524067cf | ||
|
|
cc6319a2c7 | ||
|
|
4aaaa460b2 | ||
|
|
dee3d375db | ||
|
|
462634d311 | ||
|
|
e769cf4e48 | ||
|
|
fbaffa980a | ||
|
|
26f61c1855 | ||
|
|
dae62d863a | ||
|
|
681fa198c9 | ||
|
|
e7b8e6c90d | ||
|
|
d7cc3895cb | ||
|
|
a0b21a0bd4 | ||
|
|
65e99ff0de | ||
|
|
ecd6880fe0 | ||
|
|
e20adbba61 | ||
|
|
30d58e6371 | ||
|
|
037d60633b | ||
|
|
218bb4c537 | ||
|
|
6ed2000478 | ||
|
|
1468231de7 | ||
|
|
c13a70e226 | ||
|
|
25403ea35e | ||
|
|
815c75fa9a | ||
|
|
8b3b07a710 | ||
|
|
33f88fa5e0 | ||
|
|
32dae9ad7c | ||
|
|
be5c5f9420 | ||
|
|
64c3b7a2ff | ||
|
|
61a8f589ac | ||
|
|
92ccb6e4ea | ||
|
|
5963b2baf7 | ||
|
|
7d4486b9e6 | ||
|
|
1a19e01d69 | ||
|
|
2a145bc284 | ||
|
|
9fe3e94a66 | ||
|
|
8e32fb05d1 | ||
|
|
de8538b622 | ||
|
|
fca2727966 | ||
|
|
cb03bfe74c | ||
|
|
2b1e3bd947 | ||
|
|
4858317891 | ||
|
|
6e16340f16 | ||
|
|
ac604b11e3 | ||
|
|
f68d49c4d6 |
Generated
+301
-213
@@ -5,7 +5,7 @@
|
||||
#
|
||||
# Source: githubnext/agentics/workflows/issue-triage.md@0837fb7b24c3b84ee77fb7c8cfa8735c48be347a
|
||||
#
|
||||
# Effective stop-time: 2025-11-27 03:00:29
|
||||
# Effective stop-time: 2025-12-03 20:01:19
|
||||
#
|
||||
# Job Dependency Graph:
|
||||
# ```mermaid
|
||||
@@ -33,18 +33,29 @@
|
||||
# add_labels --> update_reaction
|
||||
# missing_tool --> update_reaction
|
||||
# ```
|
||||
#
|
||||
# Pinned GitHub Actions:
|
||||
# - actions/checkout@v5 (08c6903cd8c0fde910a37f88322edcfb5dd907a8)
|
||||
# https://github.com/actions/checkout/commit/08c6903cd8c0fde910a37f88322edcfb5dd907a8
|
||||
# - actions/download-artifact@v5 (634f93cb2916e3fdff6788551b99b062d0335ce0)
|
||||
# https://github.com/actions/download-artifact/commit/634f93cb2916e3fdff6788551b99b062d0335ce0
|
||||
# - actions/github-script@v8 (ed597411d8f924073f98dfc5c65a23a2325f34cd)
|
||||
# https://github.com/actions/github-script/commit/ed597411d8f924073f98dfc5c65a23a2325f34cd
|
||||
# - actions/setup-node@v6 (2028fbc5c25fe9cf00d9f06a71cc4710d4507903)
|
||||
# https://github.com/actions/setup-node/commit/2028fbc5c25fe9cf00d9f06a71cc4710d4507903
|
||||
# - actions/upload-artifact@v4 (ea165f8d65b6e75b540449e92b4886f43607fa02)
|
||||
# https://github.com/actions/upload-artifact/commit/ea165f8d65b6e75b540449e92b4886f43607fa02
|
||||
|
||||
name: "Agentic Triage"
|
||||
"on":
|
||||
issues:
|
||||
types:
|
||||
- opened
|
||||
- reopened
|
||||
schedule:
|
||||
- cron: 0 0 * * *
|
||||
workflow_dispatch: null
|
||||
|
||||
permissions: read-all
|
||||
|
||||
concurrency:
|
||||
group: "gh-aw-${{ github.workflow }}-${{ github.event.issue.number }}"
|
||||
group: "gh-aw-${{ github.workflow }}"
|
||||
|
||||
run-name: "Agentic Triage"
|
||||
|
||||
@@ -52,7 +63,7 @@ jobs:
|
||||
activation:
|
||||
needs: pre_activation
|
||||
if: needs.pre_activation.outputs.activated == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-slim
|
||||
permissions:
|
||||
discussions: write
|
||||
issues: write
|
||||
@@ -63,24 +74,82 @@ jobs:
|
||||
comment_url: ${{ steps.react.outputs.comment-url }}
|
||||
reaction_id: ${{ steps.react.outputs.reaction-id }}
|
||||
steps:
|
||||
- name: Checkout workflows
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8
|
||||
with:
|
||||
sparse-checkout: |
|
||||
.github/workflows
|
||||
sparse-checkout-cone-mode: false
|
||||
fetch-depth: 1
|
||||
persist-credentials: false
|
||||
- name: Check workflow file timestamps
|
||||
run: |
|
||||
WORKFLOW_FILE="${GITHUB_WORKSPACE}/.github/workflows/$(basename "$GITHUB_WORKFLOW" .lock.yml).md"
|
||||
LOCK_FILE="${GITHUB_WORKSPACE}/.github/workflows/$GITHUB_WORKFLOW"
|
||||
|
||||
if [ -f "$WORKFLOW_FILE" ] && [ -f "$LOCK_FILE" ]; then
|
||||
if [ "$WORKFLOW_FILE" -nt "$LOCK_FILE" ]; then
|
||||
echo "🔴🔴🔴 WARNING: Lock file '$LOCK_FILE' is outdated! The workflow file '$WORKFLOW_FILE' has been modified more recently. Run 'gh aw compile' to regenerate the lock file." >&2
|
||||
echo "## ⚠️ Workflow Lock File Warning" >> $GITHUB_STEP_SUMMARY
|
||||
echo "🔴🔴🔴 **WARNING**: Lock file \`$LOCK_FILE\` is outdated!" >> $GITHUB_STEP_SUMMARY
|
||||
echo "The workflow file \`$WORKFLOW_FILE\` has been modified more recently." >> $GITHUB_STEP_SUMMARY
|
||||
echo "Run \`gh aw compile\` to regenerate the lock file." >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
fi
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd
|
||||
env:
|
||||
GH_AW_WORKFLOW_FILE: "issue-triage.lock.yml"
|
||||
with:
|
||||
script: |
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
async function main() {
|
||||
const workspace = process.env.GITHUB_WORKSPACE;
|
||||
const workflowFile = process.env.GH_AW_WORKFLOW_FILE;
|
||||
if (!workspace) {
|
||||
core.setFailed("Configuration error: GITHUB_WORKSPACE not available.");
|
||||
return;
|
||||
}
|
||||
if (!workflowFile) {
|
||||
core.setFailed("Configuration error: GH_AW_WORKFLOW_FILE not available.");
|
||||
return;
|
||||
}
|
||||
const workflowBasename = path.basename(workflowFile, ".lock.yml");
|
||||
const workflowMdFile = path.join(workspace, ".github", "workflows", `${workflowBasename}.md`);
|
||||
const lockFile = path.join(workspace, ".github", "workflows", workflowFile);
|
||||
core.info(`Checking workflow timestamps:`);
|
||||
core.info(` Source: ${workflowMdFile}`);
|
||||
core.info(` Lock file: ${lockFile}`);
|
||||
let workflowExists = false;
|
||||
let lockExists = false;
|
||||
try {
|
||||
fs.accessSync(workflowMdFile, fs.constants.F_OK);
|
||||
workflowExists = true;
|
||||
} catch (error) {
|
||||
core.info(`Source file does not exist: ${workflowMdFile}`);
|
||||
}
|
||||
try {
|
||||
fs.accessSync(lockFile, fs.constants.F_OK);
|
||||
lockExists = true;
|
||||
} catch (error) {
|
||||
core.info(`Lock file does not exist: ${lockFile}`);
|
||||
}
|
||||
if (!workflowExists || !lockExists) {
|
||||
core.info("Skipping timestamp check - one or both files not found");
|
||||
return;
|
||||
}
|
||||
const workflowStat = fs.statSync(workflowMdFile);
|
||||
const lockStat = fs.statSync(lockFile);
|
||||
const workflowMtime = workflowStat.mtime.getTime();
|
||||
const lockMtime = lockStat.mtime.getTime();
|
||||
core.info(` Source modified: ${workflowStat.mtime.toISOString()}`);
|
||||
core.info(` Lock modified: ${lockStat.mtime.toISOString()}`);
|
||||
if (workflowMtime > lockMtime) {
|
||||
const warningMessage = `🔴🔴🔴 WARNING: Lock file '${lockFile}' is outdated! The workflow file '${workflowMdFile}' has been modified more recently. Run 'gh aw compile' to regenerate the lock file.`;
|
||||
core.error(warningMessage);
|
||||
await core.summary
|
||||
.addRaw("## ⚠️ Workflow Lock File Warning\n\n")
|
||||
.addRaw(`🔴🔴🔴 **WARNING**: Lock file \`${lockFile}\` is outdated!\n\n`)
|
||||
.addRaw(`The workflow file \`${workflowMdFile}\` has been modified more recently.\n\n`)
|
||||
.addRaw("Run `gh aw compile` to regenerate the lock file.\n\n")
|
||||
.write();
|
||||
} else {
|
||||
core.info("✅ Lock file is up to date");
|
||||
}
|
||||
}
|
||||
main().catch(error => {
|
||||
core.setFailed(error instanceof Error ? error.message : String(error));
|
||||
});
|
||||
- name: Add eyes reaction to the triggering item
|
||||
id: react
|
||||
if: github.event_name == 'issues' || github.event_name == 'issue_comment' || github.event_name == 'pull_request_review_comment' || github.event_name == 'discussion' || github.event_name == 'discussion_comment' || (github.event_name == 'pull_request') && (github.event.pull_request.head.repo.full_name == github.repository)
|
||||
if: github.event_name == 'issues' || github.event_name == 'issue_comment' || github.event_name == 'pull_request_review_comment' || github.event_name == 'discussion' || github.event_name == 'discussion_comment' || (github.event_name == 'pull_request') && (github.event.pull_request.head.repo.id == github.repository_id)
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd
|
||||
env:
|
||||
GH_AW_REACTION: eyes
|
||||
@@ -414,9 +483,9 @@ jobs:
|
||||
- agent
|
||||
- detection
|
||||
if: >
|
||||
((!cancelled()) && (contains(needs.agent.outputs.output_types, 'add_comment'))) && (((github.event.issue.number) ||
|
||||
(github.event.pull_request.number)) || (github.event.discussion.number))
|
||||
runs-on: ubuntu-latest
|
||||
(((!cancelled()) && (needs.agent.result != 'skipped')) && (contains(needs.agent.outputs.output_types, 'add_comment'))) &&
|
||||
(((github.event.issue.number) || (github.event.pull_request.number)) || (github.event.discussion.number))
|
||||
runs-on: ubuntu-slim
|
||||
permissions:
|
||||
contents: read
|
||||
discussions: write
|
||||
@@ -805,9 +874,9 @@ jobs:
|
||||
- agent
|
||||
- detection
|
||||
if: >
|
||||
((!cancelled()) && (contains(needs.agent.outputs.output_types, 'add_labels'))) && ((github.event.issue.number) ||
|
||||
(github.event.pull_request.number))
|
||||
runs-on: ubuntu-latest
|
||||
(((!cancelled()) && (needs.agent.result != 'skipped')) && (contains(needs.agent.outputs.output_types, 'add_labels'))) &&
|
||||
((github.event.issue.number) || (github.event.pull_request.number))
|
||||
runs-on: ubuntu-slim
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
@@ -1046,6 +1115,8 @@ jobs:
|
||||
needs: activation
|
||||
runs-on: ubuntu-latest
|
||||
permissions: read-all
|
||||
concurrency:
|
||||
group: "gh-aw-copilot-${{ github.workflow }}"
|
||||
env:
|
||||
GH_AW_SAFE_OUTPUTS: /tmp/gh-aw/safeoutputs/outputs.jsonl
|
||||
GH_AW_SAFE_OUTPUTS_CONFIG: "{\"add_comment\":{\"max\":1},\"add_labels\":{\"max\":5},\"missing_tool\":{}}"
|
||||
@@ -1055,14 +1126,22 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Create gh-aw temp directory
|
||||
run: |
|
||||
mkdir -p /tmp/gh-aw/agent
|
||||
echo "Created /tmp/gh-aw/agent directory for agentic workflow temporary files"
|
||||
- name: Configure Git credentials
|
||||
env:
|
||||
REPO_NAME: ${{ github.repository }}
|
||||
run: |
|
||||
git config --global user.email "github-actions[bot]@users.noreply.github.com"
|
||||
git config --global user.name "${{ github.workflow }}"
|
||||
git config --global user.name "github-actions[bot]"
|
||||
# Re-authenticate git with GitHub token
|
||||
SERVER_URL="${{ github.server_url }}"
|
||||
SERVER_URL="${SERVER_URL#https://}"
|
||||
git remote set-url origin "https://x-access-token:${{ github.token }}@${SERVER_URL}/${REPO_NAME}.git"
|
||||
echo "Git configured with standard GitHub Actions identity"
|
||||
- name: Checkout PR branch
|
||||
if: |
|
||||
@@ -1114,15 +1193,15 @@ jobs:
|
||||
env:
|
||||
COPILOT_CLI_TOKEN: ${{ secrets.COPILOT_CLI_TOKEN }}
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
|
||||
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903
|
||||
with:
|
||||
node-version: '24'
|
||||
- name: Install GitHub Copilot CLI
|
||||
run: npm install -g @github/copilot@0.0.351
|
||||
run: npm install -g @github/copilot@0.0.353
|
||||
- name: Downloading container images
|
||||
run: |
|
||||
set -e
|
||||
docker pull ghcr.io/github/github-mcp-server:v0.19.1
|
||||
docker pull ghcr.io/github/github-mcp-server:v0.20.1
|
||||
docker pull mcp/fetch
|
||||
- name: Setup Safe Outputs Collector MCP
|
||||
run: |
|
||||
@@ -1913,6 +1992,13 @@ jobs:
|
||||
chmod +x /tmp/gh-aw/safeoutputs/mcp-server.cjs
|
||||
|
||||
- name: Setup MCPs
|
||||
env:
|
||||
GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
|
||||
GH_AW_SAFE_OUTPUTS: ${{ env.GH_AW_SAFE_OUTPUTS }}
|
||||
GH_AW_SAFE_OUTPUTS_CONFIG: ${{ toJSON(env.GH_AW_SAFE_OUTPUTS_CONFIG) }}
|
||||
GH_AW_ASSETS_BRANCH: ${{ env.GH_AW_ASSETS_BRANCH }}
|
||||
GH_AW_ASSETS_MAX_SIZE_KB: ${{ env.GH_AW_ASSETS_MAX_SIZE_KB }}
|
||||
GH_AW_ASSETS_ALLOWED_EXTS: ${{ env.GH_AW_ASSETS_ALLOWED_EXTS }}
|
||||
run: |
|
||||
mkdir -p /tmp/gh-aw/mcp-config
|
||||
mkdir -p /home/runner/.copilot
|
||||
@@ -1932,7 +2018,7 @@ jobs:
|
||||
"GITHUB_READ_ONLY=1",
|
||||
"-e",
|
||||
"GITHUB_TOOLSETS=default",
|
||||
"ghcr.io/github/github-mcp-server:v0.19.1"
|
||||
"ghcr.io/github/github-mcp-server:v0.20.1"
|
||||
],
|
||||
"tools": ["*"],
|
||||
"env": {
|
||||
@@ -1949,7 +2035,9 @@ jobs:
|
||||
"GH_AW_SAFE_OUTPUTS_CONFIG": "\${GH_AW_SAFE_OUTPUTS_CONFIG}",
|
||||
"GH_AW_ASSETS_BRANCH": "\${GH_AW_ASSETS_BRANCH}",
|
||||
"GH_AW_ASSETS_MAX_SIZE_KB": "\${GH_AW_ASSETS_MAX_SIZE_KB}",
|
||||
"GH_AW_ASSETS_ALLOWED_EXTS": "\${GH_AW_ASSETS_ALLOWED_EXTS}"
|
||||
"GH_AW_ASSETS_ALLOWED_EXTS": "\${GH_AW_ASSETS_ALLOWED_EXTS}",
|
||||
"GITHUB_REPOSITORY": "\${GITHUB_REPOSITORY}",
|
||||
"GITHUB_SERVER_URL": "\${GITHUB_SERVER_URL}"
|
||||
}
|
||||
},
|
||||
"web-fetch": {
|
||||
@@ -1978,25 +2066,28 @@ jobs:
|
||||
GH_AW_SAFE_OUTPUTS: ${{ env.GH_AW_SAFE_OUTPUTS }}
|
||||
run: |
|
||||
mkdir -p $(dirname "$GH_AW_PROMPT")
|
||||
cat > $GH_AW_PROMPT << 'PROMPT_EOF'
|
||||
cat > "$GH_AW_PROMPT" << 'PROMPT_EOF'
|
||||
# Agentic Triage
|
||||
|
||||
|
||||
|
||||
You're a triage assistant for GitHub issues. Your task is to analyze issue #${{ github.event.issue.number }} and perform some initial triage tasks related to that issue.
|
||||
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. Select appropriate labels for the issue from the provided list.
|
||||
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. 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 exit the workflow.
|
||||
2. For each issue found, perform the following triage tasks:
|
||||
|
||||
3. Next, use the GitHub tools to gather additional context about the issue:
|
||||
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
|
||||
- Find similar issues if needed using the `search_issues` tool
|
||||
- List the issues to see other open issues in the repository using the `list_issues` 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.
|
||||
|
||||
4. Analyze the issue content, considering:
|
||||
6. Analyze the issue content, considering:
|
||||
|
||||
- The issue title and description
|
||||
- The type of issue (bug report, feature request, question, etc.)
|
||||
@@ -2005,9 +2096,9 @@ jobs:
|
||||
- User impact
|
||||
- Components affected
|
||||
|
||||
5. Write notes, ideas, nudges, resource links, debugging strategies and/or reproduction steps for the team to consider relevant to the issue.
|
||||
7. Write notes, ideas, nudges, resource links, debugging strategies and/or reproduction steps for the team to consider relevant to the issue.
|
||||
|
||||
6. Select appropriate labels from the available labels list provided above:
|
||||
8. Select appropriate labels from the available labels list provided above:
|
||||
|
||||
- Choose labels that accurately reflect the issue's nature
|
||||
- Be specific but comprehensive
|
||||
@@ -2017,15 +2108,16 @@ jobs:
|
||||
- Only select labels from the provided list above
|
||||
- It's okay to not add any labels if none are clearly applicable
|
||||
|
||||
7. Apply the selected labels:
|
||||
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
|
||||
|
||||
8. Add an issue comment to the issue with your analysis:
|
||||
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
|
||||
@@ -2035,12 +2127,14 @@ jobs:
|
||||
- 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.
|
||||
|
||||
PROMPT_EOF
|
||||
- name: Append XPIA security instructions to prompt
|
||||
env:
|
||||
GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
|
||||
run: |
|
||||
cat >> $GH_AW_PROMPT << 'PROMPT_EOF'
|
||||
cat >> "$GH_AW_PROMPT" << 'PROMPT_EOF'
|
||||
|
||||
---
|
||||
|
||||
@@ -2072,7 +2166,7 @@ jobs:
|
||||
env:
|
||||
GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
|
||||
run: |
|
||||
cat >> $GH_AW_PROMPT << 'PROMPT_EOF'
|
||||
cat >> "$GH_AW_PROMPT" << 'PROMPT_EOF'
|
||||
|
||||
---
|
||||
|
||||
@@ -2085,7 +2179,7 @@ jobs:
|
||||
env:
|
||||
GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
|
||||
run: |
|
||||
cat >> $GH_AW_PROMPT << 'PROMPT_EOF'
|
||||
cat >> "$GH_AW_PROMPT" << 'PROMPT_EOF'
|
||||
|
||||
---
|
||||
|
||||
@@ -2110,7 +2204,7 @@ jobs:
|
||||
env:
|
||||
GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
|
||||
run: |
|
||||
cat >> $GH_AW_PROMPT << 'PROMPT_EOF'
|
||||
cat >> "$GH_AW_PROMPT" << 'PROMPT_EOF'
|
||||
|
||||
---
|
||||
|
||||
@@ -2179,14 +2273,14 @@ jobs:
|
||||
env:
|
||||
GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
|
||||
run: |
|
||||
echo "<details>" >> $GITHUB_STEP_SUMMARY
|
||||
echo "<summary>Generated Prompt</summary>" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo '```markdown' >> $GITHUB_STEP_SUMMARY
|
||||
cat $GH_AW_PROMPT >> $GITHUB_STEP_SUMMARY
|
||||
echo '```' >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "</details>" >> $GITHUB_STEP_SUMMARY
|
||||
echo "<details>" >> "$GITHUB_STEP_SUMMARY"
|
||||
echo "<summary>Generated Prompt</summary>" >> "$GITHUB_STEP_SUMMARY"
|
||||
echo "" >> "$GITHUB_STEP_SUMMARY"
|
||||
echo '```markdown' >> "$GITHUB_STEP_SUMMARY"
|
||||
cat "$GH_AW_PROMPT" >> "$GITHUB_STEP_SUMMARY"
|
||||
echo '```' >> "$GITHUB_STEP_SUMMARY"
|
||||
echo "" >> "$GITHUB_STEP_SUMMARY"
|
||||
echo "</details>" >> "$GITHUB_STEP_SUMMARY"
|
||||
- name: Upload prompt
|
||||
if: always()
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
|
||||
@@ -2194,13 +2288,6 @@ jobs:
|
||||
name: prompt.txt
|
||||
path: /tmp/gh-aw/aw-prompts/prompt.txt
|
||||
if-no-files-found: warn
|
||||
- name: Capture agent version
|
||||
run: |
|
||||
VERSION_OUTPUT=$(copilot --version 2>&1 || echo "unknown")
|
||||
# Extract semantic version pattern (e.g., 1.2.3, v1.2.3-beta)
|
||||
CLEAN_VERSION=$(echo "$VERSION_OUTPUT" | grep -oE 'v?[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9]+)?' | head -n1 || echo "unknown")
|
||||
echo "AGENT_VERSION=$CLEAN_VERSION" >> $GITHUB_ENV
|
||||
echo "Agent version: $VERSION_OUTPUT"
|
||||
- name: Generate agentic run info
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd
|
||||
with:
|
||||
@@ -2212,7 +2299,7 @@ jobs:
|
||||
engine_name: "GitHub Copilot CLI",
|
||||
model: "",
|
||||
version: "",
|
||||
agent_version: process.env.AGENT_VERSION || "",
|
||||
agent_version: "0.0.353",
|
||||
workflow_name: "Agentic Triage",
|
||||
experimental: false,
|
||||
supports_tools_allowlist: true,
|
||||
@@ -2226,6 +2313,9 @@ jobs:
|
||||
actor: context.actor,
|
||||
event_name: context.eventName,
|
||||
staged: false,
|
||||
steps: {
|
||||
firewall: ""
|
||||
},
|
||||
created_at: new Date().toISOString()
|
||||
};
|
||||
|
||||
@@ -2262,9 +2352,12 @@ jobs:
|
||||
GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
|
||||
GH_AW_SAFE_OUTPUTS: ${{ env.GH_AW_SAFE_OUTPUTS }}
|
||||
GH_AW_SAFE_OUTPUTS_CONFIG: "{\"add_comment\":{\"max\":1},\"add_labels\":{\"max\":5},\"missing_tool\":{}}"
|
||||
GITHUB_HEAD_REF: ${{ github.head_ref }}
|
||||
GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}
|
||||
GITHUB_REF_NAME: ${{ github.ref_name }}
|
||||
GITHUB_STEP_SUMMARY: ${{ env.GITHUB_STEP_SUMMARY }}
|
||||
GITHUB_TOKEN: ${{ secrets.COPILOT_CLI_TOKEN }}
|
||||
GITHUB_WORKSPACE: ${{ github.workspace }}
|
||||
XDG_CONFIG_HOME: /home/runner
|
||||
- name: Redact secrets in logs
|
||||
if: always()
|
||||
@@ -2399,71 +2492,135 @@ jobs:
|
||||
script: |
|
||||
async function main() {
|
||||
const fs = require("fs");
|
||||
const maxBodyLength = 65000;
|
||||
function sanitizeContent(content, maxLength) {
|
||||
if (!content || typeof content !== "string") {
|
||||
return "";
|
||||
}
|
||||
const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS;
|
||||
const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"];
|
||||
const allowedDomains = allowedDomainsEnv
|
||||
? allowedDomainsEnv
|
||||
.split(",")
|
||||
.map(d => d.trim())
|
||||
.filter(d => d)
|
||||
: defaultAllowedDomains;
|
||||
let sanitized = content;
|
||||
sanitized = neutralizeMentions(sanitized);
|
||||
sanitized = removeXmlComments(sanitized);
|
||||
sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, "");
|
||||
sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, "");
|
||||
sanitized = sanitizeUrlProtocols(sanitized);
|
||||
sanitized = sanitizeUrlDomains(sanitized);
|
||||
const lines = sanitized.split("\n");
|
||||
const maxLines = 65000;
|
||||
maxLength = maxLength || 524288;
|
||||
if (lines.length > maxLines) {
|
||||
const truncationMsg = "\n[Content truncated due to line count]";
|
||||
const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg;
|
||||
if (truncatedLines.length > maxLength) {
|
||||
sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg;
|
||||
} else {
|
||||
sanitized = truncatedLines;
|
||||
}
|
||||
} else if (sanitized.length > maxLength) {
|
||||
sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]";
|
||||
}
|
||||
sanitized = neutralizeBotTriggers(sanitized);
|
||||
return sanitized.trim();
|
||||
function sanitizeUrlDomains(s) {
|
||||
return s.replace(/\bhttps:\/\/[^\s\])}'"<>&\x00-\x1f,;]+/gi, match => {
|
||||
const urlAfterProtocol = match.slice(8);
|
||||
const hostname = urlAfterProtocol.split(/[\/:\?#]/)[0].toLowerCase();
|
||||
const isAllowed = allowedDomains.some(allowedDomain => {
|
||||
const normalizedAllowed = allowedDomain.toLowerCase();
|
||||
return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed);
|
||||
});
|
||||
return isAllowed ? match : "(redacted)";
|
||||
});
|
||||
}
|
||||
function sanitizeUrlProtocols(s) {
|
||||
return s.replace(/\b(\w+):\/\/[^\s\])}'"<>&\x00-\x1f]+/gi, (match, protocol) => {
|
||||
return protocol.toLowerCase() === "https" ? match : "(redacted)";
|
||||
});
|
||||
}
|
||||
function neutralizeMentions(s) {
|
||||
return s.replace(
|
||||
/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g,
|
||||
(_m, p1, p2) => `${p1}\`@${p2}\``
|
||||
);
|
||||
}
|
||||
function removeXmlComments(s) {
|
||||
return s.replace(/<!--[\s\S]*?-->/g, "").replace(/<!--[\s\S]*?--!>/g, "");
|
||||
}
|
||||
function neutralizeBotTriggers(s) {
|
||||
return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``);
|
||||
}
|
||||
function sanitizeContent(content, maxLength) {
|
||||
if (!content || typeof content !== "string") {
|
||||
return "";
|
||||
}
|
||||
const allowedDomainsEnv = process.env.GH_AW_ALLOWED_DOMAINS;
|
||||
const defaultAllowedDomains = ["github.com", "github.io", "githubusercontent.com", "githubassets.com", "github.dev", "codespaces.new"];
|
||||
const allowedDomains = allowedDomainsEnv
|
||||
? allowedDomainsEnv
|
||||
.split(",")
|
||||
.map(d => d.trim())
|
||||
.filter(d => d)
|
||||
: defaultAllowedDomains;
|
||||
let sanitized = content;
|
||||
sanitized = neutralizeCommands(sanitized);
|
||||
sanitized = neutralizeMentions(sanitized);
|
||||
sanitized = removeXmlComments(sanitized);
|
||||
sanitized = convertXmlTags(sanitized);
|
||||
sanitized = sanitized.replace(/\x1b\[[0-9;]*[mGKH]/g, "");
|
||||
sanitized = sanitized.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, "");
|
||||
sanitized = sanitizeUrlProtocols(sanitized);
|
||||
sanitized = sanitizeUrlDomains(sanitized);
|
||||
const lines = sanitized.split("\n");
|
||||
const maxLines = 65000;
|
||||
maxLength = maxLength || 524288;
|
||||
if (lines.length > maxLines) {
|
||||
const truncationMsg = "\n[Content truncated due to line count]";
|
||||
const truncatedLines = lines.slice(0, maxLines).join("\n") + truncationMsg;
|
||||
if (truncatedLines.length > maxLength) {
|
||||
sanitized = truncatedLines.substring(0, maxLength - truncationMsg.length) + truncationMsg;
|
||||
} else {
|
||||
sanitized = truncatedLines;
|
||||
}
|
||||
} else if (sanitized.length > maxLength) {
|
||||
sanitized = sanitized.substring(0, maxLength) + "\n[Content truncated due to length]";
|
||||
}
|
||||
sanitized = neutralizeBotTriggers(sanitized);
|
||||
return sanitized.trim();
|
||||
function sanitizeUrlDomains(s) {
|
||||
s = s.replace(/\bhttps:\/\/([^\s\])}'"<>&\x00-\x1f,;]+)/gi, (match, rest) => {
|
||||
const hostname = rest.split(/[\/:\?#]/)[0].toLowerCase();
|
||||
const isAllowed = allowedDomains.some(allowedDomain => {
|
||||
const normalizedAllowed = allowedDomain.toLowerCase();
|
||||
return hostname === normalizedAllowed || hostname.endsWith("." + normalizedAllowed);
|
||||
});
|
||||
if (isAllowed) {
|
||||
return match;
|
||||
}
|
||||
const domain = hostname;
|
||||
const truncated = domain.length > 12 ? domain.substring(0, 12) + "..." : domain;
|
||||
core.info(`Redacted URL: ${truncated}`);
|
||||
core.debug(`Redacted URL (full): ${match}`);
|
||||
const urlParts = match.split(/([?&#])/);
|
||||
let result = "(redacted)";
|
||||
for (let i = 1; i < urlParts.length; i++) {
|
||||
if (urlParts[i].match(/^[?&#]$/)) {
|
||||
result += urlParts[i];
|
||||
} else {
|
||||
result += sanitizeUrlDomains(urlParts[i]);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
});
|
||||
return s;
|
||||
}
|
||||
function sanitizeUrlProtocols(s) {
|
||||
return s.replace(/(?<![-\/\w])([A-Za-z][A-Za-z0-9+.-]*):(?:\/\/|(?=[^\s:]))[^\s\])}'"<>&\x00-\x1f]+/g, (match, protocol) => {
|
||||
if (protocol.toLowerCase() === "https") {
|
||||
return match;
|
||||
}
|
||||
if (match.includes("::")) {
|
||||
return match;
|
||||
}
|
||||
if (match.includes("://")) {
|
||||
const domainMatch = match.match(/^[^:]+:\/\/([^\/\s?#]+)/);
|
||||
const domain = domainMatch ? domainMatch[1] : match;
|
||||
const truncated = domain.length > 12 ? domain.substring(0, 12) + "..." : domain;
|
||||
core.info(`Redacted URL: ${truncated}`);
|
||||
core.debug(`Redacted URL (full): ${match}`);
|
||||
return "(redacted)";
|
||||
}
|
||||
const dangerousProtocols = ["javascript", "data", "vbscript", "file", "about", "mailto", "tel", "ssh", "ftp"];
|
||||
if (dangerousProtocols.includes(protocol.toLowerCase())) {
|
||||
const truncated = match.length > 12 ? match.substring(0, 12) + "..." : match;
|
||||
core.info(`Redacted URL: ${truncated}`);
|
||||
core.debug(`Redacted URL (full): ${match}`);
|
||||
return "(redacted)";
|
||||
}
|
||||
return match;
|
||||
});
|
||||
}
|
||||
function neutralizeCommands(s) {
|
||||
const commandName = process.env.GH_AW_COMMAND;
|
||||
if (!commandName) {
|
||||
return s;
|
||||
}
|
||||
const escapedCommand = commandName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
||||
return s.replace(new RegExp(`^(\\s*)/(${escapedCommand})\\b`, "i"), "$1`/$2`");
|
||||
}
|
||||
function neutralizeMentions(s) {
|
||||
return s.replace(
|
||||
/(^|[^\w`])@([A-Za-z0-9](?:[A-Za-z0-9-]{0,37}[A-Za-z0-9])?(?:\/[A-Za-z0-9._-]+)?)/g,
|
||||
(_m, p1, p2) => `${p1}\`@${p2}\``
|
||||
);
|
||||
}
|
||||
function removeXmlComments(s) {
|
||||
return s.replace(/<!--[\s\S]*?-->/g, "").replace(/<!--[\s\S]*?--!>/g, "");
|
||||
}
|
||||
function convertXmlTags(s) {
|
||||
const allowedTags = ["details", "summary", "code", "em", "b"];
|
||||
s = s.replace(/<!\[CDATA\[([\s\S]*?)\]\]>/g, (match, content) => {
|
||||
const convertedContent = content.replace(/<(\/?[A-Za-z][A-Za-z0-9]*(?:[^>]*?))>/g, "($1)");
|
||||
return `(![CDATA[${convertedContent}]])`;
|
||||
});
|
||||
return s.replace(/<(\/?[A-Za-z!][^>]*?)>/g, (match, tagContent) => {
|
||||
const tagNameMatch = tagContent.match(/^\/?\s*([A-Za-z][A-Za-z0-9]*)/);
|
||||
if (tagNameMatch) {
|
||||
const tagName = tagNameMatch[1].toLowerCase();
|
||||
if (allowedTags.includes(tagName)) {
|
||||
return match;
|
||||
}
|
||||
}
|
||||
return `(${tagContent})`;
|
||||
});
|
||||
}
|
||||
function neutralizeBotTriggers(s) {
|
||||
return s.replace(/\b(fixes?|closes?|resolves?|fix|close|resolve)\s+#(\w+)/gi, (match, action, ref) => `\`${action} #${ref}\``);
|
||||
}
|
||||
}
|
||||
const maxBodyLength = 65000;
|
||||
function getMaxAllowedForType(itemType, config) {
|
||||
const itemConfig = config?.[itemType];
|
||||
if (itemConfig && typeof itemConfig === "object" && "max" in itemConfig && itemConfig.max) {
|
||||
@@ -4295,7 +4452,9 @@ jobs:
|
||||
detection:
|
||||
needs: agent
|
||||
runs-on: ubuntu-latest
|
||||
permissions: read-all
|
||||
permissions: {}
|
||||
concurrency:
|
||||
group: "gh-aw-copilot-${{ github.workflow }}"
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- name: Download prompt artifact
|
||||
@@ -4444,11 +4603,11 @@ jobs:
|
||||
env:
|
||||
COPILOT_CLI_TOKEN: ${{ secrets.COPILOT_CLI_TOKEN }}
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
|
||||
uses: actions/setup-node@2028fbc5c25fe9cf00d9f06a71cc4710d4507903
|
||||
with:
|
||||
node-version: '24'
|
||||
- name: Install GitHub Copilot CLI
|
||||
run: npm install -g @github/copilot@0.0.351
|
||||
run: npm install -g @github/copilot@0.0.353
|
||||
- name: Execute GitHub Copilot CLI
|
||||
id: agentic_execution
|
||||
# Copilot CLI tool arguments (sorted):
|
||||
@@ -4471,8 +4630,11 @@ jobs:
|
||||
env:
|
||||
COPILOT_AGENT_RUNNER_TYPE: STANDALONE
|
||||
GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt
|
||||
GITHUB_HEAD_REF: ${{ github.head_ref }}
|
||||
GITHUB_REF_NAME: ${{ github.ref_name }}
|
||||
GITHUB_STEP_SUMMARY: ${{ env.GITHUB_STEP_SUMMARY }}
|
||||
GITHUB_TOKEN: ${{ secrets.COPILOT_CLI_TOKEN }}
|
||||
GITHUB_WORKSPACE: ${{ github.workspace }}
|
||||
XDG_CONFIG_HOME: /home/runner
|
||||
- name: Parse threat detection results
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd
|
||||
@@ -4522,8 +4684,8 @@ jobs:
|
||||
needs:
|
||||
- agent
|
||||
- detection
|
||||
if: (!cancelled()) && (contains(needs.agent.outputs.output_types, 'missing_tool'))
|
||||
runs-on: ubuntu-latest
|
||||
if: ((!cancelled()) && (needs.agent.result != 'skipped')) && (contains(needs.agent.outputs.output_types, 'missing_tool'))
|
||||
runs-on: ubuntu-slim
|
||||
permissions:
|
||||
contents: read
|
||||
timeout-minutes: 5
|
||||
@@ -4651,89 +4813,15 @@ jobs:
|
||||
});
|
||||
|
||||
pre_activation:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-slim
|
||||
outputs:
|
||||
activated: ${{ (steps.check_membership.outputs.is_team_member == 'true') && (steps.check_stop_time.outputs.stop_time_ok == 'true') }}
|
||||
activated: ${{ steps.check_stop_time.outputs.stop_time_ok == 'true' }}
|
||||
steps:
|
||||
- name: Check team membership for workflow
|
||||
id: check_membership
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd
|
||||
env:
|
||||
GH_AW_REQUIRED_ROLES: admin,maintainer,write
|
||||
with:
|
||||
script: |
|
||||
async function main() {
|
||||
const { eventName } = context;
|
||||
const actor = context.actor;
|
||||
const { owner, repo } = context.repo;
|
||||
const requiredPermissionsEnv = process.env.GH_AW_REQUIRED_ROLES;
|
||||
const requiredPermissions = requiredPermissionsEnv ? requiredPermissionsEnv.split(",").filter(p => p.trim() !== "") : [];
|
||||
if (eventName === "workflow_dispatch") {
|
||||
const hasWriteRole = requiredPermissions.includes("write");
|
||||
if (hasWriteRole) {
|
||||
core.info(`✅ Event ${eventName} does not require validation (write role allowed)`);
|
||||
core.setOutput("is_team_member", "true");
|
||||
core.setOutput("result", "safe_event");
|
||||
return;
|
||||
}
|
||||
core.info(`Event ${eventName} requires validation (write role not allowed)`);
|
||||
}
|
||||
const safeEvents = ["workflow_run", "schedule"];
|
||||
if (safeEvents.includes(eventName)) {
|
||||
core.info(`✅ Event ${eventName} does not require validation`);
|
||||
core.setOutput("is_team_member", "true");
|
||||
core.setOutput("result", "safe_event");
|
||||
return;
|
||||
}
|
||||
if (!requiredPermissions || requiredPermissions.length === 0) {
|
||||
core.warning("❌ Configuration error: Required permissions not specified. Contact repository administrator.");
|
||||
core.setOutput("is_team_member", "false");
|
||||
core.setOutput("result", "config_error");
|
||||
core.setOutput("error_message", "Configuration error: Required permissions not specified");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
core.info(`Checking if user '${actor}' has required permissions for ${owner}/${repo}`);
|
||||
core.info(`Required permissions: ${requiredPermissions.join(", ")}`);
|
||||
const repoPermission = await github.rest.repos.getCollaboratorPermissionLevel({
|
||||
owner: owner,
|
||||
repo: repo,
|
||||
username: actor,
|
||||
});
|
||||
const permission = repoPermission.data.permission;
|
||||
core.info(`Repository permission level: ${permission}`);
|
||||
for (const requiredPerm of requiredPermissions) {
|
||||
if (permission === requiredPerm || (requiredPerm === "maintainer" && permission === "maintain")) {
|
||||
core.info(`✅ User has ${permission} access to repository`);
|
||||
core.setOutput("is_team_member", "true");
|
||||
core.setOutput("result", "authorized");
|
||||
core.setOutput("user_permission", permission);
|
||||
return;
|
||||
}
|
||||
}
|
||||
core.warning(`User permission '${permission}' does not meet requirements: ${requiredPermissions.join(", ")}`);
|
||||
core.setOutput("is_team_member", "false");
|
||||
core.setOutput("result", "insufficient_permissions");
|
||||
core.setOutput("user_permission", permission);
|
||||
core.setOutput(
|
||||
"error_message",
|
||||
`Access denied: User '${actor}' is not authorized. Required permissions: ${requiredPermissions.join(", ")}`
|
||||
);
|
||||
} catch (repoError) {
|
||||
const errorMessage = repoError instanceof Error ? repoError.message : String(repoError);
|
||||
core.warning(`Repository permission check failed: ${errorMessage}`);
|
||||
core.setOutput("is_team_member", "false");
|
||||
core.setOutput("result", "api_error");
|
||||
core.setOutput("error_message", `Repository permission check failed: ${errorMessage}`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
await main();
|
||||
- name: Check stop-time limit
|
||||
id: check_stop_time
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd
|
||||
env:
|
||||
GH_AW_STOP_TIME: 2025-11-27 03:00:29
|
||||
GH_AW_STOP_TIME: 2025-12-03 20:01:19
|
||||
GH_AW_WORKFLOW_NAME: "Agentic Triage"
|
||||
with:
|
||||
script: |
|
||||
@@ -4776,7 +4864,7 @@ jobs:
|
||||
if: >
|
||||
(((((always()) && (needs.agent.result != 'skipped')) && (needs.activation.outputs.comment_id)) && (!contains(needs.agent.outputs.output_types, 'add_comment'))) &&
|
||||
(!contains(needs.agent.outputs.output_types, 'create_pull_request'))) && (!contains(needs.agent.outputs.output_types, 'push_to_pull_request_branch'))
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-slim
|
||||
permissions:
|
||||
contents: read
|
||||
discussions: write
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
---
|
||||
on:
|
||||
issues:
|
||||
types: [opened, reopened]
|
||||
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
|
||||
|
||||
@@ -25,20 +26,23 @@ source: githubnext/agentics/workflows/issue-triage.md@0837fb7b24c3b84ee77fb7c8cf
|
||||
|
||||
<!-- 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 issue #${{ github.event.issue.number }} and perform some initial triage tasks related to that issue.
|
||||
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. Select appropriate labels for the issue from the provided list.
|
||||
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. 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 exit the workflow.
|
||||
2. For each issue found, perform the following triage tasks:
|
||||
|
||||
3. Next, use the GitHub tools to gather additional context about the issue:
|
||||
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
|
||||
- Find similar issues if needed using the `search_issues` tool
|
||||
- List the issues to see other open issues in the repository using the `list_issues` 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.
|
||||
|
||||
4. Analyze the issue content, considering:
|
||||
6. Analyze the issue content, considering:
|
||||
|
||||
- The issue title and description
|
||||
- The type of issue (bug report, feature request, question, etc.)
|
||||
@@ -47,9 +51,9 @@ You're a triage assistant for GitHub issues. Your task is to analyze issue #${{
|
||||
- User impact
|
||||
- Components affected
|
||||
|
||||
5. Write notes, ideas, nudges, resource links, debugging strategies and/or reproduction steps for the team to consider relevant to the issue.
|
||||
7. Write notes, ideas, nudges, resource links, debugging strategies and/or reproduction steps for the team to consider relevant to the issue.
|
||||
|
||||
6. Select appropriate labels from the available labels list provided above:
|
||||
8. Select appropriate labels from the available labels list provided above:
|
||||
|
||||
- Choose labels that accurately reflect the issue's nature
|
||||
- Be specific but comprehensive
|
||||
@@ -59,15 +63,16 @@ You're a triage assistant for GitHub issues. Your task is to analyze issue #${{
|
||||
- Only select labels from the provided list above
|
||||
- It's okay to not add any labels if none are clearly applicable
|
||||
|
||||
7. Apply the selected labels:
|
||||
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
|
||||
|
||||
8. Add an issue comment to the issue with your analysis:
|
||||
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
|
||||
@@ -76,3 +81,5 @@ You're a triage assistant for GitHub issues. Your task is to analyze issue #${{
|
||||
- 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.
|
||||
|
||||
+2
-3
@@ -12,7 +12,7 @@ RUN composer install --ignore-platform-reqs --optimize-autoloader \
|
||||
--no-plugins --no-scripts --prefer-dist \
|
||||
`if [ "$TESTING" != "true" ]; then echo "--no-dev"; fi`
|
||||
|
||||
FROM appwrite/base:0.10.4 AS final
|
||||
FROM appwrite/base:0.10.5 AS final
|
||||
|
||||
LABEL maintainer="team@appwrite.io"
|
||||
|
||||
@@ -28,8 +28,6 @@ RUN \
|
||||
apk add boost boost-dev; \
|
||||
fi
|
||||
|
||||
RUN apk add libwebp
|
||||
|
||||
WORKDIR /usr/src/code
|
||||
|
||||
COPY --from=composer /usr/local/src/vendor /usr/src/code/vendor
|
||||
@@ -98,6 +96,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)
|
||||
|
||||
|
||||
+18
-10
@@ -40,8 +40,6 @@ Config::setParam('runtimes', (new Runtimes('v5'))->getAll(supported: false));
|
||||
// require controllers after overwriting runtimes
|
||||
require_once __DIR__ . '/controllers/general.php';
|
||||
|
||||
Authorization::disable();
|
||||
|
||||
CLI::setResource('register', fn () => $register);
|
||||
|
||||
CLI::setResource('cache', function ($pools) {
|
||||
@@ -59,7 +57,13 @@ CLI::setResource('pools', function (Registry $register) {
|
||||
return $register->get('pools');
|
||||
}, ['register']);
|
||||
|
||||
CLI::setResource('dbForPlatform', function ($pools, $cache) {
|
||||
CLI::setResource('authorization', function () {
|
||||
$authorization = new Authorization();
|
||||
$authorization->disable();
|
||||
return $authorization;
|
||||
}, []);
|
||||
|
||||
CLI::setResource('dbForPlatform', function ($pools, $cache, $authorization) {
|
||||
$sleep = 3;
|
||||
$maxAttempts = 5;
|
||||
$attempts = 0;
|
||||
@@ -73,6 +77,7 @@ CLI::setResource('dbForPlatform', function ($pools, $cache) {
|
||||
$dbForPlatform = new Database($adapter, $cache);
|
||||
|
||||
$dbForPlatform
|
||||
->setAuthorization($authorization)
|
||||
->setNamespace('_console')
|
||||
->setMetadata('host', \gethostname())
|
||||
->setMetadata('project', 'console');
|
||||
@@ -97,7 +102,7 @@ CLI::setResource('dbForPlatform', function ($pools, $cache) {
|
||||
}
|
||||
|
||||
return $dbForPlatform;
|
||||
}, ['pools', 'cache']);
|
||||
}, ['pools', 'cache', 'authorization']);
|
||||
|
||||
CLI::setResource('console', function () {
|
||||
return new Document(Config::getParam('console'));
|
||||
@@ -108,10 +113,10 @@ CLI::setResource(
|
||||
fn () => fn (Document $project, string $resourceType, ?string $resourceId) => false
|
||||
);
|
||||
|
||||
CLI::setResource('getProjectDB', function (Group $pools, Database $dbForPlatform, $cache) {
|
||||
CLI::setResource('getProjectDB', function (Group $pools, Database $dbForPlatform, $cache, $authorization) {
|
||||
$databases = []; // TODO: @Meldiron This should probably be responsibility of utopia-php/pools
|
||||
|
||||
return function (Document $project) use ($pools, $dbForPlatform, $cache, &$databases) {
|
||||
return function (Document $project) use ($pools, $dbForPlatform, $cache, $authorization, &$databases) {
|
||||
if ($project->isEmpty() || $project->getId() === 'console') {
|
||||
return $dbForPlatform;
|
||||
}
|
||||
@@ -144,6 +149,7 @@ CLI::setResource('getProjectDB', function (Group $pools, Database $dbForPlatform
|
||||
|
||||
$adapter = new DatabasePool($pools->get($dsn->getHost()));
|
||||
$database = new Database($adapter, $cache);
|
||||
|
||||
$databases[$dsn->getHost()] = $database;
|
||||
$sharedTables = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', ''));
|
||||
|
||||
@@ -160,17 +166,18 @@ CLI::setResource('getProjectDB', function (Group $pools, Database $dbForPlatform
|
||||
}
|
||||
|
||||
$database
|
||||
->setAuthorization($authorization)
|
||||
->setMetadata('host', \gethostname())
|
||||
->setMetadata('project', $project->getId());
|
||||
|
||||
return $database;
|
||||
};
|
||||
}, ['pools', 'dbForPlatform', 'cache']);
|
||||
}, ['pools', 'dbForPlatform', 'cache', 'authorization']);
|
||||
|
||||
CLI::setResource('getLogsDB', function (Group $pools, Cache $cache) {
|
||||
CLI::setResource('getLogsDB', function (Group $pools, Cache $cache, Authorization $authorization) {
|
||||
$database = null;
|
||||
|
||||
return function (?Document $project = null) use ($pools, $cache, $database) {
|
||||
return function (?Document $project = null) use ($pools, $cache, $database, $authorization) {
|
||||
if ($database !== null && $project !== null && !$project->isEmpty() && $project->getId() !== 'console') {
|
||||
$database->setTenant((int)$project->getSequence());
|
||||
return $database;
|
||||
@@ -180,6 +187,7 @@ CLI::setResource('getLogsDB', function (Group $pools, Cache $cache) {
|
||||
$database = new Database($adapter, $cache);
|
||||
|
||||
$database
|
||||
->setAuthorization($authorization)
|
||||
->setSharedTables(true)
|
||||
->setNamespace('logsV1')
|
||||
->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS_TASK)
|
||||
@@ -192,7 +200,7 @@ CLI::setResource('getLogsDB', function (Group $pools, Cache $cache) {
|
||||
|
||||
return $database;
|
||||
};
|
||||
}, ['pools', 'cache']);
|
||||
}, ['pools', 'cache', 'authorization']);
|
||||
CLI::setResource('publisher', function (Group $pools) {
|
||||
return new BrokerPool(publisher: $pools->get('publisher'));
|
||||
}, ['pools']);
|
||||
|
||||
@@ -364,6 +364,61 @@ return [
|
||||
'array' => false,
|
||||
'filters' => ['datetime'],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('emailCanonical'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 320,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('emailIsFree'),
|
||||
'type' => Database::VAR_BOOLEAN,
|
||||
'format' => '',
|
||||
'size' => 0,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('emailIsDisposable'),
|
||||
'type' => Database::VAR_BOOLEAN,
|
||||
'format' => '',
|
||||
'size' => 0,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('emailIsCorporate'),
|
||||
'type' => Database::VAR_BOOLEAN,
|
||||
'format' => '',
|
||||
'size' => 0,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('emailIsCanonical'),
|
||||
'type' => Database::VAR_BOOLEAN,
|
||||
'format' => '',
|
||||
'size' => 0,
|
||||
'signed' => true,
|
||||
'required' => false,
|
||||
'default' => null,
|
||||
'array' => false,
|
||||
'filters' => [],
|
||||
],
|
||||
],
|
||||
'indexes' => [
|
||||
[
|
||||
@@ -1527,6 +1582,17 @@ return [
|
||||
'required' => true,
|
||||
'array' => false,
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('transformations'),
|
||||
'type' => Database::VAR_BOOLEAN,
|
||||
'signed' => true,
|
||||
'size' => 0,
|
||||
'format' => '',
|
||||
'filters' => [],
|
||||
'required' => false,
|
||||
'array' => false,
|
||||
'default' => true,
|
||||
],
|
||||
[
|
||||
'$id' => ID::custom('search'),
|
||||
'type' => Database::VAR_STRING,
|
||||
|
||||
@@ -2345,7 +2345,7 @@ return [
|
||||
'$id' => ID::custom('errors'),
|
||||
'type' => Database::VAR_STRING,
|
||||
'format' => '',
|
||||
'size' => 65535,
|
||||
'size' => 1_000_000,
|
||||
'signed' => true,
|
||||
'required' => true,
|
||||
'default' => null,
|
||||
|
||||
@@ -522,6 +522,11 @@ return [
|
||||
'description' => 'The requested file is not publicly readable.',
|
||||
'code' => 403,
|
||||
],
|
||||
Exception::STORAGE_BUCKET_TRANSFORMATIONS_DISABLED => [
|
||||
'name' => Exception::STORAGE_BUCKET_TRANSFORMATIONS_DISABLED,
|
||||
'description' => 'Image transformations are disabled for the requested bucket.',
|
||||
'code' => 403,
|
||||
],
|
||||
|
||||
/** Tokens */
|
||||
Exception::TOKEN_NOT_FOUND => [
|
||||
|
||||
@@ -273,7 +273,7 @@ return [
|
||||
'key' => 'flutter',
|
||||
'name' => 'Flutter',
|
||||
'screenshotSleep' => 5000,
|
||||
'buildRuntime' => 'flutter-3.29',
|
||||
'buildRuntime' => 'flutter-3.35',
|
||||
'runtimes' => getVersions($templateRuntimes['FLUTTER']['versions'], 'flutter'),
|
||||
'adapters' => [
|
||||
'static' => [
|
||||
@@ -282,6 +282,7 @@ return [
|
||||
'installCommand' => 'flutter pub get',
|
||||
'outputDirectory' => './build/web',
|
||||
'startCommand' => 'bash helpers/server.sh',
|
||||
'fallbackFile' => 'index.html'
|
||||
],
|
||||
],
|
||||
],
|
||||
|
||||
@@ -60,7 +60,7 @@ return [
|
||||
[
|
||||
'key' => 'flutter',
|
||||
'name' => 'Flutter',
|
||||
'version' => '20.3.0',
|
||||
'version' => '20.3.1',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-flutter',
|
||||
'package' => 'https://pub.dev/packages/appwrite',
|
||||
'enabled' => true,
|
||||
@@ -226,7 +226,7 @@ return [
|
||||
[
|
||||
'key' => 'cli',
|
||||
'name' => 'Command Line',
|
||||
'version' => '11.1.0',
|
||||
'version' => '11.1.1',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-cli',
|
||||
'package' => 'https://www.npmjs.com/package/appwrite-cli',
|
||||
'enabled' => true,
|
||||
@@ -281,7 +281,7 @@ return [
|
||||
[
|
||||
'key' => 'php',
|
||||
'name' => 'PHP',
|
||||
'version' => '17.5.0',
|
||||
'version' => '18.0.1',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-php',
|
||||
'package' => 'https://packagist.org/packages/appwrite/appwrite',
|
||||
'enabled' => true,
|
||||
@@ -376,7 +376,7 @@ return [
|
||||
[
|
||||
'key' => 'dart',
|
||||
'name' => 'Dart',
|
||||
'version' => '19.3.0',
|
||||
'version' => '19.4.0',
|
||||
'url' => 'https://github.com/appwrite/sdk-for-dart',
|
||||
'package' => 'https://pub.dev/packages/dart_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
@@ -3,4 +3,6 @@
|
||||
use Utopia\Image\Image;
|
||||
use Utopia\System\System;
|
||||
|
||||
Image::setResourceLimit('memory', intval(System::getEnv('_APP_IMAGES_RESOURCE_LIMIT_MEMORY', 1024*1024*64)));
|
||||
if (\class_exists('Imagick')) {
|
||||
Image::setResourceLimit('memory', intval(System::getEnv('_APP_IMAGES_RESOURCE_LIMIT_MEMORY', 1024*1024*64)));
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ return [
|
||||
],
|
||||
'DART' => [
|
||||
'name' => 'dart',
|
||||
'versions' => ['3.8', '3.5', '3.3', '3.1', '3.0', '2.19', '2.18', '2.17', '2.16']
|
||||
'versions' => ['3.9', '3.8', '3.5', '3.3', '3.1', '3.0', '2.19', '2.18', '2.17', '2.16']
|
||||
],
|
||||
'GO' => [
|
||||
'name' => 'go',
|
||||
@@ -38,6 +38,6 @@ return [
|
||||
],
|
||||
'FLUTTER' => [
|
||||
'name' => 'flutter',
|
||||
'versions' => ['3.32', '3.24']
|
||||
'versions' => ['3.35', '3.32', '3.24']
|
||||
],
|
||||
];
|
||||
|
||||
@@ -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 = [
|
||||
@@ -83,7 +84,7 @@ const TEMPLATE_FRAMEWORKS = [
|
||||
'installCommand' => '',
|
||||
'buildCommand' => 'flutter build web',
|
||||
'outputDirectory' => './build/web',
|
||||
'buildRuntime' => 'flutter-3.29',
|
||||
'buildRuntime' => 'flutter-3.35',
|
||||
'adapter' => 'static',
|
||||
'fallbackFile' => '',
|
||||
],
|
||||
@@ -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'
|
||||
],
|
||||
]
|
||||
],
|
||||
];
|
||||
|
||||
+163
-66
@@ -20,7 +20,7 @@ use Appwrite\Event\Messaging;
|
||||
use Appwrite\Event\StatsUsage;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Hooks\Hooks;
|
||||
use Appwrite\Network\Validator\Email;
|
||||
use Appwrite\Network\Validator\Email as EmailValidator;
|
||||
use Appwrite\Network\Validator\Redirect;
|
||||
use Appwrite\OpenSSL\OpenSSL;
|
||||
use Appwrite\SDK\AuthType;
|
||||
@@ -57,6 +57,7 @@ use Utopia\Database\Validator\Query\Cursor;
|
||||
use Utopia\Database\Validator\Query\Limit;
|
||||
use Utopia\Database\Validator\Query\Offset;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Emails\Email;
|
||||
use Utopia\Locale\Locale;
|
||||
use Utopia\Storage\Validator\FileName;
|
||||
use Utopia\System\System;
|
||||
@@ -191,10 +192,10 @@ function sendSessionAlert(Locale $locale, Document $user, Document $project, Doc
|
||||
;
|
||||
|
||||
|
||||
$createSession = function (string $userId, string $secret, Request $request, Response $response, Document $user, Database $dbForProject, Document $project, Locale $locale, Reader $geodb, Event $queueForEvents, Mail $queueForMails) {
|
||||
$createSession = function (string $userId, string $secret, Request $request, Response $response, Document $user, Database $dbForProject, Document $project, Locale $locale, Reader $geodb, Event $queueForEvents, Mail $queueForMails, Authorization $authorization) {
|
||||
|
||||
/** @var Utopia\Database\Document $user */
|
||||
$userFromRequest = Authorization::skip(fn () => $dbForProject->getDocument('users', $userId));
|
||||
$userFromRequest = $authorization->skip(fn () => $dbForProject->getDocument('users', $userId));
|
||||
|
||||
if ($userFromRequest->isEmpty()) {
|
||||
throw new Exception(Exception::USER_INVALID_TOKEN);
|
||||
@@ -240,7 +241,7 @@ $createSession = function (string $userId, string $secret, Request $request, Res
|
||||
$detector->getDevice()
|
||||
));
|
||||
|
||||
Authorization::setRole(Role::user($user->getId())->toString());
|
||||
$authorization->addRole(Role::user($user->getId())->toString());
|
||||
|
||||
$session = $dbForProject->createDocument('sessions', $session
|
||||
->setAttribute('$permissions', [
|
||||
@@ -249,7 +250,7 @@ $createSession = function (string $userId, string $secret, Request $request, Res
|
||||
Permission::delete(Role::user($user->getId())),
|
||||
]));
|
||||
|
||||
Authorization::skip(fn () => $dbForProject->deleteDocument('tokens', $verifiedToken->getId()));
|
||||
$authorization->skip(fn () => $dbForProject->deleteDocument('tokens', $verifiedToken->getId()));
|
||||
$dbForProject->purgeCachedDocument('users', $user->getId());
|
||||
|
||||
// Magic URL + Email OTP
|
||||
@@ -337,7 +338,7 @@ App::post('/v1/account')
|
||||
))
|
||||
->label('abuse-limit', 10)
|
||||
->param('userId', '', new CustomId(), '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.')
|
||||
->param('email', '', new Email(), 'User email.')
|
||||
->param('email', '', new EmailValidator(), 'User email.')
|
||||
->param('password', '', fn ($project, $passwordsDictionary) => new PasswordDictionary($passwordsDictionary, $project->getAttribute('auths', [])['passwordDictionary'] ?? false), 'New user password. Must be between 8 and 256 chars.', false, ['project', 'passwordsDictionary'])
|
||||
->param('name', '', new Text(128), 'User name. Max length: 128 chars.', true)
|
||||
->inject('request')
|
||||
@@ -345,8 +346,9 @@ App::post('/v1/account')
|
||||
->inject('user')
|
||||
->inject('project')
|
||||
->inject('dbForProject')
|
||||
->inject('authorization')
|
||||
->inject('hooks')
|
||||
->action(function (string $userId, string $email, string $password, string $name, Request $request, Response $response, Document $user, Document $project, Database $dbForProject, Hooks $hooks) {
|
||||
->action(function (string $userId, string $email, string $password, string $name, Request $request, Response $response, Document $user, Document $project, Database $dbForProject, Authorization $authorization, Hooks $hooks) {
|
||||
|
||||
$email = \strtolower($email);
|
||||
if ('console' === $project->getId()) {
|
||||
@@ -394,6 +396,13 @@ App::post('/v1/account')
|
||||
|
||||
$passwordHistory = $project->getAttribute('auths', [])['passwordHistory'] ?? 0;
|
||||
$password = Auth::passwordHash($password, Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS);
|
||||
|
||||
try {
|
||||
$emailCanonical = new Email($email);
|
||||
} catch (Throwable) {
|
||||
$emailCanonical = null;
|
||||
}
|
||||
|
||||
try {
|
||||
$userId = $userId == 'unique()' ? ID::unique() : $userId;
|
||||
$user->setAttributes([
|
||||
@@ -422,11 +431,17 @@ App::post('/v1/account')
|
||||
'authenticators' => null,
|
||||
'search' => implode(' ', [$userId, $email, $name]),
|
||||
'accessedAt' => DateTime::now(),
|
||||
'emailCanonical' => $emailCanonical?->getCanonical(),
|
||||
'emailIsCanonical' => $emailCanonical?->isCanonicalSupported(),
|
||||
'emailIsCorporate' => $emailCanonical?->isCorporate(),
|
||||
'emailIsDisposable' => $emailCanonical?->isDisposable(),
|
||||
'emailIsFree' => $emailCanonical?->isFree(),
|
||||
]);
|
||||
|
||||
$user->removeAttribute('$sequence');
|
||||
$user = Authorization::skip(fn () => $dbForProject->createDocument('users', $user));
|
||||
$user = $authorization->skip(fn () => $dbForProject->createDocument('users', $user));
|
||||
try {
|
||||
$target = Authorization::skip(fn () => $dbForProject->createDocument('targets', new Document([
|
||||
$target = $authorization->skip(fn () => $dbForProject->createDocument('targets', new Document([
|
||||
'$permissions' => [
|
||||
Permission::read(Role::user($user->getId())),
|
||||
Permission::update(Role::user($user->getId())),
|
||||
@@ -452,9 +467,9 @@ App::post('/v1/account')
|
||||
throw new Exception(Exception::USER_ALREADY_EXISTS);
|
||||
}
|
||||
|
||||
Authorization::unsetRole(Role::guests()->toString());
|
||||
Authorization::setRole(Role::user($user->getId())->toString());
|
||||
Authorization::setRole(Role::users()->toString());
|
||||
$authorization->removeRole(Role::guests()->toString());
|
||||
$authorization->addRole(Role::user($user->getId())->toString());
|
||||
$authorization->addRole(Role::users()->toString());
|
||||
|
||||
$response
|
||||
->setStatusCode(Response::STATUS_CODE_CREATED)
|
||||
@@ -903,7 +918,7 @@ App::post('/v1/account/sessions/email')
|
||||
))
|
||||
->label('abuse-limit', 10)
|
||||
->label('abuse-key', 'url:{url},email:{param-email}')
|
||||
->param('email', '', new Email(), 'User email.')
|
||||
->param('email', '', new EmailValidator(), 'User email.')
|
||||
->param('password', '', new Password(), 'User password. Must be at least 8 chars.')
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
@@ -915,7 +930,8 @@ App::post('/v1/account/sessions/email')
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForMails')
|
||||
->inject('hooks')
|
||||
->action(function (string $email, string $password, Request $request, Response $response, Document $user, Database $dbForProject, Document $project, Locale $locale, Reader $geodb, Event $queueForEvents, Mail $queueForMails, Hooks $hooks) {
|
||||
->inject('authorization')
|
||||
->action(function (string $email, string $password, Request $request, Response $response, Document $user, Database $dbForProject, Document $project, Locale $locale, Reader $geodb, Event $queueForEvents, Mail $queueForMails, Hooks $hooks, Authorization $authorization) {
|
||||
$email = \strtolower($email);
|
||||
$protocol = $request->getProtocol();
|
||||
|
||||
@@ -958,7 +974,7 @@ App::post('/v1/account/sessions/email')
|
||||
$detector->getDevice()
|
||||
));
|
||||
|
||||
Authorization::setRole(Role::user($user->getId())->toString());
|
||||
$authorization->addRole(Role::user($user->getId())->toString());
|
||||
|
||||
// Re-hash if not using recommended algo
|
||||
if ($user->getAttribute('hash') !== Auth::DEFAULT_ALGO) {
|
||||
@@ -1050,7 +1066,8 @@ App::post('/v1/account/sessions/anonymous')
|
||||
->inject('dbForProject')
|
||||
->inject('geodb')
|
||||
->inject('queueForEvents')
|
||||
->action(function (Request $request, Response $response, Locale $locale, Document $user, Document $project, Database $dbForProject, Reader $geodb, Event $queueForEvents) {
|
||||
->inject('authorization')
|
||||
->action(function (Request $request, Response $response, Locale $locale, Document $user, Document $project, Database $dbForProject, Reader $geodb, Event $queueForEvents, Authorization $authorization) {
|
||||
$protocol = $request->getProtocol();
|
||||
|
||||
if ('console' === $project->getId()) {
|
||||
@@ -1095,7 +1112,7 @@ App::post('/v1/account/sessions/anonymous')
|
||||
'accessedAt' => DateTime::now(),
|
||||
]);
|
||||
$user->removeAttribute('$sequence');
|
||||
Authorization::skip(fn () => $dbForProject->createDocument('users', $user));
|
||||
$user = $authorization->skip(fn () => $dbForProject->createDocument('users', $user));
|
||||
|
||||
// Create session token
|
||||
$duration = $project->getAttribute('auths', [])['duration'] ?? Auth::TOKEN_EXPIRATION_LOGIN_LONG;
|
||||
@@ -1121,7 +1138,7 @@ App::post('/v1/account/sessions/anonymous')
|
||||
$detector->getDevice()
|
||||
));
|
||||
|
||||
Authorization::setRole(Role::user($user->getId())->toString());
|
||||
$authorization->addRole(Role::user($user->getId())->toString());
|
||||
|
||||
$session = $dbForProject->createDocument('sessions', $session->setAttribute('$permissions', [
|
||||
Permission::read(Role::user($user->getId())),
|
||||
@@ -1194,6 +1211,7 @@ App::post('/v1/account/sessions/token')
|
||||
->inject('geodb')
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForMails')
|
||||
->inject('authorization')
|
||||
->action($createSession);
|
||||
|
||||
App::get('/v1/account/sessions/oauth2/:provider')
|
||||
@@ -1386,7 +1404,8 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
|
||||
->inject('dbForProject')
|
||||
->inject('geodb')
|
||||
->inject('queueForEvents')
|
||||
->action(function (string $provider, string $code, string $state, string $error, string $error_description, Request $request, Response $response, Document $project, array $platforms, Document $devKey, Document $user, Database $dbForProject, Reader $geodb, Event $queueForEvents) use ($oauthDefaultSuccess) {
|
||||
->inject('authorization')
|
||||
->action(function (string $provider, string $code, string $state, string $error, string $error_description, Request $request, Response $response, Document $project, array $platforms, Document $devKey, Document $user, Database $dbForProject, Reader $geodb, Event $queueForEvents, Authorization $authorization) use ($oauthDefaultSuccess) {
|
||||
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') === 'disabled' ? 'http' : 'https';
|
||||
$port = $request->getPort();
|
||||
$callbackBase = $protocol . '://' . $request->getHostname();
|
||||
@@ -1598,6 +1617,12 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
|
||||
$failureRedirect(Exception::GENERAL_BAD_REQUEST); /** Return a generic bad request to prevent exposing existing accounts */
|
||||
}
|
||||
|
||||
try {
|
||||
$emailCanonical = new Email($email);
|
||||
} catch (Throwable) {
|
||||
$emailCanonical = null;
|
||||
}
|
||||
|
||||
try {
|
||||
$userId = ID::unique();
|
||||
$user->setAttributes([
|
||||
@@ -1625,9 +1650,15 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
|
||||
'authenticators' => null,
|
||||
'search' => implode(' ', [$userId, $email, $name]),
|
||||
'accessedAt' => DateTime::now(),
|
||||
'emailCanonical' => $emailCanonical?->getCanonical(),
|
||||
'emailIsCanonical' => $emailCanonical?->isCanonicalSupported(),
|
||||
'emailIsCorporate' => $emailCanonical?->isCorporate(),
|
||||
'emailIsDisposable' => $emailCanonical?->isDisposable(),
|
||||
'emailIsFree' => $emailCanonical?->isFree(),
|
||||
]);
|
||||
|
||||
$user->removeAttribute('$sequence');
|
||||
$userDoc = Authorization::skip(fn () => $dbForProject->createDocument('users', $user));
|
||||
$userDoc = $authorization->skip(fn () => $dbForProject->createDocument('users', $user));
|
||||
$dbForProject->createDocument('targets', new Document([
|
||||
'$permissions' => [
|
||||
Permission::read(Role::user($user->getId())),
|
||||
@@ -1646,8 +1677,8 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
|
||||
}
|
||||
}
|
||||
|
||||
Authorization::setRole(Role::user($user->getId())->toString());
|
||||
Authorization::setRole(Role::users()->toString());
|
||||
$authorization->addRole(Role::user($user->getId())->toString());
|
||||
$authorization->addRole(Role::users()->toString());
|
||||
|
||||
if (false === $user->getAttribute('status')) { // Account is blocked
|
||||
$failureRedirect(Exception::USER_BLOCKED); // User is in status blocked
|
||||
@@ -1696,6 +1727,18 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
|
||||
|
||||
if (empty($user->getAttribute('email'))) {
|
||||
$user->setAttribute('email', $oauth2->getUserEmail($accessToken));
|
||||
|
||||
try {
|
||||
$emailCanonical = new Email($user->getAttribute('email'));
|
||||
} catch (Throwable) {
|
||||
$emailCanonical = null;
|
||||
}
|
||||
|
||||
$user->setAttribute('emailCanonical', $emailCanonical?->getCanonical());
|
||||
$user->setAttribute('emailIsCanonical', $emailCanonical?->isCanonicalSupported());
|
||||
$user->setAttribute('emailIsCorporate', $emailCanonical?->isCorporate());
|
||||
$user->setAttribute('emailIsDisposable', $emailCanonical?->isDisposable());
|
||||
$user->setAttribute('emailIsFree', $emailCanonical?->isFree());
|
||||
}
|
||||
|
||||
if (empty($user->getAttribute('name'))) {
|
||||
@@ -1706,7 +1749,7 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
|
||||
|
||||
$dbForProject->updateDocument('users', $user->getId(), $user);
|
||||
|
||||
Authorization::setRole(Role::user($user->getId())->toString());
|
||||
$authorization->addRole(Role::user($user->getId())->toString());
|
||||
|
||||
$state['success'] = URLParser::parse($state['success']);
|
||||
$query = URLParser::parseQuery($state['success']['query']);
|
||||
@@ -1728,7 +1771,7 @@ App::get('/v1/account/sessions/oauth2/:provider/redirect')
|
||||
'ip' => $request->getIP(),
|
||||
]);
|
||||
|
||||
Authorization::setRole(Role::user($user->getId())->toString());
|
||||
$authorization->addRole(Role::user($user->getId())->toString());
|
||||
|
||||
$token = $dbForProject->createDocument('tokens', $token
|
||||
->setAttribute('$permissions', [
|
||||
@@ -1944,7 +1987,7 @@ App::post('/v1/account/tokens/magic-url')
|
||||
->label('abuse-limit', 60)
|
||||
->label('abuse-key', ['url:{url},email:{param-email}', 'url:{url},ip:{ip}'])
|
||||
->param('userId', '', new CustomId(), 'Unique Id. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars. If the email address has never been used, a new account is created using the provided userId. Otherwise, if the email address is already attached to an account, the user ID is ignored.')
|
||||
->param('email', '', new Email(), 'User email.')
|
||||
->param('email', '', new EmailValidator(), 'User email.')
|
||||
->param('url', '', fn ($platforms, $devKey) => $devKey->isEmpty() ? new Redirect($platforms) : new URL(), 'URL to redirect the user back to your app from the magic URL login. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', true, ['platforms', 'devKey'])
|
||||
->param('phrase', false, new Boolean(), 'Toggle for security phrase. If enabled, email will be send with a randomly generated phrase and the phrase will also be included in the response. Confirming phrases match increases the security of your authentication flow.', true)
|
||||
->inject('request')
|
||||
@@ -1955,7 +1998,8 @@ App::post('/v1/account/tokens/magic-url')
|
||||
->inject('locale')
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForMails')
|
||||
->action(function (string $userId, string $email, string $url, bool $phrase, Request $request, Response $response, Document $user, Document $project, Database $dbForProject, Locale $locale, Event $queueForEvents, Mail $queueForMails) {
|
||||
->inject('authorization')
|
||||
->action(function (string $userId, string $email, string $url, bool $phrase, Request $request, Response $response, Document $user, Document $project, Database $dbForProject, Locale $locale, Event $queueForEvents, Mail $queueForMails, Authorization $authorization) {
|
||||
if (empty(System::getEnv('_APP_SMTP_HOST'))) {
|
||||
throw new Exception(Exception::GENERAL_SMTP_DISABLED, 'SMTP disabled');
|
||||
}
|
||||
@@ -1990,6 +2034,12 @@ App::post('/v1/account/tokens/magic-url')
|
||||
|
||||
$userId = $userId === 'unique()' ? ID::unique() : $userId;
|
||||
|
||||
try {
|
||||
$emailCanonical = new Email($email);
|
||||
} catch (Throwable) {
|
||||
$emailCanonical = null;
|
||||
}
|
||||
|
||||
$user->setAttributes([
|
||||
'$id' => $userId,
|
||||
'$permissions' => [
|
||||
@@ -2014,10 +2064,15 @@ App::post('/v1/account/tokens/magic-url')
|
||||
'authenticators' => null,
|
||||
'search' => implode(' ', [$userId, $email]),
|
||||
'accessedAt' => DateTime::now(),
|
||||
'emailCanonical' => $emailCanonical?->getCanonical(),
|
||||
'emailIsCanonical' => $emailCanonical?->isCanonicalSupported(),
|
||||
'emailIsCorporate' => $emailCanonical?->isCorporate(),
|
||||
'emailIsDisposable' => $emailCanonical?->isDisposable(),
|
||||
'emailIsFree' => $emailCanonical?->isFree(),
|
||||
]);
|
||||
|
||||
$user->removeAttribute('$sequence');
|
||||
Authorization::skip(fn () => $dbForProject->createDocument('users', $user));
|
||||
$user = $authorization->skip(fn () => $dbForProject->createDocument('users', $user));
|
||||
}
|
||||
|
||||
$tokenSecret = Auth::tokenGenerator(Auth::TOKEN_LENGTH_MAGIC_URL);
|
||||
@@ -2034,7 +2089,7 @@ App::post('/v1/account/tokens/magic-url')
|
||||
'ip' => $request->getIP(),
|
||||
]);
|
||||
|
||||
Authorization::setRole(Role::user($user->getId())->toString());
|
||||
$authorization->addRole(Role::user($user->getId())->toString());
|
||||
|
||||
$token = $dbForProject->createDocument('tokens', $token
|
||||
->setAttribute('$permissions', [
|
||||
@@ -2197,7 +2252,7 @@ App::post('/v1/account/tokens/email')
|
||||
->label('abuse-limit', 10)
|
||||
->label('abuse-key', ['url:{url},email:{param-email}', 'url:{url},ip:{ip}'])
|
||||
->param('userId', '', new CustomId(), '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. If the email address has never been used, a new account is created using the provided userId. Otherwise, if the email address is already attached to an account, the user ID is ignored.')
|
||||
->param('email', '', new Email(), 'User email.')
|
||||
->param('email', '', new EmailValidator(), 'User email.')
|
||||
->param('phrase', false, new Boolean(), 'Toggle for security phrase. If enabled, email will be send with a randomly generated phrase and the phrase will also be included in the response. Confirming phrases match increases the security of your authentication flow.', true)
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
@@ -2207,7 +2262,8 @@ App::post('/v1/account/tokens/email')
|
||||
->inject('locale')
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForMails')
|
||||
->action(function (string $userId, string $email, bool $phrase, Request $request, Response $response, Document $user, Document $project, Database $dbForProject, Locale $locale, Event $queueForEvents, Mail $queueForMails) {
|
||||
->inject('authorization')
|
||||
->action(function (string $userId, string $email, bool $phrase, Request $request, Response $response, Document $user, Document $project, Database $dbForProject, Locale $locale, Event $queueForEvents, Mail $queueForMails, Authorization $authorization) {
|
||||
if (empty(System::getEnv('_APP_SMTP_HOST'))) {
|
||||
throw new Exception(Exception::GENERAL_SMTP_DISABLED, 'SMTP disabled');
|
||||
}
|
||||
@@ -2240,6 +2296,12 @@ App::post('/v1/account/tokens/email')
|
||||
|
||||
$userId = $userId === 'unique()' ? ID::unique() : $userId;
|
||||
|
||||
try {
|
||||
$emailCanonical = new Email($email);
|
||||
} catch (Throwable) {
|
||||
$emailCanonical = null;
|
||||
}
|
||||
|
||||
$user->setAttributes([
|
||||
'$id' => $userId,
|
||||
'$permissions' => [
|
||||
@@ -2262,12 +2324,17 @@ App::post('/v1/account/tokens/email')
|
||||
'memberships' => null,
|
||||
'search' => implode(' ', [$userId, $email]),
|
||||
'accessedAt' => DateTime::now(),
|
||||
'emailCanonical' => $emailCanonical?->getCanonical(),
|
||||
'emailIsCanonical' => $emailCanonical?->isCanonicalSupported(),
|
||||
'emailIsCorporate' => $emailCanonical?->isCorporate(),
|
||||
'emailIsDisposable' => $emailCanonical?->isDisposable(),
|
||||
'emailIsFree' => $emailCanonical?->isFree(),
|
||||
]);
|
||||
|
||||
$user->removeAttribute('$sequence');
|
||||
$user = Authorization::skip(fn () => $dbForProject->createDocument('users', $user));
|
||||
$user = $authorization->skip(fn () => $dbForProject->createDocument('users', $user));
|
||||
try {
|
||||
$target = Authorization::skip(fn () => $dbForProject->createDocument('targets', new Document([
|
||||
$target = $authorization->skip(fn () => $dbForProject->createDocument('targets', new Document([
|
||||
'$permissions' => [
|
||||
Permission::read(Role::user($user->getId())),
|
||||
Permission::update(Role::user($user->getId())),
|
||||
@@ -2305,7 +2372,7 @@ App::post('/v1/account/tokens/email')
|
||||
'ip' => $request->getIP(),
|
||||
]);
|
||||
|
||||
Authorization::setRole(Role::user($user->getId())->toString());
|
||||
$authorization->addRole(Role::user($user->getId())->toString());
|
||||
|
||||
$token = $dbForProject->createDocument('tokens', $token
|
||||
->setAttribute('$permissions', [
|
||||
@@ -2486,6 +2553,7 @@ App::put('/v1/account/sessions/magic-url')
|
||||
->inject('geodb')
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForMails')
|
||||
->inject('authorization')
|
||||
->action($createSession);
|
||||
|
||||
App::put('/v1/account/sessions/phone')
|
||||
@@ -2527,6 +2595,7 @@ App::put('/v1/account/sessions/phone')
|
||||
->inject('geodb')
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForMails')
|
||||
->inject('authorization')
|
||||
->action($createSession);
|
||||
|
||||
App::post('/v1/account/tokens/phone')
|
||||
@@ -2567,7 +2636,8 @@ App::post('/v1/account/tokens/phone')
|
||||
->inject('timelimit')
|
||||
->inject('queueForStatsUsage')
|
||||
->inject('plan')
|
||||
->action(function (string $userId, string $phone, Request $request, Response $response, Document $user, Document $project, Database $dbForProject, Event $queueForEvents, Messaging $queueForMessaging, Locale $locale, callable $timelimit, StatsUsage $queueForStatsUsage, array $plan) {
|
||||
->inject('authorization')
|
||||
->action(function (string $userId, string $phone, Request $request, Response $response, Document $user, Document $project, Database $dbForProject, Event $queueForEvents, Messaging $queueForMessaging, Locale $locale, callable $timelimit, StatsUsage $queueForStatsUsage, array $plan, Authorization $authorization) {
|
||||
if (empty(System::getEnv('_APP_SMS_PROVIDER'))) {
|
||||
throw new Exception(Exception::GENERAL_PHONE_DISABLED, 'Phone provider not configured');
|
||||
}
|
||||
@@ -2609,12 +2679,17 @@ App::post('/v1/account/tokens/phone')
|
||||
'memberships' => null,
|
||||
'search' => implode(' ', [$userId, $phone]),
|
||||
'accessedAt' => DateTime::now(),
|
||||
'emailCanonical' => null,
|
||||
'emailIsCanonical' => null,
|
||||
'emailIsCorporate' => null,
|
||||
'emailIsDisposable' => null,
|
||||
'emailIsFree' => null,
|
||||
]);
|
||||
|
||||
$user->removeAttribute('$sequence');
|
||||
Authorization::skip(fn () => $dbForProject->createDocument('users', $user));
|
||||
$user = $authorization->skip(fn () => $dbForProject->createDocument('users', $user));
|
||||
try {
|
||||
$target = Authorization::skip(fn () => $dbForProject->createDocument('targets', new Document([
|
||||
$target = $authorization->skip(fn () => $dbForProject->createDocument('targets', new Document([
|
||||
'$permissions' => [
|
||||
Permission::read(Role::user($user->getId())),
|
||||
Permission::update(Role::user($user->getId())),
|
||||
@@ -2660,7 +2735,7 @@ App::post('/v1/account/tokens/phone')
|
||||
'ip' => $request->getIP(),
|
||||
]);
|
||||
|
||||
Authorization::setRole(Role::user($user->getId())->toString());
|
||||
$authorization->addRole(Role::user($user->getId())->toString());
|
||||
|
||||
$token = $dbForProject->createDocument('tokens', $token
|
||||
->setAttribute('$permissions', [
|
||||
@@ -3037,7 +3112,7 @@ App::patch('/v1/account/email')
|
||||
],
|
||||
contentType: ContentType::JSON
|
||||
))
|
||||
->param('email', '', new Email(), 'User email.')
|
||||
->param('email', '', new EmailValidator(), 'User email.')
|
||||
->param('password', '', new Password(), 'User password. Must be at least 8 chars.')
|
||||
->inject('requestTimestamp')
|
||||
->inject('response')
|
||||
@@ -3046,7 +3121,8 @@ App::patch('/v1/account/email')
|
||||
->inject('queueForEvents')
|
||||
->inject('project')
|
||||
->inject('hooks')
|
||||
->action(function (string $email, string $password, ?\DateTime $requestTimestamp, Response $response, Document $user, Database $dbForProject, Event $queueForEvents, Document $project, Hooks $hooks) {
|
||||
->inject('authorization')
|
||||
->action(function (string $email, string $password, ?\DateTime $requestTimestamp, Response $response, Document $user, Database $dbForProject, Event $queueForEvents, Document $project, Hooks $hooks, Authorization $authorization) {
|
||||
// passwordUpdate will be empty if the user has never set a password
|
||||
$passwordUpdate = $user->getAttribute('passwordUpdate');
|
||||
|
||||
@@ -3072,9 +3148,20 @@ App::patch('/v1/account/email')
|
||||
throw new Exception(Exception::GENERAL_BAD_REQUEST); /** Return a generic bad request to prevent exposing existing accounts */
|
||||
}
|
||||
|
||||
try {
|
||||
$emailCanonical = new Email($email);
|
||||
} catch (Throwable) {
|
||||
$emailCanonical = null;
|
||||
}
|
||||
|
||||
$user
|
||||
->setAttribute('email', $email)
|
||||
->setAttribute('emailVerification', false) // After this user needs to confirm mail again
|
||||
->setAttribute('emailCanonical', $emailCanonical?->getCanonical())
|
||||
->setAttribute('emailIsCanonical', $emailCanonical?->isCanonicalSupported())
|
||||
->setAttribute('emailIsCorporate', $emailCanonical?->isCorporate())
|
||||
->setAttribute('emailIsDisposable', $emailCanonical?->isDisposable())
|
||||
->setAttribute('emailIsFree', $emailCanonical?->isFree())
|
||||
;
|
||||
|
||||
if (empty($passwordUpdate)) {
|
||||
@@ -3085,7 +3172,7 @@ App::patch('/v1/account/email')
|
||||
->setAttribute('passwordUpdate', DateTime::now());
|
||||
}
|
||||
|
||||
$target = Authorization::skip(fn () => $dbForProject->findOne('targets', [
|
||||
$target = $authorization->skip(fn () => $dbForProject->findOne('targets', [
|
||||
Query::equal('identifier', [$email]),
|
||||
]));
|
||||
|
||||
@@ -3101,7 +3188,7 @@ App::patch('/v1/account/email')
|
||||
$oldTarget = $user->find('identifier', $oldEmail, 'targets');
|
||||
|
||||
if ($oldTarget instanceof Document && !$oldTarget->isEmpty()) {
|
||||
Authorization::skip(fn () => $dbForProject->updateDocument('targets', $oldTarget->getId(), $oldTarget->setAttribute('identifier', $email)));
|
||||
$authorization->skip(fn () => $dbForProject->updateDocument('targets', $oldTarget->getId(), $oldTarget->setAttribute('identifier', $email)));
|
||||
}
|
||||
$dbForProject->purgeCachedDocument('users', $user->getId());
|
||||
} catch (Duplicate) {
|
||||
@@ -3143,7 +3230,8 @@ App::patch('/v1/account/phone')
|
||||
->inject('queueForEvents')
|
||||
->inject('project')
|
||||
->inject('hooks')
|
||||
->action(function (string $phone, string $password, ?\DateTime $requestTimestamp, Response $response, Document $user, Database $dbForProject, Event $queueForEvents, Document $project, Hooks $hooks) {
|
||||
->inject('authorization')
|
||||
->action(function (string $phone, string $password, ?\DateTime $requestTimestamp, Response $response, Document $user, Database $dbForProject, Event $queueForEvents, Document $project, Hooks $hooks, Authorization $authorization) {
|
||||
// passwordUpdate will be empty if the user has never set a password
|
||||
$passwordUpdate = $user->getAttribute('passwordUpdate');
|
||||
|
||||
@@ -3156,7 +3244,7 @@ App::patch('/v1/account/phone')
|
||||
|
||||
$hooks->trigger('passwordValidator', [$dbForProject, $project, $password, &$user, false]);
|
||||
|
||||
$target = Authorization::skip(fn () => $dbForProject->findOne('targets', [
|
||||
$target = $authorization->skip(fn () => $dbForProject->findOne('targets', [
|
||||
Query::equal('identifier', [$phone]),
|
||||
]));
|
||||
|
||||
@@ -3187,7 +3275,7 @@ App::patch('/v1/account/phone')
|
||||
$oldTarget = $user->find('identifier', $oldPhone, 'targets');
|
||||
|
||||
if ($oldTarget instanceof Document && !$oldTarget->isEmpty()) {
|
||||
Authorization::skip(fn () => $dbForProject->updateDocument('targets', $oldTarget->getId(), $oldTarget->setAttribute('identifier', $phone)));
|
||||
$authorization->skip(fn () => $dbForProject->updateDocument('targets', $oldTarget->getId(), $oldTarget->setAttribute('identifier', $phone)));
|
||||
}
|
||||
$dbForProject->purgeCachedDocument('users', $user->getId());
|
||||
} catch (Duplicate $th) {
|
||||
@@ -3311,7 +3399,7 @@ App::post('/v1/account/recovery')
|
||||
))
|
||||
->label('abuse-limit', 10)
|
||||
->label('abuse-key', ['url:{url},email:{param-email}', 'url:{url},ip:{ip}'])
|
||||
->param('email', '', new Email(), 'User email.')
|
||||
->param('email', '', new EmailValidator(), 'User email.')
|
||||
->param('url', '', fn ($platforms, $devKey) => $devKey->isEmpty() ? new Redirect($platforms) : new URL(), 'URL to redirect the user back to your app from the recovery email. Only URLs from hostnames in your project platform list are allowed. This requirement helps to prevent an [open redirect](https://cheatsheetseries.owasp.org/cheatsheets/Unvalidated_Redirects_and_Forwards_Cheat_Sheet.html) attack against your project API.', false, ['platforms', 'devKey'])
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
@@ -3321,7 +3409,8 @@ App::post('/v1/account/recovery')
|
||||
->inject('locale')
|
||||
->inject('queueForMails')
|
||||
->inject('queueForEvents')
|
||||
->action(function (string $email, string $url, Request $request, Response $response, Document $user, Database $dbForProject, Document $project, Locale $locale, Mail $queueForMails, Event $queueForEvents) {
|
||||
->inject('authorization')
|
||||
->action(function (string $email, string $url, Request $request, Response $response, Document $user, Database $dbForProject, Document $project, Locale $locale, Mail $queueForMails, Event $queueForEvents, Authorization $authorization) {
|
||||
|
||||
if (empty(System::getEnv('_APP_SMTP_HOST'))) {
|
||||
throw new Exception(Exception::GENERAL_SMTP_DISABLED, 'SMTP Disabled');
|
||||
@@ -3357,7 +3446,7 @@ App::post('/v1/account/recovery')
|
||||
'ip' => $request->getIP(),
|
||||
]);
|
||||
|
||||
Authorization::setRole(Role::user($profile->getId())->toString());
|
||||
$authorization->addRole(Role::user($profile->getId())->toString());
|
||||
|
||||
$recovery = $dbForProject->createDocument('tokens', $recovery
|
||||
->setAttribute('$permissions', [
|
||||
@@ -3499,7 +3588,8 @@ App::put('/v1/account/recovery')
|
||||
->inject('project')
|
||||
->inject('queueForEvents')
|
||||
->inject('hooks')
|
||||
->action(function (string $userId, string $secret, string $password, Response $response, Document $user, Database $dbForProject, Document $project, Event $queueForEvents, Hooks $hooks) {
|
||||
->inject('authorization')
|
||||
->action(function (string $userId, string $secret, string $password, Response $response, Document $user, Database $dbForProject, Document $project, Event $queueForEvents, Hooks $hooks, Authorization $authorization) {
|
||||
$profile = $dbForProject->getDocument('users', $userId);
|
||||
|
||||
if ($profile->isEmpty()) {
|
||||
@@ -3513,7 +3603,7 @@ App::put('/v1/account/recovery')
|
||||
throw new Exception(Exception::USER_INVALID_TOKEN);
|
||||
}
|
||||
|
||||
Authorization::setRole(Role::user($profile->getId())->toString());
|
||||
$authorization->addRole(Role::user($profile->getId())->toString());
|
||||
|
||||
$newPassword = Auth::passwordHash($password, Auth::DEFAULT_ALGO, Auth::DEFAULT_ALGO_OPTIONS);
|
||||
|
||||
@@ -3611,7 +3701,8 @@ App::post('/v1/account/verifications/email')
|
||||
->inject('locale')
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForMails')
|
||||
->action(function (string $url, Request $request, Response $response, Document $project, Document $user, Database $dbForProject, Locale $locale, Event $queueForEvents, Mail $queueForMails) {
|
||||
->inject('authorization')
|
||||
->action(function (string $url, Request $request, Response $response, Document $project, Document $user, Database $dbForProject, Locale $locale, Event $queueForEvents, Mail $queueForMails, Authorization $authorization) {
|
||||
|
||||
if (empty(System::getEnv('_APP_SMTP_HOST'))) {
|
||||
throw new Exception(Exception::GENERAL_SMTP_DISABLED, 'SMTP Disabled');
|
||||
@@ -3640,7 +3731,7 @@ App::post('/v1/account/verifications/email')
|
||||
'ip' => $request->getIP(),
|
||||
]);
|
||||
|
||||
Authorization::setRole(Role::user($user->getId())->toString());
|
||||
$authorization->addRole(Role::user($user->getId())->toString());
|
||||
|
||||
$verification = $dbForProject->createDocument('tokens', $verification
|
||||
->setAttribute('$permissions', [
|
||||
@@ -3823,9 +3914,10 @@ App::put('/v1/account/verifications/email')
|
||||
->inject('user')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForEvents')
|
||||
->action(function (string $userId, string $secret, Response $response, Document $user, Database $dbForProject, Event $queueForEvents) {
|
||||
->inject('authorization')
|
||||
->action(function (string $userId, string $secret, Response $response, Document $user, Database $dbForProject, Event $queueForEvents, Authorization $authorization) {
|
||||
|
||||
$profile = Authorization::skip(fn () => $dbForProject->getDocument('users', $userId));
|
||||
$profile = $authorization->skip(fn () => $dbForProject->getDocument('users', $userId));
|
||||
|
||||
if ($profile->isEmpty()) {
|
||||
throw new Exception(Exception::USER_NOT_FOUND);
|
||||
@@ -3838,7 +3930,7 @@ App::put('/v1/account/verifications/email')
|
||||
throw new Exception(Exception::USER_INVALID_TOKEN);
|
||||
}
|
||||
|
||||
Authorization::setRole(Role::user($profile->getId())->toString());
|
||||
$authorization->addRole(Role::user($profile->getId())->toString());
|
||||
|
||||
$profile = $dbForProject->updateDocument('users', $profile->getId(), $profile->setAttribute('emailVerification', true));
|
||||
|
||||
@@ -3897,7 +3989,8 @@ App::post('/v1/account/verifications/phone')
|
||||
->inject('timelimit')
|
||||
->inject('queueForStatsUsage')
|
||||
->inject('plan')
|
||||
->action(function (Request $request, Response $response, Document $user, Database $dbForProject, Event $queueForEvents, Messaging $queueForMessaging, Document $project, Locale $locale, callable $timelimit, StatsUsage $queueForStatsUsage, array $plan) {
|
||||
->inject('authorization')
|
||||
->action(function (Request $request, Response $response, Document $user, Database $dbForProject, Event $queueForEvents, Messaging $queueForMessaging, Document $project, Locale $locale, callable $timelimit, StatsUsage $queueForStatsUsage, array $plan, Authorization $authorization) {
|
||||
if (empty(System::getEnv('_APP_SMS_PROVIDER'))) {
|
||||
throw new Exception(Exception::GENERAL_PHONE_DISABLED, 'Phone provider not configured');
|
||||
}
|
||||
@@ -3936,7 +4029,7 @@ App::post('/v1/account/verifications/phone')
|
||||
'ip' => $request->getIP(),
|
||||
]);
|
||||
|
||||
Authorization::setRole(Role::user($user->getId())->toString());
|
||||
$authorization->addRole(Role::user($user->getId())->toString());
|
||||
|
||||
$verification = $dbForProject->createDocument('tokens', $verification
|
||||
->setAttribute('$permissions', [
|
||||
@@ -4041,9 +4134,10 @@ App::put('/v1/account/verifications/phone')
|
||||
->inject('user')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForEvents')
|
||||
->action(function (string $userId, string $secret, Response $response, Document $user, Database $dbForProject, Event $queueForEvents) {
|
||||
->inject('authorization')
|
||||
->action(function (string $userId, string $secret, Response $response, Document $user, Database $dbForProject, Event $queueForEvents, Authorization $authorization) {
|
||||
|
||||
$profile = Authorization::skip(fn () => $dbForProject->getDocument('users', $userId));
|
||||
$profile = $authorization->skip(fn () => $dbForProject->getDocument('users', $userId));
|
||||
|
||||
if ($profile->isEmpty()) {
|
||||
throw new Exception(Exception::USER_NOT_FOUND);
|
||||
@@ -4055,7 +4149,7 @@ App::put('/v1/account/verifications/phone')
|
||||
throw new Exception(Exception::USER_INVALID_TOKEN);
|
||||
}
|
||||
|
||||
Authorization::setRole(Role::user($profile->getId())->toString());
|
||||
$authorization->addRole(Role::user($profile->getId())->toString());
|
||||
|
||||
$profile = $dbForProject->updateDocument('users', $profile->getId(), $profile->setAttribute('phoneVerification', true));
|
||||
|
||||
@@ -5043,12 +5137,13 @@ App::post('/v1/account/targets/push')
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->action(function (string $targetId, string $identifier, string $providerId, Event $queueForEvents, Document $user, Request $request, Response $response, Database $dbForProject) {
|
||||
->inject('authorization')
|
||||
->action(function (string $targetId, string $identifier, string $providerId, Event $queueForEvents, Document $user, Request $request, Response $response, Database $dbForProject, Authorization $authorization) {
|
||||
$targetId = $targetId == 'unique()' ? ID::unique() : $targetId;
|
||||
|
||||
$provider = Authorization::skip(fn () => $dbForProject->getDocument('providers', $providerId));
|
||||
$provider = $authorization->skip(fn () => $dbForProject->getDocument('providers', $providerId));
|
||||
|
||||
$target = Authorization::skip(fn () => $dbForProject->getDocument('targets', $targetId));
|
||||
$target = $authorization->skip(fn () => $dbForProject->getDocument('targets', $targetId));
|
||||
|
||||
if (!$target->isEmpty()) {
|
||||
throw new Exception(Exception::USER_TARGET_ALREADY_EXISTS);
|
||||
@@ -5123,9 +5218,10 @@ App::put('/v1/account/targets/:targetId/push')
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->action(function (string $targetId, string $identifier, Event $queueForEvents, Document $user, Request $request, Response $response, Database $dbForProject) {
|
||||
->inject('authorization')
|
||||
->action(function (string $targetId, string $identifier, Event $queueForEvents, Document $user, Request $request, Response $response, Database $dbForProject, Authorization $authorization) {
|
||||
|
||||
$target = Authorization::skip(fn () => $dbForProject->getDocument('targets', $targetId));
|
||||
$target = $authorization->skip(fn () => $dbForProject->getDocument('targets', $targetId));
|
||||
|
||||
if ($target->isEmpty()) {
|
||||
throw new Exception(Exception::USER_TARGET_NOT_FOUND);
|
||||
@@ -5188,8 +5284,9 @@ App::delete('/v1/account/targets/:targetId/push')
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->action(function (string $targetId, Event $queueForEvents, Delete $queueForDeletes, Document $user, Request $request, Response $response, Database $dbForProject) {
|
||||
$target = Authorization::skip(fn () => $dbForProject->getDocument('targets', $targetId));
|
||||
->inject('authorization')
|
||||
->action(function (string $targetId, Event $queueForEvents, Delete $queueForDeletes, Document $user, Request $request, Response $response, Database $dbForProject, Authorization $authorization) {
|
||||
$target = $authorization->skip(fn () => $dbForProject->getDocument('targets', $targetId));
|
||||
|
||||
if ($target->isEmpty()) {
|
||||
throw new Exception(Exception::USER_TARGET_NOT_FOUND);
|
||||
|
||||
@@ -70,9 +70,9 @@ $avatarCallback = function (string $type, string $code, int $width, int $height,
|
||||
unset($image);
|
||||
};
|
||||
|
||||
$getUserGitHub = function (string $userId, Document $project, Database $dbForProject, Database $dbForPlatform, ?Logger $logger) {
|
||||
$getUserGitHub = function (string $userId, Document $project, Database $dbForProject, Database $dbForPlatform, Authorization $authorization, ?Logger $logger) {
|
||||
try {
|
||||
$user = Authorization::skip(fn () => $dbForPlatform->getDocument('users', $userId));
|
||||
$user = $authorization->skip(fn () => $dbForPlatform->getDocument('users', $userId));
|
||||
|
||||
$sessions = $user->getAttribute('sessions', []);
|
||||
|
||||
@@ -123,7 +123,7 @@ $getUserGitHub = function (string $userId, Document $project, Database $dbForPro
|
||||
->setAttribute('providerRefreshToken', $refreshToken)
|
||||
->setAttribute('providerAccessTokenExpiry', DateTime::addSeconds(new \DateTime(), (int)$oauth2->getAccessTokenExpiry('')));
|
||||
|
||||
Authorization::skip(fn () => $dbForProject->updateDocument('sessions', $gitHubSession->getId(), $gitHubSession));
|
||||
$authorization->skip(fn () => $dbForProject->updateDocument('sessions', $gitHubSession->getId(), $gitHubSession));
|
||||
|
||||
$dbForProject->purgeCachedDocument('users', $user->getId());
|
||||
} catch (Throwable $err) {
|
||||
@@ -131,7 +131,7 @@ $getUserGitHub = function (string $userId, Document $project, Database $dbForPro
|
||||
do {
|
||||
$previousAccessToken = $gitHubSession->getAttribute('providerAccessToken');
|
||||
|
||||
$user = Authorization::skip(fn () => $dbForPlatform->getDocument('users', $userId));
|
||||
$user = $authorization->skip(fn () => $dbForPlatform->getDocument('users', $userId));
|
||||
$sessions = $user->getAttribute('sessions', []);
|
||||
|
||||
$gitHubSession = new Document();
|
||||
@@ -841,8 +841,9 @@ App::get('/v1/cards/cloud')
|
||||
->inject('contributors')
|
||||
->inject('employees')
|
||||
->inject('logger')
|
||||
->action(function (string $userId, string $mock, int $width, int $height, Document $user, Document $project, Database $dbForProject, Database $dbForPlatform, Response $response, array $heroes, array $contributors, array $employees, ?Logger $logger) use ($getUserGitHub) {
|
||||
$user = Authorization::skip(fn () => $dbForPlatform->getDocument('users', $userId));
|
||||
->inject('authorization')
|
||||
->action(function (string $userId, string $mock, int $width, int $height, Document $user, Document $project, Database $dbForProject, Database $dbForPlatform, Response $response, array $heroes, array $contributors, array $employees, ?Logger $logger, Authorization $authorization) use ($getUserGitHub) {
|
||||
$user = $authorization->skip(fn () => $dbForPlatform->getDocument('users', $userId));
|
||||
|
||||
if ($user->isEmpty() && empty($mock)) {
|
||||
throw new Exception(Exception::USER_NOT_FOUND);
|
||||
@@ -853,7 +854,7 @@ App::get('/v1/cards/cloud')
|
||||
$email = $user->getAttribute('email', '');
|
||||
$createdAt = new \DateTime($user->getCreatedAt());
|
||||
|
||||
$gitHub = $getUserGitHub($user->getId(), $project, $dbForProject, $dbForPlatform, $logger);
|
||||
$gitHub = $getUserGitHub($user->getId(), $project, $dbForProject, $dbForPlatform, $logger, $authorization);
|
||||
$githubName = $gitHub['name'] ?? '';
|
||||
$githubId = $gitHub['id'] ?? '';
|
||||
|
||||
@@ -1048,8 +1049,9 @@ App::get('/v1/cards/cloud-back')
|
||||
->inject('contributors')
|
||||
->inject('employees')
|
||||
->inject('logger')
|
||||
->action(function (string $userId, string $mock, int $width, int $height, Document $user, Document $project, Database $dbForProject, Database $dbForPlatform, Response $response, array $heroes, array $contributors, array $employees, ?Logger $logger) use ($getUserGitHub) {
|
||||
$user = Authorization::skip(fn () => $dbForPlatform->getDocument('users', $userId));
|
||||
->inject('authorization')
|
||||
->action(function (string $userId, string $mock, int $width, int $height, Document $user, Document $project, Database $dbForProject, Database $dbForPlatform, Response $response, array $heroes, array $contributors, array $employees, ?Logger $logger, Authorization $authorization) use ($getUserGitHub) {
|
||||
$user = $authorization->skip(fn () => $dbForPlatform->getDocument('users', $userId));
|
||||
|
||||
if ($user->isEmpty() && empty($mock)) {
|
||||
throw new Exception(Exception::USER_NOT_FOUND);
|
||||
@@ -1059,7 +1061,7 @@ App::get('/v1/cards/cloud-back')
|
||||
$userId = $user->getId();
|
||||
$email = $user->getAttribute('email', '');
|
||||
|
||||
$gitHub = $getUserGitHub($user->getId(), $project, $dbForProject, $dbForPlatform, $logger);
|
||||
$gitHub = $getUserGitHub($user->getId(), $project, $dbForProject, $dbForPlatform, $logger, $authorization);
|
||||
$githubId = $gitHub['id'] ?? '';
|
||||
|
||||
$isHero = \array_key_exists($email, $heroes);
|
||||
@@ -1126,8 +1128,9 @@ App::get('/v1/cards/cloud-og')
|
||||
->inject('contributors')
|
||||
->inject('employees')
|
||||
->inject('logger')
|
||||
->action(function (string $userId, string $mock, int $width, int $height, Document $user, Document $project, Database $dbForProject, Database $dbForPlatform, Response $response, array $heroes, array $contributors, array $employees, ?Logger $logger) use ($getUserGitHub) {
|
||||
$user = Authorization::skip(fn () => $dbForPlatform->getDocument('users', $userId));
|
||||
->inject('authorization')
|
||||
->action(function (string $userId, string $mock, int $width, int $height, Document $user, Document $project, Database $dbForProject, Database $dbForPlatform, Response $response, array $heroes, array $contributors, array $employees, ?Logger $logger, Authorization $authorization) use ($getUserGitHub) {
|
||||
$user = $authorization->skip(fn () => $dbForPlatform->getDocument('users', $userId));
|
||||
|
||||
if ($user->isEmpty() && empty($mock)) {
|
||||
throw new Exception(Exception::USER_NOT_FOUND);
|
||||
@@ -1142,7 +1145,7 @@ App::get('/v1/cards/cloud-og')
|
||||
$email = $user->getAttribute('email', '');
|
||||
$createdAt = new \DateTime($user->getCreatedAt());
|
||||
|
||||
$gitHub = $getUserGitHub($user->getId(), $project, $dbForProject, $dbForPlatform, $logger);
|
||||
$gitHub = $getUserGitHub($user->getId(), $project, $dbForProject, $dbForPlatform, $logger, $authorization);
|
||||
$githubName = $gitHub['name'] ?? '';
|
||||
$githubId = $gitHub['id'] ?? '';
|
||||
|
||||
|
||||
@@ -28,11 +28,12 @@ use Utopia\Validator\Text;
|
||||
App::init()
|
||||
->groups(['graphql'])
|
||||
->inject('project')
|
||||
->action(function (Document $project) {
|
||||
->inject('authorization')
|
||||
->action(function (Document $project, Authorization $authorization) {
|
||||
if (
|
||||
array_key_exists('graphql', $project->getAttribute('apis', []))
|
||||
&& !$project->getAttribute('apis', [])['graphql']
|
||||
&& !(Auth::isPrivilegedUser(Authorization::getRoles()) || Auth::isAppUser(Authorization::getRoles()))
|
||||
&& !(Auth::isPrivilegedUser($authorization->getRoles()) || Auth::isAppUser($authorization->getRoles()))
|
||||
) {
|
||||
throw new AppwriteException(AppwriteException::GENERAL_API_DISABLED);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
|
||||
+106
-100
@@ -36,6 +36,7 @@ use Utopia\Database\Exception\Query as QueryException;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\Authorization\Input;
|
||||
use Utopia\Database\Validator\Datetime as DatetimeValidator;
|
||||
use Utopia\Database\Validator\Queries;
|
||||
use Utopia\Database\Validator\Query\Cursor;
|
||||
@@ -49,6 +50,7 @@ use Utopia\Validator\ArrayList;
|
||||
use Utopia\Validator\Boolean;
|
||||
use Utopia\Validator\Integer;
|
||||
use Utopia\Validator\JSON;
|
||||
use Utopia\Validator\Nullable;
|
||||
use Utopia\Validator\Range;
|
||||
use Utopia\Validator\Text;
|
||||
use Utopia\Validator\WhiteList;
|
||||
@@ -80,12 +82,12 @@ App::post('/v1/messaging/providers/mailgun')
|
||||
->param('name', '', new Text(128), 'Provider name.')
|
||||
->param('apiKey', '', new Text(0), 'Mailgun API Key.', true)
|
||||
->param('domain', '', new Text(0), 'Mailgun Domain.', true)
|
||||
->param('isEuRegion', null, new Boolean(), 'Set as EU region.', true)
|
||||
->param('isEuRegion', null, new Nullable(new Boolean()), 'Set as EU region.', true)
|
||||
->param('fromName', '', new Text(128, 0), 'Sender Name.', true)
|
||||
->param('fromEmail', '', new Email(), 'Sender email address.', true)
|
||||
->param('replyToName', '', new Text(128, 0), 'Name set in the reply to field for the mail. Default value is sender name. Reply to name must have reply to email as well.', true)
|
||||
->param('replyToEmail', '', new Email(), 'Email set in the reply to field for the mail. Default value is sender email. Reply to email must have reply to name as well.', true)
|
||||
->param('enabled', null, new Boolean(), 'Set as enabled.', true)
|
||||
->param('enabled', null, new Nullable(new Boolean()), 'Set as enabled.', true)
|
||||
->inject('queueForEvents')
|
||||
->inject('dbForProject')
|
||||
->inject('response')
|
||||
@@ -177,7 +179,7 @@ App::post('/v1/messaging/providers/sendgrid')
|
||||
->param('fromEmail', '', new Email(), 'Sender email address.', true)
|
||||
->param('replyToName', '', new Text(128, 0), 'Name set in the reply to field for the mail. Default value is sender name.', true)
|
||||
->param('replyToEmail', '', new Email(), 'Email set in the reply to field for the mail. Default value is sender email.', true)
|
||||
->param('enabled', null, new Boolean(), 'Set as enabled.', true)
|
||||
->param('enabled', null, new Nullable(new Boolean()), 'Set as enabled.', true)
|
||||
->inject('queueForEvents')
|
||||
->inject('dbForProject')
|
||||
->inject('response')
|
||||
@@ -259,7 +261,7 @@ App::post('/v1/messaging/providers/resend')
|
||||
->param('fromEmail', '', new Email(), 'Sender email address.', true)
|
||||
->param('replyToName', '', new Text(128, 0), 'Name set in the reply to field for the mail. Default value is sender name.', true)
|
||||
->param('replyToEmail', '', new Email(), 'Email set in the reply to field for the mail. Default value is sender email.', true)
|
||||
->param('enabled', null, new Boolean(), 'Set as enabled.', true)
|
||||
->param('enabled', null, new Nullable(new Boolean()), 'Set as enabled.', true)
|
||||
->inject('queueForEvents')
|
||||
->inject('dbForProject')
|
||||
->inject('response')
|
||||
@@ -366,7 +368,7 @@ App::post('/v1/messaging/providers/smtp')
|
||||
->param('fromEmail', '', new Email(), 'Sender email address.', true)
|
||||
->param('replyToName', '', new Text(128, 0), 'Name set in the reply to field for the mail. Default value is sender name.', true)
|
||||
->param('replyToEmail', '', new Email(), 'Email set in the reply to field for the mail. Default value is sender email.', true)
|
||||
->param('enabled', null, new Boolean(), 'Set as enabled.', true)
|
||||
->param('enabled', null, new Nullable(new Boolean()), 'Set as enabled.', true)
|
||||
->inject('queueForEvents')
|
||||
->inject('dbForProject')
|
||||
->inject('response')
|
||||
@@ -453,7 +455,7 @@ App::post('/v1/messaging/providers/msg91')
|
||||
->param('templateId', '', new Text(0), 'Msg91 template ID', true)
|
||||
->param('senderId', '', new Text(0), 'Msg91 sender ID.', true)
|
||||
->param('authKey', '', new Text(0), 'Msg91 auth key.', true)
|
||||
->param('enabled', null, new Boolean(), 'Set as enabled.', true)
|
||||
->param('enabled', null, new Nullable(new Boolean()), 'Set as enabled.', true)
|
||||
->inject('queueForEvents')
|
||||
->inject('dbForProject')
|
||||
->inject('response')
|
||||
@@ -536,7 +538,7 @@ App::post('/v1/messaging/providers/telesign')
|
||||
->param('from', '', new Phone(), 'Sender Phone number. Format this number with a leading \'+\' and a country code, e.g., +16175551212.', true)
|
||||
->param('customerId', '', new Text(0), 'Telesign customer ID.', true)
|
||||
->param('apiKey', '', new Text(0), 'Telesign API key.', true)
|
||||
->param('enabled', null, new Boolean(), 'Set as enabled.', true)
|
||||
->param('enabled', null, new Nullable(new Boolean()), 'Set as enabled.', true)
|
||||
->inject('queueForEvents')
|
||||
->inject('dbForProject')
|
||||
->inject('response')
|
||||
@@ -620,7 +622,7 @@ App::post('/v1/messaging/providers/textmagic')
|
||||
->param('from', '', new Phone(), 'Sender Phone number. Format this number with a leading \'+\' and a country code, e.g., +16175551212.', true)
|
||||
->param('username', '', new Text(0), 'Textmagic username.', true)
|
||||
->param('apiKey', '', new Text(0), 'Textmagic apiKey.', true)
|
||||
->param('enabled', null, new Boolean(), 'Set as enabled.', true)
|
||||
->param('enabled', null, new Nullable(new Boolean()), 'Set as enabled.', true)
|
||||
->inject('queueForEvents')
|
||||
->inject('dbForProject')
|
||||
->inject('response')
|
||||
@@ -704,7 +706,7 @@ App::post('/v1/messaging/providers/twilio')
|
||||
->param('from', '', new Phone(), 'Sender Phone number. Format this number with a leading \'+\' and a country code, e.g., +16175551212.', true)
|
||||
->param('accountSid', '', new Text(0), 'Twilio account secret ID.', true)
|
||||
->param('authToken', '', new Text(0), 'Twilio authentication token.', true)
|
||||
->param('enabled', null, new Boolean(), 'Set as enabled.', true)
|
||||
->param('enabled', null, new Nullable(new Boolean()), 'Set as enabled.', true)
|
||||
->inject('queueForEvents')
|
||||
->inject('dbForProject')
|
||||
->inject('response')
|
||||
@@ -788,7 +790,7 @@ App::post('/v1/messaging/providers/vonage')
|
||||
->param('from', '', new Phone(), 'Sender Phone number. Format this number with a leading \'+\' and a country code, e.g., +16175551212.', true)
|
||||
->param('apiKey', '', new Text(0), 'Vonage API key.', true)
|
||||
->param('apiSecret', '', new Text(0), 'Vonage API secret.', true)
|
||||
->param('enabled', null, new Boolean(), 'Set as enabled.', true)
|
||||
->param('enabled', null, new Nullable(new Boolean()), 'Set as enabled.', true)
|
||||
->inject('queueForEvents')
|
||||
->inject('dbForProject')
|
||||
->inject('response')
|
||||
@@ -888,8 +890,8 @@ App::post('/v1/messaging/providers/fcm')
|
||||
])
|
||||
->param('providerId', '', new CustomId(), 'Provider ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
|
||||
->param('name', '', new Text(128), 'Provider name.')
|
||||
->param('serviceAccountJSON', null, new JSON(), 'FCM service account JSON.', true)
|
||||
->param('enabled', null, new Boolean(), 'Set as enabled.', true)
|
||||
->param('serviceAccountJSON', null, new Nullable(new JSON()), 'FCM service account JSON.', true)
|
||||
->param('enabled', null, new Nullable(new Boolean()), 'Set as enabled.', true)
|
||||
->inject('queueForEvents')
|
||||
->inject('dbForProject')
|
||||
->inject('response')
|
||||
@@ -982,7 +984,7 @@ App::post('/v1/messaging/providers/apns')
|
||||
->param('teamId', '', new Text(0), 'APNS team ID.', true)
|
||||
->param('bundleId', '', new Text(0), 'APNS bundle ID.', true)
|
||||
->param('sandbox', false, new Boolean(), 'Use APNS sandbox environment.', true)
|
||||
->param('enabled', null, new Boolean(), 'Set as enabled.', true)
|
||||
->param('enabled', null, new Nullable(new Boolean()), 'Set as enabled.', true)
|
||||
->inject('queueForEvents')
|
||||
->inject('dbForProject')
|
||||
->inject('response')
|
||||
@@ -1069,8 +1071,9 @@ App::get('/v1/messaging/providers')
|
||||
->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
|
||||
->param('total', true, new Boolean(true), 'When set to false, the total count returned will be 0 and will not be calculated.', true)
|
||||
->inject('dbForProject')
|
||||
->inject('authorization')
|
||||
->inject('response')
|
||||
->action(function (array $queries, string $search, bool $includeTotal, Database $dbForProject, Response $response) {
|
||||
->action(function (array $queries, string $search, bool $includeTotal, Database $dbForProject, Authorization $authorization, Response $response) {
|
||||
try {
|
||||
$queries = Query::parseQueries($queries);
|
||||
} catch (QueryException $e) {
|
||||
@@ -1096,7 +1099,7 @@ App::get('/v1/messaging/providers')
|
||||
}
|
||||
|
||||
$providerId = $cursor->getValue();
|
||||
$cursorDocument = Authorization::skip(fn () => $dbForProject->getDocument('providers', $providerId));
|
||||
$cursorDocument = $authorization->skip(fn () => $dbForProject->getDocument('providers', $providerId));
|
||||
|
||||
if ($cursorDocument->isEmpty()) {
|
||||
throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Provider '{$providerId}' for the 'cursor' value not found.");
|
||||
@@ -1270,8 +1273,8 @@ App::patch('/v1/messaging/providers/mailgun/:providerId')
|
||||
->param('name', '', new Text(128), 'Provider name.', true)
|
||||
->param('apiKey', '', new Text(0), 'Mailgun API Key.', true)
|
||||
->param('domain', '', new Text(0), 'Mailgun Domain.', true)
|
||||
->param('isEuRegion', null, new Boolean(), 'Set as EU region.', true)
|
||||
->param('enabled', null, new Boolean(), 'Set as enabled.', true)
|
||||
->param('isEuRegion', null, new Nullable(new Boolean()), 'Set as EU region.', true)
|
||||
->param('enabled', null, new Nullable(new Boolean()), 'Set as enabled.', true)
|
||||
->param('fromName', '', new Text(128), 'Sender Name.', true)
|
||||
->param('fromEmail', '', new Email(), 'Sender email address.', true)
|
||||
->param('replyToName', '', new Text(128), 'Name set in the reply to field for the mail. Default value is sender name.', true)
|
||||
@@ -1381,7 +1384,7 @@ App::patch('/v1/messaging/providers/sendgrid/:providerId')
|
||||
))
|
||||
->param('providerId', '', new UID(), 'Provider ID.')
|
||||
->param('name', '', new Text(128), 'Provider name.', true)
|
||||
->param('enabled', null, new Boolean(), 'Set as enabled.', true)
|
||||
->param('enabled', null, new Nullable(new Boolean()), 'Set as enabled.', true)
|
||||
->param('apiKey', '', new Text(0), 'Sendgrid API key.', true)
|
||||
->param('fromName', '', new Text(128), 'Sender Name.', true)
|
||||
->param('fromEmail', '', new Email(), 'Sender email address.', true)
|
||||
@@ -1479,7 +1482,7 @@ App::patch('/v1/messaging/providers/resend/:providerId')
|
||||
))
|
||||
->param('providerId', '', new UID(), 'Provider ID.')
|
||||
->param('name', '', new Text(128), 'Provider name.', true)
|
||||
->param('enabled', null, new Boolean(), 'Set as enabled.', true)
|
||||
->param('enabled', null, new Nullable(new Boolean()), 'Set as enabled.', true)
|
||||
->param('apiKey', '', new Text(0), 'Resend API key.', true)
|
||||
->param('fromName', '', new Text(128), 'Sender Name.', true)
|
||||
->param('fromEmail', '', new Email(), 'Sender email address.', true)
|
||||
@@ -1597,17 +1600,17 @@ App::patch('/v1/messaging/providers/smtp/:providerId')
|
||||
->param('providerId', '', new UID(), 'Provider ID.')
|
||||
->param('name', '', new Text(128), 'Provider name.', true)
|
||||
->param('host', '', new Text(0), 'SMTP hosts. Either a single hostname or multiple semicolon-delimited hostnames. You can also specify a different port for each host such as `smtp1.example.com:25;smtp2.example.com`. You can also specify encryption type, for example: `tls://smtp1.example.com:587;ssl://smtp2.example.com:465"`. Hosts will be tried in order.', true)
|
||||
->param('port', null, new Range(1, 65535), 'SMTP port.', true)
|
||||
->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('autoTLS', null, new Boolean(), 'Enable SMTP AutoTLS feature.', true)
|
||||
->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)
|
||||
->param('fromEmail', '', new Email(), 'Sender email address.', true)
|
||||
->param('replyToName', '', new Text(128), 'Name set in the Reply To field for the mail. Default value is Sender Name.', true)
|
||||
->param('replyToEmail', '', new Text(128), 'Email set in the Reply To field for the mail. Default value is Sender Email.', true)
|
||||
->param('enabled', null, new Boolean(), 'Set as enabled.', true)
|
||||
->param('enabled', null, new Nullable(new Boolean()), 'Set as enabled.', true)
|
||||
->inject('queueForEvents')
|
||||
->inject('dbForProject')
|
||||
->inject('response')
|
||||
@@ -1725,7 +1728,7 @@ App::patch('/v1/messaging/providers/msg91/:providerId')
|
||||
))
|
||||
->param('providerId', '', new UID(), 'Provider ID.')
|
||||
->param('name', '', new Text(128), 'Provider name.', true)
|
||||
->param('enabled', null, new Boolean(), 'Set as enabled.', true)
|
||||
->param('enabled', null, new Nullable(new Boolean()), 'Set as enabled.', true)
|
||||
->param('templateId', '', new Text(0), 'Msg91 template ID.', true)
|
||||
->param('senderId', '', new Text(0), 'Msg91 sender ID.', true)
|
||||
->param('authKey', '', new Text(0), 'Msg91 auth key.', true)
|
||||
@@ -1812,7 +1815,7 @@ App::patch('/v1/messaging/providers/telesign/:providerId')
|
||||
))
|
||||
->param('providerId', '', new UID(), 'Provider ID.')
|
||||
->param('name', '', new Text(128), 'Provider name.', true)
|
||||
->param('enabled', null, new Boolean(), 'Set as enabled.', true)
|
||||
->param('enabled', null, new Nullable(new Boolean()), 'Set as enabled.', true)
|
||||
->param('customerId', '', new Text(0), 'Telesign customer ID.', true)
|
||||
->param('apiKey', '', new Text(0), 'Telesign API key.', true)
|
||||
->param('from', '', new Text(256), 'Sender number.', true)
|
||||
@@ -1901,7 +1904,7 @@ App::patch('/v1/messaging/providers/textmagic/:providerId')
|
||||
))
|
||||
->param('providerId', '', new UID(), 'Provider ID.')
|
||||
->param('name', '', new Text(128), 'Provider name.', true)
|
||||
->param('enabled', null, new Boolean(), 'Set as enabled.', true)
|
||||
->param('enabled', null, new Nullable(new Boolean()), 'Set as enabled.', true)
|
||||
->param('username', '', new Text(0), 'Textmagic username.', true)
|
||||
->param('apiKey', '', new Text(0), 'Textmagic apiKey.', true)
|
||||
->param('from', '', new Text(256), 'Sender number.', true)
|
||||
@@ -1990,7 +1993,7 @@ App::patch('/v1/messaging/providers/twilio/:providerId')
|
||||
))
|
||||
->param('providerId', '', new UID(), 'Provider ID.')
|
||||
->param('name', '', new Text(128), 'Provider name.', true)
|
||||
->param('enabled', null, new Boolean(), 'Set as enabled.', true)
|
||||
->param('enabled', null, new Nullable(new Boolean()), 'Set as enabled.', true)
|
||||
->param('accountSid', '', new Text(0), 'Twilio account secret ID.', true)
|
||||
->param('authToken', '', new Text(0), 'Twilio authentication token.', true)
|
||||
->param('from', '', new Text(256), 'Sender number.', true)
|
||||
@@ -2079,7 +2082,7 @@ App::patch('/v1/messaging/providers/vonage/:providerId')
|
||||
))
|
||||
->param('providerId', '', new UID(), 'Provider ID.')
|
||||
->param('name', '', new Text(128), 'Provider name.', true)
|
||||
->param('enabled', null, new Boolean(), 'Set as enabled.', true)
|
||||
->param('enabled', null, new Nullable(new Boolean()), 'Set as enabled.', true)
|
||||
->param('apiKey', '', new Text(0), 'Vonage API key.', true)
|
||||
->param('apiSecret', '', new Text(0), 'Vonage API secret.', true)
|
||||
->param('from', '', new Text(256), 'Sender number.', true)
|
||||
@@ -2187,8 +2190,8 @@ App::patch('/v1/messaging/providers/fcm/:providerId')
|
||||
])
|
||||
->param('providerId', '', new UID(), 'Provider ID.')
|
||||
->param('name', '', new Text(128), 'Provider name.', true)
|
||||
->param('enabled', null, new Boolean(), 'Set as enabled.', true)
|
||||
->param('serviceAccountJSON', null, new JSON(), 'FCM service account JSON.', true)
|
||||
->param('enabled', null, new Nullable(new Boolean()), 'Set as enabled.', true)
|
||||
->param('serviceAccountJSON', null, new Nullable(new JSON()), 'FCM service account JSON.', true)
|
||||
->inject('queueForEvents')
|
||||
->inject('dbForProject')
|
||||
->inject('response')
|
||||
@@ -2282,12 +2285,12 @@ App::patch('/v1/messaging/providers/apns/:providerId')
|
||||
])
|
||||
->param('providerId', '', new UID(), 'Provider ID.')
|
||||
->param('name', '', new Text(128), 'Provider name.', true)
|
||||
->param('enabled', null, new Boolean(), 'Set as enabled.', true)
|
||||
->param('enabled', null, new Nullable(new Boolean()), 'Set as enabled.', true)
|
||||
->param('authKey', '', new Text(0), 'APNS authentication key.', true)
|
||||
->param('authKeyId', '', new Text(0), 'APNS authentication key ID.', true)
|
||||
->param('teamId', '', new Text(0), 'APNS team ID.', true)
|
||||
->param('bundleId', '', new Text(0), 'APNS bundle ID.', true)
|
||||
->param('sandbox', null, new Boolean(), 'Use APNS sandbox environment.', true)
|
||||
->param('sandbox', null, new Nullable(new Boolean()), 'Use APNS sandbox environment.', true)
|
||||
->inject('queueForEvents')
|
||||
->inject('dbForProject')
|
||||
->inject('response')
|
||||
@@ -2476,8 +2479,9 @@ App::get('/v1/messaging/topics')
|
||||
->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
|
||||
->param('total', true, new Boolean(true), 'When set to false, the total count returned will be 0 and will not be calculated.', true)
|
||||
->inject('dbForProject')
|
||||
->inject('authorization')
|
||||
->inject('response')
|
||||
->action(function (array $queries, string $search, bool $includeTotal, Database $dbForProject, Response $response) {
|
||||
->action(function (array $queries, string $search, bool $includeTotal, Database $dbForProject, Authorization $authorization, Response $response) {
|
||||
try {
|
||||
$queries = Query::parseQueries($queries);
|
||||
} catch (QueryException $e) {
|
||||
@@ -2503,7 +2507,7 @@ App::get('/v1/messaging/topics')
|
||||
}
|
||||
|
||||
$topicId = $cursor->getValue();
|
||||
$cursorDocument = Authorization::skip(fn () => $dbForProject->getDocument('topics', $topicId));
|
||||
$cursorDocument = $authorization->skip(fn () => $dbForProject->getDocument('topics', $topicId));
|
||||
|
||||
if ($cursorDocument->isEmpty()) {
|
||||
throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Topic '{$topicId}' for the 'cursor' value not found.");
|
||||
@@ -2676,8 +2680,8 @@ App::patch('/v1/messaging/topics/:topicId')
|
||||
]
|
||||
))
|
||||
->param('topicId', '', new UID(), 'Topic ID.')
|
||||
->param('name', null, new Text(128), 'Topic Name.', true)
|
||||
->param('subscribe', null, new Roles(APP_LIMIT_ARRAY_PARAMS_SIZE), 'An array of role strings with subscribe permission. By default all users are granted with any subscribe permission. [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('name', null, new Nullable(new Text(128)), 'Topic Name.', true)
|
||||
->param('subscribe', null, new Nullable(new Roles(APP_LIMIT_ARRAY_PARAMS_SIZE)), 'An array of role strings with subscribe permission. By default all users are granted with any subscribe permission. [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)
|
||||
->inject('queueForEvents')
|
||||
->inject('dbForProject')
|
||||
->inject('response')
|
||||
@@ -2779,29 +2783,27 @@ App::post('/v1/messaging/topics/:topicId/subscribers')
|
||||
->param('targetId', '', new UID(), 'Target ID. The target ID to link to the specified Topic ID.')
|
||||
->inject('queueForEvents')
|
||||
->inject('dbForProject')
|
||||
->inject('authorization')
|
||||
->inject('response')
|
||||
->action(function (string $subscriberId, string $topicId, string $targetId, Event $queueForEvents, Database $dbForProject, Response $response) {
|
||||
->action(function (string $subscriberId, string $topicId, string $targetId, Event $queueForEvents, Database $dbForProject, Authorization $authorization, Response $response) {
|
||||
$subscriberId = $subscriberId == 'unique()' ? ID::unique() : $subscriberId;
|
||||
|
||||
$topic = Authorization::skip(fn () => $dbForProject->getDocument('topics', $topicId));
|
||||
$topic = $authorization->skip(fn () => $dbForProject->getDocument('topics', $topicId));
|
||||
|
||||
if ($topic->isEmpty()) {
|
||||
throw new Exception(Exception::TOPIC_NOT_FOUND);
|
||||
}
|
||||
|
||||
$validator = new Authorization('subscribe');
|
||||
|
||||
if (!$validator->isValid($topic->getAttribute('subscribe'))) {
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED, $validator->getDescription());
|
||||
if (!$authorization->isValid(new Input('subscribe', $topic->getAttribute('subscribe')))) {
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED, $authorization->getDescription());
|
||||
}
|
||||
|
||||
$target = Authorization::skip(fn () => $dbForProject->getDocument('targets', $targetId));
|
||||
$target = $authorization->skip(fn () => $dbForProject->getDocument('targets', $targetId));
|
||||
|
||||
if ($target->isEmpty()) {
|
||||
throw new Exception(Exception::USER_TARGET_NOT_FOUND);
|
||||
}
|
||||
|
||||
$user = Authorization::skip(fn () => $dbForProject->getDocument('users', $target->getAttribute('userId')));
|
||||
$user = $authorization->skip(fn () => $dbForProject->getDocument('users', $target->getAttribute('userId')));
|
||||
|
||||
$subscriber = new Document([
|
||||
'$id' => $subscriberId,
|
||||
@@ -2834,7 +2836,7 @@ App::post('/v1/messaging/topics/:topicId/subscribers')
|
||||
default => throw new Exception(Exception::TARGET_PROVIDER_INVALID_TYPE),
|
||||
};
|
||||
|
||||
Authorization::skip(fn () => $dbForProject->increaseDocumentAttribute(
|
||||
$authorization->skip(fn () => $dbForProject->increaseDocumentAttribute(
|
||||
'topics',
|
||||
$topicId,
|
||||
$totalAttribute,
|
||||
@@ -2879,8 +2881,9 @@ App::get('/v1/messaging/topics/:topicId/subscribers')
|
||||
->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
|
||||
->param('total', true, new Boolean(true), 'When set to false, the total count returned will be 0 and will not be calculated.', true)
|
||||
->inject('dbForProject')
|
||||
->inject('authorization')
|
||||
->inject('response')
|
||||
->action(function (string $topicId, array $queries, string $search, bool $includeTotal, Database $dbForProject, Response $response) {
|
||||
->action(function (string $topicId, array $queries, string $search, bool $includeTotal, Database $dbForProject, Authorization $authorization, Response $response) {
|
||||
try {
|
||||
$queries = Query::parseQueries($queries);
|
||||
} catch (QueryException $e) {
|
||||
@@ -2891,7 +2894,7 @@ App::get('/v1/messaging/topics/:topicId/subscribers')
|
||||
$queries[] = Query::search('search', $search);
|
||||
}
|
||||
|
||||
$topic = Authorization::skip(fn () => $dbForProject->getDocument('topics', $topicId));
|
||||
$topic = $authorization->skip(fn () => $dbForProject->getDocument('topics', $topicId));
|
||||
|
||||
if ($topic->isEmpty()) {
|
||||
throw new Exception(Exception::TOPIC_NOT_FOUND);
|
||||
@@ -2914,7 +2917,7 @@ App::get('/v1/messaging/topics/:topicId/subscribers')
|
||||
}
|
||||
|
||||
$subscriberId = $cursor->getValue();
|
||||
$cursorDocument = Authorization::skip(fn () => $dbForProject->getDocument('subscribers', $subscriberId));
|
||||
$cursorDocument = $authorization->skip(fn () => $dbForProject->getDocument('subscribers', $subscriberId));
|
||||
|
||||
if ($cursorDocument->isEmpty()) {
|
||||
throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Subscriber '{$subscriberId}' for the 'cursor' value not found.");
|
||||
@@ -2928,10 +2931,10 @@ App::get('/v1/messaging/topics/:topicId/subscribers')
|
||||
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.");
|
||||
}
|
||||
|
||||
$subscribers = batch(\array_map(function (Document $subscriber) use ($dbForProject) {
|
||||
return function () use ($subscriber, $dbForProject) {
|
||||
$target = Authorization::skip(fn () => $dbForProject->getDocument('targets', $subscriber->getAttribute('targetId')));
|
||||
$user = Authorization::skip(fn () => $dbForProject->getDocument('users', $target->getAttribute('userId')));
|
||||
$subscribers = batch(\array_map(function (Document $subscriber) use ($dbForProject, $authorization) {
|
||||
return function () use ($subscriber, $dbForProject, $authorization) {
|
||||
$target = $authorization->skip(fn () => $dbForProject->getDocument('targets', $subscriber->getAttribute('targetId')));
|
||||
$user = $authorization->skip(fn () => $dbForProject->getDocument('users', $target->getAttribute('userId')));
|
||||
|
||||
return $subscriber
|
||||
->setAttribute('target', $target)
|
||||
@@ -3066,9 +3069,10 @@ App::get('/v1/messaging/topics/:topicId/subscribers/:subscriberId')
|
||||
->param('topicId', '', new UID(), 'Topic ID. The topic ID subscribed to.')
|
||||
->param('subscriberId', '', new UID(), 'Subscriber ID.')
|
||||
->inject('dbForProject')
|
||||
->inject('authorization')
|
||||
->inject('response')
|
||||
->action(function (string $topicId, string $subscriberId, Database $dbForProject, Response $response) {
|
||||
$topic = Authorization::skip(fn () => $dbForProject->getDocument('topics', $topicId));
|
||||
->action(function (string $topicId, string $subscriberId, Database $dbForProject, Authorization $authorization, Response $response) {
|
||||
$topic = $authorization->skip(fn () => $dbForProject->getDocument('topics', $topicId));
|
||||
|
||||
if ($topic->isEmpty()) {
|
||||
throw new Exception(Exception::TOPIC_NOT_FOUND);
|
||||
@@ -3080,8 +3084,8 @@ App::get('/v1/messaging/topics/:topicId/subscribers/:subscriberId')
|
||||
throw new Exception(Exception::SUBSCRIBER_NOT_FOUND);
|
||||
}
|
||||
|
||||
$target = Authorization::skip(fn () => $dbForProject->getDocument('targets', $subscriber->getAttribute('targetId')));
|
||||
$user = Authorization::skip(fn () => $dbForProject->getDocument('users', $target->getAttribute('userId')));
|
||||
$target = $authorization->skip(fn () => $dbForProject->getDocument('targets', $subscriber->getAttribute('targetId')));
|
||||
$user = $authorization->skip(fn () => $dbForProject->getDocument('users', $target->getAttribute('userId')));
|
||||
|
||||
$subscriber
|
||||
->setAttribute('target', $target)
|
||||
@@ -3117,9 +3121,10 @@ App::delete('/v1/messaging/topics/:topicId/subscribers/:subscriberId')
|
||||
->param('subscriberId', '', new UID(), 'Subscriber ID.')
|
||||
->inject('queueForEvents')
|
||||
->inject('dbForProject')
|
||||
->inject('authorization')
|
||||
->inject('response')
|
||||
->action(function (string $topicId, string $subscriberId, Event $queueForEvents, Database $dbForProject, Response $response) {
|
||||
$topic = Authorization::skip(fn () => $dbForProject->getDocument('topics', $topicId));
|
||||
->action(function (string $topicId, string $subscriberId, Event $queueForEvents, Database $dbForProject, Authorization $authorization, Response $response) {
|
||||
$topic = $authorization->skip(fn () => $dbForProject->getDocument('topics', $topicId));
|
||||
|
||||
if ($topic->isEmpty()) {
|
||||
throw new Exception(Exception::TOPIC_NOT_FOUND);
|
||||
@@ -3142,7 +3147,7 @@ App::delete('/v1/messaging/topics/:topicId/subscribers/:subscriberId')
|
||||
default => throw new Exception(Exception::TARGET_PROVIDER_INVALID_TYPE),
|
||||
};
|
||||
|
||||
Authorization::skip(fn () => $dbForProject->decreaseDocumentAttribute(
|
||||
$authorization->skip(fn () => $dbForProject->decreaseDocumentAttribute(
|
||||
'topics',
|
||||
$topicId,
|
||||
$totalAttribute,
|
||||
@@ -3190,7 +3195,7 @@ App::post('/v1/messaging/messages/email')
|
||||
->param('attachments', [], new ArrayList(new CompoundUID()), 'Array of compound ID strings of bucket IDs and file IDs to be attached to the email. They should be formatted as <BUCKET_ID>:<FILE_ID>.', true)
|
||||
->param('draft', false, new Boolean(), 'Is message a draft', true)
|
||||
->param('html', false, new Boolean(), 'Is content of type HTML', true)
|
||||
->param('scheduledAt', null, 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('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)
|
||||
->inject('queueForEvents')
|
||||
->inject('dbForProject')
|
||||
->inject('dbForPlatform')
|
||||
@@ -3363,7 +3368,7 @@ App::post('/v1/messaging/messages/sms')
|
||||
->param('users', [], new ArrayList(new UID()), 'List of User IDs.', true)
|
||||
->param('targets', [], new ArrayList(new UID()), 'List of Targets IDs.', true)
|
||||
->param('draft', false, new Boolean(), 'Is message a draft', true)
|
||||
->param('scheduledAt', null, 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('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)
|
||||
->inject('queueForEvents')
|
||||
->inject('dbForProject')
|
||||
->inject('dbForPlatform')
|
||||
@@ -3486,7 +3491,7 @@ App::post('/v1/messaging/messages/push')
|
||||
->param('topics', [], new ArrayList(new UID()), 'List of Topic IDs.', true)
|
||||
->param('users', [], new ArrayList(new UID()), 'List of User IDs.', true)
|
||||
->param('targets', [], new ArrayList(new UID()), 'List of Targets IDs.', true)
|
||||
->param('data', null, new JSON(), 'Additional key-value pair data for push notification.', true)
|
||||
->param('data', null, new Nullable(new JSON()), 'Additional key-value pair data for push notification.', true)
|
||||
->param('action', '', new Text(256), 'Action for push notification.', true)
|
||||
->param('image', '', new CompoundUID(), 'Image for push notification. Must be a compound bucket ID to file ID of a jpeg, png, or bmp image in Appwrite Storage. It should be formatted as <BUCKET_ID>:<FILE_ID>.', true)
|
||||
->param('icon', '', new Text(256), 'Icon for push notification. Available only for Android and Web Platform.', true)
|
||||
@@ -3495,7 +3500,7 @@ App::post('/v1/messaging/messages/push')
|
||||
->param('tag', '', new Text(256), 'Tag for push notification. Available only for Android Platform.', true)
|
||||
->param('badge', -1, new Integer(), 'Badge for push notification. Available only for iOS Platform.', true)
|
||||
->param('draft', false, new Boolean(), 'Is message a draft', true)
|
||||
->param('scheduledAt', null, 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('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)
|
||||
@@ -3699,8 +3704,9 @@ App::get('/v1/messaging/messages')
|
||||
->param('search', '', new Text(256), 'Search term to filter your list results. Max length: 256 chars.', true)
|
||||
->param('total', true, new Boolean(true), 'When set to false, the total count returned will be 0 and will not be calculated.', true)
|
||||
->inject('dbForProject')
|
||||
->inject('authorization')
|
||||
->inject('response')
|
||||
->action(function (array $queries, string $search, bool $includeTotal, Database $dbForProject, Response $response) {
|
||||
->action(function (array $queries, string $search, bool $includeTotal, Database $dbForProject, Authorization $authorization, Response $response) {
|
||||
try {
|
||||
$queries = Query::parseQueries($queries);
|
||||
} catch (QueryException $e) {
|
||||
@@ -3726,7 +3732,7 @@ App::get('/v1/messaging/messages')
|
||||
}
|
||||
|
||||
$messageId = $cursor->getValue();
|
||||
$cursorDocument = Authorization::skip(fn () => $dbForProject->getDocument('messages', $messageId));
|
||||
$cursorDocument = $authorization->skip(fn () => $dbForProject->getDocument('messages', $messageId));
|
||||
|
||||
if ($cursorDocument->isEmpty()) {
|
||||
throw new Exception(Exception::GENERAL_CURSOR_NOT_FOUND, "Message '{$messageId}' for the 'cursor' value not found.");
|
||||
@@ -3981,17 +3987,17 @@ App::patch('/v1/messaging/messages/email/:messageId')
|
||||
]
|
||||
))
|
||||
->param('messageId', '', new UID(), 'Message ID.')
|
||||
->param('topics', null, new ArrayList(new UID()), 'List of Topic IDs.', true)
|
||||
->param('users', null, new ArrayList(new UID()), 'List of User IDs.', true)
|
||||
->param('targets', null, new ArrayList(new UID()), 'List of Targets IDs.', true)
|
||||
->param('subject', null, new Text(998), 'Email Subject.', true)
|
||||
->param('content', null, new Text(64230), 'Email Content.', true)
|
||||
->param('draft', null, new Boolean(), 'Is message a draft', true)
|
||||
->param('html', null, new Boolean(), 'Is content of type HTML', true)
|
||||
->param('cc', null, new ArrayList(new UID()), 'Array of target IDs to be added as CC.', true)
|
||||
->param('bcc', null, new ArrayList(new UID()), 'Array of target IDs to be added as BCC.', true)
|
||||
->param('scheduledAt', null, 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('attachments', null, new ArrayList(new CompoundUID()), 'Array of compound ID strings of bucket IDs and file IDs to be attached to the email. They should be formatted as <BUCKET_ID>:<FILE_ID>.', true)
|
||||
->param('topics', null, new Nullable(new ArrayList(new UID())), 'List of Topic IDs.', true)
|
||||
->param('users', null, new Nullable(new ArrayList(new UID())), 'List of User IDs.', true)
|
||||
->param('targets', null, new Nullable(new ArrayList(new UID())), 'List of Targets IDs.', true)
|
||||
->param('subject', null, new Nullable(new Text(998)), 'Email Subject.', true)
|
||||
->param('content', null, new Nullable(new Text(64230)), 'Email Content.', true)
|
||||
->param('draft', null, new Nullable(new Boolean()), 'Is message a draft', true)
|
||||
->param('html', null, new Nullable(new Boolean()), 'Is content of type HTML', true)
|
||||
->param('cc', null, new Nullable(new ArrayList(new UID())), 'Array of target IDs to be added as CC.', true)
|
||||
->param('bcc', null, new Nullable(new ArrayList(new UID())), 'Array of target IDs to be added as BCC.', true)
|
||||
->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('attachments', null, new Nullable(new ArrayList(new CompoundUID())), 'Array of compound ID strings of bucket IDs and file IDs to be attached to the email. They should be formatted as <BUCKET_ID>:<FILE_ID>.', true)
|
||||
->inject('queueForEvents')
|
||||
->inject('dbForProject')
|
||||
->inject('dbForPlatform')
|
||||
@@ -4207,12 +4213,12 @@ App::patch('/v1/messaging/messages/sms/:messageId')
|
||||
)
|
||||
])
|
||||
->param('messageId', '', new UID(), 'Message ID.')
|
||||
->param('topics', null, new ArrayList(new UID()), 'List of Topic IDs.', true)
|
||||
->param('users', null, new ArrayList(new UID()), 'List of User IDs.', true)
|
||||
->param('targets', null, new ArrayList(new UID()), 'List of Targets IDs.', true)
|
||||
->param('content', null, new Text(64230), 'Email Content.', true)
|
||||
->param('draft', null, new Boolean(), 'Is message a draft', true)
|
||||
->param('scheduledAt', null, 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('topics', null, new Nullable(new ArrayList(new UID())), 'List of Topic IDs.', true)
|
||||
->param('users', null, new Nullable(new ArrayList(new UID())), 'List of User IDs.', true)
|
||||
->param('targets', null, new Nullable(new ArrayList(new UID())), 'List of Targets IDs.', true)
|
||||
->param('content', null, new Nullable(new Text(64230)), 'Email Content.', true)
|
||||
->param('draft', null, new Nullable(new Boolean()), 'Is message a draft', true)
|
||||
->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)
|
||||
->inject('queueForEvents')
|
||||
->inject('dbForProject')
|
||||
->inject('dbForPlatform')
|
||||
@@ -4369,24 +4375,24 @@ App::patch('/v1/messaging/messages/push/:messageId')
|
||||
]
|
||||
))
|
||||
->param('messageId', '', new UID(), 'Message ID.')
|
||||
->param('topics', null, new ArrayList(new UID()), 'List of Topic IDs.', true)
|
||||
->param('users', null, new ArrayList(new UID()), 'List of User IDs.', true)
|
||||
->param('targets', null, new ArrayList(new UID()), 'List of Targets IDs.', true)
|
||||
->param('title', null, new Text(256), 'Title for push notification.', true)
|
||||
->param('body', null, new Text(64230), 'Body for push notification.', true)
|
||||
->param('data', null, new JSON(), 'Additional Data for push notification.', true)
|
||||
->param('action', null, new Text(256), 'Action for push notification.', true)
|
||||
->param('image', null, new CompoundUID(), 'Image for push notification. Must be a compound bucket ID to file ID of a jpeg, png, or bmp image in Appwrite Storage. It should be formatted as <BUCKET_ID>:<FILE_ID>.', true)
|
||||
->param('icon', null, new Text(256), 'Icon for push notification. Available only for Android and Web platforms.', true)
|
||||
->param('sound', null, new Text(256), 'Sound for push notification. Available only for Android and iOS platforms.', true)
|
||||
->param('color', null, new Text(256), 'Color for push notification. Available only for Android platforms.', true)
|
||||
->param('tag', null, new Text(256), 'Tag for push notification. Available only for Android platforms.', true)
|
||||
->param('badge', null, new Integer(), 'Badge for push notification. Available only for iOS platforms.', true)
|
||||
->param('draft', null, new Boolean(), 'Is message a draft', true)
|
||||
->param('scheduledAt', null, 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 Boolean(), 'If set to true, the notification will be delivered in the background. Available only for iOS Platform.', true)
|
||||
->param('critical', null, 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 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('topics', null, new Nullable(new ArrayList(new UID())), 'List of Topic IDs.', true)
|
||||
->param('users', null, new Nullable(new ArrayList(new UID())), 'List of User IDs.', true)
|
||||
->param('targets', null, new Nullable(new ArrayList(new UID())), 'List of Targets IDs.', true)
|
||||
->param('title', null, new Nullable(new Text(256)), 'Title for push notification.', true)
|
||||
->param('body', null, new Nullable(new Text(64230)), 'Body for push notification.', true)
|
||||
->param('data', null, new Nullable(new JSON()), 'Additional Data for push notification.', true)
|
||||
->param('action', null, new Nullable(new Text(256)), 'Action for push notification.', true)
|
||||
->param('image', null, new Nullable(new CompoundUID()), 'Image for push notification. Must be a compound bucket ID to file ID of a jpeg, png, or bmp image in Appwrite Storage. It should be formatted as <BUCKET_ID>:<FILE_ID>.', true)
|
||||
->param('icon', null, new Nullable(new Text(256)), 'Icon for push notification. Available only for Android and Web platforms.', true)
|
||||
->param('sound', null, new Nullable(new Text(256)), 'Sound for push notification. Available only for Android and iOS platforms.', true)
|
||||
->param('color', null, new Nullable(new Text(256)), 'Color for push notification. Available only for Android platforms.', true)
|
||||
->param('tag', null, new Nullable(new Text(256)), 'Tag for push notification. Available only for Android platforms.', true)
|
||||
->param('badge', null, new Nullable(new Integer()), 'Badge for push notification. Available only for iOS platforms.', true)
|
||||
->param('draft', null, new Nullable(new Boolean()), 'Is message a draft', true)
|
||||
->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)
|
||||
->inject('queueForEvents')
|
||||
->inject('dbForProject')
|
||||
->inject('dbForPlatform')
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<?php
|
||||
|
||||
use Appwrite\Auth\Auth;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Event\Migration;
|
||||
use Appwrite\Extend\Exception;
|
||||
@@ -334,26 +335,19 @@ App::post('/v1/migrations/csv/imports')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('dbForPlatform')
|
||||
->inject('authorization')
|
||||
->inject('project')
|
||||
->inject('deviceForFiles')
|
||||
->inject('deviceForMigrations')
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForMigrations')
|
||||
->action(function (
|
||||
string $bucketId,
|
||||
string $fileId,
|
||||
string $resourceId,
|
||||
bool $internalFile,
|
||||
Response $response,
|
||||
Database $dbForProject,
|
||||
Database $dbForPlatform,
|
||||
Document $project,
|
||||
Device $deviceForFiles,
|
||||
Device $deviceForMigrations,
|
||||
Event $queueForEvents,
|
||||
Migration $queueForMigrations
|
||||
) {
|
||||
$bucket = Authorization::skip(function () use ($internalFile, $dbForPlatform, $dbForProject, $bucketId) {
|
||||
->action(function (string $bucketId, string $fileId, string $resourceId, bool $internalFile, Response $response, Database $dbForProject, Database $dbForPlatform, Authorization $authorization, Document $project, Device $deviceForFiles, Device $deviceForMigrations, Event $queueForEvents, Migration $queueForMigrations) {
|
||||
$isAPIKey = Auth::isAppUser($authorization->getRoles());
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser($authorization->getRoles());
|
||||
if ($internalFile && !$isPrivilegedUser) {
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED);
|
||||
}
|
||||
$bucket = $authorization->skip(function () use ($internalFile, $dbForPlatform, $dbForProject, $bucketId) {
|
||||
if ($internalFile) {
|
||||
return $dbForPlatform->getDocument('buckets', 'default');
|
||||
}
|
||||
@@ -364,7 +358,7 @@ App::post('/v1/migrations/csv/imports')
|
||||
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
|
||||
}
|
||||
|
||||
$file = Authorization::skip(fn () => $internalFile ? $dbForPlatform->getDocument('bucket_' . $bucket->getSequence(), $fileId) : $dbForProject->getDocument('bucket_' . $bucket->getSequence(), $fileId));
|
||||
$file = $authorization->skip(fn () => $internalFile ? $dbForPlatform->getDocument('bucket_' . $bucket->getSequence(), $fileId) : $dbForProject->getDocument('bucket_' . $bucket->getSequence(), $fileId));
|
||||
if ($file->isEmpty()) {
|
||||
throw new Exception(Exception::STORAGE_FILE_NOT_FOUND);
|
||||
}
|
||||
@@ -468,7 +462,6 @@ App::post('/v1/migrations/csv/exports')
|
||||
]
|
||||
))
|
||||
->param('resourceId', null, new CompoundUID(), 'Composite ID in the format {databaseId:collectionId}, identifying a collection within a database to export.')
|
||||
->param('bucketId', '', new UID(), 'Storage bucket unique ID where the exported CSV will be stored.')
|
||||
->param('filename', '', new Text(255), 'The name of the file to be created for the export, excluding the .csv extension.')
|
||||
->param('columns', [], new ArrayList(new Text(Database::LENGTH_KEY)), 'List of attributes to export. If empty, all attributes will be exported. You can use the `*` wildcard to export all attributes from the collection.', true)
|
||||
->param('queries', [], new ArrayList(new Text(0)), 'Array of query strings generated using the Query class provided by the SDK to filter documents to export. [Learn more about queries](https://appwrite.io/docs/databases#querying-documents). Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' queries are allowed, each ' . APP_LIMIT_ARRAY_ELEMENT_SIZE . ' characters long.', true)
|
||||
@@ -480,12 +473,13 @@ App::post('/v1/migrations/csv/exports')
|
||||
->inject('user')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('dbForPlatform')
|
||||
->inject('authorization')
|
||||
->inject('project')
|
||||
->inject('queueForEvents')
|
||||
->inject('queueForMigrations')
|
||||
->action(function (
|
||||
string $resourceId,
|
||||
string $bucketId,
|
||||
string $filename,
|
||||
array $columns,
|
||||
array $queries,
|
||||
@@ -497,6 +491,8 @@ App::post('/v1/migrations/csv/exports')
|
||||
Document $user,
|
||||
Response $response,
|
||||
Database $dbForProject,
|
||||
Database $dbForPlatform,
|
||||
Authorization $authorization,
|
||||
Document $project,
|
||||
Event $queueForEvents,
|
||||
Migration $queueForMigrations
|
||||
@@ -507,7 +503,7 @@ App::post('/v1/migrations/csv/exports')
|
||||
throw new Exception(Exception::GENERAL_QUERY_INVALID, $e->getMessage());
|
||||
}
|
||||
|
||||
$bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
|
||||
$bucket = $authorization->skip(fn () => $dbForPlatform->getDocument('buckets', 'default'));
|
||||
if ($bucket->isEmpty()) {
|
||||
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
|
||||
}
|
||||
@@ -520,12 +516,12 @@ App::post('/v1/migrations/csv/exports')
|
||||
throw new Exception(Exception::COLLECTION_NOT_FOUND);
|
||||
}
|
||||
|
||||
$database = Authorization::skip(fn () => $dbForProject->getDocument('databases', $databaseId));
|
||||
$database = $authorization->skip(fn () => $dbForProject->getDocument('databases', $databaseId));
|
||||
if ($database->isEmpty()) {
|
||||
throw new Exception(Exception::DATABASE_NOT_FOUND);
|
||||
}
|
||||
|
||||
$collection = Authorization::skip(fn () => $dbForProject->getDocument('database_' . $database->getSequence(), $collectionId));
|
||||
$collection = $authorization->skip(fn () => $dbForProject->getDocument('database_' . $database->getSequence(), $collectionId));
|
||||
if ($collection->isEmpty()) {
|
||||
throw new Exception(Exception::COLLECTION_NOT_FOUND);
|
||||
}
|
||||
@@ -553,7 +549,7 @@ App::post('/v1/migrations/csv/exports')
|
||||
'resourceData' => '{}',
|
||||
'errors' => [],
|
||||
'options' => [
|
||||
'bucketId' => $bucketId,
|
||||
'bucketId' => 'default', // Always use internal bucket
|
||||
'filename' => $filename,
|
||||
'columns' => $columns,
|
||||
'queries' => $queries,
|
||||
|
||||
@@ -18,6 +18,7 @@ use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\Datetime as DateTimeValidator;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Validator\Boolean;
|
||||
use Utopia\Validator\Nullable;
|
||||
use Utopia\Validator\Text;
|
||||
use Utopia\Validator\WhiteList;
|
||||
|
||||
@@ -44,9 +45,10 @@ App::get('/v1/project/usage')
|
||||
->inject('response')
|
||||
->inject('project')
|
||||
->inject('dbForProject')
|
||||
->inject('authorization')
|
||||
->inject('getLogsDB')
|
||||
->inject('smsRates')
|
||||
->action(function (string $startDate, string $endDate, string $period, Response $response, Document $project, Database $dbForProject, callable $getLogsDB, array $smsRates) {
|
||||
->action(function (string $startDate, string $endDate, string $period, Response $response, Document $project, Database $dbForProject, Authorization $authorization, callable $getLogsDB, array $smsRates) {
|
||||
$stats = $total = $usage = [];
|
||||
$format = 'Y-m-d 00:00:00';
|
||||
$firstDay = (new DateTime($startDate))->format($format);
|
||||
@@ -101,7 +103,7 @@ App::get('/v1/project/usage')
|
||||
'1d' => 'Y-m-d\T00:00:00.000P',
|
||||
};
|
||||
|
||||
Authorization::skip(function () use ($dbForProject, $dbForLogs, $firstDay, $lastDay, $period, $metrics, $limit, &$total, &$stats) {
|
||||
$authorization->skip(function () use ($dbForProject, $dbForLogs, $firstDay, $lastDay, $period, $metrics, $limit, &$total, &$stats) {
|
||||
foreach ($metrics['total'] as $metric) {
|
||||
$db = ($metric === METRIC_FILES_IMAGES_TRANSFORMED) ? $dbForLogs : $dbForProject;
|
||||
|
||||
@@ -285,7 +287,7 @@ App::get('/v1/project/usage')
|
||||
}, $dbForProject->find('functions'));
|
||||
|
||||
// This total is includes free and paid SMS usage
|
||||
$authPhoneTotal = Authorization::skip(fn () => $dbForProject->sum('stats', 'value', [
|
||||
$authPhoneTotal = $authorization->skip(fn () => $dbForProject->sum('stats', 'value', [
|
||||
Query::equal('metric', [METRIC_AUTH_METHOD_PHONE]),
|
||||
Query::equal('period', ['1d']),
|
||||
Query::greaterThanEqual('time', $firstDay),
|
||||
@@ -293,7 +295,7 @@ App::get('/v1/project/usage')
|
||||
]));
|
||||
|
||||
// This estimate is only for paid SMS usage
|
||||
$authPhoneMetrics = Authorization::skip(fn () => $dbForProject->find('stats', [
|
||||
$authPhoneMetrics = $authorization->skip(fn () => $dbForProject->find('stats', [
|
||||
Query::startsWith('metric', METRIC_AUTH_METHOD_PHONE . '.'),
|
||||
Query::equal('period', ['1d']),
|
||||
Query::greaterThanEqual('time', $firstDay),
|
||||
@@ -526,8 +528,8 @@ App::put('/v1/project/variables/:variableId')
|
||||
))
|
||||
->param('variableId', '', new UID(), 'Variable unique ID.', false)
|
||||
->param('key', null, new Text(255), 'Variable key. Max length: 255 chars.', false)
|
||||
->param('value', null, new Text(8192, 0), 'Variable value. Max length: 8192 chars.', true)
|
||||
->param('secret', null, new Boolean(), 'Secret variables can be updated or deleted, but only projects can read them during build and runtime.', true)
|
||||
->param('value', null, new Nullable(new Text(8192, 0)), 'Variable value. Max length: 8192 chars.', true)
|
||||
->param('secret', null, new Nullable(new Boolean()), 'Secret variables can be updated or deleted, but only projects can read them during build and runtime.', true)
|
||||
->inject('project')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
|
||||
@@ -46,6 +46,7 @@ use Utopia\Validator\Boolean;
|
||||
use Utopia\Validator\Hostname;
|
||||
use Utopia\Validator\Integer;
|
||||
use Utopia\Validator\Multiple;
|
||||
use Utopia\Validator\Nullable;
|
||||
use Utopia\Validator\Range;
|
||||
use Utopia\Validator\Text;
|
||||
use Utopia\Validator\URL;
|
||||
@@ -678,9 +679,9 @@ App::patch('/v1/projects/:projectId/oauth2')
|
||||
))
|
||||
->param('projectId', '', new UID(), 'Project unique ID.')
|
||||
->param('provider', '', new WhiteList(\array_keys(Config::getParam('oAuthProviders')), true), 'Provider Name')
|
||||
->param('appId', null, new Text(256), 'Provider app ID. Max length: 256 chars.', true)
|
||||
->param('secret', null, new text(512), 'Provider secret key. Max length: 512 chars.', true)
|
||||
->param('enabled', null, new Boolean(), 'Provider status. Set to \'false\' to disable new session creation.', true)
|
||||
->param('appId', null, new Nullable(new Text(256)), 'Provider app ID. Max length: 256 chars.', true)
|
||||
->param('secret', null, new Nullable(new text(512)), 'Provider secret key. Max length: 512 chars.', true)
|
||||
->param('enabled', null, new Nullable(new Boolean()), 'Provider status. Set to \'false\' to disable new session creation.', true)
|
||||
->inject('response')
|
||||
->inject('dbForPlatform')
|
||||
->action(function (string $projectId, string $provider, ?string $appId, ?string $secret, ?bool $enabled, Response $response, Database $dbForPlatform) {
|
||||
@@ -1476,8 +1477,8 @@ App::post('/v1/projects/:projectId/keys')
|
||||
))
|
||||
->param('projectId', '', new UID(), 'Project unique ID.')
|
||||
->param('name', null, new Text(128), 'Key name. Max length: 128 chars.')
|
||||
->param('scopes', null, new ArrayList(new WhiteList(array_keys(Config::getParam('scopes')), true), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Key scopes list. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' scopes are allowed.')
|
||||
->param('expire', null, new DatetimeValidator(), 'Expiration time in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. Use null for unlimited expiration.', true)
|
||||
->param('scopes', null, new Nullable(new ArrayList(new WhiteList(array_keys(Config::getParam('scopes')), true), APP_LIMIT_ARRAY_PARAMS_SIZE)), 'Key scopes list. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' scopes are allowed.')
|
||||
->param('expire', null, new Nullable(new DatetimeValidator()), '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('dbForPlatform')
|
||||
->action(function (string $projectId, string $name, array $scopes, ?string $expire, Response $response, Database $dbForPlatform) {
|
||||
@@ -1615,8 +1616,8 @@ App::put('/v1/projects/:projectId/keys/:keyId')
|
||||
->param('projectId', '', new UID(), 'Project unique ID.')
|
||||
->param('keyId', '', new UID(), 'Key unique ID.')
|
||||
->param('name', null, new Text(128), 'Key name. Max length: 128 chars.')
|
||||
->param('scopes', null, new ArrayList(new WhiteList(array_keys(Config::getParam('scopes')), true), APP_LIMIT_ARRAY_PARAMS_SIZE), 'Key scopes list. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' events are allowed.')
|
||||
->param('expire', null, new DatetimeValidator(), 'Expiration time in [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) format. Use null for unlimited expiration.', true)
|
||||
->param('scopes', null, new Nullable(new ArrayList(new WhiteList(array_keys(Config::getParam('scopes')), true), APP_LIMIT_ARRAY_PARAMS_SIZE)), 'Key scopes list. Maximum of ' . APP_LIMIT_ARRAY_PARAMS_SIZE . ' events are allowed.')
|
||||
->param('expire', null, new Nullable(new DatetimeValidator()), '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('dbForPlatform')
|
||||
->action(function (string $projectId, string $keyId, string $name, array $scopes, ?string $expire, Response $response, Database $dbForPlatform) {
|
||||
|
||||
+123
-111
@@ -32,6 +32,7 @@ use Utopia\Database\Helpers\Permission;
|
||||
use Utopia\Database\Helpers\Role;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\Authorization\Input;
|
||||
use Utopia\Database\Validator\Permissions;
|
||||
use Utopia\Database\Validator\Query\Cursor;
|
||||
use Utopia\Database\Validator\UID;
|
||||
@@ -50,6 +51,7 @@ use Utopia\System\System;
|
||||
use Utopia\Validator\ArrayList;
|
||||
use Utopia\Validator\Boolean;
|
||||
use Utopia\Validator\HexColor;
|
||||
use Utopia\Validator\Nullable;
|
||||
use Utopia\Validator\Range;
|
||||
use Utopia\Validator\Text;
|
||||
use Utopia\Validator\WhiteList;
|
||||
@@ -77,7 +79,7 @@ App::post('/v1/storage/buckets')
|
||||
))
|
||||
->param('bucketId', '', new CustomId(), 'Unique Id. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
|
||||
->param('name', '', new Text(128), 'Bucket name')
|
||||
->param('permissions', null, new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE), 'An array of permission strings. By default, no user is granted with any permissions. [Learn more about permissions](https://appwrite.io/docs/permissions).', true)
|
||||
->param('permissions', null, new Nullable(new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE)), 'An array of permission strings. By default, no user is granted with any permissions. [Learn more about permissions](https://appwrite.io/docs/permissions).', true)
|
||||
->param('fileSecurity', false, new Boolean(true), 'Enables configuring permissions for individual file. A user needs one of file or bucket level permissions to access a file. [Learn more about permissions](https://appwrite.io/docs/permissions).', true)
|
||||
->param('enabled', true, new Boolean(true), 'Is bucket enabled? When set to \'disabled\', users cannot access the files in this bucket but Server SDKs with and API key can still access the bucket. No files are lost when this is toggled.', true)
|
||||
->param('maximumFileSize', fn (array $plan) => empty($plan['fileSize']) ? (int) System::getEnv('_APP_STORAGE_LIMIT', 0) : $plan['fileSize'] * 1000 * 1000, fn (array $plan) => new Range(1, empty($plan['fileSize']) ? (int) System::getEnv('_APP_STORAGE_LIMIT', 0) : $plan['fileSize'] * 1000 * 1000), 'Maximum file size allowed in bytes. Maximum allowed value is ' . Storage::human(System::getEnv('_APP_STORAGE_LIMIT', 0), 0) . '.', true, ['plan'])
|
||||
@@ -85,10 +87,11 @@ App::post('/v1/storage/buckets')
|
||||
->param('compression', Compression::NONE, new WhiteList([Compression::NONE, Compression::GZIP, Compression::ZSTD], true), 'Compression algorithm choosen for compression. Can be one of ' . Compression::NONE . ', [' . Compression::GZIP . '](https://en.wikipedia.org/wiki/Gzip), or [' . Compression::ZSTD . '](https://en.wikipedia.org/wiki/Zstd), For file size above ' . Storage::human(APP_STORAGE_READ_BUFFER, 0) . ' compression is skipped even if it\'s enabled', true)
|
||||
->param('encryption', true, new Boolean(true), 'Is encryption enabled? For file size above ' . Storage::human(APP_STORAGE_READ_BUFFER, 0) . ' encryption is skipped even if it\'s enabled', true)
|
||||
->param('antivirus', true, new Boolean(true), 'Is virus scanning enabled? For file size above ' . Storage::human(APP_LIMIT_ANTIVIRUS, 0) . ' AntiVirus scanning is skipped even if it\'s enabled', true)
|
||||
->param('transformations', true, new Boolean(true), 'Are image transformations enabled?', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForEvents')
|
||||
->action(function (string $bucketId, string $name, ?array $permissions, bool $fileSecurity, bool $enabled, int $maximumFileSize, array $allowedFileExtensions, ?string $compression, ?bool $encryption, bool $antivirus, Response $response, Database $dbForProject, Event $queueForEvents) {
|
||||
->action(function (string $bucketId, string $name, ?array $permissions, bool $fileSecurity, bool $enabled, int $maximumFileSize, array $allowedFileExtensions, ?string $compression, ?bool $encryption, bool $antivirus, bool $transformations, Response $response, Database $dbForProject, Event $queueForEvents) {
|
||||
|
||||
$bucketId = $bucketId === 'unique()' ? ID::unique() : $bucketId;
|
||||
|
||||
@@ -141,6 +144,7 @@ App::post('/v1/storage/buckets')
|
||||
'compression' => $compression,
|
||||
'encryption' => $encryption,
|
||||
'antivirus' => $antivirus,
|
||||
'transformations' => $transformations,
|
||||
'search' => implode(' ', [$bucketId, $name]),
|
||||
]));
|
||||
|
||||
@@ -290,7 +294,7 @@ App::put('/v1/storage/buckets/:bucketId')
|
||||
))
|
||||
->param('bucketId', '', new UID(), 'Bucket unique ID.')
|
||||
->param('name', null, new Text(128), 'Bucket name', false)
|
||||
->param('permissions', null, new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE), 'An array of permission strings. By default, the current permissions are inherited. [Learn more about permissions](https://appwrite.io/docs/permissions).', true)
|
||||
->param('permissions', null, new Nullable(new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE)), 'An array of permission strings. By default, the current permissions are inherited. [Learn more about permissions](https://appwrite.io/docs/permissions).', true)
|
||||
->param('fileSecurity', false, new Boolean(true), 'Enables configuring permissions for individual file. A user needs one of file or bucket level permissions to access a file. [Learn more about permissions](https://appwrite.io/docs/permissions).', true)
|
||||
->param('enabled', true, new Boolean(true), 'Is bucket enabled? When set to \'disabled\', users cannot access the files in this bucket but Server SDKs with and API key can still access the bucket. No files are lost when this is toggled.', true)
|
||||
->param('maximumFileSize', fn (array $plan) => empty($plan['fileSize']) ? (int) System::getEnv('_APP_STORAGE_LIMIT', 0) : $plan['fileSize'] * 1000 * 1000, fn (array $plan) => new Range(1, empty($plan['fileSize']) ? (int) System::getEnv('_APP_STORAGE_LIMIT', 0) : $plan['fileSize'] * 1000 * 1000), 'Maximum file size allowed in bytes. Maximum allowed value is ' . Storage::human(System::getEnv('_APP_STORAGE_LIMIT', 0), 0) . '.', true, ['plan'])
|
||||
@@ -298,10 +302,11 @@ App::put('/v1/storage/buckets/:bucketId')
|
||||
->param('compression', Compression::NONE, new WhiteList([Compression::NONE, Compression::GZIP, Compression::ZSTD], true), 'Compression algorithm choosen for compression. Can be one of ' . Compression::NONE . ', [' . Compression::GZIP . '](https://en.wikipedia.org/wiki/Gzip), or [' . Compression::ZSTD . '](https://en.wikipedia.org/wiki/Zstd), For file size above ' . Storage::human(APP_STORAGE_READ_BUFFER, 0) . ' compression is skipped even if it\'s enabled', true)
|
||||
->param('encryption', true, new Boolean(true), 'Is encryption enabled? For file size above ' . Storage::human(APP_STORAGE_READ_BUFFER, 0) . ' encryption is skipped even if it\'s enabled', true)
|
||||
->param('antivirus', true, new Boolean(true), 'Is virus scanning enabled? For file size above ' . Storage::human(APP_LIMIT_ANTIVIRUS, 0) . ' AntiVirus scanning is skipped even if it\'s enabled', true)
|
||||
->param('transformations', true, new Boolean(true), 'Are image transformations enabled?', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForEvents')
|
||||
->action(function (string $bucketId, string $name, ?array $permissions, bool $fileSecurity, bool $enabled, ?int $maximumFileSize, array $allowedFileExtensions, ?string $compression, ?bool $encryption, bool $antivirus, Response $response, Database $dbForProject, Event $queueForEvents) {
|
||||
->action(function (string $bucketId, string $name, ?array $permissions, bool $fileSecurity, bool $enabled, ?int $maximumFileSize, array $allowedFileExtensions, ?string $compression, ?bool $encryption, bool $antivirus, bool $transformations, Response $response, Database $dbForProject, Event $queueForEvents) {
|
||||
$bucket = $dbForProject->getDocument('buckets', $bucketId);
|
||||
|
||||
if ($bucket->isEmpty()) {
|
||||
@@ -315,6 +320,7 @@ App::put('/v1/storage/buckets/:bucketId')
|
||||
$encryption ??= $bucket->getAttribute('encryption', true);
|
||||
$antivirus ??= $bucket->getAttribute('antivirus', true);
|
||||
$compression ??= $bucket->getAttribute('compression', Compression::NONE);
|
||||
$transformations ??= $bucket->getAttribute('transformations', true);
|
||||
|
||||
// Map aggregate permissions into the multiple permissions they represent.
|
||||
$permissions = Permission::aggregate($permissions);
|
||||
@@ -328,7 +334,8 @@ App::put('/v1/storage/buckets/:bucketId')
|
||||
->setAttribute('enabled', $enabled)
|
||||
->setAttribute('encryption', $encryption)
|
||||
->setAttribute('compression', $compression)
|
||||
->setAttribute('antivirus', $antivirus));
|
||||
->setAttribute('antivirus', $antivirus)
|
||||
->setAttribute('transformations', $transformations));
|
||||
|
||||
$dbForProject->updateCollection('bucket_' . $bucket->getSequence(), $permissions, $fileSecurity);
|
||||
|
||||
@@ -418,7 +425,7 @@ App::post('/v1/storage/buckets/:bucketId/files')
|
||||
->param('bucketId', '', new UID(), 'Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](https://appwrite.io/docs/server/storage#createBucket).')
|
||||
->param('fileId', '', new CustomId(), 'File ID. Choose a custom ID or generate a random ID with `ID.unique()`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
|
||||
->param('file', [], new File(), 'Binary file. Appwrite SDKs provide helpers to handle file input. [Learn about file input](https://appwrite.io/docs/products/storage/upload-download#input-file).', skipValidation: true)
|
||||
->param('permissions', null, new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE, [Database::PERMISSION_READ, Database::PERMISSION_UPDATE, Database::PERMISSION_DELETE, Database::PERMISSION_WRITE]), 'An array of permission strings. By default, only the current user is granted all permissions. [Learn more about permissions](https://appwrite.io/docs/permissions).', true)
|
||||
->param('permissions', null, new Nullable(new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE, [Database::PERMISSION_READ, Database::PERMISSION_UPDATE, Database::PERMISSION_DELETE, Database::PERMISSION_WRITE])), 'An array of permission strings. By default, only the current user is granted all permissions. [Learn more about permissions](https://appwrite.io/docs/permissions).', true)
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
@@ -427,20 +434,20 @@ App::post('/v1/storage/buckets/:bucketId/files')
|
||||
->inject('mode')
|
||||
->inject('deviceForFiles')
|
||||
->inject('deviceForLocal')
|
||||
->action(function (string $bucketId, string $fileId, mixed $file, ?array $permissions, Request $request, Response $response, Database $dbForProject, Document $user, Event $queueForEvents, string $mode, Device $deviceForFiles, Device $deviceForLocal) {
|
||||
->inject('authorization')
|
||||
->action(function (string $bucketId, string $fileId, mixed $file, ?array $permissions, Request $request, Response $response, Database $dbForProject, Document $user, Event $queueForEvents, string $mode, Device $deviceForFiles, Device $deviceForLocal, Authorization $authorization) {
|
||||
|
||||
$bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
|
||||
$bucket = $authorization->skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
|
||||
|
||||
$isAPIKey = Auth::isAppUser(Authorization::getRoles());
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
|
||||
$isAPIKey = Auth::isAppUser($authorization->getRoles());
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser($authorization->getRoles());
|
||||
|
||||
if ($bucket->isEmpty() || (!$bucket->getAttribute('enabled') && !$isAPIKey && !$isPrivilegedUser)) {
|
||||
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
|
||||
}
|
||||
|
||||
$validator = new Authorization(Database::PERMISSION_CREATE);
|
||||
if (!$validator->isValid($bucket->getCreate())) {
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED);
|
||||
if (!$authorization->isValid(new Input(Database::PERMISSION_CREATE, $bucket->getCreate()))) {
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED, $authorization->getDescription());
|
||||
}
|
||||
|
||||
$allowedPermissions = [
|
||||
@@ -463,7 +470,7 @@ App::post('/v1/storage/buckets/:bucketId/files')
|
||||
}
|
||||
|
||||
// Users can only manage their own roles, API keys and Admin users can manage any
|
||||
$roles = Authorization::getRoles();
|
||||
$roles = $authorization->getRoles();
|
||||
if (!Auth::isAppUser($roles) && !Auth::isPrivilegedUser($roles)) {
|
||||
foreach (Database::PERMISSIONS as $type) {
|
||||
foreach ($permissions as $permission) {
|
||||
@@ -476,7 +483,7 @@ App::post('/v1/storage/buckets/:bucketId/files')
|
||||
$permission->getIdentifier(),
|
||||
$permission->getDimension()
|
||||
))->toString();
|
||||
if (!Authorization::isRole($role)) {
|
||||
if (!$authorization->hasRole($role)) {
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED, 'Permissions must be one of: (' . \implode(', ', $roles) . ')');
|
||||
}
|
||||
}
|
||||
@@ -696,11 +703,10 @@ App::post('/v1/storage/buckets/:bucketId/files')
|
||||
* However as with chunk upload even if we are updating, we are essentially creating a file
|
||||
* adding it's new chunk so we validate create permission instead of update
|
||||
*/
|
||||
$validator = new Authorization(Database::PERMISSION_CREATE);
|
||||
if (!$validator->isValid($bucket->getCreate())) {
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED);
|
||||
if (!$authorization->isValid(new Input(Database::PERMISSION_CREATE, $bucket->getCreate()))) {
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED, $authorization->getDescription());
|
||||
}
|
||||
$file = Authorization::skip(fn () => $dbForProject->updateDocument('bucket_' . $bucket->getSequence(), $fileId, $file));
|
||||
$file = $authorization->skip(fn () => $dbForProject->updateDocument('bucket_' . $bucket->getSequence(), $fileId, $file));
|
||||
}
|
||||
} else {
|
||||
if ($file->isEmpty()) {
|
||||
@@ -739,13 +745,12 @@ App::post('/v1/storage/buckets/:bucketId/files')
|
||||
* However as with chunk upload even if we are updating, we are essentially creating a file
|
||||
* adding it's new chunk so we validate create permission instead of update
|
||||
*/
|
||||
$validator = new Authorization(Database::PERMISSION_CREATE);
|
||||
if (!$validator->isValid($bucket->getCreate())) {
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED);
|
||||
if (!$authorization->isValid(new Input(Database::PERMISSION_CREATE, $bucket->getCreate()))) {
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED, $authorization->getDescription());
|
||||
}
|
||||
|
||||
try {
|
||||
$file = Authorization::skip(fn () => $dbForProject->updateDocument('bucket_' . $bucket->getSequence(), $fileId, $file));
|
||||
$file = $authorization->skip(fn () => $dbForProject->updateDocument('bucket_' . $bucket->getSequence(), $fileId, $file));
|
||||
} catch (NotFoundException) {
|
||||
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
|
||||
}
|
||||
@@ -789,22 +794,22 @@ App::get('/v1/storage/buckets/:bucketId/files')
|
||||
->param('total', true, new Boolean(true), 'When set to false, the total count returned will be 0 and will not be calculated.', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('authorization')
|
||||
->inject('mode')
|
||||
->action(function (string $bucketId, array $queries, string $search, bool $includeTotal, Response $response, Database $dbForProject, string $mode) {
|
||||
$bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
|
||||
->action(function (string $bucketId, array $queries, string $search, bool $includeTotal, Response $response, Database $dbForProject, Authorization $authorization, string $mode) {
|
||||
$bucket = $authorization->skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
|
||||
|
||||
$isAPIKey = Auth::isAppUser(Authorization::getRoles());
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
|
||||
$isAPIKey = Auth::isAppUser($authorization->getRoles());
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser($authorization->getRoles());
|
||||
|
||||
if ($bucket->isEmpty() || (!$bucket->getAttribute('enabled') && !$isAPIKey && !$isPrivilegedUser)) {
|
||||
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
|
||||
}
|
||||
|
||||
$fileSecurity = $bucket->getAttribute('fileSecurity', false);
|
||||
$validator = new Authorization(Database::PERMISSION_READ);
|
||||
$valid = $validator->isValid($bucket->getRead());
|
||||
$valid = $authorization->isValid(new Input(Database::PERMISSION_READ, $bucket->getRead()));
|
||||
if (!$fileSecurity && !$valid) {
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED);
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED, $authorization->getDescription());
|
||||
}
|
||||
|
||||
$queries = Query::parseQueries($queries);
|
||||
@@ -833,7 +838,7 @@ App::get('/v1/storage/buckets/:bucketId/files')
|
||||
if ($fileSecurity && !$valid) {
|
||||
$cursorDocument = $dbForProject->getDocument('bucket_' . $bucket->getSequence(), $fileId);
|
||||
} else {
|
||||
$cursorDocument = Authorization::skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getSequence(), $fileId));
|
||||
$cursorDocument = $authorization->skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getSequence(), $fileId));
|
||||
}
|
||||
|
||||
if ($cursorDocument->isEmpty()) {
|
||||
@@ -843,15 +848,13 @@ App::get('/v1/storage/buckets/:bucketId/files')
|
||||
$cursor->setValue($cursorDocument);
|
||||
}
|
||||
|
||||
$filterQueries = Query::groupByType($queries)['filters'];
|
||||
|
||||
try {
|
||||
if ($fileSecurity && !$valid) {
|
||||
$files = $dbForProject->find('bucket_' . $bucket->getSequence(), $queries);
|
||||
$total = $includeTotal ? $dbForProject->count('bucket_' . $bucket->getSequence(), $filterQueries, APP_LIMIT_COUNT) : 0;
|
||||
$total = $includeTotal ? $dbForProject->count('bucket_' . $bucket->getSequence(), $queries, APP_LIMIT_COUNT) : 0;
|
||||
} else {
|
||||
$files = Authorization::skip(fn () => $dbForProject->find('bucket_' . $bucket->getSequence(), $queries));
|
||||
$total = $includeTotal ? Authorization::skip(fn () => $dbForProject->count('bucket_' . $bucket->getSequence(), $filterQueries, APP_LIMIT_COUNT)) : 0;
|
||||
$files = $authorization->skip(fn () => $dbForProject->find('bucket_' . $bucket->getSequence(), $queries));
|
||||
$total = $includeTotal ? $authorization->skip(fn () => $dbForProject->count('bucket_' . $bucket->getSequence(), $queries, APP_LIMIT_COUNT)) : 0;
|
||||
}
|
||||
} catch (NotFoundException) {
|
||||
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
|
||||
@@ -890,28 +893,28 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId')
|
||||
->param('fileId', '', new UID(), 'File ID.')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('authorization')
|
||||
->inject('mode')
|
||||
->action(function (string $bucketId, string $fileId, Response $response, Database $dbForProject, string $mode) {
|
||||
$bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
|
||||
->action(function (string $bucketId, string $fileId, Response $response, Database $dbForProject, Authorization $authorization, string $mode) {
|
||||
$bucket = $authorization->skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
|
||||
|
||||
$isAPIKey = Auth::isAppUser(Authorization::getRoles());
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
|
||||
$isAPIKey = Auth::isAppUser($authorization->getRoles());
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser($authorization->getRoles());
|
||||
|
||||
if ($bucket->isEmpty() || (!$bucket->getAttribute('enabled') && !$isAPIKey && !$isPrivilegedUser)) {
|
||||
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
|
||||
}
|
||||
|
||||
$fileSecurity = $bucket->getAttribute('fileSecurity', false);
|
||||
$validator = new Authorization(Database::PERMISSION_READ);
|
||||
$valid = $validator->isValid($bucket->getRead());
|
||||
$valid = $authorization->isValid(new Input(Database::PERMISSION_READ, $bucket->getRead()));
|
||||
if (!$fileSecurity && !$valid) {
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED);
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED, $authorization->getDescription());
|
||||
}
|
||||
|
||||
if ($fileSecurity && !$valid) {
|
||||
$file = $dbForProject->getDocument('bucket_' . $bucket->getSequence(), $fileId);
|
||||
} else {
|
||||
$file = Authorization::skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getSequence(), $fileId));
|
||||
$file = $authorization->skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getSequence(), $fileId));
|
||||
}
|
||||
|
||||
if ($file->isEmpty()) {
|
||||
@@ -967,39 +970,43 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/preview')
|
||||
->inject('deviceForFiles')
|
||||
->inject('deviceForLocal')
|
||||
->inject('project')
|
||||
->action(function (string $bucketId, string $fileId, int $width, int $height, string $gravity, int $quality, int $borderWidth, string $borderColor, int $borderRadius, float $opacity, int $rotation, string $background, string $output, ?string $token, Request $request, Response $response, Database $dbForProject, Document $resourceToken, Device $deviceForFiles, Device $deviceForLocal, Document $project) {
|
||||
->inject('authorization')
|
||||
->action(function (string $bucketId, string $fileId, int $width, int $height, string $gravity, int $quality, int $borderWidth, string $borderColor, int $borderRadius, float $opacity, int $rotation, string $background, string $output, ?string $token, Request $request, Response $response, Database $dbForProject, Document $resourceToken, Device $deviceForFiles, Device $deviceForLocal, Document $project, Authorization $authorization) {
|
||||
|
||||
if (!\extension_loaded('imagick')) {
|
||||
throw new Exception(Exception::GENERAL_SERVER_ERROR, 'Imagick extension is missing');
|
||||
}
|
||||
|
||||
/* @type Document $bucket */
|
||||
$bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
|
||||
$bucket = $authorization->skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
|
||||
|
||||
$isAPIKey = Auth::isAppUser(Authorization::getRoles());
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
|
||||
$isAPIKey = Auth::isAppUser($authorization->getRoles());
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser($authorization->getRoles());
|
||||
|
||||
if ($bucket->isEmpty() || (!$bucket->getAttribute('enabled') && !$isAPIKey && !$isPrivilegedUser)) {
|
||||
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
|
||||
}
|
||||
|
||||
if (!$bucket->getAttribute('transformations', true) && !$isAPIKey && !$isPrivilegedUser) {
|
||||
throw new Exception(Exception::STORAGE_BUCKET_TRANSFORMATIONS_DISABLED);
|
||||
}
|
||||
|
||||
$isToken = !$resourceToken->isEmpty() && $resourceToken->getAttribute('bucketInternalId') === $bucket->getSequence();
|
||||
$fileSecurity = $bucket->getAttribute('fileSecurity', false);
|
||||
$validator = new Authorization(Database::PERMISSION_READ);
|
||||
$valid = $validator->isValid($bucket->getRead());
|
||||
$valid = $authorization->isValid(new Input(Database::PERMISSION_READ, $bucket->getRead()));
|
||||
if (!$fileSecurity && !$valid && !$isToken) {
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED);
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED, $authorization->getDescription());
|
||||
}
|
||||
|
||||
if ($fileSecurity && !$valid && !$isToken) {
|
||||
$file = $dbForProject->getDocument('bucket_' . $bucket->getSequence(), $fileId);
|
||||
} else {
|
||||
/* @type Document $file */
|
||||
$file = Authorization::skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getSequence(), $fileId));
|
||||
$file = $authorization->skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getSequence(), $fileId));
|
||||
}
|
||||
|
||||
if (!$resourceToken->isEmpty() && $resourceToken->getAttribute('fileInternalId') !== $file->getSequence()) {
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED);
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED, $authorization->getDescription());
|
||||
}
|
||||
|
||||
if ($file->isEmpty()) {
|
||||
@@ -1117,11 +1124,11 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/preview')
|
||||
$contentType = (\array_key_exists($output, $outputs)) ? $outputs[$output] : $outputs['jpg'];
|
||||
|
||||
//Do not update transformedAt if it's a console user
|
||||
if (!Auth::isPrivilegedUser(Authorization::getRoles())) {
|
||||
if (!Auth::isPrivilegedUser($authorization->getRoles())) {
|
||||
$transformedAt = $file->getAttribute('transformedAt', '');
|
||||
if (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_PROJECT_ACCESS)) > $transformedAt) {
|
||||
$file->setAttribute('transformedAt', DateTime::now());
|
||||
Authorization::skip(fn () => $dbForProject->updateDocument('bucket_' . $file->getAttribute('bucketInternalId'), $file->getId(), $file));
|
||||
$authorization->skip(fn () => $dbForProject->updateDocument('bucket_' . $file->getAttribute('bucketInternalId'), $file->getId(), $file));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1161,15 +1168,16 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/download')
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('authorization')
|
||||
->inject('mode')
|
||||
->inject('resourceToken')
|
||||
->inject('deviceForFiles')
|
||||
->action(function (string $bucketId, string $fileId, ?string $token, Request $request, Response $response, Database $dbForProject, string $mode, Document $resourceToken, Device $deviceForFiles) {
|
||||
->action(function (string $bucketId, string $fileId, ?string $token, Request $request, Response $response, Database $dbForProject, Authorization $authorization, string $mode, Document $resourceToken, Device $deviceForFiles) {
|
||||
/* @type Document $bucket */
|
||||
$bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
|
||||
$bucket = $authorization->skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
|
||||
|
||||
$isAPIKey = Auth::isAppUser(Authorization::getRoles());
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
|
||||
$isAPIKey = Auth::isAppUser($authorization->getRoles());
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser($authorization->getRoles());
|
||||
|
||||
if ($bucket->isEmpty() || (!$bucket->getAttribute('enabled') && !$isAPIKey && !$isPrivilegedUser)) {
|
||||
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
|
||||
@@ -1177,21 +1185,20 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/download')
|
||||
|
||||
$isToken = !$resourceToken->isEmpty() && $resourceToken->getAttribute('bucketInternalId') === $bucket->getSequence();
|
||||
$fileSecurity = $bucket->getAttribute('fileSecurity', false);
|
||||
$validator = new Authorization(Database::PERMISSION_READ);
|
||||
$valid = $validator->isValid($bucket->getRead());
|
||||
$valid = $authorization->isValid(new Input(Database::PERMISSION_READ, $bucket->getRead()));
|
||||
if (!$fileSecurity && !$valid && !$isToken) {
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED);
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED, $authorization->getDescription());
|
||||
}
|
||||
|
||||
if ($fileSecurity && !$valid && !$isToken) {
|
||||
$file = $dbForProject->getDocument('bucket_' . $bucket->getSequence(), $fileId);
|
||||
} else {
|
||||
/* @type Document $file */
|
||||
$file = Authorization::skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getSequence(), $fileId));
|
||||
$file = $authorization->skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getSequence(), $fileId));
|
||||
}
|
||||
|
||||
if (!$resourceToken->isEmpty() && $resourceToken->getAttribute('fileInternalId') !== $file->getSequence()) {
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED);
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED, $authorization->getDescription());
|
||||
}
|
||||
|
||||
if ($file->isEmpty()) {
|
||||
@@ -1325,12 +1332,13 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/view')
|
||||
->inject('mode')
|
||||
->inject('resourceToken')
|
||||
->inject('deviceForFiles')
|
||||
->action(function (string $bucketId, string $fileId, ?string $token, Response $response, Request $request, Database $dbForProject, string $mode, Document $resourceToken, Device $deviceForFiles) {
|
||||
->inject('authorization')
|
||||
->action(function (string $bucketId, string $fileId, ?string $token, Response $response, Request $request, Database $dbForProject, string $mode, Document $resourceToken, Device $deviceForFiles, Authorization $authorization) {
|
||||
/* @type Document $bucket */
|
||||
$bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
|
||||
$bucket = $authorization->skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
|
||||
|
||||
$isAPIKey = Auth::isAppUser(Authorization::getRoles());
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
|
||||
$isAPIKey = Auth::isAppUser($authorization->getRoles());
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser($authorization->getRoles());
|
||||
|
||||
if ($bucket->isEmpty() || (!$bucket->getAttribute('enabled') && !$isAPIKey && !$isPrivilegedUser)) {
|
||||
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
|
||||
@@ -1338,21 +1346,20 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/view')
|
||||
|
||||
$isToken = !$resourceToken->isEmpty() && $resourceToken->getAttribute('bucketInternalId') === $bucket->getSequence();
|
||||
$fileSecurity = $bucket->getAttribute('fileSecurity', false);
|
||||
$validator = new Authorization(Database::PERMISSION_READ);
|
||||
$valid = $validator->isValid($bucket->getRead());
|
||||
$valid = $authorization->isValid(new Input(Database::PERMISSION_READ, $bucket->getRead()));
|
||||
if (!$fileSecurity && !$valid && !$isToken) {
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED);
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED, $authorization->getDescription());
|
||||
}
|
||||
|
||||
if ($fileSecurity && !$valid && !$isToken) {
|
||||
$file = $dbForProject->getDocument('bucket_' . $bucket->getSequence(), $fileId);
|
||||
} else {
|
||||
/* @type Document $file */
|
||||
$file = Authorization::skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getSequence(), $fileId));
|
||||
$file = $authorization->skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getSequence(), $fileId));
|
||||
}
|
||||
|
||||
if (!$resourceToken->isEmpty() && $resourceToken->getAttribute('fileInternalId') !== $file->getSequence()) {
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED);
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED, $authorization->getDescription());
|
||||
}
|
||||
|
||||
if ($file->isEmpty()) {
|
||||
@@ -1477,18 +1484,18 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/push')
|
||||
->inject('response')
|
||||
->inject('request')
|
||||
->inject('dbForProject')
|
||||
->inject('dbForPlatform')
|
||||
->inject('project')
|
||||
->inject('mode')
|
||||
->inject('deviceForFiles')
|
||||
->action(function (string $bucketId, string $fileId, string $jwt, Response $response, Request $request, Database $dbForProject, Document $project, string $mode, Device $deviceForFiles) {
|
||||
$bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
|
||||
|
||||
->inject('authorization')
|
||||
->action(function (string $bucketId, string $fileId, string $jwt, Response $response, Request $request, Database $dbForProject, Database $dbForPlatform, Document $project, string $mode, Device $deviceForFiles, Authorization $authorization) {
|
||||
$decoder = new JWT(System::getEnv('_APP_OPENSSL_KEY_V1'), 'HS256', 3600, 0);
|
||||
|
||||
try {
|
||||
$decoded = $decoder->decode($jwt);
|
||||
} catch (JWTException) {
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED);
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED, $authorization->getDescription());
|
||||
}
|
||||
|
||||
if (
|
||||
@@ -1496,18 +1503,21 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/push')
|
||||
$decoded['bucketId'] !== $bucketId ||
|
||||
$decoded['fileId'] !== $fileId
|
||||
) {
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED);
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED, $authorization->getDescription());
|
||||
}
|
||||
|
||||
$isAPIKey = Auth::isAppUser(Authorization::getRoles());
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
|
||||
$isInternal = $decoded['internal'] ?? false;
|
||||
$dbForProject = $isInternal ? $dbForPlatform : $dbForProject;
|
||||
|
||||
$isAPIKey = Auth::isAppUser($authorization->getRoles());
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser($authorization->getRoles());
|
||||
|
||||
$bucket = $authorization->skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
|
||||
if ($bucket->isEmpty() || (!$bucket->getAttribute('enabled') && !$isAPIKey && !$isPrivilegedUser)) {
|
||||
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
|
||||
}
|
||||
|
||||
$file = Authorization::skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getSequence(), $fileId));
|
||||
|
||||
$file = $authorization->skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getSequence(), $fileId));
|
||||
if ($file->isEmpty()) {
|
||||
throw new Exception(Exception::STORAGE_FILE_NOT_FOUND);
|
||||
}
|
||||
@@ -1515,7 +1525,6 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/push')
|
||||
$mimes = Config::getParam('storage-mimes');
|
||||
|
||||
$path = $file->getAttribute('path', '');
|
||||
|
||||
if (!$deviceForFiles->exists($path)) {
|
||||
throw new Exception(Exception::STORAGE_FILE_NOT_FOUND, 'File not found in ' . $path);
|
||||
}
|
||||
@@ -1645,33 +1654,33 @@ App::put('/v1/storage/buckets/:bucketId/files/:fileId')
|
||||
))
|
||||
->param('bucketId', '', new UID(), 'Storage bucket unique ID. You can create a new storage bucket using the Storage service [server integration](https://appwrite.io/docs/server/storage#createBucket).')
|
||||
->param('fileId', '', new UID(), 'File unique ID.')
|
||||
->param('name', null, new Text(255), 'Name of the file', true)
|
||||
->param('permissions', null, new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE, [Database::PERMISSION_READ, Database::PERMISSION_UPDATE, Database::PERMISSION_DELETE, Database::PERMISSION_WRITE]), 'An array of permission string. By default, the current permissions are inherited. [Learn more about permissions](https://appwrite.io/docs/permissions).', true)
|
||||
->param('name', null, new Nullable(new Text(255)), 'Name of the file', true)
|
||||
->param('permissions', null, new Nullable(new Permissions(APP_LIMIT_ARRAY_PARAMS_SIZE, [Database::PERMISSION_READ, Database::PERMISSION_UPDATE, Database::PERMISSION_DELETE, Database::PERMISSION_WRITE])), 'An array of permission string. By default, the current permissions are inherited. [Learn more about permissions](https://appwrite.io/docs/permissions).', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('user')
|
||||
->inject('mode')
|
||||
->inject('queueForEvents')
|
||||
->action(function (string $bucketId, string $fileId, ?string $name, ?array $permissions, Response $response, Database $dbForProject, Document $user, string $mode, Event $queueForEvents) {
|
||||
->inject('authorization')
|
||||
->action(function (string $bucketId, string $fileId, ?string $name, ?array $permissions, Response $response, Database $dbForProject, Document $user, string $mode, Event $queueForEvents, Authorization $authorization) {
|
||||
|
||||
$bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
|
||||
$bucket = $authorization->skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
|
||||
|
||||
$isAPIKey = Auth::isAppUser(Authorization::getRoles());
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
|
||||
$isAPIKey = Auth::isAppUser($authorization->getRoles());
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser($authorization->getRoles());
|
||||
|
||||
if ($bucket->isEmpty() || (!$bucket->getAttribute('enabled') && !$isAPIKey && !$isPrivilegedUser)) {
|
||||
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
|
||||
}
|
||||
|
||||
$fileSecurity = $bucket->getAttribute('fileSecurity', false);
|
||||
$validator = new Authorization(Database::PERMISSION_UPDATE);
|
||||
$valid = $validator->isValid($bucket->getUpdate());
|
||||
$valid = $authorization->isValid(new Input(Database::PERMISSION_UPDATE, $bucket->getUpdate()));
|
||||
if (!$fileSecurity && !$valid) {
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED);
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED, $authorization->getDescription());
|
||||
}
|
||||
|
||||
// Read permission should not be required for update
|
||||
$file = Authorization::skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getSequence(), $fileId));
|
||||
$file = $authorization->skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getSequence(), $fileId));
|
||||
|
||||
if ($file->isEmpty()) {
|
||||
throw new Exception(Exception::STORAGE_FILE_NOT_FOUND);
|
||||
@@ -1685,7 +1694,7 @@ App::put('/v1/storage/buckets/:bucketId/files/:fileId')
|
||||
]);
|
||||
|
||||
// Users can only manage their own roles, API keys and Admin users can manage any
|
||||
$roles = Authorization::getRoles();
|
||||
$roles = $authorization->getRoles();
|
||||
if (!Auth::isAppUser($roles) && !Auth::isPrivilegedUser($roles) && !\is_null($permissions)) {
|
||||
foreach (Database::PERMISSIONS as $type) {
|
||||
foreach ($permissions as $permission) {
|
||||
@@ -1698,7 +1707,7 @@ App::put('/v1/storage/buckets/:bucketId/files/:fileId')
|
||||
$permission->getIdentifier(),
|
||||
$permission->getDimension()
|
||||
))->toString();
|
||||
if (!Authorization::isRole($role)) {
|
||||
if (!$authorization->hasRole($role)) {
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED, 'Permissions must be one of: (' . \implode(', ', $roles) . ')');
|
||||
}
|
||||
}
|
||||
@@ -1719,7 +1728,7 @@ App::put('/v1/storage/buckets/:bucketId/files/:fileId')
|
||||
if ($fileSecurity && !$valid) {
|
||||
$file = $dbForProject->updateDocument('bucket_' . $bucket->getSequence(), $fileId, $file);
|
||||
} else {
|
||||
$file = Authorization::skip(fn () => $dbForProject->updateDocument('bucket_' . $bucket->getSequence(), $fileId, $file));
|
||||
$file = $authorization->skip(fn () => $dbForProject->updateDocument('bucket_' . $bucket->getSequence(), $fileId, $file));
|
||||
}
|
||||
} catch (NotFoundException) {
|
||||
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
|
||||
@@ -1767,33 +1776,34 @@ App::delete('/v1/storage/buckets/:bucketId/files/:fileId')
|
||||
->inject('mode')
|
||||
->inject('deviceForFiles')
|
||||
->inject('queueForDeletes')
|
||||
->action(function (string $bucketId, string $fileId, Response $response, Database $dbForProject, Event $queueForEvents, string $mode, Device $deviceForFiles, Delete $queueForDeletes) {
|
||||
$bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
|
||||
->inject('authorization')
|
||||
->action(function (string $bucketId, string $fileId, Response $response, Database $dbForProject, Event $queueForEvents, string $mode, Device $deviceForFiles, Delete $queueForDeletes, Authorization $authorization) {
|
||||
$bucket = $authorization->skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
|
||||
|
||||
$isAPIKey = Auth::isAppUser(Authorization::getRoles());
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
|
||||
$isAPIKey = Auth::isAppUser($authorization->getRoles());
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser($authorization->getRoles());
|
||||
|
||||
if ($bucket->isEmpty() || (!$bucket->getAttribute('enabled') && !$isAPIKey && !$isPrivilegedUser)) {
|
||||
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
|
||||
}
|
||||
|
||||
$fileSecurity = $bucket->getAttribute('fileSecurity', false);
|
||||
$validator = new Authorization(Database::PERMISSION_DELETE);
|
||||
$valid = $validator->isValid($bucket->getDelete());
|
||||
$valid = $authorization->isValid(new Input(Database::PERMISSION_DELETE, $bucket->getDelete()));
|
||||
if (!$fileSecurity && !$valid) {
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED);
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED, $authorization->getDescription());
|
||||
}
|
||||
|
||||
// Read permission should not be required for delete
|
||||
$file = Authorization::skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getSequence(), $fileId));
|
||||
$file = $authorization->skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getSequence(), $fileId));
|
||||
|
||||
if ($file->isEmpty()) {
|
||||
throw new Exception(Exception::STORAGE_FILE_NOT_FOUND);
|
||||
}
|
||||
|
||||
// Make sure we don't delete the file before the document permission check occurs
|
||||
if ($fileSecurity && !$valid && !$validator->isValid($file->getDelete())) {
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED);
|
||||
$validFile = $authorization->isValid(new Input(Database::PERMISSION_DELETE, $file->getDelete()));
|
||||
if ($fileSecurity && !$valid && !$validFile) {
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED, $authorization->getDescription());
|
||||
}
|
||||
|
||||
$deviceDeleted = false;
|
||||
@@ -1817,7 +1827,7 @@ App::delete('/v1/storage/buckets/:bucketId/files/:fileId')
|
||||
if ($fileSecurity && !$valid) {
|
||||
$deleted = $dbForProject->deleteDocument('bucket_' . $bucket->getSequence(), $fileId);
|
||||
} else {
|
||||
$deleted = Authorization::skip(fn () => $dbForProject->deleteDocument('bucket_' . $bucket->getSequence(), $fileId));
|
||||
$deleted = $authorization->skip(fn () => $dbForProject->deleteDocument('bucket_' . $bucket->getSequence(), $fileId));
|
||||
}
|
||||
} catch (NotFoundException) {
|
||||
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
|
||||
@@ -1862,7 +1872,8 @@ App::get('/v1/storage/usage')
|
||||
->param('range', '30d', new WhiteList(['24h', '30d', '90d'], true), 'Date range.', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->action(function (string $range, Response $response, Database $dbForProject) {
|
||||
->inject('authorization')
|
||||
->action(function (string $range, Response $response, Database $dbForProject, Authorization $authorization) {
|
||||
|
||||
$periods = Config::getParam('usage', []);
|
||||
$stats = $usage = [];
|
||||
@@ -1874,7 +1885,7 @@ App::get('/v1/storage/usage')
|
||||
];
|
||||
|
||||
$total = [];
|
||||
Authorization::skip(function () use ($dbForProject, $days, $metrics, &$stats, &$total) {
|
||||
$authorization->skip(function () use ($dbForProject, $days, $metrics, &$stats, &$total) {
|
||||
foreach ($metrics as $metric) {
|
||||
$result = $dbForProject->findOne('stats', [
|
||||
Query::equal('metric', [$metric]),
|
||||
@@ -1952,7 +1963,8 @@ App::get('/v1/storage/:bucketId/usage')
|
||||
->inject('project')
|
||||
->inject('dbForProject')
|
||||
->inject('getLogsDB')
|
||||
->action(function (string $bucketId, string $range, Response $response, Document $project, Database $dbForProject, callable $getLogsDB) {
|
||||
->inject('authorization')
|
||||
->action(function (string $bucketId, string $range, Response $response, Document $project, Database $dbForProject, callable $getLogsDB, Authorization $authorization) {
|
||||
|
||||
$dbForLogs = call_user_func($getLogsDB, $project);
|
||||
$bucket = $dbForProject->getDocument('buckets', $bucketId);
|
||||
@@ -1970,7 +1982,7 @@ App::get('/v1/storage/:bucketId/usage')
|
||||
str_replace('{bucketInternalId}', $bucket->getSequence(), METRIC_BUCKET_ID_FILES_IMAGES_TRANSFORMED),
|
||||
];
|
||||
|
||||
Authorization::skip(function () use ($dbForProject, $dbForLogs, $bucket, $days, $metrics, &$stats) {
|
||||
$authorization->skip(function () use ($dbForProject, $dbForLogs, $bucket, $days, $metrics, &$stats) {
|
||||
foreach ($metrics as $metric) {
|
||||
$db = ($metric === str_replace('{bucketInternalId}', $bucket->getSequence(), METRIC_BUCKET_ID_FILES_IMAGES_TRANSFORMED))
|
||||
? $dbForLogs
|
||||
|
||||
@@ -10,7 +10,7 @@ use Appwrite\Event\Mail;
|
||||
use Appwrite\Event\Messaging;
|
||||
use Appwrite\Event\StatsUsage;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Network\Validator\Email;
|
||||
use Appwrite\Network\Validator\Email as EmailValidator;
|
||||
use Appwrite\Network\Validator\Redirect;
|
||||
use Appwrite\Platform\Workers\Deletes;
|
||||
use Appwrite\SDK\AuthType;
|
||||
@@ -48,6 +48,7 @@ use Utopia\Database\Validator\Query\Cursor;
|
||||
use Utopia\Database\Validator\Query\Limit;
|
||||
use Utopia\Database\Validator\Query\Offset;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Emails\Email;
|
||||
use Utopia\Locale\Locale;
|
||||
use Utopia\System\System;
|
||||
use Utopia\Validator\ArrayList;
|
||||
@@ -83,16 +84,17 @@ App::post('/v1/teams')
|
||||
->inject('response')
|
||||
->inject('user')
|
||||
->inject('dbForProject')
|
||||
->inject('authorization')
|
||||
->inject('queueForEvents')
|
||||
->action(function (string $teamId, string $name, array $roles, Response $response, Document $user, Database $dbForProject, Event $queueForEvents) {
|
||||
->action(function (string $teamId, string $name, array $roles, Response $response, Document $user, Database $dbForProject, Authorization $authorization, Event $queueForEvents) {
|
||||
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
|
||||
$isAppUser = Auth::isAppUser(Authorization::getRoles());
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser($authorization->getRoles());
|
||||
$isAppUser = Auth::isAppUser($authorization->getRoles());
|
||||
|
||||
$teamId = $teamId == 'unique()' ? ID::unique() : $teamId;
|
||||
|
||||
try {
|
||||
$team = Authorization::skip(fn () => $dbForProject->createDocument('teams', new Document([
|
||||
$team = $authorization->skip(fn () => $dbForProject->createDocument('teams', new Document([
|
||||
'$id' => $teamId,
|
||||
'$permissions' => [
|
||||
Permission::read(Role::team($teamId)),
|
||||
@@ -468,7 +470,7 @@ App::post('/v1/teams/:teamId/memberships')
|
||||
))
|
||||
->label('abuse-limit', 10)
|
||||
->param('teamId', '', new UID(), 'Team ID.')
|
||||
->param('email', '', new Email(), 'Email of the new team member.', true)
|
||||
->param('email', '', new EmailValidator(), 'Email of the new team member.', true)
|
||||
->param('userId', '', new UID(), 'ID of the user to be added to a team.', true)
|
||||
->param('phone', '', new Phone(), 'Phone number. Format this number with a leading \'+\' and a country code, e.g., +16175551212.', true)
|
||||
->param('roles', [], function (Document $project) {
|
||||
@@ -488,6 +490,7 @@ App::post('/v1/teams/:teamId/memberships')
|
||||
->inject('project')
|
||||
->inject('user')
|
||||
->inject('dbForProject')
|
||||
->inject('authorization')
|
||||
->inject('locale')
|
||||
->inject('queueForMails')
|
||||
->inject('queueForMessaging')
|
||||
@@ -495,9 +498,9 @@ App::post('/v1/teams/:teamId/memberships')
|
||||
->inject('timelimit')
|
||||
->inject('queueForStatsUsage')
|
||||
->inject('plan')
|
||||
->action(function (string $teamId, string $email, string $userId, string $phone, array $roles, string $url, string $name, Response $response, Document $project, Document $user, Database $dbForProject, Locale $locale, Mail $queueForMails, Messaging $queueForMessaging, Event $queueForEvents, callable $timelimit, StatsUsage $queueForStatsUsage, array $plan) {
|
||||
$isAppUser = Auth::isAppUser(Authorization::getRoles());
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
|
||||
->action(function (string $teamId, string $email, string $userId, string $phone, array $roles, string $url, string $name, Response $response, Document $project, Document $user, Database $dbForProject, Authorization $authorization, Locale $locale, Mail $queueForMails, Messaging $queueForMessaging, Event $queueForEvents, callable $timelimit, StatsUsage $queueForStatsUsage, array $plan) {
|
||||
$isAppUser = Auth::isAppUser($authorization->getRoles());
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser($authorization->getRoles());
|
||||
|
||||
$url = htmlentities($url);
|
||||
if (empty($url)) {
|
||||
@@ -566,9 +569,15 @@ App::post('/v1/teams/:teamId/memberships')
|
||||
throw new Exception(Exception::USER_EMAIL_ALREADY_EXISTS);
|
||||
}
|
||||
|
||||
try {
|
||||
$emailCanonical = new Email($email);
|
||||
} catch (Throwable) {
|
||||
$emailCanonical = null;
|
||||
}
|
||||
|
||||
try {
|
||||
$userId = ID::unique();
|
||||
$invitee = Authorization::skip(fn () => $dbForProject->createDocument('users', new Document([
|
||||
$invitee = $authorization->skip(fn () => $dbForProject->createDocument('users', new Document([
|
||||
'$id' => $userId,
|
||||
'$permissions' => [
|
||||
Permission::read(Role::any()),
|
||||
@@ -598,13 +607,18 @@ App::post('/v1/teams/:teamId/memberships')
|
||||
'tokens' => null,
|
||||
'memberships' => null,
|
||||
'search' => implode(' ', [$userId, $email, $name]),
|
||||
'emailCanonical' => $emailCanonical?->getCanonical(),
|
||||
'emailIsCanonical' => $emailCanonical?->isCanonicalSupported(),
|
||||
'emailIsCorporate' => $emailCanonical?->isCorporate(),
|
||||
'emailIsDisposable' => $emailCanonical?->isDisposable(),
|
||||
'emailIsFree' => $emailCanonical?->isFree(),
|
||||
])));
|
||||
} catch (Duplicate $th) {
|
||||
throw new Exception(Exception::USER_ALREADY_EXISTS);
|
||||
}
|
||||
}
|
||||
|
||||
$isOwner = Authorization::isRole('team:' . $team->getId() . '/owner');
|
||||
$isOwner = $authorization->hasRole('team:' . $team->getId() . '/owner');
|
||||
|
||||
if (!$isOwner && !$isPrivilegedUser && !$isAppUser) { // Not owner, not admin, not app (server)
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED, 'User is not allowed to send invitations for this team');
|
||||
@@ -640,11 +654,11 @@ App::post('/v1/teams/:teamId/memberships')
|
||||
]);
|
||||
|
||||
$membership = ($isPrivilegedUser || $isAppUser) ?
|
||||
Authorization::skip(fn () => $dbForProject->createDocument('memberships', $membership)) :
|
||||
$authorization->skip(fn () => $dbForProject->createDocument('memberships', $membership)) :
|
||||
$dbForProject->createDocument('memberships', $membership);
|
||||
|
||||
if ($isPrivilegedUser || $isAppUser) {
|
||||
Authorization::skip(fn () => $dbForProject->increaseDocumentAttribute('teams', $team->getId(), 'total', 1));
|
||||
$authorization->skip(fn () => $dbForProject->increaseDocumentAttribute('teams', $team->getId(), 'total', 1));
|
||||
}
|
||||
|
||||
} elseif ($membership->getAttribute('confirm') === false) {
|
||||
@@ -657,7 +671,7 @@ App::post('/v1/teams/:teamId/memberships')
|
||||
}
|
||||
|
||||
$membership = ($isPrivilegedUser || $isAppUser) ?
|
||||
Authorization::skip(fn () => $dbForProject->updateDocument('memberships', $membership->getId(), $membership)) :
|
||||
$authorization->skip(fn () => $dbForProject->updateDocument('memberships', $membership->getId(), $membership)) :
|
||||
$dbForProject->updateDocument('memberships', $membership->getId(), $membership);
|
||||
} else {
|
||||
throw new Exception(Exception::MEMBERSHIP_ALREADY_CONFIRMED);
|
||||
@@ -844,7 +858,8 @@ App::get('/v1/teams/:teamId/memberships')
|
||||
->inject('response')
|
||||
->inject('project')
|
||||
->inject('dbForProject')
|
||||
->action(function (string $teamId, array $queries, string $search, bool $includeTotal, Response $response, Document $project, Database $dbForProject) {
|
||||
->inject('authorization')
|
||||
->action(function (string $teamId, array $queries, string $search, bool $includeTotal, Response $response, Document $project, Database $dbForProject, Authorization $authorization) {
|
||||
$team = $dbForProject->getDocument('teams', $teamId);
|
||||
|
||||
if ($team->isEmpty()) {
|
||||
@@ -914,7 +929,7 @@ App::get('/v1/teams/:teamId/memberships')
|
||||
'mfa' => $project->getAttribute('auths', [])['membershipsMfa'] ?? true,
|
||||
];
|
||||
|
||||
$roles = Authorization::getRoles();
|
||||
$roles = $authorization->getRoles();
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser($roles);
|
||||
$isAppUser = Auth::isAppUser($roles);
|
||||
|
||||
@@ -985,7 +1000,8 @@ App::get('/v1/teams/:teamId/memberships/:membershipId')
|
||||
->inject('response')
|
||||
->inject('project')
|
||||
->inject('dbForProject')
|
||||
->action(function (string $teamId, string $membershipId, Response $response, Document $project, Database $dbForProject) {
|
||||
->inject('authorization')
|
||||
->action(function (string $teamId, string $membershipId, Response $response, Document $project, Database $dbForProject, Authorization $authorization) {
|
||||
|
||||
$team = $dbForProject->getDocument('teams', $teamId);
|
||||
|
||||
@@ -1005,7 +1021,7 @@ App::get('/v1/teams/:teamId/memberships/:membershipId')
|
||||
'mfa' => $project->getAttribute('auths', [])['membershipsMfa'] ?? true,
|
||||
];
|
||||
|
||||
$roles = Authorization::getRoles();
|
||||
$roles = $authorization->getRoles();
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser($roles);
|
||||
$isAppUser = Auth::isAppUser($roles);
|
||||
|
||||
@@ -1084,8 +1100,9 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId')
|
||||
->inject('user')
|
||||
->inject('project')
|
||||
->inject('dbForProject')
|
||||
->inject('authorization')
|
||||
->inject('queueForEvents')
|
||||
->action(function (string $teamId, string $membershipId, array $roles, Request $request, Response $response, Document $user, Document $project, Database $dbForProject, Event $queueForEvents) {
|
||||
->action(function (string $teamId, string $membershipId, array $roles, Request $request, Response $response, Document $user, Document $project, Database $dbForProject, Authorization $authorization, Event $queueForEvents) {
|
||||
|
||||
$team = $dbForProject->getDocument('teams', $teamId);
|
||||
if ($team->isEmpty()) {
|
||||
@@ -1102,9 +1119,9 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId')
|
||||
throw new Exception(Exception::USER_NOT_FOUND);
|
||||
}
|
||||
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
|
||||
$isAppUser = Auth::isAppUser(Authorization::getRoles());
|
||||
$isOwner = Authorization::isRole('team:' . $team->getId() . '/owner');
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser($authorization->getRoles());
|
||||
$isAppUser = Auth::isAppUser($authorization->getRoles());
|
||||
$isOwner = $authorization->hasRole('team:' . $team->getId() . '/owner');
|
||||
|
||||
if ($project->getId() === 'console') {
|
||||
// Quick check: fetch up to 2 owners to determine if only one exists
|
||||
@@ -1185,10 +1202,11 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
|
||||
->inject('response')
|
||||
->inject('user')
|
||||
->inject('dbForProject')
|
||||
->inject('authorization')
|
||||
->inject('project')
|
||||
->inject('geodb')
|
||||
->inject('queueForEvents')
|
||||
->action(function (string $teamId, string $membershipId, string $userId, string $secret, Request $request, Response $response, Document $user, Database $dbForProject, Document $project, Reader $geodb, Event $queueForEvents) {
|
||||
->action(function (string $teamId, string $membershipId, string $userId, string $secret, Request $request, Response $response, Document $user, Database $dbForProject, Authorization $authorization, Document $project, Reader $geodb, Event $queueForEvents) {
|
||||
$protocol = $request->getProtocol();
|
||||
|
||||
$membership = $dbForProject->getDocument('memberships', $membershipId);
|
||||
@@ -1197,7 +1215,7 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
|
||||
throw new Exception(Exception::MEMBERSHIP_NOT_FOUND);
|
||||
}
|
||||
|
||||
$team = Authorization::skip(fn () => $dbForProject->getDocument('teams', $teamId));
|
||||
$team = $authorization->skip(fn () => $dbForProject->getDocument('teams', $teamId));
|
||||
|
||||
if ($team->isEmpty()) {
|
||||
throw new Exception(Exception::TEAM_NOT_FOUND);
|
||||
@@ -1233,11 +1251,11 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
|
||||
->setAttribute('confirm', true)
|
||||
;
|
||||
|
||||
Authorization::skip(fn () => $dbForProject->updateDocument('users', $user->getId(), $user->setAttribute('emailVerification', true)));
|
||||
$authorization->skip(fn () => $dbForProject->updateDocument('users', $user->getId(), $user->setAttribute('emailVerification', true)));
|
||||
|
||||
// Create session for the user if not logged in
|
||||
if (!$hasSession) {
|
||||
Authorization::setRole(Role::user($user->getId())->toString());
|
||||
$authorization->addRole(Role::user($user->getId())->toString());
|
||||
|
||||
$detector = new Detector($request->getUserAgent('UNKNOWN'));
|
||||
$record = $geodb->get($request->getIP());
|
||||
@@ -1265,7 +1283,7 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
|
||||
|
||||
$session = $dbForProject->createDocument('sessions', $session);
|
||||
|
||||
Authorization::setRole(Role::user($userId)->toString());
|
||||
$authorization->addRole(Role::user($userId)->toString());
|
||||
|
||||
if (!Config::getParam('domainVerification')) {
|
||||
$response->addHeader('X-Fallback-Cookies', \json_encode([Auth::$cookieName => Auth::encodeSession($user->getId(), $secret)]));
|
||||
@@ -1298,7 +1316,7 @@ App::patch('/v1/teams/:teamId/memberships/:membershipId/status')
|
||||
|
||||
$dbForProject->purgeCachedDocument('users', $user->getId());
|
||||
|
||||
Authorization::skip(fn () => $dbForProject->increaseDocumentAttribute('teams', $team->getId(), 'total', 1));
|
||||
$authorization->skip(fn () => $dbForProject->increaseDocumentAttribute('teams', $team->getId(), 'total', 1));
|
||||
|
||||
$queueForEvents
|
||||
->setParam('userId', $user->getId())
|
||||
@@ -1342,8 +1360,9 @@ App::delete('/v1/teams/:teamId/memberships/:membershipId')
|
||||
->inject('project')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('authorization')
|
||||
->inject('queueForEvents')
|
||||
->action(function (string $teamId, string $membershipId, Document $user, Document $project, Response $response, Database $dbForProject, Event $queueForEvents) {
|
||||
->action(function (string $teamId, string $membershipId, Document $user, Document $project, Response $response, Database $dbForProject, Authorization $authorization, Event $queueForEvents) {
|
||||
|
||||
$membership = $dbForProject->getDocument('memberships', $membershipId);
|
||||
|
||||
@@ -1401,7 +1420,7 @@ App::delete('/v1/teams/:teamId/memberships/:membershipId')
|
||||
$dbForProject->purgeCachedDocument('users', $profile->getId());
|
||||
|
||||
if ($membership->getAttribute('confirm')) { // Count only confirmed members
|
||||
Authorization::skip(fn () => $dbForProject->decreaseDocumentAttribute('teams', $team->getId(), 'total', 1, 0));
|
||||
$authorization->skip(fn () => $dbForProject->decreaseDocumentAttribute('teams', $team->getId(), 'total', 1, 0));
|
||||
}
|
||||
|
||||
$queueForEvents
|
||||
|
||||
@@ -16,7 +16,7 @@ use Appwrite\Event\Delete;
|
||||
use Appwrite\Event\Event;
|
||||
use Appwrite\Extend\Exception;
|
||||
use Appwrite\Hooks\Hooks;
|
||||
use Appwrite\Network\Validator\Email;
|
||||
use Appwrite\Network\Validator\Email as EmailValidator;
|
||||
use Appwrite\SDK\AuthType;
|
||||
use Appwrite\SDK\ContentType;
|
||||
use Appwrite\SDK\Deprecated;
|
||||
@@ -49,12 +49,14 @@ use Utopia\Database\Validator\Query\Cursor;
|
||||
use Utopia\Database\Validator\Query\Limit;
|
||||
use Utopia\Database\Validator\Query\Offset;
|
||||
use Utopia\Database\Validator\UID;
|
||||
use Utopia\Emails\Email;
|
||||
use Utopia\Locale\Locale;
|
||||
use Utopia\System\System;
|
||||
use Utopia\Validator\ArrayList;
|
||||
use Utopia\Validator\Assoc;
|
||||
use Utopia\Validator\Boolean;
|
||||
use Utopia\Validator\Integer;
|
||||
use Utopia\Validator\Nullable;
|
||||
use Utopia\Validator\Range;
|
||||
use Utopia\Validator\Text;
|
||||
use Utopia\Validator\WhiteList;
|
||||
@@ -97,6 +99,12 @@ function createUser(string $hash, mixed $hashOptions, string $userId, ?string $e
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
$emailCanonical = new Email($email);
|
||||
} catch (Throwable) {
|
||||
$emailCanonical = null;
|
||||
}
|
||||
|
||||
$password = (!empty($password)) ? ($hash === 'plaintext' ? Auth::passwordHash($password, $hash, $hashOptionsObject) : $password) : null;
|
||||
$user = new Document([
|
||||
'$id' => $userId,
|
||||
@@ -124,6 +132,11 @@ function createUser(string $hash, mixed $hashOptions, string $userId, ?string $e
|
||||
'tokens' => null,
|
||||
'memberships' => null,
|
||||
'search' => implode(' ', [$userId, $email, $phone, $name]),
|
||||
'emailCanonical' => $emailCanonical?->getCanonical(),
|
||||
'emailIsCanonical' => $emailCanonical?->isCanonicalSupported(),
|
||||
'emailIsCorporate' => $emailCanonical?->isCorporate(),
|
||||
'emailIsDisposable' => $emailCanonical?->isDisposable(),
|
||||
'emailIsFree' => $emailCanonical?->isFree(),
|
||||
]);
|
||||
|
||||
if ($hash === 'plaintext') {
|
||||
@@ -208,8 +221,8 @@ App::post('/v1/users')
|
||||
]
|
||||
))
|
||||
->param('userId', '', new CustomId(), '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.')
|
||||
->param('email', null, new Email(), 'User email.', true)
|
||||
->param('phone', null, new Phone(), 'Phone number. Format this number with a leading \'+\' and a country code, e.g., +16175551212.', true)
|
||||
->param('email', null, new Nullable(new EmailValidator()), 'User email.', true)
|
||||
->param('phone', null, new Nullable(new Phone()), 'Phone number. Format this number with a leading \'+\' and a country code, e.g., +16175551212.', true)
|
||||
->param('password', '', fn ($project, $passwordsDictionary) => new PasswordDictionary($passwordsDictionary, $project->getAttribute('auths', [])['passwordDictionary'] ?? false), 'Plain text user password. Must be at least 8 chars.', true, ['project', 'passwordsDictionary'])
|
||||
->param('name', '', new Text(128), 'User name. Max length: 128 chars.', true)
|
||||
->inject('response')
|
||||
@@ -243,7 +256,7 @@ App::post('/v1/users/bcrypt')
|
||||
]
|
||||
))
|
||||
->param('userId', '', new CustomId(), '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.')
|
||||
->param('email', '', new Email(), 'User email.')
|
||||
->param('email', '', new EmailValidator(), 'User email.')
|
||||
->param('password', '', new Password(), 'User password hashed using Bcrypt.')
|
||||
->param('name', '', new Text(128), 'User name. Max length: 128 chars.', true)
|
||||
->inject('response')
|
||||
@@ -278,7 +291,7 @@ App::post('/v1/users/md5')
|
||||
]
|
||||
))
|
||||
->param('userId', '', new CustomId(), '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.')
|
||||
->param('email', '', new Email(), 'User email.')
|
||||
->param('email', '', new EmailValidator(), 'User email.')
|
||||
->param('password', '', new Password(), 'User password hashed using MD5.')
|
||||
->param('name', '', new Text(128), 'User name. Max length: 128 chars.', true)
|
||||
->inject('response')
|
||||
@@ -313,7 +326,7 @@ App::post('/v1/users/argon2')
|
||||
]
|
||||
))
|
||||
->param('userId', '', new CustomId(), '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.')
|
||||
->param('email', '', new Email(), 'User email.')
|
||||
->param('email', '', new EmailValidator(), 'User email.')
|
||||
->param('password', '', new Password(), 'User password hashed using Argon2.')
|
||||
->param('name', '', new Text(128), 'User name. Max length: 128 chars.', true)
|
||||
->inject('response')
|
||||
@@ -348,7 +361,7 @@ App::post('/v1/users/sha')
|
||||
]
|
||||
))
|
||||
->param('userId', '', new CustomId(), '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.')
|
||||
->param('email', '', new Email(), 'User email.')
|
||||
->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('name', '', new Text(128), 'User name. Max length: 128 chars.', true)
|
||||
@@ -390,7 +403,7 @@ App::post('/v1/users/phpass')
|
||||
]
|
||||
))
|
||||
->param('userId', '', new CustomId(), 'User ID. Choose a custom ID or pass the string `ID.unique()`to auto generate it. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can\'t start with a special char. Max length is 36 chars.')
|
||||
->param('email', '', new Email(), 'User email.')
|
||||
->param('email', '', new EmailValidator(), 'User email.')
|
||||
->param('password', '', new Password(), 'User password hashed using PHPass.')
|
||||
->param('name', '', new Text(128), 'User name. Max length: 128 chars.', true)
|
||||
->inject('response')
|
||||
@@ -425,7 +438,7 @@ App::post('/v1/users/scrypt')
|
||||
]
|
||||
))
|
||||
->param('userId', '', new CustomId(), '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.')
|
||||
->param('email', '', new Email(), 'User email.')
|
||||
->param('email', '', new EmailValidator(), 'User email.')
|
||||
->param('password', '', new Password(), 'User password hashed using Scrypt.')
|
||||
->param('passwordSalt', '', new Text(128), 'Optional salt used to hash password.')
|
||||
->param('passwordCpu', 8, new Integer(), 'Optional CPU cost used to hash password.')
|
||||
@@ -473,7 +486,7 @@ App::post('/v1/users/scrypt-modified')
|
||||
]
|
||||
))
|
||||
->param('userId', '', new CustomId(), '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.')
|
||||
->param('email', '', new Email(), 'User email.')
|
||||
->param('email', '', new EmailValidator(), 'User email.')
|
||||
->param('password', '', new Password(), 'User password hashed using Scrypt Modified.')
|
||||
->param('passwordSalt', '', new Text(128), 'Salt used to hash password.')
|
||||
->param('passwordSaltSeparator', '', new Text(128), 'Salt separator used to hash password.')
|
||||
@@ -527,7 +540,7 @@ App::post('/v1/users/:userId/targets')
|
||||
|
||||
switch ($providerType) {
|
||||
case 'email':
|
||||
$validator = new Email();
|
||||
$validator = new EmailValidator();
|
||||
if (!$validator->isValid($identifier)) {
|
||||
throw new Exception(Exception::GENERAL_INVALID_EMAIL);
|
||||
}
|
||||
@@ -1402,7 +1415,7 @@ App::patch('/v1/users/:userId/email')
|
||||
]
|
||||
))
|
||||
->param('userId', '', new UID(), 'User ID.')
|
||||
->param('email', '', new Email(allowEmpty: true), 'User email.')
|
||||
->param('email', '', new EmailValidator(allowEmpty: true), 'User email.')
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('queueForEvents')
|
||||
@@ -1437,9 +1450,20 @@ App::patch('/v1/users/:userId/email')
|
||||
|
||||
$oldEmail = $user->getAttribute('email');
|
||||
|
||||
try {
|
||||
$emailCanonical = new Email($email);
|
||||
} catch (Throwable) {
|
||||
$emailCanonical = null;
|
||||
}
|
||||
|
||||
$user
|
||||
->setAttribute('email', $email)
|
||||
->setAttribute('emailVerification', false)
|
||||
->setAttribute('emailCanonical', $emailCanonical?->getCanonical())
|
||||
->setAttribute('emailIsCanonical', $emailCanonical?->isCanonicalSupported())
|
||||
->setAttribute('emailIsCorporate', $emailCanonical?->isCorporate())
|
||||
->setAttribute('emailIsDisposable', $emailCanonical?->isDisposable())
|
||||
->setAttribute('emailIsFree', $emailCanonical?->isFree())
|
||||
;
|
||||
|
||||
try {
|
||||
@@ -1700,7 +1724,7 @@ App::patch('/v1/users/:userId/targets/:targetId')
|
||||
|
||||
switch ($providerType) {
|
||||
case 'email':
|
||||
$validator = new Email();
|
||||
$validator = new EmailValidator();
|
||||
if (!$validator->isValid($identifier)) {
|
||||
throw new Exception(Exception::GENERAL_INVALID_EMAIL);
|
||||
}
|
||||
@@ -2623,8 +2647,8 @@ App::get('/v1/users/usage')
|
||||
->param('range', '30d', new WhiteList(['24h', '30d', '90d'], true), 'Date range.', true)
|
||||
->inject('response')
|
||||
->inject('dbForProject')
|
||||
->inject('register')
|
||||
->action(function (string $range, Response $response, Database $dbForProject) {
|
||||
->inject('authorization')
|
||||
->action(function (string $range, Response $response, Database $dbForProject, Authorization $authorization) {
|
||||
|
||||
$periods = Config::getParam('usage', []);
|
||||
$stats = $usage = [];
|
||||
@@ -2634,7 +2658,7 @@ App::get('/v1/users/usage')
|
||||
METRIC_SESSIONS,
|
||||
];
|
||||
|
||||
Authorization::skip(function () use ($dbForProject, $days, $metrics, &$stats) {
|
||||
$authorization->skip(function () use ($dbForProject, $days, $metrics, &$stats) {
|
||||
foreach ($metrics as $count => $metric) {
|
||||
$result = $dbForProject->findOne('stats', [
|
||||
Query::equal('metric', [$metric]),
|
||||
|
||||
+110
-29
@@ -14,6 +14,7 @@ use Appwrite\Utopia\Database\Validator\Queries\Installations;
|
||||
use Appwrite\Utopia\Request;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Appwrite\Vcs\Comment;
|
||||
use Swoole\Coroutine\WaitGroup;
|
||||
use Utopia\App;
|
||||
use Utopia\CLI\Console;
|
||||
use Utopia\Config\Config;
|
||||
@@ -71,7 +72,7 @@ use Utopia\VCS\Exception\RepositoryNotFound;
|
||||
|
||||
use function Swoole\Coroutine\batch;
|
||||
|
||||
$createGitDeployments = function (GitHub $github, string $providerInstallationId, array $repositories, string $providerBranch, string $providerBranchUrl, string $providerRepositoryName, string $providerRepositoryUrl, string $providerRepositoryOwner, string $providerCommitHash, string $providerCommitAuthor, string $providerCommitAuthorUrl, string $providerCommitMessage, string $providerCommitUrl, string $providerPullRequestId, bool $external, Database $dbForPlatform, Build $queueForBuilds, callable $getProjectDB, Request $request) {
|
||||
$createGitDeployments = function (GitHub $github, string $providerInstallationId, array $repositories, string $providerBranch, string $providerBranchUrl, string $providerRepositoryName, string $providerRepositoryUrl, string $providerRepositoryOwner, string $providerCommitHash, string $providerCommitAuthor, string $providerCommitAuthorUrl, string $providerCommitMessage, string $providerCommitUrl, string $providerPullRequestId, bool $external, Database $dbForPlatform, Authorization $authorization, Build $queueForBuilds, callable $getProjectDB, Request $request) {
|
||||
$errors = [];
|
||||
foreach ($repositories as $repository) {
|
||||
try {
|
||||
@@ -82,12 +83,12 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
|
||||
}
|
||||
|
||||
$projectId = $repository->getAttribute('projectId');
|
||||
$project = Authorization::skip(fn () => $dbForPlatform->getDocument('projects', $projectId));
|
||||
$project = $authorization->skip(fn () => $dbForPlatform->getDocument('projects', $projectId));
|
||||
$dbForProject = $getProjectDB($project);
|
||||
|
||||
$resourceCollection = $resourceType === "function" ? 'functions' : 'sites';
|
||||
$resourceId = $repository->getAttribute('resourceId');
|
||||
$resource = Authorization::skip(fn () => $dbForProject->getDocument($resourceCollection, $resourceId));
|
||||
$resource = $authorization->skip(fn () => $dbForProject->getDocument($resourceCollection, $resourceId));
|
||||
$resourceInternalId = $resource->getSequence();
|
||||
|
||||
$deploymentId = ID::unique();
|
||||
@@ -136,7 +137,7 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
|
||||
$latestCommentId = '';
|
||||
|
||||
if (!empty($providerPullRequestId) && $resource->getAttribute('providerSilentMode', false) === false) {
|
||||
$latestComment = Authorization::skip(fn () => $dbForPlatform->findOne('vcsComments', [
|
||||
$latestComment = $authorization->skip(fn () => $dbForPlatform->findOne('vcsComments', [
|
||||
Query::equal('providerRepositoryId', [$providerRepositoryId]),
|
||||
Query::equal('providerPullRequestId', [$providerPullRequestId]),
|
||||
Query::orderDesc('$createdAt'),
|
||||
@@ -175,7 +176,7 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
|
||||
|
||||
$latestCommentId = \strval($github->updateComment($owner, $repositoryName, $latestCommentId, $comment->generateComment()));
|
||||
} finally {
|
||||
Authorization::skip(fn () => $dbForPlatform->deleteDocument('vcsCommentLocks', $latestCommentId));
|
||||
$authorization->skip(fn () => $dbForPlatform->deleteDocument('vcsCommentLocks', $latestCommentId));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -186,7 +187,7 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
|
||||
if (!empty($latestCommentId)) {
|
||||
$teamId = $project->getAttribute('teamId', '');
|
||||
|
||||
$latestComment = Authorization::skip(fn () => $dbForPlatform->createDocument('vcsComments', new Document([
|
||||
$latestComment = $authorization->skip(fn () => $dbForPlatform->createDocument('vcsComments', new Document([
|
||||
'$id' => ID::unique(),
|
||||
'$permissions' => [
|
||||
Permission::read(Role::team(ID::custom($teamId))),
|
||||
@@ -207,7 +208,7 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
|
||||
}
|
||||
}
|
||||
} elseif (!empty($providerBranch)) {
|
||||
$latestComments = Authorization::skip(fn () => $dbForPlatform->find('vcsComments', [
|
||||
$latestComments = $authorization->skip(fn () => $dbForPlatform->find('vcsComments', [
|
||||
Query::equal('providerRepositoryId', [$providerRepositoryId]),
|
||||
Query::equal('providerBranch', [$providerBranch]),
|
||||
Query::orderDesc('$createdAt'),
|
||||
@@ -246,7 +247,7 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
|
||||
|
||||
$latestCommentId = \strval($github->updateComment($owner, $repositoryName, $latestCommentId, $comment->generateComment()));
|
||||
} finally {
|
||||
Authorization::skip(fn () => $dbForPlatform->deleteDocument('vcsCommentLocks', $latestCommentId));
|
||||
$authorization->skip(fn () => $dbForPlatform->deleteDocument('vcsCommentLocks', $latestCommentId));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -289,7 +290,7 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
|
||||
$commands[] = $resource->getAttribute('commands', '');
|
||||
}
|
||||
|
||||
$deployment = Authorization::skip(fn () => $dbForProject->createDocument('deployments', new Document([
|
||||
$deployment = $authorization->skip(fn () => $dbForProject->createDocument('deployments', new Document([
|
||||
'$id' => $deploymentId,
|
||||
'$permissions' => [
|
||||
Permission::read(Role::any()),
|
||||
@@ -329,7 +330,7 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
|
||||
->setAttribute('latestDeploymentInternalId', $deployment->getSequence())
|
||||
->setAttribute('latestDeploymentCreatedAt', $deployment->getCreatedAt())
|
||||
->setAttribute('latestDeploymentStatus', $deployment->getAttribute('status', ''));
|
||||
Authorization::skip(fn () => $dbForProject->updateDocument($resource->getCollection(), $resource->getId(), $resource));
|
||||
$authorization->skip(fn () => $dbForProject->updateDocument($resource->getCollection(), $resource->getId(), $resource));
|
||||
|
||||
if ($resource->getCollection() === 'sites') {
|
||||
$projectId = $project->getId();
|
||||
@@ -339,7 +340,7 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
|
||||
$domain = ID::unique() . "." . $sitesDomain;
|
||||
$ruleId = md5($domain);
|
||||
$previewRuleId = $ruleId;
|
||||
Authorization::skip(
|
||||
$authorization->skip(
|
||||
fn () => $dbForPlatform->createDocument('rules', new Document([
|
||||
'$id' => $ruleId,
|
||||
'projectId' => $project->getId(),
|
||||
@@ -372,7 +373,7 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
|
||||
$domain = "branch-{$branchPrefix}-{$resourceProjectHash}.{$sitesDomain}";
|
||||
$ruleId = md5($domain);
|
||||
try {
|
||||
Authorization::skip(
|
||||
$authorization->skip(
|
||||
fn () => $dbForPlatform->createDocument('rules', new Document([
|
||||
'$id' => $ruleId,
|
||||
'projectId' => $project->getId(),
|
||||
@@ -403,7 +404,7 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
|
||||
$domain = "commit-" . substr($providerCommitHash, 0, 16) . ".{$sitesDomain}";
|
||||
$ruleId = md5($domain);
|
||||
try {
|
||||
Authorization::skip(
|
||||
$authorization->skip(
|
||||
fn () => $dbForPlatform->createDocument('rules', new Document([
|
||||
'$id' => $ruleId,
|
||||
'projectId' => $project->getId(),
|
||||
@@ -455,7 +456,7 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
|
||||
if ($lockAcquired) {
|
||||
// Wrap in try/finally to ensure lock file gets deleted
|
||||
try {
|
||||
$rule = Authorization::skip(fn () => $dbForPlatform->getDocument('rules', $previewRuleId));
|
||||
$rule = $authorization->skip(fn () => $dbForPlatform->getDocument('rules', $previewRuleId));
|
||||
|
||||
$protocol = System::getEnv('_APP_OPTIONS_FORCE_HTTPS') === 'disabled' ? 'http' : 'https';
|
||||
$previewUrl = !empty($rule) ? ("{$protocol}://" . $rule->getAttribute('domain', '')) : '';
|
||||
@@ -467,7 +468,7 @@ $createGitDeployments = function (GitHub $github, string $providerInstallationId
|
||||
$github->updateComment($owner, $repositoryName, $latestCommentId, $comment->generateComment());
|
||||
}
|
||||
} finally {
|
||||
Authorization::skip(fn () => $dbForPlatform->deleteDocument('vcsCommentLocks', $latestCommentId));
|
||||
$authorization->skip(fn () => $dbForPlatform->deleteDocument('vcsCommentLocks', $latestCommentId));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -963,6 +964,46 @@ App::post('/v1/vcs/github/installations/:installationId/detections')
|
||||
throw new Exception(Exception::FUNCTION_RUNTIME_NOT_DETECTED);
|
||||
}
|
||||
}
|
||||
|
||||
$wg = new WaitGroup();
|
||||
$envs = [];
|
||||
foreach ($files as $file) {
|
||||
if (!(\str_starts_with($file, '.env'))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$wg->add();
|
||||
go(function () use ($github, $owner, $repositoryName, $providerRootDirectory, $file, $wg, &$envs) {
|
||||
try {
|
||||
$contentResponse = $github->getRepositoryContent($owner, $repositoryName, \rtrim($providerRootDirectory, '/') . '/' . $file);
|
||||
$envFile = $contentResponse['content'] ?? '';
|
||||
|
||||
$envLines = \explode("\n", $envFile);
|
||||
foreach ($envLines as $line) {
|
||||
$parts = \explode('=', $line, 2);
|
||||
$envName = \trim($parts[0] ?? '');
|
||||
$envValue = \trim($parts[1] ?? '');
|
||||
if (!empty($envName)) {
|
||||
$envs[$envName] = $envValue;
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
$wg->done();
|
||||
}
|
||||
});
|
||||
}
|
||||
$wg->wait();
|
||||
|
||||
$variables = [];
|
||||
foreach ($envs as $key => $value) {
|
||||
$variables[] = [
|
||||
'name' => $key,
|
||||
'value' => $value,
|
||||
];
|
||||
}
|
||||
|
||||
$output->setAttribute('variables', $variables);
|
||||
|
||||
$response->dynamic($output, $type === 'framework' ? Response::MODEL_DETECTION_FRAMEWORK : Response::MODEL_DETECTION_RUNTIME);
|
||||
});
|
||||
|
||||
@@ -1137,6 +1178,44 @@ App::get('/v1/vcs/github/installations/:installationId/providerRepositories')
|
||||
$repo['runtime'] = $runtimeWithVersion ?? '';
|
||||
}
|
||||
}
|
||||
|
||||
$wg = new WaitGroup();
|
||||
$envs = [];
|
||||
foreach ($files as $file) {
|
||||
if (!(\str_starts_with($file, '.env'))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$wg->add();
|
||||
go(function () use ($github, $repo, $file, $wg, &$envs) {
|
||||
try {
|
||||
$contentResponse = $github->getRepositoryContent($repo['organization'], $repo['name'], $file);
|
||||
$envFile = $contentResponse['content'] ?? '';
|
||||
|
||||
$envLines = \explode("\n", $envFile);
|
||||
foreach ($envLines as $line) {
|
||||
$parts = \explode('=', $line, 2);
|
||||
$envName = \trim($parts[0] ?? '');
|
||||
$envValue = \trim($parts[1] ?? '');
|
||||
if (!empty($envName)) {
|
||||
$envs[$envName] = $envValue;
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
$wg->done();
|
||||
}
|
||||
});
|
||||
}
|
||||
$wg->wait();
|
||||
|
||||
$repo['variables'] = [];
|
||||
foreach ($envs as $key => $value) {
|
||||
$repo['variables'][] = [
|
||||
'name' => $key,
|
||||
'value' => $value,
|
||||
];
|
||||
}
|
||||
|
||||
return $repo;
|
||||
};
|
||||
}, $repos));
|
||||
@@ -1383,10 +1462,11 @@ App::post('/v1/vcs/github/events')
|
||||
->inject('request')
|
||||
->inject('response')
|
||||
->inject('dbForPlatform')
|
||||
->inject('authorization')
|
||||
->inject('getProjectDB')
|
||||
->inject('queueForBuilds')
|
||||
->action(
|
||||
function (GitHub $github, Request $request, Response $response, Database $dbForPlatform, callable $getProjectDB, Build $queueForBuilds) use ($createGitDeployments) {
|
||||
function (GitHub $github, Request $request, Response $response, Database $dbForPlatform, Authorization $authorization, callable $getProjectDB, Build $queueForBuilds) use ($createGitDeployments) {
|
||||
$payload = $request->getRawPayload();
|
||||
$signatureRemote = $request->getHeader('x-hub-signature-256', '');
|
||||
$signatureLocal = System::getEnv('_APP_VCS_GITHUB_WEBHOOK_SECRET', '');
|
||||
@@ -1422,14 +1502,14 @@ App::post('/v1/vcs/github/events')
|
||||
$github->initializeVariables($providerInstallationId, $privateKey, $githubAppId);
|
||||
|
||||
//find resourceId from relevant resources table
|
||||
$repositories = Authorization::skip(fn () => $dbForPlatform->find('repositories', [
|
||||
$repositories = $authorization->skip(fn () => $dbForPlatform->find('repositories', [
|
||||
Query::equal('providerRepositoryId', [$providerRepositoryId]),
|
||||
Query::limit(100),
|
||||
]));
|
||||
|
||||
// create new deployment only on push (not committed by us) and not when branch is created or deleted
|
||||
if ($providerCommitAuthorEmail !== APP_VCS_GITHUB_EMAIL && !$providerBranchCreated && !$providerBranchDeleted) {
|
||||
$createGitDeployments($github, $providerInstallationId, $repositories, $providerBranch, $providerBranchUrl, $providerRepositoryName, $providerRepositoryUrl, $providerRepositoryOwner, $providerCommitHash, $providerCommitAuthorName, $providerCommitAuthorUrl, $providerCommitMessage, $providerCommitUrl, '', false, $dbForPlatform, $queueForBuilds, $getProjectDB, $request);
|
||||
$createGitDeployments($github, $providerInstallationId, $repositories, $providerBranch, $providerBranchUrl, $providerRepositoryName, $providerRepositoryUrl, $providerRepositoryOwner, $providerCommitHash, $providerCommitAuthorName, $providerCommitAuthorUrl, $providerCommitMessage, $providerCommitUrl, '', false, $dbForPlatform, $authorization, $queueForBuilds, $getProjectDB, $request);
|
||||
}
|
||||
} elseif ($event == $github::EVENT_INSTALLATION) {
|
||||
if ($parsedPayload["action"] == "deleted") {
|
||||
@@ -1442,16 +1522,16 @@ App::post('/v1/vcs/github/events')
|
||||
]);
|
||||
|
||||
foreach ($installations as $installation) {
|
||||
$repositories = Authorization::skip(fn () => $dbForPlatform->find('repositories', [
|
||||
$repositories = $authorization->skip(fn () => $dbForPlatform->find('repositories', [
|
||||
Query::equal('installationInternalId', [$installation->getSequence()]),
|
||||
Query::limit(1000)
|
||||
]));
|
||||
|
||||
foreach ($repositories as $repository) {
|
||||
Authorization::skip(fn () => $dbForPlatform->deleteDocument('repositories', $repository->getId()));
|
||||
$authorization->skip(fn () => $dbForPlatform->deleteDocument('repositories', $repository->getId()));
|
||||
}
|
||||
|
||||
Authorization::skip(fn () => $dbForPlatform->deleteDocument('installations', $installation->getId()));
|
||||
$authorization->skip(fn () => $dbForPlatform->deleteDocument('installations', $installation->getId()));
|
||||
}
|
||||
}
|
||||
} elseif ($event == $github::EVENT_PULL_REQUEST) {
|
||||
@@ -1480,12 +1560,12 @@ App::post('/v1/vcs/github/events')
|
||||
$providerCommitAuthor = $commitDetails["commitAuthor"] ?? '';
|
||||
$providerCommitMessage = $commitDetails["commitMessage"] ?? '';
|
||||
|
||||
$repositories = Authorization::skip(fn () => $dbForPlatform->find('repositories', [
|
||||
$repositories = $authorization->skip(fn () => $dbForPlatform->find('repositories', [
|
||||
Query::equal('providerRepositoryId', [$providerRepositoryId]),
|
||||
Query::orderDesc('$createdAt')
|
||||
]));
|
||||
|
||||
$createGitDeployments($github, $providerInstallationId, $repositories, $providerBranch, $providerBranchUrl, $providerRepositoryName, $providerRepositoryUrl, $providerRepositoryOwner, $providerCommitHash, $providerCommitAuthor, $providerCommitAuthorUrl, $providerCommitMessage, $providerCommitUrl, $providerPullRequestId, $external, $dbForPlatform, $queueForBuilds, $getProjectDB, $request);
|
||||
$createGitDeployments($github, $providerInstallationId, $repositories, $providerBranch, $providerBranchUrl, $providerRepositoryName, $providerRepositoryUrl, $providerRepositoryOwner, $providerCommitHash, $providerCommitAuthor, $providerCommitAuthorUrl, $providerCommitMessage, $providerCommitUrl, $providerPullRequestId, $external, $dbForPlatform, $authorization, $queueForBuilds, $getProjectDB, $request);
|
||||
} elseif ($parsedPayload["action"] == "closed") {
|
||||
// Allowed external contributions cleanup
|
||||
|
||||
@@ -1494,7 +1574,7 @@ App::post('/v1/vcs/github/events')
|
||||
$external = $parsedPayload["external"] ?? true;
|
||||
|
||||
if ($external) {
|
||||
$repositories = Authorization::skip(fn () => $dbForPlatform->find('repositories', [
|
||||
$repositories = $authorization->skip(fn () => $dbForPlatform->find('repositories', [
|
||||
Query::equal('providerRepositoryId', [$providerRepositoryId]),
|
||||
Query::orderDesc('$createdAt')
|
||||
]));
|
||||
@@ -1505,7 +1585,7 @@ App::post('/v1/vcs/github/events')
|
||||
if (\in_array($providerPullRequestId, $providerPullRequestIds)) {
|
||||
$providerPullRequestIds = \array_diff($providerPullRequestIds, [$providerPullRequestId]);
|
||||
$repository = $repository->setAttribute('providerPullRequestIds', $providerPullRequestIds);
|
||||
$repository = Authorization::skip(fn () => $dbForPlatform->updateDocument('repositories', $repository->getId(), $repository));
|
||||
$repository = $authorization->skip(fn () => $dbForPlatform->updateDocument('repositories', $repository->getId(), $repository));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1693,16 +1773,17 @@ App::patch('/v1/vcs/github/installations/:installationId/repositories/:repositor
|
||||
->inject('response')
|
||||
->inject('project')
|
||||
->inject('dbForPlatform')
|
||||
->inject('authorization')
|
||||
->inject('getProjectDB')
|
||||
->inject('queueForBuilds')
|
||||
->action(function (string $installationId, string $repositoryId, string $providerPullRequestId, GitHub $github, Request $request, Response $response, Document $project, Database $dbForPlatform, callable $getProjectDB, Build $queueForBuilds) use ($createGitDeployments) {
|
||||
->action(function (string $installationId, string $repositoryId, string $providerPullRequestId, GitHub $github, Request $request, Response $response, Document $project, Database $dbForPlatform, Authorization $authorization, callable $getProjectDB, Build $queueForBuilds) use ($createGitDeployments) {
|
||||
$installation = $dbForPlatform->getDocument('installations', $installationId);
|
||||
|
||||
if ($installation->isEmpty()) {
|
||||
throw new Exception(Exception::INSTALLATION_NOT_FOUND);
|
||||
}
|
||||
|
||||
$repository = Authorization::skip(fn () => $dbForPlatform->getDocument('repositories', $repositoryId, [
|
||||
$repository = $authorization->skip(fn () => $dbForPlatform->getDocument('repositories', $repositoryId, [
|
||||
Query::equal('projectInternalId', [$project->getSequence()])
|
||||
]));
|
||||
|
||||
@@ -1719,7 +1800,7 @@ App::patch('/v1/vcs/github/installations/:installationId/repositories/:repositor
|
||||
|
||||
// TODO: Delete from array when PR is closed
|
||||
|
||||
$repository = Authorization::skip(fn () => $dbForPlatform->updateDocument('repositories', $repository->getId(), $repository));
|
||||
$repository = $authorization->skip(fn () => $dbForPlatform->updateDocument('repositories', $repository->getId(), $repository));
|
||||
|
||||
$privateKey = System::getEnv('_APP_VCS_GITHUB_PRIVATE_KEY');
|
||||
$githubAppId = System::getEnv('_APP_VCS_GITHUB_APP_ID');
|
||||
@@ -1743,7 +1824,7 @@ App::patch('/v1/vcs/github/installations/:installationId/repositories/:repositor
|
||||
$providerBranch = \explode(':', $pullRequestResponse['head']['label'])[1] ?? '';
|
||||
$providerCommitHash = $pullRequestResponse['head']['sha'] ?? '';
|
||||
|
||||
$createGitDeployments($github, $providerInstallationId, $repositories, $providerBranch, $providerCommitHash, $providerPullRequestId, true, $dbForPlatform, $queueForBuilds, $getProjectDB, $request);
|
||||
$createGitDeployments($github, $providerInstallationId, $repositories, $providerBranch, '', '', '', '', $providerCommitHash, '', '', '', '', $providerPullRequestId, true, $dbForPlatform, $authorization, $queueForBuilds, $getProjectDB, $request);
|
||||
|
||||
$response->noContent();
|
||||
});
|
||||
|
||||
+38
-28
@@ -23,6 +23,7 @@ use Appwrite\Utopia\Request\Filters\V17 as RequestV17;
|
||||
use Appwrite\Utopia\Request\Filters\V18 as RequestV18;
|
||||
use Appwrite\Utopia\Request\Filters\V19 as RequestV19;
|
||||
use Appwrite\Utopia\Request\Filters\V20 as RequestV20;
|
||||
use Appwrite\Utopia\Request\Filters\V21 as RequestV21;
|
||||
use Appwrite\Utopia\Response;
|
||||
use Appwrite\Utopia\Response\Filters\V16 as ResponseV16;
|
||||
use Appwrite\Utopia\Response\Filters\V17 as ResponseV17;
|
||||
@@ -56,7 +57,7 @@ Config::setParam('domainVerification', false);
|
||||
Config::setParam('cookieDomain', 'localhost');
|
||||
Config::setParam('cookieSamesite', Response::COOKIE_SAMESITE_NONE);
|
||||
|
||||
function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Event $queueForEvents, StatsUsage $queueForStatsUsage, Func $queueForFunctions, Executor $executor, Reader $geodb, callable $isResourceBlocked, string $previewHostname, ?Key $apiKey)
|
||||
function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Event $queueForEvents, StatsUsage $queueForStatsUsage, Func $queueForFunctions, Executor $executor, Reader $geodb, callable $isResourceBlocked, string $previewHostname, Authorization $authorization, ?Key $apiKey)
|
||||
{
|
||||
$host = $request->getHostname() ?? '';
|
||||
if (!empty($previewHostname)) {
|
||||
@@ -65,9 +66,9 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw
|
||||
|
||||
// TODO: @christyjacob remove once we migrate the rules in 1.7.x
|
||||
if (System::getEnv('_APP_RULES_FORMAT') === 'md5') {
|
||||
$rule = Authorization::skip(fn () => $dbForPlatform->getDocument('rules', md5($host)));
|
||||
$rule = $authorization->skip(fn () => $dbForPlatform->getDocument('rules', md5($host)));
|
||||
} else {
|
||||
$rule = Authorization::skip(
|
||||
$rule = $authorization->skip(
|
||||
fn () => $dbForPlatform->find('rules', [
|
||||
Query::equal('domain', [$host]),
|
||||
Query::limit(1)
|
||||
@@ -108,7 +109,7 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw
|
||||
}
|
||||
|
||||
$projectId = $rule->getAttribute('projectId');
|
||||
$project = Authorization::skip(
|
||||
$project = $authorization->skip(
|
||||
fn () => $dbForPlatform->getDocument('projects', $projectId)
|
||||
);
|
||||
|
||||
@@ -116,7 +117,7 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw
|
||||
$accessedAt = $project->getAttribute('accessedAt', 0);
|
||||
if (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_PROJECT_ACCESS)) > $accessedAt) {
|
||||
$project->setAttribute('accessedAt', DateTime::now());
|
||||
Authorization::skip(fn () => $dbForPlatform->updateDocument('projects', $project->getId(), $project));
|
||||
$authorization->skip(fn () => $dbForPlatform->updateDocument('projects', $project->getId(), $project));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -155,7 +156,7 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw
|
||||
|
||||
/** @var Document $deployment */
|
||||
if (!empty($rule->getAttribute('deploymentId', ''))) {
|
||||
$deployment = Authorization::skip(fn () => $dbForProject->getDocument('deployments', $rule->getAttribute('deploymentId')));
|
||||
$deployment = $authorization->skip(fn () => $dbForProject->getDocument('deployments', $rule->getAttribute('deploymentId')));
|
||||
} else {
|
||||
// 1.6.x DB schema compatibility
|
||||
// TODO: Make sure deploymentId is never empty, and remove this code
|
||||
@@ -169,15 +170,15 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw
|
||||
|
||||
// Document of site or function
|
||||
$resource = $resourceType === 'function' ?
|
||||
Authorization::skip(fn () => $dbForProject->getDocument('functions', $resourceId)) :
|
||||
Authorization::skip(fn () => $dbForProject->getDocument('sites', $resourceId));
|
||||
$authorization->skip(fn () => $dbForProject->getDocument('functions', $resourceId)) :
|
||||
$authorization->skip(fn () => $dbForProject->getDocument('sites', $resourceId));
|
||||
|
||||
// ID of active deployments
|
||||
// Attempts to use attribute from both schemas (1.6 and 1.7)
|
||||
$activeDeploymentId = $resource->getAttribute('deploymentId', $resource->getAttribute('deployment', ''));
|
||||
|
||||
// Get deployment document, as intended originally
|
||||
$deployment = Authorization::skip(fn () => $dbForProject->getDocument('deployments', $activeDeploymentId));
|
||||
$deployment = $authorization->skip(fn () => $dbForProject->getDocument('deployments', $activeDeploymentId));
|
||||
}
|
||||
|
||||
if ($deployment->getAttribute('resourceType', '') === 'functions') {
|
||||
@@ -196,8 +197,8 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw
|
||||
}
|
||||
|
||||
$resource = $type === 'function' ?
|
||||
Authorization::skip(fn () => $dbForProject->getDocument('functions', $deployment->getAttribute('resourceId', ''))) :
|
||||
Authorization::skip(fn () => $dbForProject->getDocument('sites', $deployment->getAttribute('resourceId', '')));
|
||||
$authorization->skip(fn () => $dbForProject->getDocument('functions', $deployment->getAttribute('resourceId', ''))) :
|
||||
$authorization->skip(fn () => $dbForProject->getDocument('sites', $deployment->getAttribute('resourceId', '')));
|
||||
|
||||
$isPreview = $type === 'function' ? false : ($rule->getAttribute('trigger', '') !== 'manual');
|
||||
|
||||
@@ -239,7 +240,7 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw
|
||||
$userExists = false;
|
||||
$userId = $payload['userId'] ?? '';
|
||||
if (!empty($userId)) {
|
||||
$user = Authorization::skip(fn () => $dbForPlatform->getDocument('users', $userId));
|
||||
$user = $authorization->skip(fn () => $dbForPlatform->getDocument('users', $userId));
|
||||
if (!$user->isEmpty() && $user->getAttribute('status', false)) {
|
||||
$userExists = true;
|
||||
}
|
||||
@@ -252,7 +253,7 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw
|
||||
}
|
||||
|
||||
$membershipExists = false;
|
||||
$project = Authorization::skip(fn () => $dbForPlatform->getDocument('projects', $projectId));
|
||||
$project = $authorization->skip(fn () => $dbForPlatform->getDocument('projects', $projectId));
|
||||
if (!$project->isEmpty() && isset($user)) {
|
||||
$teamId = $project->getAttribute('teamId', '');
|
||||
$membership = $user->find('teamId', $teamId, 'memberships');
|
||||
@@ -863,7 +864,8 @@ App::init()
|
||||
->inject('apiKey')
|
||||
->inject('httpReferrer')
|
||||
->inject('httpReferrerSafe')
|
||||
->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Document $console, Document $project, Database $dbForPlatform, callable $getProjectDB, Locale $locale, array $localeCodes, array $platforms, Reader $geodb, StatsUsage $queueForStatsUsage, Event $queueForEvents, Certificate $queueForCertificates, Func $queueForFunctions, Executor $executor, callable $isResourceBlocked, string $previewHostname, Document $devKey, ?Key $apiKey, string $httpReferrer, string $httpReferrerSafe) {
|
||||
->inject('authorization')
|
||||
->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Document $console, Document $project, Database $dbForPlatform, callable $getProjectDB, Locale $locale, array $localeCodes, array $platforms, Reader $geodb, StatsUsage $queueForStatsUsage, Event $queueForEvents, Certificate $queueForCertificates, Func $queueForFunctions, Executor $executor, callable $isResourceBlocked, string $previewHostname, Document $devKey, ?Key $apiKey, string $httpReferrer, string $httpReferrerSafe, Authorization $authorization) {
|
||||
/*
|
||||
* Appwrite Router
|
||||
*/
|
||||
@@ -871,7 +873,7 @@ App::init()
|
||||
$mainDomain = System::getEnv('_APP_DOMAIN', '');
|
||||
// Only run Router when external domain
|
||||
if ($host !== $mainDomain || !empty($previewHostname)) {
|
||||
if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $log, $queueForEvents, $queueForStatsUsage, $queueForFunctions, $executor, $geodb, $isResourceBlocked, $previewHostname, $apiKey)) {
|
||||
if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $log, $queueForEvents, $queueForStatsUsage, $queueForFunctions, $executor, $geodb, $isResourceBlocked, $previewHostname, $authorization, $apiKey)) {
|
||||
$utopia->getRoute()?->label('router', true);
|
||||
}
|
||||
}
|
||||
@@ -906,6 +908,9 @@ App::init()
|
||||
$dbForProject = $getProjectDB($project);
|
||||
$request->addFilter(new RequestV20($dbForProject, $route->getPathValues($request)));
|
||||
}
|
||||
if (version_compare($requestFormat, '1.9.0', '<')) {
|
||||
$request->addFilter(new RequestV21());
|
||||
}
|
||||
}
|
||||
|
||||
$domain = $request->getHostname();
|
||||
@@ -919,7 +924,7 @@ App::init()
|
||||
} elseif (str_starts_with($request->getURI(), '/.well-known/acme-challenge')) {
|
||||
Console::warning('Skipping SSL certificates generation on ACME challenge.');
|
||||
} else {
|
||||
Authorization::disable();
|
||||
$authorization->disable();
|
||||
|
||||
$envDomain = System::getEnv('_APP_DOMAIN', '');
|
||||
$mainDomain = null;
|
||||
@@ -989,7 +994,7 @@ App::init()
|
||||
}
|
||||
$domains[$domain->get()] = true;
|
||||
|
||||
Authorization::reset(); // ensure authorization is re-enabled
|
||||
$authorization->reset(); // ensure authorization is re-enabled
|
||||
}
|
||||
Config::setParam('domains', $domains);
|
||||
}
|
||||
@@ -1125,7 +1130,8 @@ App::options()
|
||||
->inject('project')
|
||||
->inject('devKey')
|
||||
->inject('apiKey')
|
||||
->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Database $dbForPlatform, callable $getProjectDB, Event $queueForEvents, StatsUsage $queueForStatsUsage, Func $queueForFunctions, Executor $executor, Reader $geodb, callable $isResourceBlocked, string $previewHostname, Document $project, Document $devKey, ?Key $apiKey) {
|
||||
->inject('authorization')
|
||||
->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Database $dbForPlatform, callable $getProjectDB, Event $queueForEvents, StatsUsage $queueForStatsUsage, Func $queueForFunctions, Executor $executor, Reader $geodb, callable $isResourceBlocked, string $previewHostname, Document $project, Document $devKey, ?Key $apiKey, Authorization $authorization) {
|
||||
/*
|
||||
* Appwrite Router
|
||||
*/
|
||||
@@ -1133,7 +1139,7 @@ App::options()
|
||||
$mainDomain = System::getEnv('_APP_DOMAIN', '');
|
||||
// Only run Router when external domain
|
||||
if ($host !== $mainDomain || !empty($previewHostname)) {
|
||||
if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $log, $queueForEvents, $queueForStatsUsage, $queueForFunctions, $executor, $geodb, $isResourceBlocked, $previewHostname, $apiKey)) {
|
||||
if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $log, $queueForEvents, $queueForStatsUsage, $queueForFunctions, $executor, $geodb, $isResourceBlocked, $previewHostname, $authorization, $apiKey)) {
|
||||
$utopia->getRoute()?->label('router', true);
|
||||
}
|
||||
}
|
||||
@@ -1174,7 +1180,8 @@ App::error()
|
||||
->inject('log')
|
||||
->inject('queueForStatsUsage')
|
||||
->inject('devKey')
|
||||
->action(function (Throwable $error, App $utopia, Request $request, Response $response, Document $project, ?Logger $logger, Log $log, StatsUsage $queueForStatsUsage) {
|
||||
->inject('authorization')
|
||||
->action(function (Throwable $error, App $utopia, Request $request, Response $response, Document $project, ?Logger $logger, Log $log, StatsUsage $queueForStatsUsage, Document $devKey, Authorization $authorization) {
|
||||
$version = System::getEnv('_APP_VERSION', 'UNKNOWN');
|
||||
$route = $utopia->getRoute();
|
||||
$class = \get_class($error);
|
||||
@@ -1256,7 +1263,7 @@ App::error()
|
||||
* If not a publishable error, track usage stats. Publishable errors are >= 500 or those explicitly marked as publish=true in errors.php
|
||||
*/
|
||||
if (!$publish && $project->getId() !== 'console') {
|
||||
if (!Auth::isPrivilegedUser(Authorization::getRoles())) {
|
||||
if (!Auth::isPrivilegedUser($authorization->getRoles())) {
|
||||
$fileSize = 0;
|
||||
$file = $request->getFiles('file');
|
||||
if (!empty($file)) {
|
||||
@@ -1318,7 +1325,7 @@ App::error()
|
||||
$log->addExtra('file', $error->getFile());
|
||||
$log->addExtra('line', $error->getLine());
|
||||
$log->addExtra('trace', $error->getTraceAsString());
|
||||
$log->addExtra('roles', Authorization::getRoles());
|
||||
$log->addExtra('roles', $authorization->getRoles());
|
||||
|
||||
$action = 'UNKNOWN_NAMESPACE.UNKNOWN.METHOD';
|
||||
if (!empty($sdk)) {
|
||||
@@ -1441,7 +1448,8 @@ App::get('/robots.txt')
|
||||
->inject('isResourceBlocked')
|
||||
->inject('previewHostname')
|
||||
->inject('apiKey')
|
||||
->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Database $dbForPlatform, callable $getProjectDB, Event $queueForEvents, StatsUsage $queueForStatsUsage, Func $queueForFunctions, Executor $executor, Reader $geodb, callable $isResourceBlocked, string $previewHostname, ?Key $apiKey) {
|
||||
->inject('authorization')
|
||||
->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Database $dbForPlatform, callable $getProjectDB, Event $queueForEvents, StatsUsage $queueForStatsUsage, Func $queueForFunctions, Executor $executor, Reader $geodb, callable $isResourceBlocked, string $previewHostname, ?Key $apiKey, Authorization $authorization) {
|
||||
$host = $request->getHostname() ?? '';
|
||||
$consoleDomain = System::getEnv('_APP_CONSOLE_DOMAIN', '');
|
||||
$mainDomain = System::getEnv('_APP_DOMAIN', '');
|
||||
@@ -1450,7 +1458,7 @@ App::get('/robots.txt')
|
||||
$template = new View(__DIR__ . '/../views/general/robots.phtml');
|
||||
$response->text($template->render(false));
|
||||
} else {
|
||||
if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $log, $queueForEvents, $queueForStatsUsage, $queueForFunctions, $executor, $geodb, $isResourceBlocked, $previewHostname, $apiKey)) {
|
||||
if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $log, $queueForEvents, $queueForStatsUsage, $queueForFunctions, $executor, $geodb, $isResourceBlocked, $previewHostname, $authorization, $apiKey)) {
|
||||
$utopia->getRoute()?->label('router', true);
|
||||
}
|
||||
}
|
||||
@@ -1475,7 +1483,8 @@ App::get('/humans.txt')
|
||||
->inject('isResourceBlocked')
|
||||
->inject('previewHostname')
|
||||
->inject('apiKey')
|
||||
->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Database $dbForPlatform, callable $getProjectDB, Event $queueForEvents, StatsUsage $queueForStatsUsage, Func $queueForFunctions, Executor $executor, Reader $geodb, callable $isResourceBlocked, string $previewHostname, ?Key $apiKey) {
|
||||
->inject('authorization')
|
||||
->action(function (App $utopia, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Database $dbForPlatform, callable $getProjectDB, Event $queueForEvents, StatsUsage $queueForStatsUsage, Func $queueForFunctions, Executor $executor, Reader $geodb, callable $isResourceBlocked, string $previewHostname, ?Key $apiKey, Authorization $authorization) {
|
||||
$host = $request->getHostname() ?? '';
|
||||
$consoleDomain = System::getEnv('_APP_CONSOLE_DOMAIN', '');
|
||||
$mainDomain = System::getEnv('_APP_DOMAIN', '');
|
||||
@@ -1484,7 +1493,7 @@ App::get('/humans.txt')
|
||||
$template = new View(__DIR__ . '/../views/general/humans.phtml');
|
||||
$response->text($template->render(false));
|
||||
} else {
|
||||
if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $log, $queueForEvents, $queueForStatsUsage, $queueForFunctions, $executor, $geodb, $isResourceBlocked, $previewHostname, $apiKey)) {
|
||||
if (router($utopia, $dbForPlatform, $getProjectDB, $swooleRequest, $request, $response, $log, $queueForEvents, $queueForStatsUsage, $queueForFunctions, $executor, $geodb, $isResourceBlocked, $previewHostname, $authorization, $apiKey)) {
|
||||
$utopia->getRoute()?->label('router', true);
|
||||
}
|
||||
}
|
||||
@@ -1568,7 +1577,8 @@ App::get('/v1/ping')
|
||||
->inject('project')
|
||||
->inject('dbForPlatform')
|
||||
->inject('queueForEvents')
|
||||
->action(function (Response $response, Document $project, Database $dbForPlatform, Event $queueForEvents) {
|
||||
->inject('authorization')
|
||||
->action(function (Response $response, Document $project, Database $dbForPlatform, Event $queueForEvents, Authorization $authorization) {
|
||||
if ($project->isEmpty() || $project->getId() === 'console') {
|
||||
throw new AppwriteException(AppwriteException::PROJECT_NOT_FOUND);
|
||||
}
|
||||
@@ -1580,7 +1590,7 @@ App::get('/v1/ping')
|
||||
->setAttribute('pingCount', $pingCount)
|
||||
->setAttribute('pingedAt', $pingedAt);
|
||||
|
||||
Authorization::skip(function () use ($dbForPlatform, $project) {
|
||||
$authorization->skip(function () use ($dbForPlatform, $project) {
|
||||
$dbForPlatform->updateDocument('projects', $project->getId(), $project);
|
||||
});
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@ use Utopia\Database\DateTime;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Helpers\Role;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Database\Validator\Authorization\Input;
|
||||
use Utopia\Queue\Publisher;
|
||||
use Utopia\System\System;
|
||||
use Utopia\Telemetry\Adapter as Telemetry;
|
||||
@@ -232,9 +233,12 @@ App::init()
|
||||
->inject('mode')
|
||||
->inject('team')
|
||||
->inject('apiKey')
|
||||
->action(function (App $utopia, Request $request, Database $dbForPlatform, Database $dbForProject, Audit $queueForAudits, Document $project, Document $user, ?Document $session, array $servers, string $mode, Document $team, ?Key $apiKey) {
|
||||
->inject('authorization')
|
||||
->action(function (App $utopia, Request $request, Database $dbForPlatform, Database $dbForProject, Audit $queueForAudits, Document $project, Document $user, ?Document $session, array $servers, string $mode, Document $team, ?Key $apiKey, Authorization $authorization) {
|
||||
$route = $utopia->getRoute();
|
||||
|
||||
if (System::getEnv('_APP_EDITION', 'self-hosted') === 'self-hosted' && str_starts_with($route->getPath(), '/v1/backups')) {
|
||||
throw new Exception(Exception::GENERAL_BAD_REQUEST, 'Database Backups are available on Appwrite Cloud');
|
||||
}
|
||||
if ($project->isEmpty()) {
|
||||
throw new Exception(Exception::PROJECT_NOT_FOUND);
|
||||
}
|
||||
@@ -262,7 +266,7 @@ App::init()
|
||||
|
||||
if ($apiKey->getRole() === Auth::USER_ROLE_APPS) {
|
||||
// Disable authorization checks for API keys
|
||||
Authorization::setDefaultStatus(false);
|
||||
$authorization->setDefaultStatus(false);
|
||||
|
||||
$user = new Document([
|
||||
'$id' => '',
|
||||
@@ -335,14 +339,14 @@ App::init()
|
||||
$scopes = \array_merge($scopes, $roles[$role]['scopes']);
|
||||
}
|
||||
|
||||
Authorization::setDefaultStatus(false); // Cancel security segmentation for admin users.
|
||||
$authorization->setDefaultStatus(false); // Cancel security segmentation for admin users.
|
||||
}
|
||||
|
||||
$scopes = \array_unique($scopes);
|
||||
|
||||
Authorization::setRole($role);
|
||||
foreach (Auth::getRoles($user) as $authRole) {
|
||||
Authorization::setRole($authRole);
|
||||
$authorization->addRole($role);
|
||||
foreach (Auth::getRoles($user, $authorization) as $authRole) {
|
||||
$authorization->addRole($authRole);
|
||||
}
|
||||
|
||||
// Update project last activity
|
||||
@@ -350,7 +354,7 @@ App::init()
|
||||
$accessedAt = $project->getAttribute('accessedAt', 0);
|
||||
if (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_PROJECT_ACCESS)) > $accessedAt) {
|
||||
$project->setAttribute('accessedAt', DateTime::now());
|
||||
Authorization::skip(fn () => $dbForPlatform->updateDocument('projects', $project->getId(), $project));
|
||||
$authorization->skip(fn () => $dbForPlatform->updateDocument('projects', $project->getId(), $project));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -385,7 +389,7 @@ App::init()
|
||||
if (
|
||||
array_key_exists($namespace, $project->getAttribute('services', []))
|
||||
&& !$project->getAttribute('services', [])[$namespace]
|
||||
&& !(Auth::isPrivilegedUser(Authorization::getRoles()) || Auth::isAppUser(Authorization::getRoles()))
|
||||
&& !(Auth::isPrivilegedUser($authorization->getRoles()) || Auth::isAppUser($authorization->getRoles()))
|
||||
) {
|
||||
throw new Exception(Exception::GENERAL_SERVICE_DISABLED);
|
||||
}
|
||||
@@ -445,14 +449,15 @@ App::init()
|
||||
->inject('plan')
|
||||
->inject('devKey')
|
||||
->inject('telemetry')
|
||||
->action(function (App $utopia, Request $request, Response $response, Document $project, Document $user, Publisher $publisher, Publisher $publisherFunctions, Publisher $publisherWebhooks, Event $queueForEvents, Messaging $queueForMessaging, Audit $queueForAudits, Delete $queueForDeletes, EventDatabase $queueForDatabase, Build $queueForBuilds, StatsUsage $queueForStatsUsage, Database $dbForProject, callable $timelimit, Document $resourceToken, string $mode, ?Key $apiKey, array $plan, Document $devKey, Telemetry $telemetry) use ($usageDatabaseListener, $eventDatabaseListener) {
|
||||
->inject('authorization')
|
||||
->action(function (App $utopia, Request $request, Response $response, Document $project, Document $user, Publisher $publisher, Publisher $publisherFunctions, Publisher $publisherWebhooks, Event $queueForEvents, Messaging $queueForMessaging, Audit $queueForAudits, Delete $queueForDeletes, EventDatabase $queueForDatabase, Build $queueForBuilds, StatsUsage $queueForStatsUsage, Database $dbForProject, callable $timelimit, Document $resourceToken, string $mode, ?Key $apiKey, array $plan, Document $devKey, Telemetry $telemetry, Authorization $authorization) use ($usageDatabaseListener, $eventDatabaseListener) {
|
||||
|
||||
$route = $utopia->getRoute();
|
||||
|
||||
if (
|
||||
array_key_exists('rest', $project->getAttribute('apis', []))
|
||||
&& !$project->getAttribute('apis', [])['rest']
|
||||
&& !(Auth::isPrivilegedUser(Authorization::getRoles()) || Auth::isAppUser(Authorization::getRoles()))
|
||||
&& !(Auth::isPrivilegedUser($authorization->getRoles()) || Auth::isAppUser($authorization->getRoles()))
|
||||
) {
|
||||
throw new AppwriteException(AppwriteException::GENERAL_API_DISABLED);
|
||||
}
|
||||
@@ -482,7 +487,7 @@ App::init()
|
||||
|
||||
$closestLimit = null;
|
||||
|
||||
$roles = Authorization::getRoles();
|
||||
$roles = $authorization->getRoles();
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser($roles);
|
||||
$isAppUser = Auth::isAppUser($roles);
|
||||
|
||||
@@ -582,10 +587,10 @@ App::init()
|
||||
if ($useCache) {
|
||||
$route = $utopia->match($request);
|
||||
$isImageTransformation = $route->getPath() === '/v1/storage/buckets/:bucketId/files/:fileId/preview';
|
||||
$isDisabled = isset($plan['imageTransformations']) && $plan['imageTransformations'] === -1 && !Auth::isPrivilegedUser(Authorization::getRoles());
|
||||
$isDisabled = isset($plan['imageTransformations']) && $plan['imageTransformations'] === -1 && !Auth::isPrivilegedUser($authorization->getRoles());
|
||||
|
||||
$key = $request->cacheIdentifier();
|
||||
$cacheLog = Authorization::skip(fn () => $dbForProject->getDocument('cache', $key));
|
||||
$cacheLog = $authorization->skip(fn () => $dbForProject->getDocument('cache', $key));
|
||||
$cache = new Cache(
|
||||
new Filesystem(APP_STORAGE_CACHE . DIRECTORY_SEPARATOR . 'app-' . $project->getId())
|
||||
);
|
||||
@@ -602,18 +607,21 @@ App::init()
|
||||
|
||||
if ($type === 'bucket' && (!$isImageTransformation || !$isDisabled)) {
|
||||
$bucketId = $parts[1] ?? null;
|
||||
$bucket = Authorization::skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
|
||||
$bucket = $authorization->skip(fn () => $dbForProject->getDocument('buckets', $bucketId));
|
||||
|
||||
$isToken = !$resourceToken->isEmpty() && $resourceToken->getAttribute('bucketInternalId') === $bucket->getSequence();
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser($authorization->getRoles());
|
||||
|
||||
if ($bucket->isEmpty() || (!$bucket->getAttribute('enabled') && !$isAppUser && !$isPrivilegedUser)) {
|
||||
throw new Exception(Exception::STORAGE_BUCKET_NOT_FOUND);
|
||||
}
|
||||
|
||||
if (!$bucket->getAttribute('transformations', true) && !$isAppUser && !$isPrivilegedUser) {
|
||||
throw new Exception(Exception::STORAGE_BUCKET_TRANSFORMATIONS_DISABLED);
|
||||
}
|
||||
|
||||
$fileSecurity = $bucket->getAttribute('fileSecurity', false);
|
||||
$validator = new Authorization(Database::PERMISSION_READ);
|
||||
$valid = $validator->isValid($bucket->getRead());
|
||||
$valid = $authorization->isValid(new Input(Database::PERMISSION_READ, $bucket->getRead()));
|
||||
if (!$fileSecurity && !$valid && !$isToken) {
|
||||
throw new Exception(Exception::USER_UNAUTHORIZED);
|
||||
}
|
||||
@@ -624,7 +632,7 @@ App::init()
|
||||
if ($fileSecurity && !$valid && !$isToken) {
|
||||
$file = $dbForProject->getDocument('bucket_' . $bucket->getSequence(), $fileId);
|
||||
} else {
|
||||
$file = Authorization::skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getSequence(), $fileId));
|
||||
$file = $authorization->skip(fn () => $dbForProject->getDocument('bucket_' . $bucket->getSequence(), $fileId));
|
||||
}
|
||||
|
||||
if (!$resourceToken->isEmpty() && $resourceToken->getAttribute('fileInternalId') !== $file->getSequence()) {
|
||||
@@ -635,11 +643,11 @@ App::init()
|
||||
throw new Exception(Exception::STORAGE_FILE_NOT_FOUND);
|
||||
}
|
||||
//Do not update transformedAt if it's a console user
|
||||
if (!Auth::isPrivilegedUser(Authorization::getRoles())) {
|
||||
if (!Auth::isPrivilegedUser($authorization->getRoles())) {
|
||||
$transformedAt = $file->getAttribute('transformedAt', '');
|
||||
if (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_PROJECT_ACCESS)) > $transformedAt) {
|
||||
$file->setAttribute('transformedAt', DateTime::now());
|
||||
Authorization::skip(fn () => $dbForProject->updateDocument('bucket_' . $file->getAttribute('bucketInternalId'), $file->getId(), $file));
|
||||
$authorization->skip(fn () => $dbForProject->updateDocument('bucket_' . $file->getAttribute('bucketInternalId'), $file->getId(), $file));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -735,7 +743,8 @@ App::shutdown()
|
||||
->inject('queueForWebhooks')
|
||||
->inject('queueForRealtime')
|
||||
->inject('dbForProject')
|
||||
->action(function (App $utopia, Request $request, Response $response, Document $project, Document $user, Event $queueForEvents, Audit $queueForAudits, StatsUsage $queueForStatsUsage, Delete $queueForDeletes, EventDatabase $queueForDatabase, Build $queueForBuilds, Messaging $queueForMessaging, Func $queueForFunctions, Event $queueForWebhooks, Realtime $queueForRealtime, Database $dbForProject) use ($parseLabel) {
|
||||
->inject('authorization')
|
||||
->action(function (App $utopia, Request $request, Response $response, Document $project, Document $user, Event $queueForEvents, Audit $queueForAudits, StatsUsage $queueForStatsUsage, Delete $queueForDeletes, EventDatabase $queueForDatabase, Build $queueForBuilds, Messaging $queueForMessaging, Func $queueForFunctions, Event $queueForWebhooks, Realtime $queueForRealtime, Database $dbForProject, Authorization $authorization) use ($parseLabel) {
|
||||
|
||||
$responsePayload = $response->getPayload();
|
||||
|
||||
@@ -861,11 +870,11 @@ App::shutdown()
|
||||
|
||||
$key = $request->cacheIdentifier();
|
||||
$signature = md5($data['payload']);
|
||||
$cacheLog = Authorization::skip(fn () => $dbForProject->getDocument('cache', $key));
|
||||
$cacheLog = $authorization->skip(fn () => $dbForProject->getDocument('cache', $key));
|
||||
$accessedAt = $cacheLog->getAttribute('accessedAt', 0);
|
||||
$now = DateTime::now();
|
||||
if ($cacheLog->isEmpty()) {
|
||||
Authorization::skip(fn () => $dbForProject->createDocument('cache', new Document([
|
||||
$authorization->skip(fn () => $dbForProject->createDocument('cache', new Document([
|
||||
'$id' => $key,
|
||||
'resource' => $resource,
|
||||
'resourceType' => $resourceType,
|
||||
@@ -875,7 +884,7 @@ App::shutdown()
|
||||
])));
|
||||
} elseif (DateTime::formatTz(DateTime::addSeconds(new \DateTime(), -APP_CACHE_UPDATE)) > $accessedAt) {
|
||||
$cacheLog->setAttribute('accessedAt', $now);
|
||||
Authorization::skip(fn () => $dbForProject->updateDocument('cache', $cacheLog->getId(), $cacheLog));
|
||||
$authorization->skip(fn () => $dbForProject->updateDocument('cache', $cacheLog->getId(), $cacheLog));
|
||||
// Overwrite the file every APP_CACHE_UPDATE seconds to update the file modified time that is used in the TTL checks in cache->load()
|
||||
$cache->save($key, $data['payload']);
|
||||
}
|
||||
@@ -887,7 +896,7 @@ App::shutdown()
|
||||
}
|
||||
|
||||
if ($project->getId() !== 'console') {
|
||||
if (!Auth::isPrivilegedUser(Authorization::getRoles())) {
|
||||
if (!Auth::isPrivilegedUser($authorization->getRoles())) {
|
||||
$fileSize = 0;
|
||||
$file = $request->getFiles('file');
|
||||
if (!empty($file)) {
|
||||
|
||||
@@ -36,7 +36,8 @@ App::init()
|
||||
->inject('request')
|
||||
->inject('project')
|
||||
->inject('geodb')
|
||||
->action(function (App $utopia, Request $request, Document $project, Reader $geodb) {
|
||||
->inject('authorization')
|
||||
->action(function (App $utopia, Request $request, Document $project, Reader $geodb, Authorization $authorization) {
|
||||
$denylist = System::getEnv('_APP_CONSOLE_COUNTRIES_DENYLIST', '');
|
||||
if (!empty($denylist && $project->getId() === 'console')) {
|
||||
$countries = explode(',', $denylist);
|
||||
@@ -49,8 +50,8 @@ App::init()
|
||||
|
||||
$route = $utopia->match($request);
|
||||
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser(Authorization::getRoles());
|
||||
$isAppUser = Auth::isAppUser(Authorization::getRoles());
|
||||
$isPrivilegedUser = Auth::isPrivilegedUser($authorization->getRoles());
|
||||
$isAppUser = Auth::isAppUser($authorization->getRoles());
|
||||
|
||||
if ($isAppUser || $isPrivilegedUser) { // Skip limits for app and console devs
|
||||
return;
|
||||
|
||||
+17
-11
@@ -25,7 +25,6 @@ use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Database\Helpers\Permission;
|
||||
use Utopia\Database\Helpers\Role;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\Logger\Log;
|
||||
use Utopia\Logger\Log\User;
|
||||
use Utopia\Pools\Group;
|
||||
@@ -259,7 +258,9 @@ $http->on(Constant::EVENT_START, function (Server $http) use ($payloadSize, $reg
|
||||
createDatabase($app, 'getLogsDB', 'logs', $collections['logs'], $pools);
|
||||
|
||||
// create appwrite database, `dbForPlatform` is a direct access call.
|
||||
createDatabase($app, 'dbForPlatform', 'appwrite', $collections['console'], $pools, function (Database $dbForPlatform) use ($collections) {
|
||||
createDatabase($app, 'dbForPlatform', 'appwrite', $collections['console'], $pools, function (Database $dbForPlatform) use ($collections, $app) {
|
||||
$authorization = $app->getResource('authorization');
|
||||
|
||||
if ($dbForPlatform->getCollection(Audit::COLLECTION)->isEmpty()) {
|
||||
$audit = new Audit($dbForPlatform);
|
||||
$audit->setup();
|
||||
@@ -318,9 +319,9 @@ $http->on(Constant::EVENT_START, function (Server $http) use ($payloadSize, $reg
|
||||
$dbForPlatform->createCollection('bucket_' . $bucket->getSequence(), $attributes, $indexes);
|
||||
}
|
||||
|
||||
if (Authorization::skip(fn () => $dbForPlatform->getDocument('buckets', 'screenshots')->isEmpty())) {
|
||||
if ($authorization->skip(fn () => $dbForPlatform->getDocument('buckets', 'screenshots')->isEmpty())) {
|
||||
Console::info(" └── Creating screenshots bucket...");
|
||||
Authorization::skip(fn () => $dbForPlatform->createDocument('buckets', new Document([
|
||||
$authorization->skip(fn () => $dbForPlatform->createDocument('buckets', new Document([
|
||||
'$id' => ID::custom('screenshots'),
|
||||
'$collection' => ID::custom('buckets'),
|
||||
'name' => 'Screenshots',
|
||||
@@ -335,7 +336,7 @@ $http->on(Constant::EVENT_START, function (Server $http) use ($payloadSize, $reg
|
||||
'search' => 'buckets Screenshots',
|
||||
])));
|
||||
|
||||
$bucket = Authorization::skip(fn () => $dbForPlatform->getDocument('buckets', 'screenshots'));
|
||||
$bucket = $authorization->skip(fn () => $dbForPlatform->getDocument('buckets', 'screenshots'));
|
||||
|
||||
Console::info(" └── Creating files collection for screenshots bucket...");
|
||||
$files = $collections['buckets']['files'] ?? [];
|
||||
@@ -363,7 +364,7 @@ $http->on(Constant::EVENT_START, function (Server $http) use ($payloadSize, $reg
|
||||
'orders' => $index['orders'],
|
||||
]), $files['indexes']);
|
||||
|
||||
Authorization::skip(fn () => $dbForPlatform->createCollection('bucket_' . $bucket->getSequence(), $attributes, $indexes));
|
||||
$authorization->skip(fn () => $dbForPlatform->createCollection('bucket_' . $bucket->getSequence(), $attributes, $indexes));
|
||||
}
|
||||
});
|
||||
|
||||
@@ -454,8 +455,12 @@ $http->on(Constant::EVENT_REQUEST, function (SwooleRequest $swooleRequest, Swool
|
||||
App::setResource('pools', fn () => $pools);
|
||||
|
||||
try {
|
||||
Authorization::cleanRoles();
|
||||
Authorization::setRole(Role::any()->toString());
|
||||
$authorization = $app->getResource('authorization');
|
||||
|
||||
$request->setAuthorization($authorization);
|
||||
$response->setAuthorization($authorization);
|
||||
$authorization->cleanRoles();
|
||||
$authorization->addRole(Role::any()->toString());
|
||||
|
||||
$app->run($request, $response);
|
||||
} catch (\Throwable $th) {
|
||||
@@ -497,7 +502,7 @@ $http->on(Constant::EVENT_REQUEST, function (SwooleRequest $swooleRequest, Swool
|
||||
$log->addExtra('file', $th->getFile());
|
||||
$log->addExtra('line', $th->getLine());
|
||||
$log->addExtra('trace', $th->getTraceAsString());
|
||||
$log->addExtra('roles', Authorization::getRoles());
|
||||
$log->addExtra('roles', isset($authorization) ? $authorization->getRoles() : []);
|
||||
|
||||
$sdk = $route->getLabel("sdk", false);
|
||||
|
||||
@@ -556,7 +561,7 @@ $http->on(Constant::EVENT_TASK, function () use ($register, $domains) {
|
||||
/** @var Utopia\Database\Database $dbForPlatform */
|
||||
$dbForPlatform = $app->getResource('dbForPlatform');
|
||||
|
||||
Timer::tick(DOMAIN_SYNC_TIMER * 1000, function () use ($dbForPlatform, $domains, &$lastSyncUpdate) {
|
||||
Timer::tick(DOMAIN_SYNC_TIMER * 1000, function () use ($dbForPlatform, $domains, &$lastSyncUpdate, $app) {
|
||||
try {
|
||||
$time = DateTime::now();
|
||||
$limit = 1000;
|
||||
@@ -573,7 +578,8 @@ $http->on(Constant::EVENT_TASK, function () use ($register, $domains) {
|
||||
}
|
||||
$results = [];
|
||||
try {
|
||||
$results = Authorization::skip(fn () => $dbForPlatform->find('rules', $queries));
|
||||
$authorization = $app->getResource('authorization');
|
||||
$results = $authorization->skip(fn () => $dbForPlatform->find('rules', $queries));
|
||||
} catch (Throwable $th) {
|
||||
Console::error($th->getMessage());
|
||||
}
|
||||
|
||||
@@ -138,6 +138,7 @@ const DELETE_TYPE_TOPIC = 'topic';
|
||||
const DELETE_TYPE_TARGET = 'target';
|
||||
const DELETE_TYPE_EXPIRED_TARGETS = 'invalid_targets';
|
||||
const DELETE_TYPE_SESSION_TARGETS = 'session_targets';
|
||||
const DELETE_TYPE_CSV_EXPORTS = 'csv_exports';
|
||||
const DELETE_TYPE_MAINTENANCE = 'maintenance';
|
||||
|
||||
// Message types
|
||||
@@ -271,6 +272,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';
|
||||
|
||||
@@ -4,7 +4,6 @@ use Appwrite\OpenSSL\OpenSSL;
|
||||
use Utopia\Database\Database;
|
||||
use Utopia\Database\Document;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\System\System;
|
||||
|
||||
Database::addFilter(
|
||||
@@ -176,7 +175,7 @@ Database::addFilter(
|
||||
return;
|
||||
},
|
||||
function (mixed $value, Document $document, Database $database) {
|
||||
return Authorization::skip(fn () => $database->find('sessions', [
|
||||
return $database->getAuthorization()->skip(fn () => $database->find('sessions', [
|
||||
Query::equal('userInternalId', [$document->getSequence()]),
|
||||
Query::limit(APP_LIMIT_SUBQUERY),
|
||||
]));
|
||||
@@ -189,7 +188,7 @@ Database::addFilter(
|
||||
return;
|
||||
},
|
||||
function (mixed $value, Document $document, Database $database) {
|
||||
return Authorization::skip(fn () => $database
|
||||
return $database->getAuthorization()->skip(fn () => $database
|
||||
->find('tokens', [
|
||||
Query::equal('userInternalId', [$document->getSequence()]),
|
||||
Query::limit(APP_LIMIT_SUBQUERY),
|
||||
@@ -203,7 +202,7 @@ Database::addFilter(
|
||||
return;
|
||||
},
|
||||
function (mixed $value, Document $document, Database $database) {
|
||||
return Authorization::skip(fn () => $database
|
||||
return $database->getAuthorization()->skip(fn () => $database
|
||||
->find('challenges', [
|
||||
Query::equal('userInternalId', [$document->getSequence()]),
|
||||
Query::limit(APP_LIMIT_SUBQUERY),
|
||||
@@ -217,7 +216,7 @@ Database::addFilter(
|
||||
return;
|
||||
},
|
||||
function (mixed $value, Document $document, Database $database) {
|
||||
return Authorization::skip(fn () => $database
|
||||
return $database->getAuthorization()->skip(fn () => $database
|
||||
->find('authenticators', [
|
||||
Query::equal('userInternalId', [$document->getSequence()]),
|
||||
Query::limit(APP_LIMIT_SUBQUERY),
|
||||
@@ -231,7 +230,7 @@ Database::addFilter(
|
||||
return;
|
||||
},
|
||||
function (mixed $value, Document $document, Database $database) {
|
||||
return Authorization::skip(fn () => $database
|
||||
return $database->getAuthorization()->skip(fn () => $database
|
||||
->find('memberships', [
|
||||
Query::equal('userInternalId', [$document->getSequence()]),
|
||||
Query::limit(APP_LIMIT_SUBQUERY),
|
||||
@@ -331,7 +330,7 @@ Database::addFilter(
|
||||
return;
|
||||
},
|
||||
function (mixed $value, Document $document, Database $database) {
|
||||
return Authorization::skip(fn () => $database
|
||||
return $database->getAuthorization()->skip(fn () => $database
|
||||
->find('targets', [
|
||||
Query::equal('userInternalId', [$document->getSequence()]),
|
||||
Query::limit(APP_LIMIT_SUBQUERY)
|
||||
@@ -345,7 +344,7 @@ Database::addFilter(
|
||||
return;
|
||||
},
|
||||
function (mixed $value, Document $document, Database $database) {
|
||||
$targetIds = Authorization::skip(fn () => \array_map(
|
||||
$targetIds = $database->getAuthorization()->skip(fn () => \array_map(
|
||||
fn ($document) => $document->getAttribute('targetInternalId'),
|
||||
$database->find('subscribers', [
|
||||
Query::equal('topicInternalId', [$document->getSequence()]),
|
||||
|
||||
+53
-43
@@ -152,7 +152,7 @@ App::setResource('queueForMigrations', function (Publisher $publisher) {
|
||||
App::setResource('queueForStatsResources', function (Publisher $publisher) {
|
||||
return new StatsResources($publisher);
|
||||
}, ['publisher']);
|
||||
App::setResource('platforms', function (Request $request, Document $console, Document $project, Database $dbForPlatform) {
|
||||
App::setResource('platforms', function (Request $request, Document $console, Document $project, Database $dbForPlatform, Authorization $authorization) {
|
||||
$console->setAttribute('platforms', [ // Always allow current host
|
||||
'$collection' => ID::custom('platforms'),
|
||||
'name' => 'Current Host',
|
||||
@@ -200,9 +200,9 @@ App::setResource('platforms', function (Request $request, Document $console, Doc
|
||||
// Safe if rule with same project ID exists
|
||||
if (!empty($origin)) {
|
||||
if (System::getEnv('_APP_RULES_FORMAT') === 'md5') {
|
||||
$rule = Authorization::skip(fn () => $dbForPlatform->getDocument('rules', md5($origin ?? '')));
|
||||
$rule = $authorization->skip(fn () => $dbForPlatform->getDocument('rules', md5($origin ?? '')));
|
||||
} else {
|
||||
$rule = Authorization::skip(
|
||||
$rule = $authorization->skip(
|
||||
fn () => $dbForPlatform->find('rules', [
|
||||
Query::equal('domain', [$origin]),
|
||||
Query::limit(1)
|
||||
@@ -224,17 +224,18 @@ App::setResource('platforms', function (Request $request, Document $console, Doc
|
||||
...$console->getAttribute('platforms', []),
|
||||
...$project->getAttribute('platforms', []),
|
||||
];
|
||||
}, ['request', 'console', 'project', 'dbForPlatform']);
|
||||
}, ['request', 'console', 'project', 'dbForPlatform', 'authorization']);
|
||||
|
||||
App::setResource('user', function ($mode, $project, $console, $request, $response, $dbForProject, $dbForPlatform) {
|
||||
App::setResource('user', function ($mode, $project, $console, $request, $response, $dbForProject, $dbForPlatform, $authorization) {
|
||||
/** @var Appwrite\Utopia\Request $request */
|
||||
/** @var Appwrite\Utopia\Response $response */
|
||||
/** @var Utopia\Database\Document $project */
|
||||
/** @var Utopia\Database\Database $dbForProject */
|
||||
/** @var Utopia\Database\Database $dbForPlatform */
|
||||
/** @var Utopia\Database\Authorization $authorization */
|
||||
/** @var string $mode */
|
||||
|
||||
Authorization::setDefaultStatus(true);
|
||||
$authorization->setDefaultStatus(true);
|
||||
|
||||
Auth::setCookieName('a_session_' . $project->getId());
|
||||
|
||||
@@ -298,7 +299,7 @@ App::setResource('user', function ($mode, $project, $console, $request, $respons
|
||||
|
||||
// if (APP_MODE_ADMIN === $mode) {
|
||||
// if ($user->find('teamInternalId', $project->getAttribute('teamInternalId'), 'memberships')) {
|
||||
// Authorization::setDefaultStatus(false); // Cancel security segmentation for admin users.
|
||||
// $authorization->setDefaultStatus(false); // Cancel security segmentation for admin users.
|
||||
// } else {
|
||||
// $user = new Document([]);
|
||||
// }
|
||||
@@ -336,9 +337,9 @@ App::setResource('user', function ($mode, $project, $console, $request, $respons
|
||||
$dbForPlatform->setMetadata('user', $user->getId());
|
||||
|
||||
return $user;
|
||||
}, ['mode', 'project', 'console', 'request', 'response', 'dbForProject', 'dbForPlatform']);
|
||||
}, ['mode', 'project', 'console', 'request', 'response', 'dbForProject', 'dbForPlatform', 'authorization']);
|
||||
|
||||
App::setResource('project', function ($dbForPlatform, $request, $console) {
|
||||
App::setResource('project', function ($dbForPlatform, $request, $console, $authorization) {
|
||||
/** @var Appwrite\Utopia\Request $request */
|
||||
/** @var Utopia\Database\Database $dbForPlatform */
|
||||
/** @var Utopia\Database\Document $console */
|
||||
@@ -349,10 +350,10 @@ App::setResource('project', function ($dbForPlatform, $request, $console) {
|
||||
return $console;
|
||||
}
|
||||
|
||||
$project = Authorization::skip(fn () => $dbForPlatform->getDocument('projects', $projectId));
|
||||
$project = $authorization->skip(fn () => $dbForPlatform->getDocument('projects', $projectId));
|
||||
|
||||
return $project;
|
||||
}, ['dbForPlatform', 'request', 'console']);
|
||||
}, ['dbForPlatform', 'request', 'console', 'authorization']);
|
||||
|
||||
App::setResource('session', function (Document $user) {
|
||||
if ($user->isEmpty()) {
|
||||
@@ -379,7 +380,11 @@ App::setResource('console', function () {
|
||||
return new Document(Config::getParam('console'));
|
||||
}, []);
|
||||
|
||||
App::setResource('dbForProject', function (Group $pools, Database $dbForPlatform, Cache $cache, Document $project) {
|
||||
App::setResource('authorization', function () {
|
||||
return new Authorization();
|
||||
}, []);
|
||||
|
||||
App::setResource('dbForProject', function (Group $pools, Database $dbForPlatform, Cache $cache, Document $project, Authorization $authorization) {
|
||||
if ($project->isEmpty() || $project->getId() === 'console') {
|
||||
return $dbForPlatform;
|
||||
}
|
||||
@@ -395,6 +400,7 @@ App::setResource('dbForProject', function (Group $pools, Database $dbForPlatform
|
||||
$database = new Database($adapter, $cache);
|
||||
|
||||
$database
|
||||
->setAuthorization($authorization)
|
||||
->setMetadata('host', \gethostname())
|
||||
->setMetadata('project', $project->getId())
|
||||
->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS_API)
|
||||
@@ -415,13 +421,15 @@ App::setResource('dbForProject', function (Group $pools, Database $dbForPlatform
|
||||
}
|
||||
|
||||
return $database;
|
||||
}, ['pools', 'dbForPlatform', 'cache', 'project']);
|
||||
}, ['pools', 'dbForPlatform', 'cache', 'project', 'authorization']);
|
||||
|
||||
App::setResource('dbForPlatform', function (Group $pools, Cache $cache, Authorization $authorization) {
|
||||
|
||||
App::setResource('dbForPlatform', function (Group $pools, Cache $cache) {
|
||||
$adapter = new DatabasePool($pools->get('console'));
|
||||
$database = new Database($adapter, $cache);
|
||||
|
||||
$database
|
||||
->setAuthorization($authorization)
|
||||
->setNamespace('_console')
|
||||
->setMetadata('host', \gethostname())
|
||||
->setMetadata('project', 'console')
|
||||
@@ -429,12 +437,12 @@ App::setResource('dbForPlatform', function (Group $pools, Cache $cache) {
|
||||
->setMaxQueryValues(APP_DATABASE_QUERY_MAX_VALUES);
|
||||
|
||||
return $database;
|
||||
}, ['pools', 'cache']);
|
||||
}, ['pools', 'cache', 'authorization']);
|
||||
|
||||
App::setResource('getProjectDB', function (Group $pools, Database $dbForPlatform, $cache) {
|
||||
App::setResource('getProjectDB', function (Group $pools, Database $dbForPlatform, $cache, Authorization $authorization) {
|
||||
$databases = [];
|
||||
|
||||
return function (Document $project) use ($pools, $dbForPlatform, $cache, &$databases) {
|
||||
return function (Document $project) use ($pools, $dbForPlatform, $cache, $authorization, &$databases) {
|
||||
if ($project->isEmpty() || $project->getId() === 'console') {
|
||||
return $dbForPlatform;
|
||||
}
|
||||
@@ -446,8 +454,9 @@ App::setResource('getProjectDB', function (Group $pools, Database $dbForPlatform
|
||||
$dsn = new DSN('mysql://' . $project->getAttribute('database'));
|
||||
}
|
||||
|
||||
$configure = (function (Database $database) use ($project, $dsn) {
|
||||
$configure = (function (Database $database) use ($project, $dsn, $authorization) {
|
||||
$database
|
||||
->setAuthorization($authorization)
|
||||
->setMetadata('host', \gethostname())
|
||||
->setMetadata('project', $project->getId())
|
||||
->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS_API)
|
||||
@@ -481,12 +490,12 @@ App::setResource('getProjectDB', function (Group $pools, Database $dbForPlatform
|
||||
|
||||
return $database;
|
||||
};
|
||||
}, ['pools', 'dbForPlatform', 'cache']);
|
||||
}, ['pools', 'dbForPlatform', 'cache', 'authorization']);
|
||||
|
||||
App::setResource('getLogsDB', function (Group $pools, Cache $cache) {
|
||||
App::setResource('getLogsDB', function (Group $pools, Cache $cache, Authorization $authorization) {
|
||||
$database = null;
|
||||
|
||||
return function (?Document $project = null) use ($pools, $cache, &$database) {
|
||||
return function (?Document $project = null) use ($pools, $cache, $authorization, &$database) {
|
||||
if ($database !== null && $project !== null && !$project->isEmpty() && $project->getId() !== 'console') {
|
||||
$database->setTenant((int) $project->getSequence());
|
||||
return $database;
|
||||
@@ -496,6 +505,7 @@ App::setResource('getLogsDB', function (Group $pools, Cache $cache) {
|
||||
$database = new Database($adapter, $cache);
|
||||
|
||||
$database
|
||||
->setAuthorization($authorization)
|
||||
->setSharedTables(true)
|
||||
->setNamespace('logsV1')
|
||||
->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS_API)
|
||||
@@ -508,7 +518,7 @@ App::setResource('getLogsDB', function (Group $pools, Cache $cache) {
|
||||
|
||||
return $database;
|
||||
};
|
||||
}, ['pools', 'cache']);
|
||||
}, ['pools', 'cache', 'authorization']);
|
||||
|
||||
App::setResource('telemetry', fn () => new NoTelemetry());
|
||||
|
||||
@@ -702,7 +712,7 @@ App::setResource('promiseAdapter', function ($register) {
|
||||
return $register->get('promiseAdapter');
|
||||
}, ['register']);
|
||||
|
||||
App::setResource('schema', function ($utopia, $dbForProject) {
|
||||
App::setResource('schema', function ($utopia, $dbForProject, $authorization) {
|
||||
|
||||
$complexity = function (int $complexity, array $args) {
|
||||
$queries = Query::parseQueries($args['queries'] ?? []);
|
||||
@@ -712,8 +722,8 @@ App::setResource('schema', function ($utopia, $dbForProject) {
|
||||
return $complexity * $limit;
|
||||
};
|
||||
|
||||
$attributes = function (int $limit, int $offset) use ($dbForProject) {
|
||||
$attrs = Authorization::skip(fn () => $dbForProject->find('attributes', [
|
||||
$attributes = function (int $limit, int $offset) use ($dbForProject, $authorization) {
|
||||
$attrs = $authorization->skip(fn () => $dbForProject->find('attributes', [
|
||||
Query::limit($limit),
|
||||
Query::offset($offset),
|
||||
]));
|
||||
@@ -787,7 +797,7 @@ App::setResource('schema', function ($utopia, $dbForProject) {
|
||||
$urls,
|
||||
$params,
|
||||
);
|
||||
}, ['utopia', 'dbForProject']);
|
||||
}, ['utopia', 'dbForProject', 'authorization']);
|
||||
|
||||
App::setResource('contributors', function () {
|
||||
$path = 'app/config/contributors.json';
|
||||
@@ -833,7 +843,7 @@ App::setResource('smsRates', function () {
|
||||
return [];
|
||||
});
|
||||
|
||||
App::setResource('devKey', function (Request $request, Document $project, array $servers, Database $dbForPlatform) {
|
||||
App::setResource('devKey', function (Request $request, Document $project, array $servers, Database $dbForPlatform, Authorization $authorization) {
|
||||
$devKey = $request->getHeader('x-appwrite-dev-key', $request->getParam('devKey', ''));
|
||||
|
||||
// Check if given key match project's development keys
|
||||
@@ -852,7 +862,7 @@ App::setResource('devKey', function (Request $request, Document $project, array
|
||||
$accessedAt = $key->getAttribute('accessedAt', 0);
|
||||
if (empty($accessedAt) || DatabaseDateTime::formatTz(DatabaseDateTime::addSeconds(new \DateTime(), -APP_KEY_ACCESS)) > $accessedAt) {
|
||||
$key->setAttribute('accessedAt', DatabaseDateTime::now());
|
||||
Authorization::skip(fn () => $dbForPlatform->updateDocument('devKeys', $key->getId(), $key));
|
||||
$authorization->skip(fn () => $dbForPlatform->updateDocument('devKeys', $key->getId(), $key));
|
||||
$dbForPlatform->purgeCachedDocument('projects', $project->getId());
|
||||
}
|
||||
|
||||
@@ -869,14 +879,14 @@ App::setResource('devKey', function (Request $request, Document $project, array
|
||||
|
||||
/** Update access time as well */
|
||||
$key->setAttribute('accessedAt', DatabaseDateTime::now());
|
||||
$key = Authorization::skip(fn () => $dbForPlatform->updateDocument('devKeys', $key->getId(), $key));
|
||||
$key = $authorization->skip(fn () => $dbForPlatform->updateDocument('devKeys', $key->getId(), $key));
|
||||
$dbForPlatform->purgeCachedDocument('projects', $project->getId());
|
||||
}
|
||||
}
|
||||
return $key;
|
||||
}, ['request', 'project', 'servers', 'dbForPlatform']);
|
||||
}, ['request', 'project', 'servers', 'dbForPlatform', 'authorization']);
|
||||
|
||||
App::setResource('team', function (Document $project, Database $dbForPlatform, App $utopia, Request $request) {
|
||||
App::setResource('team', function (Document $project, Database $dbForPlatform, App $utopia, Request $request, Authorization $authorization) {
|
||||
$teamInternalId = '';
|
||||
if ($project->getId() !== 'console') {
|
||||
$teamInternalId = $project->getAttribute('teamInternalId', '');
|
||||
@@ -886,7 +896,7 @@ App::setResource('team', function (Document $project, Database $dbForPlatform, A
|
||||
if (str_starts_with($path, '/v1/projects/:projectId')) {
|
||||
$uri = $request->getURI();
|
||||
$pid = explode('/', $uri)[3];
|
||||
$p = Authorization::skip(fn () => $dbForPlatform->getDocument('projects', $pid));
|
||||
$p = $authorization->skip(fn () => $dbForPlatform->getDocument('projects', $pid));
|
||||
$teamInternalId = $p->getAttribute('teamInternalId', '');
|
||||
} elseif ($path === '/v1/projects') {
|
||||
$teamId = $request->getParam('teamId', '');
|
||||
@@ -895,7 +905,7 @@ App::setResource('team', function (Document $project, Database $dbForPlatform, A
|
||||
return new Document([]);
|
||||
}
|
||||
|
||||
$team = Authorization::skip(fn () => $dbForPlatform->getDocument('teams', $teamId));
|
||||
$team = $authorization->skip(fn () => $dbForPlatform->getDocument('teams', $teamId));
|
||||
return $team;
|
||||
}
|
||||
}
|
||||
@@ -904,14 +914,14 @@ App::setResource('team', function (Document $project, Database $dbForPlatform, A
|
||||
return new Document([]);
|
||||
}
|
||||
|
||||
$team = Authorization::skip(function () use ($dbForPlatform, $teamInternalId) {
|
||||
$team = $authorization->skip(function () use ($dbForPlatform, $teamInternalId) {
|
||||
return $dbForPlatform->findOne('teams', [
|
||||
Query::equal('$sequence', [$teamInternalId]),
|
||||
]);
|
||||
});
|
||||
|
||||
return $team;
|
||||
}, ['project', 'dbForPlatform', 'utopia', 'request']);
|
||||
}, ['project', 'dbForPlatform', 'utopia', 'request', 'authorization']);
|
||||
|
||||
App::setResource(
|
||||
'isResourceBlocked',
|
||||
@@ -949,7 +959,7 @@ App::setResource('apiKey', function (Request $request, Document $project): ?Key
|
||||
|
||||
App::setResource('executor', fn () => new Executor());
|
||||
|
||||
App::setResource('resourceToken', function ($project, $dbForProject, $request) {
|
||||
App::setResource('resourceToken', function ($project, $dbForProject, $request, Authorization $authorization) {
|
||||
$tokenJWT = $request->getParam('token');
|
||||
|
||||
if (!empty($tokenJWT) && !$project->isEmpty()) { // JWT authentication
|
||||
@@ -966,7 +976,7 @@ App::setResource('resourceToken', function ($project, $dbForProject, $request) {
|
||||
return new Document([]);
|
||||
}
|
||||
|
||||
$token = Authorization::skip(fn () => $dbForProject->getDocument('resourceTokens', $tokenId));
|
||||
$token = $authorization->skip(fn () => $dbForProject->getDocument('resourceTokens', $tokenId));
|
||||
|
||||
if ($token->isEmpty()) {
|
||||
return new Document([]);
|
||||
@@ -984,7 +994,7 @@ App::setResource('resourceToken', function ($project, $dbForProject, $request) {
|
||||
}
|
||||
|
||||
return match ($token->getAttribute('resourceType')) {
|
||||
TOKENS_RESOURCE_TYPE_FILES => (function () use ($token, $dbForProject) {
|
||||
TOKENS_RESOURCE_TYPE_FILES => (function () use ($token, $dbForProject, $authorization) {
|
||||
$sequences = explode(':', $token->getAttribute('resourceInternalId'));
|
||||
$ids = explode(':', $token->getAttribute('resourceId'));
|
||||
|
||||
@@ -995,7 +1005,7 @@ App::setResource('resourceToken', function ($project, $dbForProject, $request) {
|
||||
$accessedAt = $token->getAttribute('accessedAt', 0);
|
||||
if (empty($accessedAt) || DatabaseDateTime::formatTz(DatabaseDateTime::addSeconds(new \DateTime(), -APP_RESOURCE_TOKEN_ACCESS)) > $accessedAt) {
|
||||
$token->setAttribute('accessedAt', DatabaseDateTime::now());
|
||||
Authorization::skip(fn () => $dbForProject->updateDocument('resourceTokens', $token->getId(), $token));
|
||||
$authorization->skip(fn () => $dbForProject->updateDocument('resourceTokens', $token->getId(), $token));
|
||||
}
|
||||
|
||||
return new Document([
|
||||
@@ -1010,7 +1020,7 @@ App::setResource('resourceToken', function ($project, $dbForProject, $request) {
|
||||
};
|
||||
}
|
||||
return new Document([]);
|
||||
}, ['project', 'dbForProject', 'request']);
|
||||
}, ['project', 'dbForProject', 'request', 'authorization']);
|
||||
|
||||
App::setResource('httpReferrer', function (Request $request): string {
|
||||
$referrer = $request->getReferer();
|
||||
@@ -1043,6 +1053,6 @@ App::setResource('httpReferrerSafe', function (Request $request, string $httpRef
|
||||
return $referrer;
|
||||
}, ['request', 'httpReferrer', 'platforms', 'dbForPlatform', 'project', 'utopia']);
|
||||
|
||||
App::setResource('transactionState', function (Database $dbForProject) {
|
||||
return new TransactionState($dbForProject);
|
||||
}, ['dbForProject']);
|
||||
App::setResource('transactionState', function (Database $dbForProject, Authorization $authorization) {
|
||||
return new TransactionState($dbForProject, $authorization);
|
||||
}, ['dbForProject', 'authorization']);
|
||||
|
||||
+11
-12
@@ -28,7 +28,6 @@ use Utopia\Database\Document;
|
||||
use Utopia\Database\Helpers\ID;
|
||||
use Utopia\Database\Helpers\Role;
|
||||
use Utopia\Database\Query;
|
||||
use Utopia\Database\Validator\Authorization;
|
||||
use Utopia\DSN\DSN;
|
||||
use Utopia\Logger\Log;
|
||||
use Utopia\Pools\Group;
|
||||
@@ -299,7 +298,7 @@ $server->onStart(function () use ($stats, $register, $containerId, &$statsDocume
|
||||
'value' => '{}'
|
||||
]);
|
||||
|
||||
$statsDocument = Authorization::skip(fn () => $database->createDocument('realtime', $document));
|
||||
$statsDocument = $database->getAuthorization()->skip(fn () => $database->createDocument('realtime', $document));
|
||||
break;
|
||||
} catch (Throwable) {
|
||||
Console::warning("Collection not ready. Retrying connection ({$attempts})...");
|
||||
@@ -329,7 +328,7 @@ $server->onStart(function () use ($stats, $register, $containerId, &$statsDocume
|
||||
->setAttribute('timestamp', DateTime::now())
|
||||
->setAttribute('value', json_encode($payload));
|
||||
|
||||
Authorization::skip(fn () => $database->updateDocument('realtime', $statsDocument->getId(), $statsDocument));
|
||||
$database->getAuthorization()->skip(fn () => $database->updateDocument('realtime', $statsDocument->getId(), $statsDocument));
|
||||
} catch (Throwable $th) {
|
||||
$logError($th, "updateWorkerDocument");
|
||||
}
|
||||
@@ -360,7 +359,7 @@ $server->onWorkerStart(function (int $workerId) use ($server, $register, $stats,
|
||||
|
||||
$payload = [];
|
||||
|
||||
$list = Authorization::skip(fn () => $database->find('realtime', [
|
||||
$list = $database->getAuthorization()->skip(fn () => $database->find('realtime', [
|
||||
Query::greaterThan('timestamp', DateTime::addSeconds(new \DateTime(), -15)),
|
||||
]));
|
||||
|
||||
@@ -454,12 +453,11 @@ $server->onWorkerStart(function (int $workerId) use ($server, $register, $stats,
|
||||
if ($realtime->hasSubscriber($projectId, 'user:' . $userId)) {
|
||||
$connection = array_key_first(reset($realtime->subscriptions[$projectId]['user:' . $userId]));
|
||||
$consoleDatabase = getConsoleDB();
|
||||
$project = Authorization::skip(fn () => $consoleDatabase->getDocument('projects', $projectId));
|
||||
$project = $consoleDatabase->getAuthorization()->skip(fn () => $consoleDatabase->getDocument('projects', $projectId));
|
||||
$database = getProjectDB($project);
|
||||
|
||||
$user = $database->getDocument('users', $userId);
|
||||
|
||||
$roles = Auth::getRoles($user);
|
||||
$roles = Auth::getRoles($user, $database->getAuthorization());
|
||||
$channels = $realtime->connections[$connection]['channels'];
|
||||
|
||||
$realtime->unsubscribe($connection);
|
||||
@@ -515,6 +513,7 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
|
||||
try {
|
||||
/** @var Document $project */
|
||||
$project = $app->getResource('project');
|
||||
$authorization = $app->getResource('authorization');
|
||||
|
||||
/*
|
||||
* Project Check
|
||||
@@ -526,7 +525,7 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
|
||||
if (
|
||||
array_key_exists('realtime', $project->getAttribute('apis', []))
|
||||
&& !$project->getAttribute('apis', [])['realtime']
|
||||
&& !(Auth::isPrivilegedUser(Authorization::getRoles()) || Auth::isAppUser(Authorization::getRoles()))
|
||||
&& !(Auth::isPrivilegedUser($authorization->getRoles()) || Auth::isAppUser($authorization->getRoles()))
|
||||
) {
|
||||
throw new AppwriteException(AppwriteException::GENERAL_API_DISABLED);
|
||||
}
|
||||
@@ -563,7 +562,7 @@ $server->onOpen(function (int $connection, SwooleRequest $request) use ($server,
|
||||
throw new Exception(Exception::REALTIME_POLICY_VIOLATION, $originValidator->getDescription());
|
||||
}
|
||||
|
||||
$roles = Auth::getRoles($user);
|
||||
$roles = Auth::getRoles($user, $authorization);
|
||||
|
||||
$channels = Realtime::convertChannels($request->getQuery('channels', []), $user->getId());
|
||||
|
||||
@@ -637,8 +636,8 @@ $server->onMessage(function (int $connection, string $message) use ($server, $re
|
||||
$database = getConsoleDB();
|
||||
|
||||
if ($projectId !== 'console') {
|
||||
$project = Authorization::skip(fn () => $database->getDocument('projects', $projectId));
|
||||
$database = getProjectDB($project);
|
||||
$project = $database->getAuthorization()->skip(fn () => $database->getDocument('projects', $projectId));
|
||||
$database = getProjectDB($project, $database->getAuthorization());
|
||||
} else {
|
||||
$project = null;
|
||||
}
|
||||
@@ -692,7 +691,7 @@ $server->onMessage(function (int $connection, string $message) use ($server, $re
|
||||
throw new Exception(Exception::REALTIME_MESSAGE_FORMAT_INVALID, 'Session is not valid.');
|
||||
}
|
||||
|
||||
$roles = Auth::getRoles($user);
|
||||
$roles = Auth::getRoles($user, $database->getAuthorization());
|
||||
$channels = Realtime::convertChannels(array_flip($realtime->connections[$connection]['channels']), $user->getId());
|
||||
$realtime->subscribe($realtime->connections[$connection]['projectId'], $connection, $roles, $channels);
|
||||
|
||||
|
||||
+32
-17
@@ -45,19 +45,28 @@ use Utopia\System\System;
|
||||
use Utopia\Telemetry\Adapter as Telemetry;
|
||||
use Utopia\Telemetry\Adapter\None as NoTelemetry;
|
||||
|
||||
Authorization::disable();
|
||||
Runtime::enableCoroutine();
|
||||
|
||||
Server::setResource('register', fn () => $register);
|
||||
|
||||
Server::setResource('dbForPlatform', function (Cache $cache, Registry $register) {
|
||||
Server::setResource('authorization', function () {
|
||||
$authorization = new Authorization();
|
||||
$authorization->disable();
|
||||
return $authorization;
|
||||
}, []);
|
||||
|
||||
Server::setResource('dbForPlatform', function (Cache $cache, Registry $register, Authorization $authorization) {
|
||||
$pools = $register->get('pools');
|
||||
$adapter = new DatabasePool($pools->get('console'));
|
||||
$dbForPlatform = new Database($adapter, $cache);
|
||||
$dbForPlatform->setNamespace('_console');
|
||||
|
||||
$dbForPlatform
|
||||
->setAuthorization($authorization)
|
||||
->setNamespace('_console');
|
||||
|
||||
|
||||
return $dbForPlatform;
|
||||
}, ['cache', 'register']);
|
||||
}, ['cache', 'register', 'authorization']);
|
||||
|
||||
Server::setResource('project', function (Message $message, Database $dbForPlatform) {
|
||||
$payload = $message->getPayload() ?? [];
|
||||
@@ -70,7 +79,7 @@ Server::setResource('project', function (Message $message, Database $dbForPlatfo
|
||||
return $dbForPlatform->getDocument('projects', $project->getId());
|
||||
}, ['message', 'dbForPlatform']);
|
||||
|
||||
Server::setResource('dbForProject', function (Cache $cache, Registry $register, Message $message, Document $project, Database $dbForPlatform) {
|
||||
Server::setResource('dbForProject', function (Cache $cache, Registry $register, Message $message, Document $project, Database $dbForPlatform, Authorization $authorization) {
|
||||
if ($project->isEmpty() || $project->getId() === 'console') {
|
||||
return $dbForPlatform;
|
||||
}
|
||||
@@ -101,15 +110,17 @@ Server::setResource('dbForProject', function (Cache $cache, Registry $register,
|
||||
->setNamespace('_' . $project->getSequence());
|
||||
}
|
||||
|
||||
$database->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS_WORKER);
|
||||
$database
|
||||
->setAuthorization($authorization)
|
||||
->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS_WORKER);
|
||||
|
||||
return $database;
|
||||
}, ['cache', 'register', 'message', 'project', 'dbForPlatform']);
|
||||
}, ['cache', 'register', 'message', 'project', 'dbForPlatform', 'authorization']);
|
||||
|
||||
Server::setResource('getProjectDB', function (Group $pools, Database $dbForPlatform, $cache) {
|
||||
Server::setResource('getProjectDB', function (Group $pools, Database $dbForPlatform, $cache, Authorization $authorization) {
|
||||
$databases = []; // TODO: @Meldiron This should probably be responsibility of utopia-php/pools
|
||||
|
||||
return function (Document $project) use ($pools, $dbForPlatform, $cache, &$databases): Database {
|
||||
return function (Document $project) use ($pools, $dbForPlatform, $cache, $authorization, &$databases): Database {
|
||||
if ($project->isEmpty() || $project->getId() === 'console') {
|
||||
return $dbForPlatform;
|
||||
}
|
||||
@@ -123,7 +134,7 @@ Server::setResource('getProjectDB', function (Group $pools, Database $dbForPlatf
|
||||
|
||||
if (isset($databases[$dsn->getHost()])) {
|
||||
$database = $databases[$dsn->getHost()];
|
||||
|
||||
$database->setAuthorization($authorization);
|
||||
$sharedTables = \explode(',', System::getEnv('_APP_DATABASE_SHARED_TABLES', ''));
|
||||
|
||||
if (\in_array($dsn->getHost(), $sharedTables)) {
|
||||
@@ -160,15 +171,17 @@ Server::setResource('getProjectDB', function (Group $pools, Database $dbForPlatf
|
||||
->setNamespace('_' . $project->getSequence());
|
||||
}
|
||||
|
||||
$database->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS_WORKER);
|
||||
$database
|
||||
->setAuthorization($authorization)
|
||||
->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS_WORKER);
|
||||
|
||||
return $database;
|
||||
};
|
||||
}, ['pools', 'dbForPlatform', 'cache']);
|
||||
}, ['pools', 'dbForPlatform', 'cache', 'authorization']);
|
||||
|
||||
Server::setResource('getLogsDB', function (Group $pools, Cache $cache) {
|
||||
Server::setResource('getLogsDB', function (Group $pools, Cache $cache, Authorization $authorization) {
|
||||
$database = null;
|
||||
return function (?Document $project = null) use ($pools, $cache, $database) {
|
||||
return function (?Document $project = null) use ($pools, $cache, $database, $authorization) {
|
||||
if ($database !== null && $project !== null && !$project->isEmpty() && $project->getId() !== 'console') {
|
||||
$database->setTenant((int)$project->getSequence());
|
||||
return $database;
|
||||
@@ -178,6 +191,7 @@ Server::setResource('getLogsDB', function (Group $pools, Cache $cache) {
|
||||
$database = new Database($adapter, $cache);
|
||||
|
||||
$database
|
||||
->setAuthorization($authorization)
|
||||
->setSharedTables(true)
|
||||
->setNamespace('logsV1')
|
||||
->setTimeout(APP_DATABASE_TIMEOUT_MILLISECONDS_WORKER)
|
||||
@@ -190,7 +204,7 @@ Server::setResource('getLogsDB', function (Group $pools, Cache $cache) {
|
||||
|
||||
return $database;
|
||||
};
|
||||
}, ['pools', 'cache']);
|
||||
}, ['pools', 'cache', 'authorization']);
|
||||
|
||||
Server::setResource('abuseRetention', function () {
|
||||
return time() - (int) System::getEnv('_APP_MAINTENANCE_RETENTION_ABUSE', 86400); // 1 day
|
||||
@@ -478,7 +492,8 @@ $worker
|
||||
->inject('log')
|
||||
->inject('pools')
|
||||
->inject('project')
|
||||
->action(function (Throwable $error, ?Logger $logger, Log $log, Group $pools, Document $project) use ($worker, $queueName) {
|
||||
->inject('authorization')
|
||||
->action(function (Throwable $error, ?Logger $logger, Log $log, Group $pools, Document $project, Authorization $authorization) use ($worker, $queueName) {
|
||||
$version = System::getEnv('_APP_VERSION', 'UNKNOWN');
|
||||
|
||||
if ($logger) {
|
||||
@@ -494,7 +509,7 @@ $worker
|
||||
$log->addExtra('file', $error->getFile());
|
||||
$log->addExtra('line', $error->getLine());
|
||||
$log->addExtra('trace', $error->getTraceAsString());
|
||||
$log->addExtra('roles', Authorization::getRoles());
|
||||
$log->addExtra('roles', $authorization->getRoles());
|
||||
|
||||
$isProduction = System::getEnv('_APP_ENV', 'development') === 'production';
|
||||
$log->setEnvironment($isProduction ? Log::ENVIRONMENT_PRODUCTION : Log::ENVIRONMENT_STAGING);
|
||||
|
||||
+1
-1
@@ -51,7 +51,7 @@
|
||||
"utopia-php/cache": "0.13.*",
|
||||
"utopia-php/cli": "0.15.*",
|
||||
"utopia-php/config": "0.2.*",
|
||||
"utopia-php/database": "3.*",
|
||||
"utopia-php/database": "4.*",
|
||||
"utopia-php/detector": "0.2.*",
|
||||
"utopia-php/domains": "0.9.*",
|
||||
"utopia-php/emails": "0.6.*",
|
||||
|
||||
Generated
+117
-111
@@ -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": "ad28b7155175986191bd19bbcd13d623",
|
||||
"content-hash": "3b502f78f5e31f2ea7b4c69e3301283a",
|
||||
"packages": [
|
||||
{
|
||||
"name": "adhocore/jwt",
|
||||
@@ -161,16 +161,16 @@
|
||||
},
|
||||
{
|
||||
"name": "appwrite/php-runtimes",
|
||||
"version": "0.19.1",
|
||||
"version": "0.19.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/appwrite/runtimes.git",
|
||||
"reference": "7bd0cc3cb97de625d7b07230bd91b121f88e72ae"
|
||||
"reference": "e5c142519df5aced37de9c302971c29c079ce3d9"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/appwrite/runtimes/zipball/7bd0cc3cb97de625d7b07230bd91b121f88e72ae",
|
||||
"reference": "7bd0cc3cb97de625d7b07230bd91b121f88e72ae",
|
||||
"url": "https://api.github.com/repos/appwrite/runtimes/zipball/e5c142519df5aced37de9c302971c29c079ce3d9",
|
||||
"reference": "e5c142519df5aced37de9c302971c29c079ce3d9",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -210,9 +210,9 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/appwrite/runtimes/issues",
|
||||
"source": "https://github.com/appwrite/runtimes/tree/0.19.1"
|
||||
"source": "https://github.com/appwrite/runtimes/tree/0.19.2"
|
||||
},
|
||||
"time": "2025-05-27T07:12:56+00:00"
|
||||
"time": "2025-11-11T13:44:44+00:00"
|
||||
},
|
||||
{
|
||||
"name": "beberlei/assert",
|
||||
@@ -756,16 +756,16 @@
|
||||
},
|
||||
{
|
||||
"name": "google/protobuf",
|
||||
"version": "v4.33.0",
|
||||
"version": "v4.33.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/protocolbuffers/protobuf-php.git",
|
||||
"reference": "b50269e23204e5ae859a326ec3d90f09efe3047d"
|
||||
"reference": "0cd73ccf0cd26c3e72299cce1ea6144091a57e12"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/protocolbuffers/protobuf-php/zipball/b50269e23204e5ae859a326ec3d90f09efe3047d",
|
||||
"reference": "b50269e23204e5ae859a326ec3d90f09efe3047d",
|
||||
"url": "https://api.github.com/repos/protocolbuffers/protobuf-php/zipball/0cd73ccf0cd26c3e72299cce1ea6144091a57e12",
|
||||
"reference": "0cd73ccf0cd26c3e72299cce1ea6144091a57e12",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -794,9 +794,9 @@
|
||||
"proto"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/protocolbuffers/protobuf-php/tree/v4.33.0"
|
||||
"source": "https://github.com/protocolbuffers/protobuf-php/tree/v4.33.1"
|
||||
},
|
||||
"time": "2025-10-15T20:10:28+00:00"
|
||||
"time": "2025-11-12T21:58:05+00:00"
|
||||
},
|
||||
{
|
||||
"name": "league/csv",
|
||||
@@ -961,16 +961,16 @@
|
||||
},
|
||||
{
|
||||
"name": "mongodb/mongodb",
|
||||
"version": "2.1.1",
|
||||
"version": "2.1.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/mongodb/mongo-php-library.git",
|
||||
"reference": "f399d24905dd42f97dfe0af9706129743ef247ac"
|
||||
"reference": "0a2472ba9cbb932f7e43a8770aedb2fc30612a67"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/mongodb/mongo-php-library/zipball/f399d24905dd42f97dfe0af9706129743ef247ac",
|
||||
"reference": "f399d24905dd42f97dfe0af9706129743ef247ac",
|
||||
"url": "https://api.github.com/repos/mongodb/mongo-php-library/zipball/0a2472ba9cbb932f7e43a8770aedb2fc30612a67",
|
||||
"reference": "0a2472ba9cbb932f7e43a8770aedb2fc30612a67",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -986,7 +986,7 @@
|
||||
"require-dev": {
|
||||
"doctrine/coding-standard": "^12.0",
|
||||
"phpunit/phpunit": "^10.5.35",
|
||||
"rector/rector": "^1.2",
|
||||
"rector/rector": "^2.1.4",
|
||||
"squizlabs/php_codesniffer": "^3.7",
|
||||
"vimeo/psalm": "6.5.*"
|
||||
},
|
||||
@@ -1032,9 +1032,9 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/mongodb/mongo-php-library/issues",
|
||||
"source": "https://github.com/mongodb/mongo-php-library/tree/2.1.1"
|
||||
"source": "https://github.com/mongodb/mongo-php-library/tree/2.1.2"
|
||||
},
|
||||
"time": "2025-08-13T20:50:05+00:00"
|
||||
"time": "2025-10-06T12:12:40+00:00"
|
||||
},
|
||||
{
|
||||
"name": "mustangostang/spyc",
|
||||
@@ -2673,16 +2673,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/http-client",
|
||||
"version": "v7.3.4",
|
||||
"version": "v7.3.6",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/http-client.git",
|
||||
"reference": "4b62871a01c49457cf2a8e560af7ee8a94b87a62"
|
||||
"reference": "3c0a55a2c8e21e30a37022801c11c7ab5a6cb2de"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/http-client/zipball/4b62871a01c49457cf2a8e560af7ee8a94b87a62",
|
||||
"reference": "4b62871a01c49457cf2a8e560af7ee8a94b87a62",
|
||||
"url": "https://api.github.com/repos/symfony/http-client/zipball/3c0a55a2c8e21e30a37022801c11c7ab5a6cb2de",
|
||||
"reference": "3c0a55a2c8e21e30a37022801c11c7ab5a6cb2de",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -2749,7 +2749,7 @@
|
||||
"http"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/http-client/tree/v7.3.4"
|
||||
"source": "https://github.com/symfony/http-client/tree/v7.3.6"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -2769,7 +2769,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2025-09-11T10:12:26+00:00"
|
||||
"time": "2025-11-05T17:41:46+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/http-client-contracts",
|
||||
@@ -3176,16 +3176,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/service-contracts",
|
||||
"version": "v3.6.0",
|
||||
"version": "v3.6.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/service-contracts.git",
|
||||
"reference": "f021b05a130d35510bd6b25fe9053c2a8a15d5d4"
|
||||
"reference": "45112560a3ba2d715666a509a0bc9521d10b6c43"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/service-contracts/zipball/f021b05a130d35510bd6b25fe9053c2a8a15d5d4",
|
||||
"reference": "f021b05a130d35510bd6b25fe9053c2a8a15d5d4",
|
||||
"url": "https://api.github.com/repos/symfony/service-contracts/zipball/45112560a3ba2d715666a509a0bc9521d10b6c43",
|
||||
"reference": "45112560a3ba2d715666a509a0bc9521d10b6c43",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -3239,7 +3239,7 @@
|
||||
"standards"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/service-contracts/tree/v3.6.0"
|
||||
"source": "https://github.com/symfony/service-contracts/tree/v3.6.1"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -3250,12 +3250,16 @@
|
||||
"url": "https://github.com/fabpot",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/nicolas-grekas",
|
||||
"type": "github"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2025-04-25T09:37:31+00:00"
|
||||
"time": "2025-07-15T11:30:57+00:00"
|
||||
},
|
||||
{
|
||||
"name": "tbachert/spi",
|
||||
@@ -3547,21 +3551,21 @@
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/audit",
|
||||
"version": "1.0.2",
|
||||
"version": "1.0.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/utopia-php/audit.git",
|
||||
"reference": "8c17065c2473d4ca799f65585ca74eb53e1be211"
|
||||
"reference": "15656acfddb9d6f03c395b73673fc66c793c10a5"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/utopia-php/audit/zipball/8c17065c2473d4ca799f65585ca74eb53e1be211",
|
||||
"reference": "8c17065c2473d4ca799f65585ca74eb53e1be211",
|
||||
"url": "https://api.github.com/repos/utopia-php/audit/zipball/15656acfddb9d6f03c395b73673fc66c793c10a5",
|
||||
"reference": "15656acfddb9d6f03c395b73673fc66c793c10a5",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=8.0",
|
||||
"utopia-php/database": "*"
|
||||
"utopia-php/database": "4.*"
|
||||
},
|
||||
"require-dev": {
|
||||
"laravel/pint": "1.*",
|
||||
@@ -3588,9 +3592,9 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/utopia-php/audit/issues",
|
||||
"source": "https://github.com/utopia-php/audit/tree/1.0.2"
|
||||
"source": "https://github.com/utopia-php/audit/tree/1.0.3"
|
||||
},
|
||||
"time": "2025-10-20T07:14:26+00:00"
|
||||
"time": "2025-11-04T11:27:42+00:00"
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/cache",
|
||||
@@ -3840,16 +3844,16 @@
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/database",
|
||||
"version": "3.1.5",
|
||||
"version": "4.3.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/utopia-php/database.git",
|
||||
"reference": "76568b81f25d89fc1e0c53f0370f139130eeb939"
|
||||
"reference": "fe7a1326ad623609e65587fe8c01a630a7075fee"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/utopia-php/database/zipball/76568b81f25d89fc1e0c53f0370f139130eeb939",
|
||||
"reference": "76568b81f25d89fc1e0c53f0370f139130eeb939",
|
||||
"url": "https://api.github.com/repos/utopia-php/database/zipball/fe7a1326ad623609e65587fe8c01a630a7075fee",
|
||||
"reference": "fe7a1326ad623609e65587fe8c01a630a7075fee",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -3892,9 +3896,9 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/utopia-php/database/issues",
|
||||
"source": "https://github.com/utopia-php/database/tree/3.1.5"
|
||||
"source": "https://github.com/utopia-php/database/tree/4.3.0"
|
||||
},
|
||||
"time": "2025-11-05T10:17:55+00:00"
|
||||
"time": "2025-11-14T03:43:10+00:00"
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/detector",
|
||||
@@ -3943,22 +3947,24 @@
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/dns",
|
||||
"version": "1.1.0",
|
||||
"version": "1.1.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/utopia-php/dns.git",
|
||||
"reference": "d6eca184883262bdcb4261e57491c91b16079b9a"
|
||||
"reference": "1e6b4bac735329c9e5ec69a6a5d899ec2d050707"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/utopia-php/dns/zipball/d6eca184883262bdcb4261e57491c91b16079b9a",
|
||||
"reference": "d6eca184883262bdcb4261e57491c91b16079b9a",
|
||||
"url": "https://api.github.com/repos/utopia-php/dns/zipball/1e6b4bac735329c9e5ec69a6a5d899ec2d050707",
|
||||
"reference": "1e6b4bac735329c9e5ec69a6a5d899ec2d050707",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=8.3",
|
||||
"utopia-php/console": "0.0.*",
|
||||
"utopia-php/telemetry": "0.1.*"
|
||||
"utopia-php/domains": "0.9.*",
|
||||
"utopia-php/telemetry": "0.1.*",
|
||||
"utopia-php/validators": "^0.0.2"
|
||||
},
|
||||
"require-dev": {
|
||||
"laravel/pint": "1.25.*",
|
||||
@@ -3992,9 +3998,9 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/utopia-php/dns/issues",
|
||||
"source": "https://github.com/utopia-php/dns/tree/1.1.0"
|
||||
"source": "https://github.com/utopia-php/dns/tree/1.1.3"
|
||||
},
|
||||
"time": "2025-11-03T22:49:02+00:00"
|
||||
"time": "2025-11-06T19:08:29+00:00"
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/domains",
|
||||
@@ -4206,16 +4212,16 @@
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/framework",
|
||||
"version": "0.33.28",
|
||||
"version": "0.33.29",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/utopia-php/http.git",
|
||||
"reference": "5aaa94d406577b0059ad28c78022606890dc6de0"
|
||||
"reference": "6e63939fdb33b847f92839499cd6e8df626c278d"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/utopia-php/http/zipball/5aaa94d406577b0059ad28c78022606890dc6de0",
|
||||
"reference": "5aaa94d406577b0059ad28c78022606890dc6de0",
|
||||
"url": "https://api.github.com/repos/utopia-php/http/zipball/6e63939fdb33b847f92839499cd6e8df626c278d",
|
||||
"reference": "6e63939fdb33b847f92839499cd6e8df626c278d",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -4247,9 +4253,9 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/utopia-php/http/issues",
|
||||
"source": "https://github.com/utopia-php/http/tree/0.33.28"
|
||||
"source": "https://github.com/utopia-php/http/tree/0.33.29"
|
||||
},
|
||||
"time": "2025-09-25T10:44:24+00:00"
|
||||
"time": "2025-11-14T06:33:29+00:00"
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/image",
|
||||
@@ -4454,16 +4460,16 @@
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/migration",
|
||||
"version": "1.3.3",
|
||||
"version": "1.3.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/utopia-php/migration.git",
|
||||
"reference": "731b3a963c58c30e0b2368695d57a7e8fcb7455c"
|
||||
"reference": "81e1be6ff3257d4768aa7483cf64628836244a09"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/utopia-php/migration/zipball/731b3a963c58c30e0b2368695d57a7e8fcb7455c",
|
||||
"reference": "731b3a963c58c30e0b2368695d57a7e8fcb7455c",
|
||||
"url": "https://api.github.com/repos/utopia-php/migration/zipball/81e1be6ff3257d4768aa7483cf64628836244a09",
|
||||
"reference": "81e1be6ff3257d4768aa7483cf64628836244a09",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -4472,7 +4478,7 @@
|
||||
"ext-openssl": "*",
|
||||
"php": ">=8.1",
|
||||
"utopia-php/console": "0.0.*",
|
||||
"utopia-php/database": "3.*",
|
||||
"utopia-php/database": "4.*",
|
||||
"utopia-php/dsn": "0.2.*",
|
||||
"utopia-php/storage": "0.18.*"
|
||||
},
|
||||
@@ -4503,9 +4509,9 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/utopia-php/migration/issues",
|
||||
"source": "https://github.com/utopia-php/migration/tree/1.3.3"
|
||||
"source": "https://github.com/utopia-php/migration/tree/1.3.4"
|
||||
},
|
||||
"time": "2025-10-28T04:02:08+00:00"
|
||||
"time": "2025-11-04T11:28:50+00:00"
|
||||
},
|
||||
{
|
||||
"name": "utopia-php/mongo",
|
||||
@@ -5377,16 +5383,16 @@
|
||||
"packages-dev": [
|
||||
{
|
||||
"name": "appwrite/sdk-generator",
|
||||
"version": "1.5.1",
|
||||
"version": "1.5.7",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/appwrite/sdk-generator.git",
|
||||
"reference": "cd712674e34136f706e9170641ed6f4ce160e772"
|
||||
"reference": "dc6720ba92ed98e2c62b2a319d4371f167ccc808"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/cd712674e34136f706e9170641ed6f4ce160e772",
|
||||
"reference": "cd712674e34136f706e9170641ed6f4ce160e772",
|
||||
"url": "https://api.github.com/repos/appwrite/sdk-generator/zipball/dc6720ba92ed98e2c62b2a319d4371f167ccc808",
|
||||
"reference": "dc6720ba92ed98e2c62b2a319d4371f167ccc808",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -5422,9 +5428,9 @@
|
||||
"description": "Appwrite PHP library for generating API SDKs for multiple programming languages and platforms",
|
||||
"support": {
|
||||
"issues": "https://github.com/appwrite/sdk-generator/issues",
|
||||
"source": "https://github.com/appwrite/sdk-generator/tree/1.5.1"
|
||||
"source": "https://github.com/appwrite/sdk-generator/tree/1.5.7"
|
||||
},
|
||||
"time": "2025-11-04T09:55:47+00:00"
|
||||
"time": "2025-11-18T05:57:01+00:00"
|
||||
},
|
||||
{
|
||||
"name": "doctrine/annotations",
|
||||
@@ -6077,24 +6083,24 @@
|
||||
},
|
||||
{
|
||||
"name": "phpbench/container",
|
||||
"version": "2.2.2",
|
||||
"version": "2.2.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/phpbench/container.git",
|
||||
"reference": "a59b929e00b87b532ca6d0edd8eca0967655af33"
|
||||
"reference": "0c7b2d36c1ea53fe27302fb8873ded7172047196"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/phpbench/container/zipball/a59b929e00b87b532ca6d0edd8eca0967655af33",
|
||||
"reference": "a59b929e00b87b532ca6d0edd8eca0967655af33",
|
||||
"url": "https://api.github.com/repos/phpbench/container/zipball/0c7b2d36c1ea53fe27302fb8873ded7172047196",
|
||||
"reference": "0c7b2d36c1ea53fe27302fb8873ded7172047196",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"psr/container": "^1.0|^2.0",
|
||||
"symfony/options-resolver": "^4.2 || ^5.0 || ^6.0 || ^7.0"
|
||||
"symfony/options-resolver": "^4.2 || ^5.0 || ^6.0 || ^7.0 || ^8.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"friendsofphp/php-cs-fixer": "^2.16",
|
||||
"php-cs-fixer/shim": "^3.89",
|
||||
"phpstan/phpstan": "^0.12.52",
|
||||
"phpunit/phpunit": "^8"
|
||||
},
|
||||
@@ -6122,22 +6128,22 @@
|
||||
"description": "Simple, configurable, service container.",
|
||||
"support": {
|
||||
"issues": "https://github.com/phpbench/container/issues",
|
||||
"source": "https://github.com/phpbench/container/tree/2.2.2"
|
||||
"source": "https://github.com/phpbench/container/tree/2.2.3"
|
||||
},
|
||||
"time": "2023-10-30T13:38:26+00:00"
|
||||
"time": "2025-11-06T09:05:13+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpbench/phpbench",
|
||||
"version": "1.4.2",
|
||||
"version": "1.4.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/phpbench/phpbench.git",
|
||||
"reference": "bb61ae6c54b3d58642be154eb09f4e73c3511018"
|
||||
"reference": "b641dde59d969ea42eed70a39f9b51950bc96878"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/phpbench/phpbench/zipball/bb61ae6c54b3d58642be154eb09f4e73c3511018",
|
||||
"reference": "bb61ae6c54b3d58642be154eb09f4e73c3511018",
|
||||
"url": "https://api.github.com/repos/phpbench/phpbench/zipball/b641dde59d969ea42eed70a39f9b51950bc96878",
|
||||
"reference": "b641dde59d969ea42eed70a39f9b51950bc96878",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -6152,26 +6158,26 @@
|
||||
"phpbench/container": "^2.2",
|
||||
"psr/log": "^1.1 || ^2.0 || ^3.0",
|
||||
"seld/jsonlint": "^1.1",
|
||||
"symfony/console": "^6.1 || ^7.0",
|
||||
"symfony/filesystem": "^6.1 || ^7.0",
|
||||
"symfony/finder": "^6.1 || ^7.0",
|
||||
"symfony/options-resolver": "^6.1 || ^7.0",
|
||||
"symfony/process": "^6.1 || ^7.0",
|
||||
"symfony/console": "^6.1 || ^7.0 || ^8.0",
|
||||
"symfony/filesystem": "^6.1 || ^7.0 || ^8.0",
|
||||
"symfony/finder": "^6.1 || ^7.0 || ^8.0",
|
||||
"symfony/options-resolver": "^6.1 || ^7.0 || ^8.0",
|
||||
"symfony/process": "^6.1 || ^7.0 || ^8.0",
|
||||
"webmozart/glob": "^4.6"
|
||||
},
|
||||
"require-dev": {
|
||||
"dantleech/invoke": "^2.0",
|
||||
"ergebnis/composer-normalize": "^2.39",
|
||||
"friendsofphp/php-cs-fixer": "^3.0",
|
||||
"jangregor/phpstan-prophecy": "^1.0",
|
||||
"php-cs-fixer/shim": "^3.9",
|
||||
"phpspec/prophecy": "^1.22",
|
||||
"phpstan/extension-installer": "^1.1",
|
||||
"phpstan/phpstan": "^1.0",
|
||||
"phpstan/phpstan-phpunit": "^1.0",
|
||||
"phpunit/phpunit": "^10.4 || ^11.0",
|
||||
"rector/rector": "^1.2",
|
||||
"symfony/error-handler": "^6.1 || ^7.0",
|
||||
"symfony/var-dumper": "^6.1 || ^7.0"
|
||||
"symfony/error-handler": "^6.1 || ^7.0 || ^8.0",
|
||||
"symfony/var-dumper": "^6.1 || ^7.0 || ^8.0"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-xdebug": "For Xdebug profiling extension."
|
||||
@@ -6214,7 +6220,7 @@
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/phpbench/phpbench/issues",
|
||||
"source": "https://github.com/phpbench/phpbench/tree/1.4.2"
|
||||
"source": "https://github.com/phpbench/phpbench/tree/1.4.3"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -6222,7 +6228,7 @@
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2025-10-26T14:21:59+00:00"
|
||||
"time": "2025-11-06T19:07:31+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpstan/phpstan",
|
||||
@@ -7871,16 +7877,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/console",
|
||||
"version": "v7.3.5",
|
||||
"version": "v7.3.6",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/console.git",
|
||||
"reference": "cdb80fa5869653c83cfe1a9084a673b6daf57ea7"
|
||||
"reference": "c28ad91448f86c5f6d9d2c70f0cf68bf135f252a"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/console/zipball/cdb80fa5869653c83cfe1a9084a673b6daf57ea7",
|
||||
"reference": "cdb80fa5869653c83cfe1a9084a673b6daf57ea7",
|
||||
"url": "https://api.github.com/repos/symfony/console/zipball/c28ad91448f86c5f6d9d2c70f0cf68bf135f252a",
|
||||
"reference": "c28ad91448f86c5f6d9d2c70f0cf68bf135f252a",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -7945,7 +7951,7 @@
|
||||
"terminal"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/console/tree/v7.3.5"
|
||||
"source": "https://github.com/symfony/console/tree/v7.3.6"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -7965,20 +7971,20 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2025-10-14T15:46:26+00:00"
|
||||
"time": "2025-11-04T01:21:42+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/filesystem",
|
||||
"version": "v7.3.2",
|
||||
"version": "v7.3.6",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/filesystem.git",
|
||||
"reference": "edcbb768a186b5c3f25d0643159a787d3e63b7fd"
|
||||
"reference": "e9bcfd7837928ab656276fe00464092cc9e1826a"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/filesystem/zipball/edcbb768a186b5c3f25d0643159a787d3e63b7fd",
|
||||
"reference": "edcbb768a186b5c3f25d0643159a787d3e63b7fd",
|
||||
"url": "https://api.github.com/repos/symfony/filesystem/zipball/e9bcfd7837928ab656276fe00464092cc9e1826a",
|
||||
"reference": "e9bcfd7837928ab656276fe00464092cc9e1826a",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -8015,7 +8021,7 @@
|
||||
"description": "Provides basic utilities for the filesystem",
|
||||
"homepage": "https://symfony.com",
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/filesystem/tree/v7.3.2"
|
||||
"source": "https://github.com/symfony/filesystem/tree/v7.3.6"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -8035,7 +8041,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2025-07-07T08:17:47+00:00"
|
||||
"time": "2025-11-05T09:52:27+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/finder",
|
||||
@@ -8712,16 +8718,16 @@
|
||||
},
|
||||
{
|
||||
"name": "theseer/tokenizer",
|
||||
"version": "1.2.3",
|
||||
"version": "1.3.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/theseer/tokenizer.git",
|
||||
"reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2"
|
||||
"reference": "b7489ce515e168639d17feec34b8847c326b0b3c"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/theseer/tokenizer/zipball/737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2",
|
||||
"reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2",
|
||||
"url": "https://api.github.com/repos/theseer/tokenizer/zipball/b7489ce515e168639d17feec34b8847c326b0b3c",
|
||||
"reference": "b7489ce515e168639d17feec34b8847c326b0b3c",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -8750,7 +8756,7 @@
|
||||
"description": "A small library for converting tokenized PHP source code into XML and potentially other formats",
|
||||
"support": {
|
||||
"issues": "https://github.com/theseer/tokenizer/issues",
|
||||
"source": "https://github.com/theseer/tokenizer/tree/1.2.3"
|
||||
"source": "https://github.com/theseer/tokenizer/tree/1.3.1"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -8758,7 +8764,7 @@
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2024-03-03T12:36:25+00:00"
|
||||
"time": "2025-11-17T20:03:58+00:00"
|
||||
},
|
||||
{
|
||||
"name": "twig/twig",
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
services:
|
||||
appwrite:
|
||||
# volumes:
|
||||
# - ~/.ssh:/root/.ssh
|
||||
environment:
|
||||
- GH_TOKEN=
|
||||
- GIT_EMAIL=
|
||||
@@ -0,0 +1,44 @@
|
||||
import io.appwrite.Client;
|
||||
import io.appwrite.coroutines.CoroutineCallback;
|
||||
import io.appwrite.services.Avatars;
|
||||
import io.appwrite.enums.Theme;
|
||||
import io.appwrite.enums.Timezone;
|
||||
import io.appwrite.enums.Output;
|
||||
|
||||
Client client = new Client(context)
|
||||
.setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
|
||||
.setProject("<YOUR_PROJECT_ID>"); // Your project ID
|
||||
|
||||
Avatars avatars = new Avatars(client);
|
||||
|
||||
avatars.getScreenshot(
|
||||
"https://example.com", // url
|
||||
mapOf( "a" to "b" ), // headers (optional)
|
||||
1, // viewportWidth (optional)
|
||||
1, // viewportHeight (optional)
|
||||
0.1, // scale (optional)
|
||||
Theme.LIGHT, // theme (optional)
|
||||
"<USER_AGENT>", // userAgent (optional)
|
||||
false, // fullpage (optional)
|
||||
"<LOCALE>", // locale (optional)
|
||||
Timezone.AFRICA_ABIDJAN, // timezone (optional)
|
||||
-90, // latitude (optional)
|
||||
-180, // longitude (optional)
|
||||
0, // accuracy (optional)
|
||||
false, // touch (optional)
|
||||
listOf(), // permissions (optional)
|
||||
0, // sleep (optional)
|
||||
0, // width (optional)
|
||||
0, // height (optional)
|
||||
-1, // quality (optional)
|
||||
Output.JPG, // output (optional)
|
||||
new CoroutineCallback<>((result, error) -> {
|
||||
if (error != null) {
|
||||
error.printStackTrace();
|
||||
return;
|
||||
}
|
||||
|
||||
Log.d("Appwrite", result.toString());
|
||||
})
|
||||
);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import io.appwrite.Client;
|
||||
import io.appwrite.coroutines.CoroutineCallback;
|
||||
import io.appwrite.services.Functions;
|
||||
import io.appwrite.enums.ExecutionMethod;
|
||||
|
||||
Client client = new Client(context)
|
||||
.setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import io.appwrite.Client;
|
||||
import io.appwrite.coroutines.CoroutineCallback;
|
||||
import io.appwrite.services.Storage;
|
||||
import io.appwrite.enums.ImageGravity;
|
||||
import io.appwrite.enums.ImageFormat;
|
||||
|
||||
Client client = new Client(context)
|
||||
.setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
import io.appwrite.Client
|
||||
import io.appwrite.coroutines.CoroutineCallback
|
||||
import io.appwrite.services.Avatars
|
||||
import io.appwrite.enums.Theme
|
||||
import io.appwrite.enums.Timezone
|
||||
import io.appwrite.enums.Output
|
||||
|
||||
val client = Client(context)
|
||||
.setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
|
||||
.setProject("<YOUR_PROJECT_ID>") // Your project ID
|
||||
|
||||
val avatars = Avatars(client)
|
||||
|
||||
val result = avatars.getScreenshot(
|
||||
url = "https://example.com",
|
||||
headers = mapOf( "a" to "b" ), // (optional)
|
||||
viewportWidth = 1, // (optional)
|
||||
viewportHeight = 1, // (optional)
|
||||
scale = 0.1, // (optional)
|
||||
theme = theme.LIGHT, // (optional)
|
||||
userAgent = "<USER_AGENT>", // (optional)
|
||||
fullpage = false, // (optional)
|
||||
locale = "<LOCALE>", // (optional)
|
||||
timezone = timezone.AFRICA_ABIDJAN, // (optional)
|
||||
latitude = -90, // (optional)
|
||||
longitude = -180, // (optional)
|
||||
accuracy = 0, // (optional)
|
||||
touch = false, // (optional)
|
||||
permissions = listOf(), // (optional)
|
||||
sleep = 0, // (optional)
|
||||
width = 0, // (optional)
|
||||
height = 0, // (optional)
|
||||
quality = -1, // (optional)
|
||||
output = output.JPG, // (optional)
|
||||
)
|
||||
@@ -1,6 +1,7 @@
|
||||
import io.appwrite.Client
|
||||
import io.appwrite.coroutines.CoroutineCallback
|
||||
import io.appwrite.services.Functions
|
||||
import io.appwrite.enums.ExecutionMethod
|
||||
|
||||
val client = Client(context)
|
||||
.setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import io.appwrite.Client
|
||||
import io.appwrite.coroutines.CoroutineCallback
|
||||
import io.appwrite.services.Storage
|
||||
import io.appwrite.enums.ImageGravity
|
||||
import io.appwrite.enums.ImageFormat
|
||||
|
||||
val client = Client(context)
|
||||
.setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
import Appwrite
|
||||
import AppwriteEnums
|
||||
|
||||
let client = Client()
|
||||
.setEndpoint("https://<REGION>.cloud.appwrite.io/v1") // Your API Endpoint
|
||||
.setProject("<YOUR_PROJECT_ID>") // Your project ID
|
||||
|
||||
let avatars = Avatars(client)
|
||||
|
||||
let bytes = try await avatars.getScreenshot(
|
||||
url: "https://example.com",
|
||||
headers: [:], // optional
|
||||
viewportWidth: 1, // optional
|
||||
viewportHeight: 1, // optional
|
||||
scale: 0.1, // optional
|
||||
theme: .light, // optional
|
||||
userAgent: "<USER_AGENT>", // optional
|
||||
fullpage: false, // optional
|
||||
locale: "<LOCALE>", // optional
|
||||
timezone: .africaAbidjan, // optional
|
||||
latitude: -90, // optional
|
||||
longitude: -180, // optional
|
||||
accuracy: 0, // optional
|
||||
touch: false, // optional
|
||||
permissions: [], // optional
|
||||
sleep: 0, // optional
|
||||
width: 0, // optional
|
||||
height: 0, // optional
|
||||
quality: -1, // optional
|
||||
output: .jpg // optional
|
||||
)
|
||||
|
||||
@@ -7,7 +7,7 @@ Client client = Client()
|
||||
Avatars avatars = Avatars(client);
|
||||
|
||||
// Downloading file
|
||||
UInt8List bytes = await avatars.getBrowser(
|
||||
Uint8List bytes = await avatars.getBrowser(
|
||||
code: Browser.avantBrowser,
|
||||
width: 0, // optional
|
||||
height: 0, // optional
|
||||
|
||||
@@ -7,7 +7,7 @@ Client client = Client()
|
||||
Avatars avatars = Avatars(client);
|
||||
|
||||
// Downloading file
|
||||
UInt8List bytes = await avatars.getCreditCard(
|
||||
Uint8List bytes = await avatars.getCreditCard(
|
||||
code: CreditCard.americanExpress,
|
||||
width: 0, // optional
|
||||
height: 0, // optional
|
||||
|
||||
@@ -7,7 +7,7 @@ Client client = Client()
|
||||
Avatars avatars = Avatars(client);
|
||||
|
||||
// Downloading file
|
||||
UInt8List bytes = await avatars.getFavicon(
|
||||
Uint8List bytes = await avatars.getFavicon(
|
||||
url: 'https://example.com',
|
||||
)
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ Client client = Client()
|
||||
Avatars avatars = Avatars(client);
|
||||
|
||||
// Downloading file
|
||||
UInt8List bytes = await avatars.getFlag(
|
||||
Uint8List bytes = await avatars.getFlag(
|
||||
code: Flag.afghanistan,
|
||||
width: 0, // optional
|
||||
height: 0, // optional
|
||||
|
||||
@@ -7,7 +7,7 @@ Client client = Client()
|
||||
Avatars avatars = Avatars(client);
|
||||
|
||||
// Downloading file
|
||||
UInt8List bytes = await avatars.getImage(
|
||||
Uint8List bytes = await avatars.getImage(
|
||||
url: 'https://example.com',
|
||||
width: 0, // optional
|
||||
height: 0, // optional
|
||||
|
||||
@@ -7,7 +7,7 @@ Client client = Client()
|
||||
Avatars avatars = Avatars(client);
|
||||
|
||||
// Downloading file
|
||||
UInt8List bytes = await avatars.getInitials(
|
||||
Uint8List bytes = await avatars.getInitials(
|
||||
name: '<NAME>', // optional
|
||||
width: 0, // optional
|
||||
height: 0, // optional
|
||||
|
||||
@@ -7,7 +7,7 @@ Client client = Client()
|
||||
Avatars avatars = Avatars(client);
|
||||
|
||||
// Downloading file
|
||||
UInt8List bytes = await avatars.getQR(
|
||||
Uint8List bytes = await avatars.getQR(
|
||||
text: '<TEXT>',
|
||||
size: 1, // optional
|
||||
margin: 0, // optional
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
import 'package:appwrite/appwrite.dart';
|
||||
|
||||
Client client = Client()
|
||||
.setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
|
||||
.setProject('<YOUR_PROJECT_ID>'); // Your project ID
|
||||
|
||||
Avatars avatars = Avatars(client);
|
||||
|
||||
// Downloading file
|
||||
Uint8List bytes = await avatars.getScreenshot(
|
||||
url: 'https://example.com',
|
||||
headers: {}, // optional
|
||||
viewportWidth: 1, // optional
|
||||
viewportHeight: 1, // optional
|
||||
scale: 0.1, // optional
|
||||
theme: Theme.light, // optional
|
||||
userAgent: '<USER_AGENT>', // optional
|
||||
fullpage: false, // optional
|
||||
locale: '<LOCALE>', // optional
|
||||
timezone: Timezone.africaAbidjan, // optional
|
||||
latitude: -90, // optional
|
||||
longitude: -180, // optional
|
||||
accuracy: 0, // optional
|
||||
touch: false, // optional
|
||||
permissions: [], // optional
|
||||
sleep: 0, // optional
|
||||
width: 0, // optional
|
||||
height: 0, // optional
|
||||
quality: -1, // optional
|
||||
output: Output.jpg, // optional
|
||||
)
|
||||
|
||||
final file = File('path_to_file/filename.ext');
|
||||
file.writeAsBytesSync(bytes);
|
||||
|
||||
// Displaying image preview
|
||||
FutureBuilder(
|
||||
future: avatars.getScreenshot(
|
||||
url:'https://example.com' ,
|
||||
headers:{} , // optional
|
||||
viewportWidth:1 , // optional
|
||||
viewportHeight:1 , // optional
|
||||
scale:0.1 , // optional
|
||||
theme: Theme.light, // optional
|
||||
userAgent:'<USER_AGENT>' , // optional
|
||||
fullpage:false , // optional
|
||||
locale:'<LOCALE>' , // optional
|
||||
timezone: Timezone.africaAbidjan, // optional
|
||||
latitude:-90 , // optional
|
||||
longitude:-180 , // optional
|
||||
accuracy:0 , // optional
|
||||
touch:false , // optional
|
||||
permissions:[] , // optional
|
||||
sleep:0 , // optional
|
||||
width:0 , // optional
|
||||
height:0 , // optional
|
||||
quality:-1 , // optional
|
||||
output: Output.jpg, // optional
|
||||
), // Works for both public file and private file, for private files you need to be logged in
|
||||
builder: (context, snapshot) {
|
||||
return snapshot.hasData && snapshot.data != null
|
||||
? Image.memory(snapshot.data)
|
||||
: CircularProgressIndicator();
|
||||
}
|
||||
);
|
||||
@@ -7,7 +7,7 @@ Client client = Client()
|
||||
Storage storage = Storage(client);
|
||||
|
||||
// Downloading file
|
||||
UInt8List bytes = await storage.getFileDownload(
|
||||
Uint8List bytes = await storage.getFileDownload(
|
||||
bucketId: '<BUCKET_ID>',
|
||||
fileId: '<FILE_ID>',
|
||||
token: '<TOKEN>', // optional
|
||||
|
||||
@@ -7,7 +7,7 @@ Client client = Client()
|
||||
Storage storage = Storage(client);
|
||||
|
||||
// Downloading file
|
||||
UInt8List bytes = await storage.getFilePreview(
|
||||
Uint8List bytes = await storage.getFilePreview(
|
||||
bucketId: '<BUCKET_ID>',
|
||||
fileId: '<FILE_ID>',
|
||||
width: 0, // optional
|
||||
|
||||
@@ -7,7 +7,7 @@ Client client = Client()
|
||||
Storage storage = Storage(client);
|
||||
|
||||
// Downloading file
|
||||
UInt8List bytes = await storage.getFileView(
|
||||
Uint8List bytes = await storage.getFileView(
|
||||
bucketId: '<BUCKET_ID>',
|
||||
fileId: '<FILE_ID>',
|
||||
token: '<TOKEN>', // optional
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
import { Client, Avatars, Theme, Timezone, Output } from "react-native-appwrite";
|
||||
|
||||
const client = new Client()
|
||||
.setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
|
||||
.setProject('<YOUR_PROJECT_ID>'); // Your project ID
|
||||
|
||||
const avatars = new Avatars(client);
|
||||
|
||||
const result = avatars.getScreenshot({
|
||||
url: 'https://example.com',
|
||||
headers: {}, // optional
|
||||
viewportWidth: 1, // optional
|
||||
viewportHeight: 1, // optional
|
||||
scale: 0.1, // optional
|
||||
theme: Theme.Light, // optional
|
||||
userAgent: '<USER_AGENT>', // optional
|
||||
fullpage: false, // optional
|
||||
locale: '<LOCALE>', // optional
|
||||
timezone: Timezone.AfricaAbidjan, // optional
|
||||
latitude: -90, // optional
|
||||
longitude: -180, // optional
|
||||
accuracy: 0, // optional
|
||||
touch: false, // optional
|
||||
permissions: [], // optional
|
||||
sleep: 0, // optional
|
||||
width: 0, // optional
|
||||
height: 0, // optional
|
||||
quality: -1, // optional
|
||||
output: Output.Jpg // optional
|
||||
});
|
||||
|
||||
console.log(result);
|
||||
@@ -0,0 +1,6 @@
|
||||
GET /v1/avatars/screenshots HTTP/1.1
|
||||
Host: cloud.appwrite.io
|
||||
X-Appwrite-Response-Format: 1.8.0
|
||||
X-Appwrite-Project: <YOUR_PROJECT_ID>
|
||||
X-Appwrite-Session:
|
||||
X-Appwrite-JWT: <YOUR_JWT>
|
||||
@@ -0,0 +1,32 @@
|
||||
import { Client, Avatars, Theme, Timezone, Output } from "appwrite";
|
||||
|
||||
const client = new Client()
|
||||
.setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
|
||||
.setProject('<YOUR_PROJECT_ID>'); // Your project ID
|
||||
|
||||
const avatars = new Avatars(client);
|
||||
|
||||
const result = avatars.getScreenshot({
|
||||
url: 'https://example.com',
|
||||
headers: {}, // optional
|
||||
viewportWidth: 1, // optional
|
||||
viewportHeight: 1, // optional
|
||||
scale: 0.1, // optional
|
||||
theme: Theme.Light, // optional
|
||||
userAgent: '<USER_AGENT>', // optional
|
||||
fullpage: false, // optional
|
||||
locale: '<LOCALE>', // optional
|
||||
timezone: Timezone.AfricaAbidjan, // optional
|
||||
latitude: -90, // optional
|
||||
longitude: -180, // optional
|
||||
accuracy: 0, // optional
|
||||
touch: false, // optional
|
||||
permissions: [], // optional
|
||||
sleep: 0, // optional
|
||||
width: 0, // optional
|
||||
height: 0, // optional
|
||||
quality: -1, // optional
|
||||
output: Output.Jpg // optional
|
||||
});
|
||||
|
||||
console.log(result);
|
||||
@@ -3,4 +3,5 @@ appwrite functions create-template-deployment \
|
||||
--repository <REPOSITORY> \
|
||||
--owner <OWNER> \
|
||||
--root-directory <ROOT_DIRECTORY> \
|
||||
--version <VERSION>
|
||||
--type commit \
|
||||
--reference <REFERENCE>
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
appwrite migrations create-csv-export \
|
||||
--resource-id <ID1:ID2> \
|
||||
--bucket-id <BUCKET_ID> \
|
||||
--filename <FILENAME>
|
||||
|
||||
@@ -3,4 +3,5 @@ appwrite sites create-template-deployment \
|
||||
--repository <REPOSITORY> \
|
||||
--owner <OWNER> \
|
||||
--root-directory <ROOT_DIRECTORY> \
|
||||
--version <VERSION>
|
||||
--type branch \
|
||||
--reference <REFERENCE>
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
import { Client, Avatars, Theme, Timezone, Output } from "@appwrite.io/console";
|
||||
|
||||
const client = new Client()
|
||||
.setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
|
||||
.setProject('<YOUR_PROJECT_ID>'); // Your project ID
|
||||
|
||||
const avatars = new Avatars(client);
|
||||
|
||||
const result = avatars.getScreenshot({
|
||||
url: 'https://example.com',
|
||||
headers: {}, // optional
|
||||
viewportWidth: 1, // optional
|
||||
viewportHeight: 1, // optional
|
||||
scale: 0.1, // optional
|
||||
theme: Theme.Light, // optional
|
||||
userAgent: '<USER_AGENT>', // optional
|
||||
fullpage: false, // optional
|
||||
locale: '<LOCALE>', // optional
|
||||
timezone: Timezone.AfricaAbidjan, // optional
|
||||
latitude: -90, // optional
|
||||
longitude: -180, // optional
|
||||
accuracy: 0, // optional
|
||||
touch: false, // optional
|
||||
permissions: [], // optional
|
||||
sleep: 0, // optional
|
||||
width: 0, // optional
|
||||
height: 0, // optional
|
||||
quality: -1, // optional
|
||||
output: Output.Jpg // optional
|
||||
});
|
||||
|
||||
console.log(result);
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Client, Functions } from "@appwrite.io/console";
|
||||
import { Client, Functions, } from "@appwrite.io/console";
|
||||
|
||||
const client = new Client()
|
||||
.setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
|
||||
@@ -11,7 +11,8 @@ const result = await functions.createTemplateDeployment({
|
||||
repository: '<REPOSITORY>',
|
||||
owner: '<OWNER>',
|
||||
rootDirectory: '<ROOT_DIRECTORY>',
|
||||
version: '<VERSION>',
|
||||
type: .Commit,
|
||||
reference: '<REFERENCE>',
|
||||
activate: false // optional
|
||||
});
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Client, Functions, } from "@appwrite.io/console";
|
||||
import { Client, Functions, Runtime } from "@appwrite.io/console";
|
||||
|
||||
const client = new Client()
|
||||
.setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
|
||||
@@ -9,7 +9,7 @@ const functions = new Functions(client);
|
||||
const result = await functions.create({
|
||||
functionId: '<FUNCTION_ID>',
|
||||
name: '<NAME>',
|
||||
runtime: .Node145,
|
||||
runtime: Runtime.Node145,
|
||||
execute: ["any"], // optional
|
||||
events: [], // optional
|
||||
schedule: '', // optional
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Client, Functions, } from "@appwrite.io/console";
|
||||
import { Client, Functions, Runtime } from "@appwrite.io/console";
|
||||
|
||||
const client = new Client()
|
||||
.setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
|
||||
@@ -9,7 +9,7 @@ const functions = new Functions(client);
|
||||
const result = await functions.update({
|
||||
functionId: '<FUNCTION_ID>',
|
||||
name: '<NAME>',
|
||||
runtime: .Node145, // optional
|
||||
runtime: Runtime.Node145, // optional
|
||||
execute: ["any"], // optional
|
||||
events: [], // optional
|
||||
schedule: '', // optional
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Client, Health, } from "@appwrite.io/console";
|
||||
import { Client, Health, Name } from "@appwrite.io/console";
|
||||
|
||||
const client = new Client()
|
||||
.setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
|
||||
@@ -7,7 +7,7 @@ const client = new Client()
|
||||
const health = new Health(client);
|
||||
|
||||
const result = await health.getFailedJobs({
|
||||
name: .V1Database,
|
||||
name: Name.V1Database,
|
||||
threshold: null // optional
|
||||
});
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@ const migrations = new Migrations(client);
|
||||
|
||||
const result = await migrations.createCSVExport({
|
||||
resourceId: '<ID1:ID2>',
|
||||
bucketId: '<BUCKET_ID>',
|
||||
filename: '<FILENAME>',
|
||||
columns: [], // optional
|
||||
queries: [], // optional
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Client, Projects, } from "@appwrite.io/console";
|
||||
import { Client, Projects, Region } from "@appwrite.io/console";
|
||||
|
||||
const client = new Client()
|
||||
.setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
|
||||
@@ -10,7 +10,7 @@ const result = await projects.create({
|
||||
projectId: '',
|
||||
name: '<NAME>',
|
||||
teamId: '<TEAM_ID>',
|
||||
region: .Default, // optional
|
||||
region: Region.Default, // optional
|
||||
description: '<DESCRIPTION>', // optional
|
||||
logo: '<LOGO>', // optional
|
||||
url: 'https://example.com', // optional
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Client, Projects, } from "@appwrite.io/console";
|
||||
import { Client, Projects, Api } from "@appwrite.io/console";
|
||||
|
||||
const client = new Client()
|
||||
.setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
|
||||
@@ -8,7 +8,7 @@ const projects = new Projects(client);
|
||||
|
||||
const result = await projects.updateAPIStatus({
|
||||
projectId: '<PROJECT_ID>',
|
||||
api: .Rest,
|
||||
api: Api.Rest,
|
||||
status: false
|
||||
});
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Client, Proxy, , ProxyResourceType } from "@appwrite.io/console";
|
||||
import { Client, Proxy, StatusCode, ProxyResourceType } from "@appwrite.io/console";
|
||||
|
||||
const client = new Client()
|
||||
.setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
|
||||
@@ -9,7 +9,7 @@ const proxy = new Proxy(client);
|
||||
const result = await proxy.createRedirectRule({
|
||||
domain: '',
|
||||
url: 'https://example.com',
|
||||
statusCode: .MovedPermanently301,
|
||||
statusCode: StatusCode.MovedPermanently301,
|
||||
resourceId: '<RESOURCE_ID>',
|
||||
resourceType: ProxyResourceType.Site
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Client, Sites } from "@appwrite.io/console";
|
||||
import { Client, Sites, } from "@appwrite.io/console";
|
||||
|
||||
const client = new Client()
|
||||
.setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
|
||||
@@ -11,7 +11,8 @@ const result = await sites.createTemplateDeployment({
|
||||
repository: '<REPOSITORY>',
|
||||
owner: '<OWNER>',
|
||||
rootDirectory: '<ROOT_DIRECTORY>',
|
||||
version: '<VERSION>',
|
||||
type: .Branch,
|
||||
reference: '<REFERENCE>',
|
||||
activate: false // optional
|
||||
});
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Client, Sites, , , } from "@appwrite.io/console";
|
||||
import { Client, Sites, Framework, BuildRuntime, Adapter } from "@appwrite.io/console";
|
||||
|
||||
const client = new Client()
|
||||
.setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
|
||||
@@ -9,15 +9,15 @@ const sites = new Sites(client);
|
||||
const result = await sites.create({
|
||||
siteId: '<SITE_ID>',
|
||||
name: '<NAME>',
|
||||
framework: .Analog,
|
||||
buildRuntime: .Node145,
|
||||
framework: Framework.Analog,
|
||||
buildRuntime: BuildRuntime.Node145,
|
||||
enabled: false, // optional
|
||||
logging: false, // optional
|
||||
timeout: 1, // optional
|
||||
installCommand: '<INSTALL_COMMAND>', // optional
|
||||
buildCommand: '<BUILD_COMMAND>', // optional
|
||||
outputDirectory: '<OUTPUT_DIRECTORY>', // optional
|
||||
adapter: .Static, // optional
|
||||
adapter: Adapter.Static, // optional
|
||||
installationId: '<INSTALLATION_ID>', // optional
|
||||
fallbackFile: '<FALLBACK_FILE>', // optional
|
||||
providerRepositoryId: '<PROVIDER_REPOSITORY_ID>', // optional
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Client, Sites, , , } from "@appwrite.io/console";
|
||||
import { Client, Sites, Framework, BuildRuntime, Adapter } from "@appwrite.io/console";
|
||||
|
||||
const client = new Client()
|
||||
.setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
|
||||
@@ -9,15 +9,15 @@ const sites = new Sites(client);
|
||||
const result = await sites.update({
|
||||
siteId: '<SITE_ID>',
|
||||
name: '<NAME>',
|
||||
framework: .Analog,
|
||||
framework: Framework.Analog,
|
||||
enabled: false, // optional
|
||||
logging: false, // optional
|
||||
timeout: 1, // optional
|
||||
installCommand: '<INSTALL_COMMAND>', // optional
|
||||
buildCommand: '<BUILD_COMMAND>', // optional
|
||||
outputDirectory: '<OUTPUT_DIRECTORY>', // optional
|
||||
buildRuntime: .Node145, // optional
|
||||
adapter: .Static, // optional
|
||||
buildRuntime: BuildRuntime.Node145, // optional
|
||||
adapter: Adapter.Static, // optional
|
||||
fallbackFile: '<FALLBACK_FILE>', // optional
|
||||
installationId: '<INSTALLATION_ID>', // optional
|
||||
providerRepositoryId: '<PROVIDER_REPOSITORY_ID>', // optional
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Client, Storage, , Permission, Role } from "@appwrite.io/console";
|
||||
import { Client, Storage, Compression, Permission, Role } from "@appwrite.io/console";
|
||||
|
||||
const client = new Client()
|
||||
.setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
|
||||
@@ -14,9 +14,10 @@ const result = await storage.createBucket({
|
||||
enabled: false, // optional
|
||||
maximumFileSize: 1, // optional
|
||||
allowedFileExtensions: [], // optional
|
||||
compression: .None, // optional
|
||||
compression: Compression.None, // optional
|
||||
encryption: false, // optional
|
||||
antivirus: false // optional
|
||||
antivirus: false, // optional
|
||||
transformations: false // optional
|
||||
});
|
||||
|
||||
console.log(result);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Client, Storage, , Permission, Role } from "@appwrite.io/console";
|
||||
import { Client, Storage, Compression, Permission, Role } from "@appwrite.io/console";
|
||||
|
||||
const client = new Client()
|
||||
.setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
|
||||
@@ -14,9 +14,10 @@ const result = await storage.updateBucket({
|
||||
enabled: false, // optional
|
||||
maximumFileSize: 1, // optional
|
||||
allowedFileExtensions: [], // optional
|
||||
compression: .None, // optional
|
||||
compression: Compression.None, // optional
|
||||
encryption: false, // optional
|
||||
antivirus: false // optional
|
||||
antivirus: false, // optional
|
||||
transformations: false // optional
|
||||
});
|
||||
|
||||
console.log(result);
|
||||
|
||||
@@ -7,7 +7,7 @@ Client client = Client()
|
||||
|
||||
Avatars avatars = Avatars(client);
|
||||
|
||||
UInt8List result = await avatars.getBrowser(
|
||||
Uint8List result = await avatars.getBrowser(
|
||||
code: Browser.avantBrowser,
|
||||
width: 0, // (optional)
|
||||
height: 0, // (optional)
|
||||
|
||||
@@ -7,7 +7,7 @@ Client client = Client()
|
||||
|
||||
Avatars avatars = Avatars(client);
|
||||
|
||||
UInt8List result = await avatars.getCreditCard(
|
||||
Uint8List result = await avatars.getCreditCard(
|
||||
code: CreditCard.americanExpress,
|
||||
width: 0, // (optional)
|
||||
height: 0, // (optional)
|
||||
|
||||
@@ -7,6 +7,6 @@ Client client = Client()
|
||||
|
||||
Avatars avatars = Avatars(client);
|
||||
|
||||
UInt8List result = await avatars.getFavicon(
|
||||
Uint8List result = await avatars.getFavicon(
|
||||
url: 'https://example.com',
|
||||
);
|
||||
|
||||
@@ -7,7 +7,7 @@ Client client = Client()
|
||||
|
||||
Avatars avatars = Avatars(client);
|
||||
|
||||
UInt8List result = await avatars.getFlag(
|
||||
Uint8List result = await avatars.getFlag(
|
||||
code: Flag.afghanistan,
|
||||
width: 0, // (optional)
|
||||
height: 0, // (optional)
|
||||
|
||||
@@ -7,7 +7,7 @@ Client client = Client()
|
||||
|
||||
Avatars avatars = Avatars(client);
|
||||
|
||||
UInt8List result = await avatars.getImage(
|
||||
Uint8List result = await avatars.getImage(
|
||||
url: 'https://example.com',
|
||||
width: 0, // (optional)
|
||||
height: 0, // (optional)
|
||||
|
||||
@@ -7,7 +7,7 @@ Client client = Client()
|
||||
|
||||
Avatars avatars = Avatars(client);
|
||||
|
||||
UInt8List result = await avatars.getInitials(
|
||||
Uint8List result = await avatars.getInitials(
|
||||
name: '<NAME>', // (optional)
|
||||
width: 0, // (optional)
|
||||
height: 0, // (optional)
|
||||
|
||||
@@ -7,7 +7,7 @@ Client client = Client()
|
||||
|
||||
Avatars avatars = Avatars(client);
|
||||
|
||||
UInt8List result = await avatars.getQR(
|
||||
Uint8List result = await avatars.getQR(
|
||||
text: '<TEXT>',
|
||||
size: 1, // (optional)
|
||||
margin: 0, // (optional)
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import 'package:dart_appwrite/dart_appwrite.dart';
|
||||
|
||||
Client client = Client()
|
||||
.setEndpoint('https://<REGION>.cloud.appwrite.io/v1') // Your API Endpoint
|
||||
.setProject('<YOUR_PROJECT_ID>') // Your project ID
|
||||
.setSession(''); // The user session to authenticate with
|
||||
|
||||
Avatars avatars = Avatars(client);
|
||||
|
||||
Uint8List result = await avatars.getScreenshot(
|
||||
url: 'https://example.com',
|
||||
headers: {}, // (optional)
|
||||
viewportWidth: 1, // (optional)
|
||||
viewportHeight: 1, // (optional)
|
||||
scale: 0.1, // (optional)
|
||||
theme: Theme.light, // (optional)
|
||||
userAgent: '<USER_AGENT>', // (optional)
|
||||
fullpage: false, // (optional)
|
||||
locale: '<LOCALE>', // (optional)
|
||||
timezone: Timezone.africaAbidjan, // (optional)
|
||||
latitude: -90, // (optional)
|
||||
longitude: -180, // (optional)
|
||||
accuracy: 0, // (optional)
|
||||
touch: false, // (optional)
|
||||
permissions: [], // (optional)
|
||||
sleep: 0, // (optional)
|
||||
width: 0, // (optional)
|
||||
height: 0, // (optional)
|
||||
quality: -1, // (optional)
|
||||
output: Output.jpg, // (optional)
|
||||
);
|
||||
@@ -12,6 +12,7 @@ Deployment result = await functions.createTemplateDeployment(
|
||||
repository: '<REPOSITORY>',
|
||||
owner: '<OWNER>',
|
||||
rootDirectory: '<ROOT_DIRECTORY>',
|
||||
version: '<VERSION>',
|
||||
type: .commit,
|
||||
reference: '<REFERENCE>',
|
||||
activate: false, // (optional)
|
||||
);
|
||||
|
||||
@@ -10,7 +10,7 @@ Functions functions = Functions(client);
|
||||
Func result = await functions.create(
|
||||
functionId: '<FUNCTION_ID>',
|
||||
name: '<NAME>',
|
||||
runtime: .node145,
|
||||
runtime: Runtime.node145,
|
||||
execute: ["any"], // (optional)
|
||||
events: [], // (optional)
|
||||
schedule: '', // (optional)
|
||||
|
||||
@@ -7,7 +7,7 @@ Client client = Client()
|
||||
|
||||
Functions functions = Functions(client);
|
||||
|
||||
UInt8List result = await functions.getDeploymentDownload(
|
||||
Uint8List result = await functions.getDeploymentDownload(
|
||||
functionId: '<FUNCTION_ID>',
|
||||
deploymentId: '<DEPLOYMENT_ID>',
|
||||
type: DeploymentDownloadType.source, // (optional)
|
||||
|
||||
@@ -10,7 +10,7 @@ Functions functions = Functions(client);
|
||||
Func result = await functions.update(
|
||||
functionId: '<FUNCTION_ID>',
|
||||
name: '<NAME>',
|
||||
runtime: .node145, // (optional)
|
||||
runtime: Runtime.node145, // (optional)
|
||||
execute: ["any"], // (optional)
|
||||
events: [], // (optional)
|
||||
schedule: '', // (optional)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user