mirror of
https://github.com/penpot/penpot.git
synced 2026-01-28 16:21:43 -05:00
Compare commits
3 Commits
eva-extrac
...
eva-replac
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bdcbde37c3 | ||
|
|
81c6cb52ca | ||
|
|
1dc274e6e2 |
@@ -45,15 +45,6 @@
|
|||||||
:potok/reify-type
|
:potok/reify-type
|
||||||
{:level :error}
|
{:level :error}
|
||||||
|
|
||||||
:redundant-primitive-coercion
|
|
||||||
{:level :off}
|
|
||||||
|
|
||||||
:unused-excluded-var
|
|
||||||
{:level :off}
|
|
||||||
|
|
||||||
:unresolved-excluded-var
|
|
||||||
{:level :off}
|
|
||||||
|
|
||||||
:missing-protocol-method
|
:missing-protocol-method
|
||||||
{:level :off}
|
{:level :off}
|
||||||
|
|
||||||
|
|||||||
@@ -2,11 +2,6 @@
|
|||||||
:remove-multiple-non-indenting-spaces? false
|
:remove-multiple-non-indenting-spaces? false
|
||||||
:remove-surrounding-whitespace? true
|
:remove-surrounding-whitespace? true
|
||||||
:remove-consecutive-blank-lines? false
|
:remove-consecutive-blank-lines? false
|
||||||
:indent-line-comments? true
|
|
||||||
:parallel? true
|
|
||||||
:align-form-columns? false
|
|
||||||
;; :align-map-columns? false
|
|
||||||
;; :align-single-column-lines? false
|
|
||||||
:extra-indents {rumext.v2/fnc [[:inner 0]]
|
:extra-indents {rumext.v2/fnc [[:inner 0]]
|
||||||
cljs.test/async [[:inner 0]]
|
cljs.test/async [[:inner 0]]
|
||||||
promesa.exec/thread [[:inner 0]]
|
promesa.exec/thread [[:inner 0]]
|
||||||
|
|||||||
38
.github/ISSUE_TEMPLATE/new-render-bug-report.md
vendored
38
.github/ISSUE_TEMPLATE/new-render-bug-report.md
vendored
@@ -1,38 +0,0 @@
|
|||||||
---
|
|
||||||
name: New Render Bug Report
|
|
||||||
about: Create a report about the bugs you have found in the new render
|
|
||||||
title: ''
|
|
||||||
labels: new render
|
|
||||||
assignees: claragvinola
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Describe the bug**
|
|
||||||
A clear and concise description of what the bug is.
|
|
||||||
|
|
||||||
**Steps to Reproduce**
|
|
||||||
Steps to reproduce the behavior:
|
|
||||||
1. Go to '...'
|
|
||||||
2. Click on '....'
|
|
||||||
3. Scroll down to '....'
|
|
||||||
4. See error
|
|
||||||
|
|
||||||
**Expected behavior**
|
|
||||||
A clear and concise description of what you expected to happen.
|
|
||||||
|
|
||||||
**Screenshots or screen recordings**
|
|
||||||
If applicable, add screenshots or screen recording to help illustrate your problem.
|
|
||||||
|
|
||||||
**Desktop (please complete the following information):**
|
|
||||||
- OS: [e.g. iOS]
|
|
||||||
- Browser [e.g. chrome, safari]
|
|
||||||
- Version [e.g. 22]
|
|
||||||
|
|
||||||
**Smartphone (please complete the following information):**
|
|
||||||
- Device: [e.g. iPhone6]
|
|
||||||
- OS: [e.g. iOS8.1]
|
|
||||||
- Browser [e.g. stock browser, safari]
|
|
||||||
- Version [e.g. 22]
|
|
||||||
|
|
||||||
**Additional context**
|
|
||||||
Add any other context about the problem here.
|
|
||||||
2
.github/workflows/build-bundle.yml
vendored
2
.github/workflows/build-bundle.yml
vendored
@@ -40,7 +40,7 @@ on:
|
|||||||
jobs:
|
jobs:
|
||||||
build-bundle:
|
build-bundle:
|
||||||
name: Build and Upload Penpot Bundle
|
name: Build and Upload Penpot Bundle
|
||||||
runs-on: penpot-runner-01
|
runs-on: ubuntu-24.04
|
||||||
env:
|
env:
|
||||||
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||||
|
|||||||
7
.github/workflows/build-docker-devenv.yml
vendored
7
.github/workflows/build-docker-devenv.yml
vendored
@@ -7,14 +7,9 @@ jobs:
|
|||||||
build-and-push:
|
build-and-push:
|
||||||
name: Build and push DevEnv Docker image
|
name: Build and push DevEnv Docker image
|
||||||
environment: release-admins
|
environment: release-admins
|
||||||
runs-on: penpot-runner-02
|
runs-on: ubuntu-24.04
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Set common environment variables
|
|
||||||
run: |
|
|
||||||
# Each job execution will use its own docker configuration.
|
|
||||||
echo "DOCKER_CONFIG=${{ runner.temp }}/.docker-${{ github.run_id }}-${{ github.job }}" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
|||||||
16
.github/workflows/build-docker.yml
vendored
16
.github/workflows/build-docker.yml
vendored
@@ -19,14 +19,9 @@ on:
|
|||||||
jobs:
|
jobs:
|
||||||
build-and-push:
|
build-and-push:
|
||||||
name: Build and Push Penpot Docker Images
|
name: Build and Push Penpot Docker Images
|
||||||
runs-on: penpot-runner-02
|
runs-on: ubuntu-24.04-arm
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Set common environment variables
|
|
||||||
run: |
|
|
||||||
# Each job execution will use its own docker configuration.
|
|
||||||
echo "DOCKER_CONFIG=${{ runner.temp }}/.docker-${{ github.run_id }}-${{ github.job }}" >> $GITHUB_ENV
|
|
||||||
|
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
@@ -71,15 +66,6 @@ jobs:
|
|||||||
username: ${{ secrets.DOCKER_USERNAME }}
|
username: ${{ secrets.DOCKER_USERNAME }}
|
||||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||||
|
|
||||||
# To avoid the “429 Too Many Requests” error when downloading
|
|
||||||
# images from DockerHub for unregistered users.
|
|
||||||
# https://docs.docker.com/docker-hub/usage/
|
|
||||||
- name: Login to DockerHub Registry
|
|
||||||
uses: docker/login-action@v3
|
|
||||||
with:
|
|
||||||
username: ${{ secrets.PUB_DOCKER_USERNAME }}
|
|
||||||
password: ${{ secrets.PUB_DOCKER_PASSWORD }}
|
|
||||||
|
|
||||||
- name: Extract metadata (tags, labels)
|
- name: Extract metadata (tags, labels)
|
||||||
id: meta
|
id: meta
|
||||||
uses: docker/metadata-action@v5
|
uses: docker/metadata-action@v5
|
||||||
|
|||||||
14
.github/workflows/build-staging-render.yml
vendored
14
.github/workflows/build-staging-render.yml
vendored
@@ -1,14 +0,0 @@
|
|||||||
name: _STAGING RENDER
|
|
||||||
|
|
||||||
on:
|
|
||||||
schedule:
|
|
||||||
- cron: '36 5-20 * * 1-5'
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build-bundle:
|
|
||||||
uses: ./.github/workflows/build-bundle.yml
|
|
||||||
secrets: inherit
|
|
||||||
with:
|
|
||||||
gh_ref: "staging-render"
|
|
||||||
build_wasm: "yes"
|
|
||||||
build_storybook: "yes"
|
|
||||||
5
.github/workflows/build-tag.yml
vendored
5
.github/workflows/build-tag.yml
vendored
@@ -11,7 +11,7 @@ jobs:
|
|||||||
secrets: inherit
|
secrets: inherit
|
||||||
with:
|
with:
|
||||||
gh_ref: ${{ github.ref_name }}
|
gh_ref: ${{ github.ref_name }}
|
||||||
build_wasm: "yes"
|
build_wasm: "no"
|
||||||
build_storybook: "yes"
|
build_storybook: "yes"
|
||||||
|
|
||||||
build-docker:
|
build-docker:
|
||||||
@@ -23,7 +23,6 @@ jobs:
|
|||||||
|
|
||||||
notify:
|
notify:
|
||||||
name: Notifications
|
name: Notifications
|
||||||
runs-on: ubuntu-24.04
|
|
||||||
needs: build-docker
|
needs: build-docker
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
@@ -33,7 +32,7 @@ jobs:
|
|||||||
MATTERMOST_WEBHOOK_URL: ${{ secrets.MATTERMOST_WEBHOOK }}
|
MATTERMOST_WEBHOOK_URL: ${{ secrets.MATTERMOST_WEBHOOK }}
|
||||||
MATTERMOST_CHANNEL: bot-alerts-cicd
|
MATTERMOST_CHANNEL: bot-alerts-cicd
|
||||||
TEXT: |
|
TEXT: |
|
||||||
🐳 *[PENPOT] Docker image available: ${{ github.ref_name }}*
|
🐳 *[PENPOT] Docker image available.*
|
||||||
🔗 Run: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
🔗 Run: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
||||||
@infra
|
@infra
|
||||||
|
|
||||||
|
|||||||
2
.github/workflows/commit-checker.yml
vendored
2
.github/workflows/commit-checker.yml
vendored
@@ -26,7 +26,7 @@ jobs:
|
|||||||
- name: Check Commit Type
|
- name: Check Commit Type
|
||||||
uses: gsactions/commit-message-checker@v2
|
uses: gsactions/commit-message-checker@v2
|
||||||
with:
|
with:
|
||||||
pattern: '^(((:(lipstick|globe_with_meridians|wrench|books|arrow_up|arrow_down|zap|ambulance|construction|boom|fire|whale|bug|sparkles|paperclip|tada|recycle|rewind|construction_worker):)\s[A-Z].*[^.])|(Merge|Revert|Reapply).+[^.])$'
|
pattern: '^(((:(lipstick|globe_with_meridians|wrench|books|arrow_up|arrow_down|zap|ambulance|construction|boom|fire|whale|bug|sparkles|paperclip|tada|recycle|rewind|construction_worker):)\s[A-Z].*[^.])|(Merge|Revert).+[^.])$'
|
||||||
flags: 'gm'
|
flags: 'gm'
|
||||||
error: 'Commit should match CONTRIBUTING.md guideline'
|
error: 'Commit should match CONTRIBUTING.md guideline'
|
||||||
checkAllCommitMessages: 'true' # optional: this checks all commits associated with a pull request
|
checkAllCommitMessages: 'true' # optional: this checks all commits associated with a pull request
|
||||||
|
|||||||
125
.github/workflows/plugins-deploy-api-doc.yml
vendored
125
.github/workflows/plugins-deploy-api-doc.yml
vendored
@@ -1,125 +0,0 @@
|
|||||||
name: Plugins/api-doc deployer
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- develop
|
|
||||||
- staging
|
|
||||||
- main
|
|
||||||
paths:
|
|
||||||
- 'plugins/libs/plugin-types/index.d.ts'
|
|
||||||
- 'plugins/libs/plugin-types/REAME.md'
|
|
||||||
- 'plugins/tools/typedoc.css'
|
|
||||||
- 'plugins/CHANGELOG.md'
|
|
||||||
- 'plugins/wrangler-penpot-plugins-api-doc.toml'
|
|
||||||
workflow_dispatch:
|
|
||||||
inputs:
|
|
||||||
gh_ref:
|
|
||||||
description: 'Name of the branch'
|
|
||||||
type: choice
|
|
||||||
required: true
|
|
||||||
default: 'develop'
|
|
||||||
options:
|
|
||||||
- develop
|
|
||||||
- staging
|
|
||||||
- main
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
deploy:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Extract some useful variables
|
|
||||||
id: vars
|
|
||||||
run: |
|
|
||||||
echo "gh_ref=${{ inputs.gh_ref || github.ref_name }}" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
ref: ${{ steps.vars.outputs.gh_ref }}
|
|
||||||
|
|
||||||
# START: Setup Node and PNPM enabling cache
|
|
||||||
- name: Setup Node.js
|
|
||||||
uses: actions/setup-node@v6
|
|
||||||
with:
|
|
||||||
node-version-file: .nvmrc
|
|
||||||
|
|
||||||
- name: Enable PNPM
|
|
||||||
working-directory: ./plugins
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
corepack enable;
|
|
||||||
corepack install;
|
|
||||||
|
|
||||||
- name: Get pnpm store path
|
|
||||||
id: pnpm-store
|
|
||||||
working-directory: ./plugins
|
|
||||||
shell: bash
|
|
||||||
run: echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
- name: Cache pnpm store
|
|
||||||
uses: actions/cache@v4
|
|
||||||
with:
|
|
||||||
path: ${{ steps.pnpm-store.outputs.STORE_PATH }}
|
|
||||||
key: ${{ runner.os }}-pnpm-${{ hashFiles('plugins/pnpm-lock.yaml') }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-pnpm-
|
|
||||||
# END: Setup Node and PNPM enabling cache
|
|
||||||
|
|
||||||
- name: Install deps
|
|
||||||
working-directory: ./plugins
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
pnpm install --no-frozen-lockfile;
|
|
||||||
pnpm add -D -w wrangler@latest;
|
|
||||||
|
|
||||||
- name: Build docs
|
|
||||||
working-directory: plugins
|
|
||||||
shell: bash
|
|
||||||
run: pnpm run build:doc
|
|
||||||
|
|
||||||
- name: Select Worker name
|
|
||||||
run: |
|
|
||||||
REF="${{ steps.vars.outputs.gh_ref }}"
|
|
||||||
case "$REF" in
|
|
||||||
main)
|
|
||||||
echo "WORKER_NAME=penpot-plugins-api-doc-pro" >> $GITHUB_ENV
|
|
||||||
echo "WORKER_URI=doc.plugins.penpot.app" >> $GITHUB_ENV ;;
|
|
||||||
staging)
|
|
||||||
echo "WORKER_NAME=penpot-plugins-api-doc-pre" >> $GITHUB_ENV
|
|
||||||
echo "WORKER_URI=doc.plugins.penpot.dev" >> $GITHUB_ENV ;;
|
|
||||||
develop)
|
|
||||||
echo "WORKER_NAME=penpot-plugins-api-doc-hourly" >> $GITHUB_ENV
|
|
||||||
echo "WORKER_URI=doc.plugins.hourly.penpot.dev" >> $GITHUB_ENV ;;
|
|
||||||
*) echo "Unsupported branch ${REF}" && exit 1 ;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
- name: Set the custom url
|
|
||||||
working-directory: plugins
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
sed -i "s/WORKER_URI/${{ env.WORKER_URI }}/g" wrangler-penpot-plugins-api-doc.toml
|
|
||||||
|
|
||||||
- name: Deploy to Cloudflare Workers
|
|
||||||
uses: cloudflare/wrangler-action@v3
|
|
||||||
with:
|
|
||||||
workingDirectory: plugins
|
|
||||||
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
|
||||||
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
|
||||||
command: deploy --config wrangler-penpot-plugins-api-doc.toml --name ${{ env.WORKER_NAME }}
|
|
||||||
|
|
||||||
- name: Notify Mattermost
|
|
||||||
if: failure()
|
|
||||||
uses: mattermost/action-mattermost-notify@master
|
|
||||||
with:
|
|
||||||
MATTERMOST_WEBHOOK_URL: ${{ secrets.MATTERMOST_WEBHOOK }}
|
|
||||||
MATTERMOST_CHANNEL: bot-alerts-cicd
|
|
||||||
TEXT: |
|
|
||||||
❌ 🧩📚 *[PENPOT PLUGINS] Error deploying API documentation.*
|
|
||||||
📄 Triggered from ref: `${{ inputs.gh_ref }}`
|
|
||||||
🔗 Run: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
|
||||||
@infra
|
|
||||||
127
.github/workflows/plugins-deploy-package.yml
vendored
127
.github/workflows/plugins-deploy-package.yml
vendored
@@ -1,127 +0,0 @@
|
|||||||
name: Plugins/package deployer
|
|
||||||
|
|
||||||
on:
|
|
||||||
# Deploy package from manual action
|
|
||||||
workflow_dispatch:
|
|
||||||
inputs:
|
|
||||||
gh_ref:
|
|
||||||
description: 'Name of the branch'
|
|
||||||
type: choice
|
|
||||||
required: true
|
|
||||||
default: 'develop'
|
|
||||||
options:
|
|
||||||
- develop
|
|
||||||
- staging
|
|
||||||
- main
|
|
||||||
plugin_name:
|
|
||||||
description: 'Pluging name (like plugins/apps/<plugin_name>-plugin)'
|
|
||||||
type: string
|
|
||||||
required: true
|
|
||||||
workflow_call:
|
|
||||||
inputs:
|
|
||||||
gh_ref:
|
|
||||||
description: 'Name of the branch'
|
|
||||||
type: string
|
|
||||||
required: true
|
|
||||||
default: 'develop'
|
|
||||||
plugin_name:
|
|
||||||
description: 'Publig name (from plugins/apps/<plugin_name>-plugin)'
|
|
||||||
type: string
|
|
||||||
required: true
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
deploy:
|
|
||||||
runs-on: penpot-runner-01
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
ref: ${{ inputs.gh_ref }}
|
|
||||||
|
|
||||||
# START: Setup Node and PNPM enabling cache
|
|
||||||
- name: Setup Node.js
|
|
||||||
uses: actions/setup-node@v6
|
|
||||||
with:
|
|
||||||
node-version-file: .nvmrc
|
|
||||||
|
|
||||||
- name: Enable PNPM
|
|
||||||
working-directory: ./plugins
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
corepack enable;
|
|
||||||
corepack install;
|
|
||||||
|
|
||||||
- name: Get pnpm store path
|
|
||||||
id: pnpm-store
|
|
||||||
working-directory: ./plugins
|
|
||||||
shell: bash
|
|
||||||
run: echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
- name: Cache pnpm store
|
|
||||||
uses: actions/cache@v4
|
|
||||||
with:
|
|
||||||
path: ${{ steps.pnpm-store.outputs.STORE_PATH }}
|
|
||||||
key: ${{ runner.os }}-pnpm-${{ hashFiles('plugins/pnpm-lock.yaml') }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-pnpm-
|
|
||||||
# END: Setup Node and PNPM enabling cache
|
|
||||||
|
|
||||||
- name: Install deps
|
|
||||||
working-directory: ./plugins
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
pnpm install --no-frozen-lockfile;
|
|
||||||
pnpm add -D -w wrangler@latest;
|
|
||||||
|
|
||||||
- name: "Build package for ${{ inputs.plugin_name }}-plugin"
|
|
||||||
working-directory: plugins
|
|
||||||
shell: bash
|
|
||||||
run: npx nx build ${{ inputs.plugin_name }}-plugin
|
|
||||||
|
|
||||||
- name: Select Worker name
|
|
||||||
run: |
|
|
||||||
REF="${{ inputs.gh_ref }}"
|
|
||||||
case "$REF" in
|
|
||||||
main)
|
|
||||||
echo "WORKER_NAME=${{ inputs.plugin_name }}-plugin-pro" >> $GITHUB_ENV
|
|
||||||
echo "WORKER_URI=${{ inputs.plugin_name }}.plugins.penpot.app" >> $GITHUB_ENV ;;
|
|
||||||
staging)
|
|
||||||
echo "WORKER_NAME=${{ inputs.plugin_name }}-plugin-pre" >> $GITHUB_ENV
|
|
||||||
echo "WORKER_URI=${{ inputs.plugin_name }}.plugins.penpot.dev" >> $GITHUB_ENV ;;
|
|
||||||
develop)
|
|
||||||
echo "WORKER_NAME=${{ inputs.plugin_name }}-plugin-hourly" >> $GITHUB_ENV
|
|
||||||
echo "WORKER_URI=${{ inputs.plugin_name }}.plugins.hourly.penpot.dev" >> $GITHUB_ENV ;;
|
|
||||||
*) echo "Unsupported branch ${REF}" && exit 1 ;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
- name: Set the custom url
|
|
||||||
working-directory: plugins
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
sed -i "s/WORKER_URI/${{ env.WORKER_URI }}/g" apps/${{ inputs.plugin_name }}-plugin/wrangler.toml
|
|
||||||
|
|
||||||
- name: Deploy to Cloudflare Workers
|
|
||||||
uses: cloudflare/wrangler-action@v3
|
|
||||||
with:
|
|
||||||
workingDirectory: plugins
|
|
||||||
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
|
||||||
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
|
||||||
command: deploy --config apps/${{ inputs.plugin_name }}-plugin/wrangler.toml --name ${{ env.WORKER_NAME }}
|
|
||||||
|
|
||||||
- name: Notify Mattermost
|
|
||||||
if: failure()
|
|
||||||
uses: mattermost/action-mattermost-notify@master
|
|
||||||
with:
|
|
||||||
MATTERMOST_WEBHOOK_URL: ${{ secrets.MATTERMOST_WEBHOOK }}
|
|
||||||
MATTERMOST_CHANNEL: bot-alerts-cicd
|
|
||||||
TEXT: |
|
|
||||||
❌ 🧩📦 *[PENPOT PLUGINS] Error deploying ${{ env.WORKER_NAME }}.*
|
|
||||||
📄 Triggered from ref: `${{ inputs.gh_ref }}`
|
|
||||||
Plugin name: `${{ inputs.plugin_name }}-plugin`
|
|
||||||
Cloudflare worker name: `${{ env.WORKER_NAME }}`
|
|
||||||
🔗 Run: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
|
||||||
@infra
|
|
||||||
143
.github/workflows/plugins-deploy-packages.yml
vendored
143
.github/workflows/plugins-deploy-packages.yml
vendored
@@ -1,143 +0,0 @@
|
|||||||
name: Plugins/packages deployer
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- develop
|
|
||||||
- staging
|
|
||||||
- main
|
|
||||||
paths:
|
|
||||||
- 'plugins/apps/*-plugin/**'
|
|
||||||
- 'libs/plugins-styles/**'
|
|
||||||
workflow_dispatch:
|
|
||||||
inputs:
|
|
||||||
gh_ref:
|
|
||||||
description: 'Name of the branch'
|
|
||||||
type: choice
|
|
||||||
required: true
|
|
||||||
default: 'develop'
|
|
||||||
options:
|
|
||||||
- develop
|
|
||||||
- staging
|
|
||||||
- main
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
detect-changes:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
outputs:
|
|
||||||
colors_to_tokens: ${{ steps.filter.outputs.colors_to_tokens }}
|
|
||||||
create_palette: ${{ steps.filter.outputs.create_palette }}
|
|
||||||
lorem_ipsum: ${{ steps.filter.outputs.lorem_ipsum }}
|
|
||||||
rename_layers: ${{ steps.filter.outputs.rename_layers }}
|
|
||||||
contrast: ${{ steps.filter.outputs.contrast }}
|
|
||||||
icons: ${{ steps.filter.outputs.icons }}
|
|
||||||
poc_state: ${{ steps.filter.outputs.poc_state }}
|
|
||||||
table: ${{ steps.filter.outputs.table }}
|
|
||||||
# [For new plugins]
|
|
||||||
# Add more outputs here
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- id: filter
|
|
||||||
uses: dorny/paths-filter@v3
|
|
||||||
with:
|
|
||||||
filters: |
|
|
||||||
colors_to_tokens:
|
|
||||||
- 'plugins/apps/colors-to-tokens-plugin/**'
|
|
||||||
- 'libs/plugins-styles/**'
|
|
||||||
contrast:
|
|
||||||
- 'plugins/apps/contrast-plugin/**'
|
|
||||||
- 'libs/plugins-styles/**'
|
|
||||||
create_palette:
|
|
||||||
- 'plugins/apps/create-palette-plugin/**'
|
|
||||||
- 'libs/plugins-styles/**'
|
|
||||||
icons:
|
|
||||||
- 'plugins/apps/icons-plugin/**'
|
|
||||||
- 'libs/plugins-styles/**'
|
|
||||||
lorem_ipsum:
|
|
||||||
- 'plugins/apps/lorem-ipsum-plugin/**'
|
|
||||||
- 'libs/plugins-styles/**'
|
|
||||||
rename_layers:
|
|
||||||
- 'plugins/apps/rename-layers-plugin/**'
|
|
||||||
- 'libs/plugins-styles/**'
|
|
||||||
table:
|
|
||||||
- 'plugins/apps/table-plugin/**'
|
|
||||||
- 'libs/plugins-styles/**'
|
|
||||||
# [For new plugins]
|
|
||||||
# Add more plugin filters here
|
|
||||||
# another_plugin:
|
|
||||||
# - 'plugins/apps/another-plugin/**'
|
|
||||||
# - 'libs/plugins-styles/**'
|
|
||||||
|
|
||||||
colors-to-tokens-plugin:
|
|
||||||
needs: detect-changes
|
|
||||||
if: github.event_name == 'workflow_dispatch' || needs.detect-changes.outputs.colors_to_tokens == 'true'
|
|
||||||
uses: ./.github/workflows/plugins-deploy-package.yml
|
|
||||||
secrets: inherit
|
|
||||||
with:
|
|
||||||
gh_ref: "${{ inputs.gh_ref || github.ref_name }}"
|
|
||||||
plugin_name: colors-to-tokens
|
|
||||||
|
|
||||||
contrast-plugin:
|
|
||||||
needs: detect-changes
|
|
||||||
if: github.event_name == 'workflow_dispatch' || needs.detect-changes.outputs.contrast == 'true'
|
|
||||||
uses: ./.github/workflows/plugins-deploy-package.yml
|
|
||||||
secrets: inherit
|
|
||||||
with:
|
|
||||||
gh_ref: "${{ inputs.gh_ref || github.ref_name }}"
|
|
||||||
plugin_name: contrast
|
|
||||||
|
|
||||||
create-palette-plugin:
|
|
||||||
needs: detect-changes
|
|
||||||
if: github.event_name == 'workflow_dispatch' || needs.detect-changes.outputs.create_palette == 'true'
|
|
||||||
uses: ./.github/workflows/plugins-deploy-package.yml
|
|
||||||
secrets: inherit
|
|
||||||
with:
|
|
||||||
gh_ref: "${{ inputs.gh_ref || github.ref_name }}"
|
|
||||||
plugin_name: create-palette
|
|
||||||
|
|
||||||
icons-plugin:
|
|
||||||
needs: detect-changes
|
|
||||||
if: github.event_name == 'workflow_dispatch' || needs.detect-changes.outputs.icons == 'true'
|
|
||||||
uses: ./.github/workflows/plugins-deploy-package.yml
|
|
||||||
secrets: inherit
|
|
||||||
with:
|
|
||||||
gh_ref: "${{ inputs.gh_ref || github.ref_name }}"
|
|
||||||
plugin_name: icons
|
|
||||||
|
|
||||||
lorem-ipsum-plugin:
|
|
||||||
needs: detect-changes
|
|
||||||
if: github.event_name == 'workflow_dispatch' || needs.detect-changes.outputs.lorem_ipsum == 'true'
|
|
||||||
uses: ./.github/workflows/plugins-deploy-package.yml
|
|
||||||
secrets: inherit
|
|
||||||
with:
|
|
||||||
gh_ref: "${{ inputs.gh_ref || github.ref_name }}"
|
|
||||||
plugin_name: lorem-ipsum
|
|
||||||
|
|
||||||
rename-layers-plugin:
|
|
||||||
needs: detect-changes
|
|
||||||
if: github.event_name == 'workflow_dispatch' || needs.detect-changes.outputs.rename_layers == 'true'
|
|
||||||
uses: ./.github/workflows/plugins-deploy-package.yml
|
|
||||||
secrets: inherit
|
|
||||||
with:
|
|
||||||
gh_ref: "${{ inputs.gh_ref || github.ref_name }}"
|
|
||||||
plugin_name: rename-layers
|
|
||||||
|
|
||||||
table-plugin:
|
|
||||||
needs: detect-changes
|
|
||||||
if: github.event_name == 'workflow_dispatch' || needs.detect-changes.outputs.table == 'true'
|
|
||||||
uses: ./.github/workflows/plugins-deploy-package.yml
|
|
||||||
secrets: inherit
|
|
||||||
with:
|
|
||||||
gh_ref: "${{ inputs.gh_ref || github.ref_name }}"
|
|
||||||
plugin_name: table
|
|
||||||
|
|
||||||
# [For new plugins]
|
|
||||||
# Add more jobs for other plugins below, following the same pattern
|
|
||||||
# another-plugin:
|
|
||||||
# needs: detect-changes
|
|
||||||
# if: github.event_name == 'workflow_dispatch' || needs.detect-changes.outputs.another_plugin == 'true'
|
|
||||||
# uses: ./.github/workflows/plugins-deploy-package.yml
|
|
||||||
# secrets: inherit
|
|
||||||
# with:
|
|
||||||
# gh_ref: "${{ inputs.gh_ref || github.ref_name }}"
|
|
||||||
# plugin_name: another
|
|
||||||
123
.github/workflows/plugins-deploy-styles-doc.yml
vendored
123
.github/workflows/plugins-deploy-styles-doc.yml
vendored
@@ -1,123 +0,0 @@
|
|||||||
name: Plugins/styles-doc deployer
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- develop
|
|
||||||
- staging
|
|
||||||
- main
|
|
||||||
paths:
|
|
||||||
- 'plugins/apps/example-styles/**'
|
|
||||||
- 'plugins/libs/plugins-styles/**'
|
|
||||||
- 'plugins/wrangler-penpot-plugins-styles-doc.toml'
|
|
||||||
workflow_dispatch:
|
|
||||||
inputs:
|
|
||||||
gh_ref:
|
|
||||||
description: 'Name of the branch'
|
|
||||||
type: choice
|
|
||||||
required: true
|
|
||||||
default: 'develop'
|
|
||||||
options:
|
|
||||||
- develop
|
|
||||||
- staging
|
|
||||||
- main
|
|
||||||
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
deploy:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Extract some useful variables
|
|
||||||
id: vars
|
|
||||||
run: |
|
|
||||||
echo "gh_ref=${{ inputs.gh_ref || github.ref_name }}" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
with:
|
|
||||||
fetch-depth: 0
|
|
||||||
ref: ${{ steps.vars.outputs.gh_ref }}
|
|
||||||
|
|
||||||
# START: Setup Node and PNPM enabling cache
|
|
||||||
- name: Setup Node.js
|
|
||||||
uses: actions/setup-node@v6
|
|
||||||
with:
|
|
||||||
node-version-file: .nvmrc
|
|
||||||
|
|
||||||
- name: Enable PNPM
|
|
||||||
working-directory: ./plugins
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
corepack enable;
|
|
||||||
corepack install;
|
|
||||||
|
|
||||||
- name: Get pnpm store path
|
|
||||||
id: pnpm-store
|
|
||||||
working-directory: ./plugins
|
|
||||||
shell: bash
|
|
||||||
run: echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
- name: Cache pnpm store
|
|
||||||
uses: actions/cache@v4
|
|
||||||
with:
|
|
||||||
path: ${{ steps.pnpm-store.outputs.STORE_PATH }}
|
|
||||||
key: ${{ runner.os }}-pnpm-${{ hashFiles('plugins/pnpm-lock.yaml') }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-pnpm-
|
|
||||||
# END: Setup Node and PNPM enabling cache
|
|
||||||
|
|
||||||
- name: Install deps
|
|
||||||
working-directory: ./plugins
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
pnpm install --no-frozen-lockfile;
|
|
||||||
pnpm add -D -w wrangler@latest;
|
|
||||||
|
|
||||||
- name: Build styles
|
|
||||||
working-directory: plugins
|
|
||||||
shell: bash
|
|
||||||
run: npx nx run example-styles:build
|
|
||||||
|
|
||||||
- name: Select Worker name
|
|
||||||
run: |
|
|
||||||
REF="${{ steps.vars.outputs.gh_ref }}"
|
|
||||||
case "$REF" in
|
|
||||||
main)
|
|
||||||
echo "WORKER_NAME=penpot-plugins-styles-doc-pro" >> $GITHUB_ENV
|
|
||||||
echo "WORKER_URI=styles-doc.plugins.penpot.app" >> $GITHUB_ENV ;;
|
|
||||||
staging)
|
|
||||||
echo "WORKER_NAME=penpot-plugins-styles-doc-pre" >> $GITHUB_ENV
|
|
||||||
echo "WORKER_URI=styles-doc.plugins.penpot.dev" >> $GITHUB_ENV ;;
|
|
||||||
develop)
|
|
||||||
echo "WORKER_NAME=penpot-plugins-styles-doc-hourly" >> $GITHUB_ENV
|
|
||||||
echo "WORKER_URI=styles-doc.plugins.hourly.penpot.dev" >> $GITHUB_ENV ;;
|
|
||||||
*) echo "Unsupported branch ${REF}" && exit 1 ;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
- name: Set the custom url
|
|
||||||
working-directory: plugins
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
sed -i "s/WORKER_URI/${{ env.WORKER_URI }}/g" wrangler-penpot-plugins-styles-doc.toml
|
|
||||||
|
|
||||||
- name: Deploy to Cloudflare Workers
|
|
||||||
uses: cloudflare/wrangler-action@v3
|
|
||||||
with:
|
|
||||||
workingDirectory: plugins
|
|
||||||
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
|
|
||||||
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
|
|
||||||
command: deploy --config wrangler-penpot-plugins-styles-doc.toml --name ${{ env.WORKER_NAME }}
|
|
||||||
|
|
||||||
- name: Notify Mattermost
|
|
||||||
if: failure()
|
|
||||||
uses: mattermost/action-mattermost-notify@master
|
|
||||||
with:
|
|
||||||
MATTERMOST_WEBHOOK_URL: ${{ secrets.MATTERMOST_WEBHOOK }}
|
|
||||||
MATTERMOST_CHANNEL: bot-alerts-cicd
|
|
||||||
TEXT: |
|
|
||||||
❌ 🧩💅 *[PENPOT PLUGINS] Error deploying Styles documentation.*
|
|
||||||
📄 Triggered from ref: `${{ inputs.gh_ref }}`
|
|
||||||
🔗 Run: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
|
||||||
@infra
|
|
||||||
70
.github/workflows/tests.yml
vendored
70
.github/workflows/tests.yml
vendored
@@ -21,7 +21,7 @@ concurrency:
|
|||||||
jobs:
|
jobs:
|
||||||
lint:
|
lint:
|
||||||
name: "Linter"
|
name: "Linter"
|
||||||
runs-on: penpot-runner-02
|
runs-on: ubuntu-24.04
|
||||||
container: penpotapp/devenv:latest
|
container: penpotapp/devenv:latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
@@ -34,7 +34,7 @@ jobs:
|
|||||||
|
|
||||||
test-common:
|
test-common:
|
||||||
name: "Common Tests"
|
name: "Common Tests"
|
||||||
runs-on: penpot-runner-02
|
runs-on: ubuntu-24.04
|
||||||
container: penpotapp/devenv:latest
|
container: penpotapp/devenv:latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
@@ -51,55 +51,9 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
./scripts/test
|
./scripts/test
|
||||||
|
|
||||||
test-plugins:
|
|
||||||
name: Plugins Runtime Linter & Tests
|
|
||||||
runs-on: penpot-runner-02
|
|
||||||
container: penpotapp/devenv:latest
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: Setup Node
|
|
||||||
id: setup-node
|
|
||||||
uses: actions/setup-node@v6
|
|
||||||
with:
|
|
||||||
node-version-file: .nvmrc
|
|
||||||
|
|
||||||
- name: Install deps
|
|
||||||
working-directory: ./plugins
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
corepack enable;
|
|
||||||
corepack install;
|
|
||||||
pnpm install;
|
|
||||||
|
|
||||||
- name: Run Lint
|
|
||||||
working-directory: ./plugins
|
|
||||||
run: pnpm run lint
|
|
||||||
|
|
||||||
- name: Run Format Check
|
|
||||||
working-directory: ./plugins
|
|
||||||
run: pnpm run format:check
|
|
||||||
|
|
||||||
- name: Run Test
|
|
||||||
working-directory: ./plugins
|
|
||||||
run: pnpm run test
|
|
||||||
|
|
||||||
- name: Build runtime
|
|
||||||
working-directory: ./plugins
|
|
||||||
run: pnpm run build
|
|
||||||
|
|
||||||
- name: Build plugins
|
|
||||||
working-directory: ./plugins
|
|
||||||
run: pnpm run build:plugins
|
|
||||||
|
|
||||||
- name: Build styles
|
|
||||||
working-directory: ./plugins
|
|
||||||
run: pnpm run build:styles-example
|
|
||||||
|
|
||||||
test-frontend:
|
test-frontend:
|
||||||
name: "Frontend Tests"
|
name: "Frontend Tests"
|
||||||
runs-on: penpot-runner-02
|
runs-on: ubuntu-24.04
|
||||||
container: penpotapp/devenv:latest
|
container: penpotapp/devenv:latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
@@ -113,14 +67,12 @@ jobs:
|
|||||||
|
|
||||||
- name: Component Tests
|
- name: Component Tests
|
||||||
working-directory: ./frontend
|
working-directory: ./frontend
|
||||||
env:
|
|
||||||
VITEST_BROWSER_TIMEOUT: 120000
|
|
||||||
run: |
|
run: |
|
||||||
./scripts/test-components
|
./scripts/test-components
|
||||||
|
|
||||||
test-render-wasm:
|
test-render-wasm:
|
||||||
name: "Render WASM Tests"
|
name: "Render WASM Tests"
|
||||||
runs-on: penpot-runner-02
|
runs-on: ubuntu-24.04
|
||||||
container: penpotapp/devenv:latest
|
container: penpotapp/devenv:latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
@@ -144,7 +96,7 @@ jobs:
|
|||||||
|
|
||||||
test-backend:
|
test-backend:
|
||||||
name: "Backend Tests"
|
name: "Backend Tests"
|
||||||
runs-on: penpot-runner-02
|
runs-on: ubuntu-24.04
|
||||||
container: penpotapp/devenv:latest
|
container: penpotapp/devenv:latest
|
||||||
|
|
||||||
services:
|
services:
|
||||||
@@ -183,7 +135,7 @@ jobs:
|
|||||||
|
|
||||||
test-library:
|
test-library:
|
||||||
name: "Library Tests"
|
name: "Library Tests"
|
||||||
runs-on: penpot-runner-02
|
runs-on: ubuntu-24.04
|
||||||
container: penpotapp/devenv:latest
|
container: penpotapp/devenv:latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
@@ -197,7 +149,7 @@ jobs:
|
|||||||
|
|
||||||
build-integration:
|
build-integration:
|
||||||
name: "Build Integration Bundle"
|
name: "Build Integration Bundle"
|
||||||
runs-on: penpot-runner-02
|
runs-on: ubuntu-24.04
|
||||||
container: penpotapp/devenv:latest
|
container: penpotapp/devenv:latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
@@ -218,7 +170,7 @@ jobs:
|
|||||||
|
|
||||||
test-integration-1:
|
test-integration-1:
|
||||||
name: "Integration Tests 1/4"
|
name: "Integration Tests 1/4"
|
||||||
runs-on: penpot-runner-02
|
runs-on: ubuntu-24.04
|
||||||
container: penpotapp/devenv:latest
|
container: penpotapp/devenv:latest
|
||||||
needs: build-integration
|
needs: build-integration
|
||||||
|
|
||||||
@@ -248,7 +200,7 @@ jobs:
|
|||||||
|
|
||||||
test-integration-2:
|
test-integration-2:
|
||||||
name: "Integration Tests 2/4"
|
name: "Integration Tests 2/4"
|
||||||
runs-on: penpot-runner-02
|
runs-on: ubuntu-24.04
|
||||||
container: penpotapp/devenv:latest
|
container: penpotapp/devenv:latest
|
||||||
needs: build-integration
|
needs: build-integration
|
||||||
|
|
||||||
@@ -278,7 +230,7 @@ jobs:
|
|||||||
|
|
||||||
test-integration-3:
|
test-integration-3:
|
||||||
name: "Integration Tests 3/4"
|
name: "Integration Tests 3/4"
|
||||||
runs-on: penpot-runner-02
|
runs-on: ubuntu-24.04
|
||||||
container: penpotapp/devenv:latest
|
container: penpotapp/devenv:latest
|
||||||
needs: build-integration
|
needs: build-integration
|
||||||
|
|
||||||
@@ -308,7 +260,7 @@ jobs:
|
|||||||
|
|
||||||
test-integration-4:
|
test-integration-4:
|
||||||
name: "Integration Tests 4/4"
|
name: "Integration Tests 4/4"
|
||||||
runs-on: penpot-runner-02
|
runs-on: ubuntu-24.04
|
||||||
container: penpotapp/devenv:latest
|
container: penpotapp/devenv:latest
|
||||||
needs: build-integration
|
needs: build-integration
|
||||||
|
|
||||||
|
|||||||
7
.gitignore
vendored
7
.gitignore
vendored
@@ -5,7 +5,6 @@
|
|||||||
!.yarn/releases
|
!.yarn/releases
|
||||||
!.yarn/sdks
|
!.yarn/sdks
|
||||||
!.yarn/versions
|
!.yarn/versions
|
||||||
.pnpm-store
|
|
||||||
*-init.clj
|
*-init.clj
|
||||||
*.css.json
|
*.css.json
|
||||||
*.jar
|
*.jar
|
||||||
@@ -21,7 +20,6 @@
|
|||||||
.rebel_readline_history
|
.rebel_readline_history
|
||||||
.repl
|
.repl
|
||||||
.shadow-cljs
|
.shadow-cljs
|
||||||
.pnpm-store/
|
|
||||||
/*.jpg
|
/*.jpg
|
||||||
/*.md
|
/*.md
|
||||||
/*.png
|
/*.png
|
||||||
@@ -45,7 +43,6 @@
|
|||||||
/backend/resources/public/media
|
/backend/resources/public/media
|
||||||
/backend/target/
|
/backend/target/
|
||||||
/backend/experiments
|
/backend/experiments
|
||||||
/backend/scripts/_env.local
|
|
||||||
/bundle*
|
/bundle*
|
||||||
/cd.md
|
/cd.md
|
||||||
/clj-profiler/
|
/clj-profiler/
|
||||||
@@ -56,8 +53,6 @@
|
|||||||
/exporter/target
|
/exporter/target
|
||||||
/frontend/.storybook/preview-body.html
|
/frontend/.storybook/preview-body.html
|
||||||
/frontend/.storybook/preview-head.html
|
/frontend/.storybook/preview-head.html
|
||||||
/frontend/playwright-report/
|
|
||||||
/frontend/text-editor/src/wasm/
|
|
||||||
/frontend/dist/
|
/frontend/dist/
|
||||||
/frontend/npm-debug.log
|
/frontend/npm-debug.log
|
||||||
/frontend/out/
|
/frontend/out/
|
||||||
@@ -66,7 +61,6 @@
|
|||||||
/frontend/resources/public/*
|
/frontend/resources/public/*
|
||||||
/frontend/storybook-static/
|
/frontend/storybook-static/
|
||||||
/frontend/target/
|
/frontend/target/
|
||||||
/frontend/test-results/
|
|
||||||
/other/
|
/other/
|
||||||
/scripts/
|
/scripts/
|
||||||
/telemetry/
|
/telemetry/
|
||||||
@@ -77,7 +71,6 @@
|
|||||||
/library/target/
|
/library/target/
|
||||||
/library/*.zip
|
/library/*.zip
|
||||||
/external
|
/external
|
||||||
/penpot-nitrate
|
|
||||||
|
|
||||||
clj-profiler/
|
clj-profiler/
|
||||||
node_modules
|
node_modules
|
||||||
|
|||||||
105
.gitpod.yml
Normal file
105
.gitpod.yml
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
image:
|
||||||
|
file: docker/gitpod/Dockerfile
|
||||||
|
|
||||||
|
ports:
|
||||||
|
# nginx
|
||||||
|
- port: 3449
|
||||||
|
onOpen: open-preview
|
||||||
|
|
||||||
|
# frontend nREPL
|
||||||
|
- port: 3447
|
||||||
|
onOpen: ignore
|
||||||
|
visibility: private
|
||||||
|
|
||||||
|
# frontend shadow server
|
||||||
|
- port: 3448
|
||||||
|
onOpen: ignore
|
||||||
|
visibility: private
|
||||||
|
|
||||||
|
# backend
|
||||||
|
- port: 6060
|
||||||
|
onOpen: ignore
|
||||||
|
|
||||||
|
# exporter shadow server
|
||||||
|
- port: 9630
|
||||||
|
onOpen: ignore
|
||||||
|
visibility: private
|
||||||
|
|
||||||
|
# exporter http server
|
||||||
|
- port: 6061
|
||||||
|
onOpen: ignore
|
||||||
|
|
||||||
|
# mailhog web interface
|
||||||
|
- port: 8025
|
||||||
|
onOpen: ignore
|
||||||
|
|
||||||
|
# mailhog postfix
|
||||||
|
- port: 1025
|
||||||
|
onOpen: ignore
|
||||||
|
|
||||||
|
# postgres
|
||||||
|
- port: 5432
|
||||||
|
onOpen: ignore
|
||||||
|
|
||||||
|
# redis
|
||||||
|
- port: 6379
|
||||||
|
onOpen: ignore
|
||||||
|
|
||||||
|
# openldap
|
||||||
|
- port: 389
|
||||||
|
onOpen: ignore
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
# https://github.com/gitpod-io/gitpod/issues/666#issuecomment-534347856
|
||||||
|
- name: gulp
|
||||||
|
command: >
|
||||||
|
cd $GITPOD_REPO_ROOT/frontend/;
|
||||||
|
yarn && gp sync-done 'frontend-yarn';
|
||||||
|
npx gulp --theme=${PENPOT_THEME} watch
|
||||||
|
|
||||||
|
- name: frontend shadow watch
|
||||||
|
command: >
|
||||||
|
cd $GITPOD_REPO_ROOT/frontend/;
|
||||||
|
gp sync-await 'frontend-yarn';
|
||||||
|
npx shadow-cljs watch main
|
||||||
|
|
||||||
|
- init: gp await-port 5432 && psql -f $GITPOD_REPO_ROOT/docker/gitpod/files/postgresql_init.sql
|
||||||
|
name: backend
|
||||||
|
command: >
|
||||||
|
cd $GITPOD_REPO_ROOT/backend/;
|
||||||
|
./scripts/start-dev
|
||||||
|
|
||||||
|
- name: exporter shadow watch
|
||||||
|
command:
|
||||||
|
cd $GITPOD_REPO_ROOT/exporter/;
|
||||||
|
gp sync-await 'frontend-yarn';
|
||||||
|
yarn && npx shadow-cljs watch main
|
||||||
|
|
||||||
|
- name: exporter web server
|
||||||
|
command: >
|
||||||
|
cd $GITPOD_REPO_ROOT/exporter/;
|
||||||
|
./scripts/wait-and-start.sh
|
||||||
|
|
||||||
|
- name: signed terminal
|
||||||
|
before: >
|
||||||
|
[[ ! -z ${GNUGPG} ]] &&
|
||||||
|
cd ~ &&
|
||||||
|
rm -rf .gnupg &&
|
||||||
|
echo ${GNUGPG} | base64 -d | tar --no-same-owner -xzvf -
|
||||||
|
init: >
|
||||||
|
[[ ! -z ${GNUGPG_KEY} ]] &&
|
||||||
|
git config --global commit.gpgsign true &&
|
||||||
|
git config --global user.signingkey ${GNUGPG_KEY}
|
||||||
|
command: cd $GITPOD_REPO_ROOT
|
||||||
|
|
||||||
|
- name: redis
|
||||||
|
command: redis-server
|
||||||
|
|
||||||
|
- before: go get github.com/mailhog/MailHog
|
||||||
|
name: mailhog
|
||||||
|
command: MailHog
|
||||||
|
|
||||||
|
- name: Nginx
|
||||||
|
command: >
|
||||||
|
nginx &&
|
||||||
|
multitail /var/log/nginx/access.log -I /var/log/nginx/error.log
|
||||||
40
.travis.yml
Normal file
40
.travis.yml
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
dist: xenial
|
||||||
|
|
||||||
|
language: generic
|
||||||
|
sudo: required
|
||||||
|
|
||||||
|
cache:
|
||||||
|
directories:
|
||||||
|
- $HOME/.m2
|
||||||
|
|
||||||
|
services:
|
||||||
|
- docker
|
||||||
|
|
||||||
|
branches:
|
||||||
|
only:
|
||||||
|
- master
|
||||||
|
- develop
|
||||||
|
|
||||||
|
install:
|
||||||
|
- curl -O https://download.clojure.org/install/linux-install-1.10.1.447.sh
|
||||||
|
- chmod +x linux-install-1.10.1.447.sh
|
||||||
|
- sudo ./linux-install-1.10.1.447.sh
|
||||||
|
|
||||||
|
before_script:
|
||||||
|
- env | sort
|
||||||
|
|
||||||
|
script:
|
||||||
|
- ./manage.sh build-devenv
|
||||||
|
- ./manage.sh run-frontend-tests
|
||||||
|
- ./manage.sh run-backend-tests
|
||||||
|
- ./manage.sh build-images
|
||||||
|
- ./manage.sh run
|
||||||
|
|
||||||
|
after_script:
|
||||||
|
- docker images
|
||||||
|
|
||||||
|
notifications:
|
||||||
|
email: false
|
||||||
|
|
||||||
|
env:
|
||||||
|
- NODE_VERSION=10.16.0
|
||||||
11
.yarnrc.yml
Normal file
11
.yarnrc.yml
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
enableGlobalCache: true
|
||||||
|
|
||||||
|
enableImmutableCache: false
|
||||||
|
|
||||||
|
enableImmutableInstalls: false
|
||||||
|
|
||||||
|
enableTelemetry: false
|
||||||
|
|
||||||
|
httpTimeout: 600000
|
||||||
|
|
||||||
|
nodeLinker: node-modules
|
||||||
79
CHANGES.md
79
CHANGES.md
@@ -1,37 +1,5 @@
|
|||||||
# CHANGELOG
|
# CHANGELOG
|
||||||
|
|
||||||
## 2.14.0 (Unreleased)
|
|
||||||
|
|
||||||
### :boom: Breaking changes & Deprecations
|
|
||||||
|
|
||||||
### :rocket: Epics and highlights
|
|
||||||
|
|
||||||
### :heart: Community contributions (Thank you!)
|
|
||||||
|
|
||||||
### :sparkles: New features & Enhancements
|
|
||||||
|
|
||||||
- Remap references when renaming tokens [Taiga #10202](https://tree.taiga.io/project/penpot/us/10202)
|
|
||||||
- Tokens panel nested path view [Taiga #9966](https://tree.taiga.io/project/penpot/us/9966)
|
|
||||||
- Improve usability of lock and hide buttons in the layer panel. [Taiga #12916](https://tree.taiga.io/project/penpot/issue/12916)
|
|
||||||
- Optimize sidebar performance for deeply nested shapes [Taiga #13017](https://tree.taiga.io/project/penpot/task/13017)
|
|
||||||
- Remove tokens path node and bulk remove tokens [Taiga #13007](https://tree.taiga.io/project/penpot/us/13007)
|
|
||||||
- Replace themes management modal radio buttons for switches [Taiga #9215](https://tree.taiga.io/project/penpot/us/9215)
|
|
||||||
|
|
||||||
### :bug: Bugs fixed
|
|
||||||
|
|
||||||
- Remove whitespaces from asset export filename [Github #8133](https://github.com/penpot/penpot/pull/8133)
|
|
||||||
- Fix prototype connections lost when switching between variants [Taiga #12812](https://tree.taiga.io/project/penpot/issue/12812)
|
|
||||||
- Fix wrong image in the onboarding invitation block [Taiga #13040](https://tree.taiga.io/project/penpot/issue/13040)
|
|
||||||
- Fix wrong register image [Taiga #12955](https://tree.taiga.io/project/penpot/task/12955)
|
|
||||||
- Fix error message on components doesn't close automatically [Taiga #12012](https://tree.taiga.io/project/penpot/issue/12012)
|
|
||||||
- Fix incorrect handling of input values on layout gap and padding inputs [Github #8113](https://github.com/penpot/penpot/issues/8113)
|
|
||||||
- Fix incorrect default option on tokens import dialog [Github #8051](https://github.com/penpot/penpot/pull/8051)
|
|
||||||
- Fix unhandled exception tokens creation dialog [Github #8110](https://github.com/penpot/penpot/issues/8110)
|
|
||||||
- Fix displaying a hidden user avatar when there is only one more [Taiga #13058](https://tree.taiga.io/project/penpot/issue/13058)
|
|
||||||
- Fix unhandled exception on open-new-window helper [Github #7787](https://github.com/penpot/penpot/issues/7787)
|
|
||||||
- Fix exception on uploading large fonts [Github #8135](https://github.com/penpot/penpot/pull/8135)
|
|
||||||
- Fix boolean operators in menu for boards [Taiga #13174](https://tree.taiga.io/project/penpot/issue/13174)
|
|
||||||
|
|
||||||
## 2.13.0 (Unreleased)
|
## 2.13.0 (Unreleased)
|
||||||
|
|
||||||
### :boom: Breaking changes & Deprecations
|
### :boom: Breaking changes & Deprecations
|
||||||
@@ -40,48 +8,15 @@
|
|||||||
|
|
||||||
### :heart: Community contributions (Thank you!)
|
### :heart: Community contributions (Thank you!)
|
||||||
|
|
||||||
- Fix mask issues with component swap (by @dfelinto) [Github #7675](https://github.com/penpot/penpot/issues/7675)
|
|
||||||
|
|
||||||
### :sparkles: New features & Enhancements
|
### :sparkles: New features & Enhancements
|
||||||
|
|
||||||
- Add new Box Shadow Tokens [Taiga #10201](https://tree.taiga.io/project/penpot/us/10201)
|
|
||||||
- Make i18n translation files load on-demand [Taiga #11474](https://tree.taiga.io/project/penpot/us/11474)
|
|
||||||
- Add deleted files to dashboard [Taiga #8149](https://tree.taiga.io/project/penpot/us/8149)
|
|
||||||
|
|
||||||
### :bug: Bugs fixed
|
### :bug: Bugs fixed
|
||||||
|
|
||||||
- Fix problem when drag+duplicate a full grid [Taiga #12565](https://tree.taiga.io/project/penpot/issue/12565)
|
- Fix problem when drag+duplicate a full grid [Taiga #12565](https://tree.taiga.io/project/penpot/issue/12565)
|
||||||
- Fix problem when pasting elements in reverse flex layout [Taiga #12460](https://tree.taiga.io/project/penpot/issue/12460)
|
- Fix problem when pasting elements in reverse flex layout [Taiga #12460](https://tree.taiga.io/project/penpot/issue/12460)
|
||||||
- Fix wrong board size presets in Android [Taiga #12339](https://tree.taiga.io/project/penpot/issue/12339)
|
|
||||||
- Fix problem with grid layout components and auto sizing [Github #7797](https://github.com/penpot/penpot/issues/7797)
|
|
||||||
- Fix some alignments on inspect tab [Taiga #12915](https://tree.taiga.io/project/penpot/issue/12915)
|
|
||||||
- Fix problem with text editor maintaining previous styles [Taiga #12835](https://tree.taiga.io/project/penpot/issue/12835)
|
|
||||||
- Fix color assets from shared libraries not appearing as assets in Selected colors panel [Taiga #12957](https://tree.taiga.io/project/penpot/issue/12957)
|
|
||||||
- Fix CSS generated box-shadow property [Taiga #12997](https://tree.taiga.io/project/penpot/issue/12997)
|
|
||||||
- Fix inner shadow selector on shadow token [Taiga #12951](https://tree.taiga.io/project/penpot/issue/12951)
|
|
||||||
- Fix missing text color token from selected shapes in selected colors list [Taiga #12956](https://tree.taiga.io/project/penpot/issue/12956)
|
|
||||||
- Fix dropdown option width in Guides columns dropdown [Taiga #12959](https://tree.taiga.io/project/penpot/issue/12959)
|
|
||||||
- Fix typos on download modal [Taiga #12865](https://tree.taiga.io/project/penpot/issue/12865)
|
|
||||||
- Fix problem with text editor maintaining previous styles [Taiga #12835](https://tree.taiga.io/project/penpot/issue/12835)
|
|
||||||
- Fix unhandled exception tokens creation dialog [Github #8110](https://github.com/penpot/penpot/issues/8110)
|
|
||||||
- Fix allow negative spread values on shadow token creation [Taiga #13167](https://tree.taiga.io/project/penpot/issue/13167)
|
|
||||||
- Fix spanish translations on import export token modal [Taiga #13171](https://tree.taiga.io/project/penpot/issue/13171)
|
|
||||||
- Remove whitespaces from asset export filename [Github #8133](https://github.com/penpot/penpot/pull/8133)
|
|
||||||
- Fix exception on uploading large fonts [Github #8135](https://github.com/penpot/penpot/pull/8135)
|
|
||||||
- Fix unhandled exception on open-new-window helper [Github #7787](https://github.com/penpot/penpot/issues/7787)
|
|
||||||
- Fix incorrect handling of input values on layout gap and padding inputs [Github #8113](https://github.com/penpot/penpot/issues/8113)
|
|
||||||
- Fix several race conditions on path editor [Github #8187](https://github.com/penpot/penpot/pull/8187)
|
|
||||||
- Fix app freeze when introducing an error on a very long token name [Taiga #13214](https://tree.taiga.io/project/penpot/issue/13214)
|
|
||||||
|
|
||||||
## 2.12.1
|
|
||||||
|
|
||||||
### :bug: Bugs fixed
|
## 2.12.0 (Unreleased)
|
||||||
|
|
||||||
- Fix setting a portion of text as bold or underline messes things up [Github #7980](https://github.com/penpot/penpot/issues/7980)
|
|
||||||
- Fix problem with style in fonts input [Taiga #12935](https://tree.taiga.io/project/penpot/issue/12935)
|
|
||||||
- Fix problem with path editor and right click [Github #7917](https://github.com/penpot/penpot/issues/7917)
|
|
||||||
|
|
||||||
## 2.12.0
|
|
||||||
|
|
||||||
### :boom: Breaking changes & Deprecations
|
### :boom: Breaking changes & Deprecations
|
||||||
|
|
||||||
@@ -92,6 +27,7 @@ The backend RPC API URLS are changed from `/api/rpc/command/<name>` to
|
|||||||
compatibility; however, if you are a user of this API, it is strongly
|
compatibility; however, if you are a user of this API, it is strongly
|
||||||
recommended that you adapt your code to use the new PATH.
|
recommended that you adapt your code to use the new PATH.
|
||||||
|
|
||||||
|
|
||||||
#### Updated SSO Callback URL
|
#### Updated SSO Callback URL
|
||||||
|
|
||||||
The OAuth / Single Sign-On (SSO) callback endpoint has changed to
|
The OAuth / Single Sign-On (SSO) callback endpoint has changed to
|
||||||
@@ -124,6 +60,7 @@ This update standardizes all authentication flows under the single URL
|
|||||||
and makis it more modular, enabling the ability to configure SSO auth
|
and makis it more modular, enabling the ability to configure SSO auth
|
||||||
provider dinamically.
|
provider dinamically.
|
||||||
|
|
||||||
|
|
||||||
#### Changes on default docker compose
|
#### Changes on default docker compose
|
||||||
|
|
||||||
We have updated the `docker/images/docker-compose.yaml` with a small
|
We have updated the `docker/images/docker-compose.yaml` with a small
|
||||||
@@ -140,8 +77,6 @@ example. It's still usable as before, we just removed the example.
|
|||||||
### :heart: Community contributions (Thank you!)
|
### :heart: Community contributions (Thank you!)
|
||||||
|
|
||||||
- Ensure consistent snap behavior across all zoom levels [Github #7774](https://github.com/penpot/penpot/pull/7774) by [@Tokytome](https://github.com/Tokytome)
|
- Ensure consistent snap behavior across all zoom levels [Github #7774](https://github.com/penpot/penpot/pull/7774) by [@Tokytome](https://github.com/Tokytome)
|
||||||
- Fix crash in token grid view due to tooltip validation (by @dfelinto) [Github #7887](https://github.com/penpot/penpot/pull/7887)
|
|
||||||
- Enable Hindi translations on the application
|
|
||||||
|
|
||||||
### :sparkles: New features & Enhancements
|
### :sparkles: New features & Enhancements
|
||||||
|
|
||||||
@@ -169,13 +104,6 @@ example. It's still usable as before, we just removed the example.
|
|||||||
- Fix input confirmation behavior is not uniform [Taiga #12294](https://tree.taiga.io/project/penpot/issue/12294)
|
- Fix input confirmation behavior is not uniform [Taiga #12294](https://tree.taiga.io/project/penpot/issue/12294)
|
||||||
- Fix copy/pasting application/transit+json [Taiga #12721](https://tree.taiga.io/project/penpot/issue/12721)
|
- Fix copy/pasting application/transit+json [Taiga #12721](https://tree.taiga.io/project/penpot/issue/12721)
|
||||||
- Fix problem with plugins content attribute [Plugins #209](https://github.com/penpot/penpot-plugins/issues/209)
|
- Fix problem with plugins content attribute [Plugins #209](https://github.com/penpot/penpot-plugins/issues/209)
|
||||||
- Fix U and E icon displayed in project list [Taiga #12806](https://tree.taiga.io/project/penpot/issue/12806)
|
|
||||||
- Fix unpublish library modal not scrolling a long file list [Taiga #12285](https://tree.taiga.io/project/penpot/issue/12285)
|
|
||||||
- Fix incorrect interaction betwen hower and scroll on assets sidebar [Taiga #12389](https://tree.taiga.io/project/penpot/issue/12389)
|
|
||||||
- Fix switch variants with paths [Taiga #12841](https://tree.taiga.io/project/penpot/issue/12841)
|
|
||||||
- Fix referencing typography tokens on font-family tokens [Taiga #12492](https://tree.taiga.io/project/penpot/issue/12492)
|
|
||||||
- Fix horizontal scroll on layer panel [Taiga #12843](https://tree.taiga.io/project/penpot/issue/12843)
|
|
||||||
- Fix unicode handling on email template abbreviation filter [Github #7966](https://github.com/penpot/penpot/pull/7966)
|
|
||||||
|
|
||||||
## 2.11.1
|
## 2.11.1
|
||||||
|
|
||||||
@@ -187,6 +115,7 @@ example. It's still usable as before, we just removed the example.
|
|||||||
|
|
||||||
- Deprecated configuration variables with the prefix `PENPOT_ASSETS_*`, and will be
|
- Deprecated configuration variables with the prefix `PENPOT_ASSETS_*`, and will be
|
||||||
removed in future versions:
|
removed in future versions:
|
||||||
|
|
||||||
- The `PENPOT_ASSETS_STORAGE_BACKEND` becomes `PENPOT_OBJECTS_STORAGE_BACKEND` and its
|
- The `PENPOT_ASSETS_STORAGE_BACKEND` becomes `PENPOT_OBJECTS_STORAGE_BACKEND` and its
|
||||||
values passes from (`assets-fs` or `assets-s3`) to (`fs` or `s3`)
|
values passes from (`assets-fs` or `assets-s3`) to (`fs` or `s3`)
|
||||||
- The `PENPOT_STORAGE_ASSETS_FS_DIRECTORY` becomes `PENPOT_OBJECTS_STORAGE_FS_DIRECTORY`
|
- The `PENPOT_STORAGE_ASSETS_FS_DIRECTORY` becomes `PENPOT_OBJECTS_STORAGE_FS_DIRECTORY`
|
||||||
|
|||||||
@@ -120,12 +120,17 @@ them on your system, you can run them with:
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Check formatting
|
# Check formatting
|
||||||
./scripts/fmt
|
yarn fmt:clj:check
|
||||||
|
|
||||||
# Lint
|
# Check and fix formatting
|
||||||
./scripts/lint
|
yarn fmt:clj
|
||||||
|
|
||||||
|
# Run the linter
|
||||||
|
yarn lint:clj
|
||||||
```
|
```
|
||||||
|
|
||||||
|
There are more choices in `package.json`.
|
||||||
|
|
||||||
Ideally, you should run these commands as git pre-commit hooks. A convenient way
|
Ideally, you should run these commands as git pre-commit hooks. A convenient way
|
||||||
of defining them is to use [Husky](https://typicode.github.io/husky/#/).
|
of defining them is to use [Husky](https://typicode.github.io/husky/#/).
|
||||||
|
|
||||||
|
|||||||
7
backend/.gitignore
vendored
Normal file
7
backend/.gitignore
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
.pnp.*
|
||||||
|
.yarn/*
|
||||||
|
!.yarn/patches
|
||||||
|
!.yarn/plugins
|
||||||
|
!.yarn/releases
|
||||||
|
!.yarn/sdks
|
||||||
|
!.yarn/versions
|
||||||
@@ -97,8 +97,8 @@
|
|||||||
|
|
||||||
:jmx-remote
|
:jmx-remote
|
||||||
{:jvm-opts ["-Dcom.sun.management.jmxremote"
|
{:jvm-opts ["-Dcom.sun.management.jmxremote"
|
||||||
"-Dcom.sun.management.jmxremote.port=9000"
|
"-Dcom.sun.management.jmxremote.port=9090"
|
||||||
"-Dcom.sun.management.jmxremote.rmi.port=9000"
|
"-Dcom.sun.management.jmxremote.rmi.port=9090"
|
||||||
"-Dcom.sun.management.jmxremote.local.only=false"
|
"-Dcom.sun.management.jmxremote.local.only=false"
|
||||||
"-Dcom.sun.management.jmxremote.authenticate=false"
|
"-Dcom.sun.management.jmxremote.authenticate=false"
|
||||||
"-Dcom.sun.management.jmxremote.ssl=false"
|
"-Dcom.sun.management.jmxremote.ssl=false"
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"author": "Kaleidos INC",
|
"author": "Kaleidos INC",
|
||||||
"private": true,
|
"private": true,
|
||||||
"packageManager": "pnpm@10.26.2+sha512.0e308ff2005fc7410366f154f625f6631ab2b16b1d2e70238444dd6ae9d630a8482d92a451144debc492416896ed16f7b114a86ec68b8404b2443869e68ffda6",
|
"packageManager": "yarn@4.9.2+sha512.1fc009bc09d13cfd0e19efa44cbfc2b9cf6ca61482725eb35bbc5e257e093ebf4130db6dfe15d604ff4b79efd8e1e8e99b25fa7d0a6197c9f9826358d4d65c3c",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/penpot/penpot"
|
"url": "https://github.com/penpot/penpot"
|
||||||
|
|||||||
306
backend/pnpm-lock.yaml
generated
306
backend/pnpm-lock.yaml
generated
@@ -1,306 +0,0 @@
|
|||||||
lockfileVersion: '9.0'
|
|
||||||
|
|
||||||
settings:
|
|
||||||
autoInstallPeers: true
|
|
||||||
excludeLinksFromLockfile: false
|
|
||||||
|
|
||||||
importers:
|
|
||||||
|
|
||||||
.:
|
|
||||||
dependencies:
|
|
||||||
luxon:
|
|
||||||
specifier: ^3.4.4
|
|
||||||
version: 3.7.2
|
|
||||||
sax:
|
|
||||||
specifier: ^1.4.1
|
|
||||||
version: 1.4.3
|
|
||||||
devDependencies:
|
|
||||||
nodemon:
|
|
||||||
specifier: ^3.1.2
|
|
||||||
version: 3.1.11
|
|
||||||
source-map-support:
|
|
||||||
specifier: ^0.5.21
|
|
||||||
version: 0.5.21
|
|
||||||
ws:
|
|
||||||
specifier: ^8.17.0
|
|
||||||
version: 8.18.3
|
|
||||||
|
|
||||||
packages:
|
|
||||||
|
|
||||||
anymatch@3.1.3:
|
|
||||||
resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==}
|
|
||||||
engines: {node: '>= 8'}
|
|
||||||
|
|
||||||
balanced-match@1.0.2:
|
|
||||||
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
|
|
||||||
|
|
||||||
binary-extensions@2.3.0:
|
|
||||||
resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==}
|
|
||||||
engines: {node: '>=8'}
|
|
||||||
|
|
||||||
brace-expansion@1.1.12:
|
|
||||||
resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==}
|
|
||||||
|
|
||||||
braces@3.0.3:
|
|
||||||
resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==}
|
|
||||||
engines: {node: '>=8'}
|
|
||||||
|
|
||||||
buffer-from@1.1.2:
|
|
||||||
resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
|
|
||||||
|
|
||||||
chokidar@3.6.0:
|
|
||||||
resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==}
|
|
||||||
engines: {node: '>= 8.10.0'}
|
|
||||||
|
|
||||||
concat-map@0.0.1:
|
|
||||||
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
|
|
||||||
|
|
||||||
debug@4.4.3:
|
|
||||||
resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==}
|
|
||||||
engines: {node: '>=6.0'}
|
|
||||||
peerDependencies:
|
|
||||||
supports-color: '*'
|
|
||||||
peerDependenciesMeta:
|
|
||||||
supports-color:
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
fill-range@7.1.1:
|
|
||||||
resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
|
|
||||||
engines: {node: '>=8'}
|
|
||||||
|
|
||||||
fsevents@2.3.3:
|
|
||||||
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
|
|
||||||
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
|
|
||||||
os: [darwin]
|
|
||||||
|
|
||||||
glob-parent@5.1.2:
|
|
||||||
resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
|
|
||||||
engines: {node: '>= 6'}
|
|
||||||
|
|
||||||
has-flag@3.0.0:
|
|
||||||
resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==}
|
|
||||||
engines: {node: '>=4'}
|
|
||||||
|
|
||||||
ignore-by-default@1.0.1:
|
|
||||||
resolution: {integrity: sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==}
|
|
||||||
|
|
||||||
is-binary-path@2.1.0:
|
|
||||||
resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==}
|
|
||||||
engines: {node: '>=8'}
|
|
||||||
|
|
||||||
is-extglob@2.1.1:
|
|
||||||
resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
|
|
||||||
engines: {node: '>=0.10.0'}
|
|
||||||
|
|
||||||
is-glob@4.0.3:
|
|
||||||
resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
|
|
||||||
engines: {node: '>=0.10.0'}
|
|
||||||
|
|
||||||
is-number@7.0.0:
|
|
||||||
resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
|
|
||||||
engines: {node: '>=0.12.0'}
|
|
||||||
|
|
||||||
luxon@3.7.2:
|
|
||||||
resolution: {integrity: sha512-vtEhXh/gNjI9Yg1u4jX/0YVPMvxzHuGgCm6tC5kZyb08yjGWGnqAjGJvcXbqQR2P3MyMEFnRbpcdFS6PBcLqew==}
|
|
||||||
engines: {node: '>=12'}
|
|
||||||
|
|
||||||
minimatch@3.1.2:
|
|
||||||
resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
|
|
||||||
|
|
||||||
ms@2.1.3:
|
|
||||||
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
|
|
||||||
|
|
||||||
nodemon@3.1.11:
|
|
||||||
resolution: {integrity: sha512-is96t8F/1//UHAjNPHpbsNY46ELPpftGUoSVNXwUfMk/qdjSylYrWSu1XavVTBOn526kFiOR733ATgNBCQyH0g==}
|
|
||||||
engines: {node: '>=10'}
|
|
||||||
hasBin: true
|
|
||||||
|
|
||||||
normalize-path@3.0.0:
|
|
||||||
resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
|
|
||||||
engines: {node: '>=0.10.0'}
|
|
||||||
|
|
||||||
picomatch@2.3.1:
|
|
||||||
resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
|
|
||||||
engines: {node: '>=8.6'}
|
|
||||||
|
|
||||||
pstree.remy@1.1.8:
|
|
||||||
resolution: {integrity: sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==}
|
|
||||||
|
|
||||||
readdirp@3.6.0:
|
|
||||||
resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
|
|
||||||
engines: {node: '>=8.10.0'}
|
|
||||||
|
|
||||||
sax@1.4.3:
|
|
||||||
resolution: {integrity: sha512-yqYn1JhPczigF94DMS+shiDMjDowYO6y9+wB/4WgO0Y19jWYk0lQ4tuG5KI7kj4FTp1wxPj5IFfcrz/s1c3jjQ==}
|
|
||||||
|
|
||||||
semver@7.7.3:
|
|
||||||
resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==}
|
|
||||||
engines: {node: '>=10'}
|
|
||||||
hasBin: true
|
|
||||||
|
|
||||||
simple-update-notifier@2.0.0:
|
|
||||||
resolution: {integrity: sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==}
|
|
||||||
engines: {node: '>=10'}
|
|
||||||
|
|
||||||
source-map-support@0.5.21:
|
|
||||||
resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==}
|
|
||||||
|
|
||||||
source-map@0.6.1:
|
|
||||||
resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==}
|
|
||||||
engines: {node: '>=0.10.0'}
|
|
||||||
|
|
||||||
supports-color@5.5.0:
|
|
||||||
resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==}
|
|
||||||
engines: {node: '>=4'}
|
|
||||||
|
|
||||||
to-regex-range@5.0.1:
|
|
||||||
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
|
|
||||||
engines: {node: '>=8.0'}
|
|
||||||
|
|
||||||
touch@3.1.1:
|
|
||||||
resolution: {integrity: sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==}
|
|
||||||
hasBin: true
|
|
||||||
|
|
||||||
undefsafe@2.0.5:
|
|
||||||
resolution: {integrity: sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==}
|
|
||||||
|
|
||||||
ws@8.18.3:
|
|
||||||
resolution: {integrity: sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==}
|
|
||||||
engines: {node: '>=10.0.0'}
|
|
||||||
peerDependencies:
|
|
||||||
bufferutil: ^4.0.1
|
|
||||||
utf-8-validate: '>=5.0.2'
|
|
||||||
peerDependenciesMeta:
|
|
||||||
bufferutil:
|
|
||||||
optional: true
|
|
||||||
utf-8-validate:
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
snapshots:
|
|
||||||
|
|
||||||
anymatch@3.1.3:
|
|
||||||
dependencies:
|
|
||||||
normalize-path: 3.0.0
|
|
||||||
picomatch: 2.3.1
|
|
||||||
|
|
||||||
balanced-match@1.0.2: {}
|
|
||||||
|
|
||||||
binary-extensions@2.3.0: {}
|
|
||||||
|
|
||||||
brace-expansion@1.1.12:
|
|
||||||
dependencies:
|
|
||||||
balanced-match: 1.0.2
|
|
||||||
concat-map: 0.0.1
|
|
||||||
|
|
||||||
braces@3.0.3:
|
|
||||||
dependencies:
|
|
||||||
fill-range: 7.1.1
|
|
||||||
|
|
||||||
buffer-from@1.1.2: {}
|
|
||||||
|
|
||||||
chokidar@3.6.0:
|
|
||||||
dependencies:
|
|
||||||
anymatch: 3.1.3
|
|
||||||
braces: 3.0.3
|
|
||||||
glob-parent: 5.1.2
|
|
||||||
is-binary-path: 2.1.0
|
|
||||||
is-glob: 4.0.3
|
|
||||||
normalize-path: 3.0.0
|
|
||||||
readdirp: 3.6.0
|
|
||||||
optionalDependencies:
|
|
||||||
fsevents: 2.3.3
|
|
||||||
|
|
||||||
concat-map@0.0.1: {}
|
|
||||||
|
|
||||||
debug@4.4.3(supports-color@5.5.0):
|
|
||||||
dependencies:
|
|
||||||
ms: 2.1.3
|
|
||||||
optionalDependencies:
|
|
||||||
supports-color: 5.5.0
|
|
||||||
|
|
||||||
fill-range@7.1.1:
|
|
||||||
dependencies:
|
|
||||||
to-regex-range: 5.0.1
|
|
||||||
|
|
||||||
fsevents@2.3.3:
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
glob-parent@5.1.2:
|
|
||||||
dependencies:
|
|
||||||
is-glob: 4.0.3
|
|
||||||
|
|
||||||
has-flag@3.0.0: {}
|
|
||||||
|
|
||||||
ignore-by-default@1.0.1: {}
|
|
||||||
|
|
||||||
is-binary-path@2.1.0:
|
|
||||||
dependencies:
|
|
||||||
binary-extensions: 2.3.0
|
|
||||||
|
|
||||||
is-extglob@2.1.1: {}
|
|
||||||
|
|
||||||
is-glob@4.0.3:
|
|
||||||
dependencies:
|
|
||||||
is-extglob: 2.1.1
|
|
||||||
|
|
||||||
is-number@7.0.0: {}
|
|
||||||
|
|
||||||
luxon@3.7.2: {}
|
|
||||||
|
|
||||||
minimatch@3.1.2:
|
|
||||||
dependencies:
|
|
||||||
brace-expansion: 1.1.12
|
|
||||||
|
|
||||||
ms@2.1.3: {}
|
|
||||||
|
|
||||||
nodemon@3.1.11:
|
|
||||||
dependencies:
|
|
||||||
chokidar: 3.6.0
|
|
||||||
debug: 4.4.3(supports-color@5.5.0)
|
|
||||||
ignore-by-default: 1.0.1
|
|
||||||
minimatch: 3.1.2
|
|
||||||
pstree.remy: 1.1.8
|
|
||||||
semver: 7.7.3
|
|
||||||
simple-update-notifier: 2.0.0
|
|
||||||
supports-color: 5.5.0
|
|
||||||
touch: 3.1.1
|
|
||||||
undefsafe: 2.0.5
|
|
||||||
|
|
||||||
normalize-path@3.0.0: {}
|
|
||||||
|
|
||||||
picomatch@2.3.1: {}
|
|
||||||
|
|
||||||
pstree.remy@1.1.8: {}
|
|
||||||
|
|
||||||
readdirp@3.6.0:
|
|
||||||
dependencies:
|
|
||||||
picomatch: 2.3.1
|
|
||||||
|
|
||||||
sax@1.4.3: {}
|
|
||||||
|
|
||||||
semver@7.7.3: {}
|
|
||||||
|
|
||||||
simple-update-notifier@2.0.0:
|
|
||||||
dependencies:
|
|
||||||
semver: 7.7.3
|
|
||||||
|
|
||||||
source-map-support@0.5.21:
|
|
||||||
dependencies:
|
|
||||||
buffer-from: 1.1.2
|
|
||||||
source-map: 0.6.1
|
|
||||||
|
|
||||||
source-map@0.6.1: {}
|
|
||||||
|
|
||||||
supports-color@5.5.0:
|
|
||||||
dependencies:
|
|
||||||
has-flag: 3.0.0
|
|
||||||
|
|
||||||
to-regex-range@5.0.1:
|
|
||||||
dependencies:
|
|
||||||
is-number: 7.0.0
|
|
||||||
|
|
||||||
touch@3.1.1: {}
|
|
||||||
|
|
||||||
undefsafe@2.0.5: {}
|
|
||||||
|
|
||||||
ws@8.18.3: {}
|
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
:file-uri "https://github.com/penpot/penpot-files/raw/refs/heads/main/Tokens%20starter%20kit.penpot"}
|
:file-uri "https://github.com/penpot/penpot-files/raw/refs/heads/main/Tokens%20starter%20kit.penpot"}
|
||||||
{:id "penpot-design-system"
|
{:id "penpot-design-system"
|
||||||
:name "Penpot Design System | Pencil"
|
:name "Penpot Design System | Pencil"
|
||||||
:file-uri "https://github.com/penpot/penpot-files/raw/refs/heads/main/Pencil-Penpot-Design-System.penpot"}
|
:file-uri "https://github.com/penpot/penpot-files/raw/refs/heads/main/penpot-app.penpot"}
|
||||||
{:id "wireframing-kit"
|
{:id "wireframing-kit"
|
||||||
:name "Wireframe library"
|
:name "Wireframe library"
|
||||||
:file-uri "https://github.com/penpot/penpot-files/raw/refs/heads/main/Wireframing%20kit%20v1.1.penpot"}
|
:file-uri "https://github.com/penpot/penpot-files/raw/refs/heads/main/Wireframing%20kit%20v1.1.penpot"}
|
||||||
|
|||||||
@@ -12,22 +12,43 @@ Debug Main Page
|
|||||||
</nav>
|
</nav>
|
||||||
<main class="dashboard">
|
<main class="dashboard">
|
||||||
<section class="widget">
|
<section class="widget">
|
||||||
|
<fieldset>
|
||||||
|
<legend>Error reports</legend>
|
||||||
|
<desc><a href="/dbg/error">CLICK HERE TO SEE THE ERROR REPORTS</a> </desc>
|
||||||
|
</fieldset>
|
||||||
|
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<legend>CURRENT PROFILE</legend>
|
<legend>Profile Management</legend>
|
||||||
<desc>
|
<form method="post" action="/dbg/actions/resend-email-verification">
|
||||||
<p>
|
<div class="row">
|
||||||
Name: <b>{{profile.fullname}}</b> <br />
|
<input type="email" name="email" placeholder="example@example.com" value="" />
|
||||||
Email: <b>{{profile.email}}</b>
|
</div>
|
||||||
</p>
|
|
||||||
</desc>
|
<div class="row">
|
||||||
|
<label for="force-verify">Are you sure?</label>
|
||||||
|
<input id="force-verify" type="checkbox" name="force" />
|
||||||
|
<br />
|
||||||
|
<small>
|
||||||
|
This is a just a security double check for prevent non intentional submits.
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<input type="submit" name="resend" value="Resend Verification" />
|
||||||
|
<input type="submit" name="verify" value="Verify" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<input type="submit" class="danger" name="block" value="Block" />
|
||||||
|
<input type="submit" class="danger" name="unblock" value="Unblock" />
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<legend>VIRTUAL CLOCK</legend>
|
<legend>VIRTUAL CLOCK</legend>
|
||||||
|
|
||||||
<desc>
|
<desc>
|
||||||
<p><b>IMPORTANT:</b> The virtual clock is profile based and only affects the currently logged-in profile.</p>
|
|
||||||
<p>
|
<p>
|
||||||
CURRENT CLOCK: <b>{{current-clock}}</b>
|
CURRENT CLOCK: <b>{{current-clock}}</b>
|
||||||
<br />
|
<br />
|
||||||
@@ -60,93 +81,8 @@ Debug Main Page
|
|||||||
</form>
|
</form>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|
||||||
<fieldset>
|
|
||||||
<legend>ERROR REPORTS</legend>
|
|
||||||
<desc><a href="/dbg/error">CLICK HERE TO SEE THE ERROR REPORTS</a> </desc>
|
|
||||||
</fieldset>
|
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|
||||||
<section class="widget">
|
|
||||||
<fieldset>
|
|
||||||
<legend>Profile Management</legend>
|
|
||||||
<form method="post" action="/dbg/actions/resend-email-verification">
|
|
||||||
<div class="row">
|
|
||||||
<input type="email" name="email" placeholder="example@example.com" value="" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
<label for="force-verify">Are you sure?</label>
|
|
||||||
<input id="force-verify" type="checkbox" name="force" />
|
|
||||||
<br />
|
|
||||||
<small>
|
|
||||||
This is a just a security double check for prevent non intentional submits.
|
|
||||||
</small>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
<input type="submit" name="resend" value="Resend Verification" />
|
|
||||||
<input type="submit" name="verify" value="Verify" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
<input type="submit" class="danger" name="block" value="Block" />
|
|
||||||
<input type="submit" class="danger" name="unblock" value="Unblock" />
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</fieldset>
|
|
||||||
|
|
||||||
|
|
||||||
<fieldset>
|
|
||||||
<legend>Feature Flags for Team</legend>
|
|
||||||
<desc>Add a feature flag to a team</desc>
|
|
||||||
<form method="post" action="/dbg/actions/handle-team-features">
|
|
||||||
<div class="row">
|
|
||||||
<input type="text" style="width:300px" name="team-id" placeholder="team-id" />
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<select type="text" style="width:100px" name="feature">
|
|
||||||
{% for feature in supported-features %}
|
|
||||||
<option value="{{feature}}">{{feature}}</option>
|
|
||||||
{% endfor %}
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
<select style="width:100px" name="action">
|
|
||||||
<option value="">Action...</option>
|
|
||||||
<option value="show">Show</option>
|
|
||||||
<option value="enable">Enable</option>
|
|
||||||
<option value="disable">Disable</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
<label for="check-feature">Skip feature check</label>
|
|
||||||
<input id="check-feature" type="checkbox" name="skip-check" />
|
|
||||||
<br />
|
|
||||||
<small>
|
|
||||||
Do not check if the feature is supported
|
|
||||||
</small>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
<label for="force-version">Are you sure?</label>
|
|
||||||
<input id="force-version" type="checkbox" name="force" />
|
|
||||||
<br />
|
|
||||||
<small>
|
|
||||||
This is a just a security double check for prevent non intentional submits.
|
|
||||||
</small>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row">
|
|
||||||
<input type="submit" value="Submit" />
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</fieldset>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
|
|
||||||
<section class="widget">
|
<section class="widget">
|
||||||
|
|
||||||
<fieldset>
|
<fieldset>
|
||||||
@@ -237,5 +173,55 @@ Debug Main Page
|
|||||||
</form>
|
</form>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<section class="widget">
|
||||||
|
<fieldset>
|
||||||
|
<legend>Feature Flags for Team</legend>
|
||||||
|
<desc>Add a feature flag to a team</desc>
|
||||||
|
<form method="post" action="/dbg/actions/handle-team-features">
|
||||||
|
<div class="row">
|
||||||
|
<input type="text" style="width:300px" name="team-id" placeholder="team-id" />
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<select type="text" style="width:100px" name="feature">
|
||||||
|
{% for feature in supported-features %}
|
||||||
|
<option value="{{feature}}">{{feature}}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<select style="width:100px" name="action">
|
||||||
|
<option value="">Action...</option>
|
||||||
|
<option value="show">Show</option>
|
||||||
|
<option value="enable">Enable</option>
|
||||||
|
<option value="disable">Disable</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<label for="check-feature">Skip feature check</label>
|
||||||
|
<input id="check-feature" type="checkbox" name="skip-check" />
|
||||||
|
<br />
|
||||||
|
<small>
|
||||||
|
Do not check if the feature is supported
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<label for="force-version">Are you sure?</label>
|
||||||
|
<input id="force-version" type="checkbox" name="force" />
|
||||||
|
<br />
|
||||||
|
<small>
|
||||||
|
This is a just a security double check for prevent non intentional submits.
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<input type="submit" value="Submit" />
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</fieldset>
|
||||||
|
</section>
|
||||||
</main>
|
</main>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
@@ -1,14 +1,8 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
export PENPOT_NITRATE_SHARED_KEY=super-secret-nitrate-api-key
|
|
||||||
export PENPOT_EXPORTER_SHARED_KEY=super-secret-exporter-api-key
|
|
||||||
export PENPOT_SECRET_KEY=super-secret-devenv-key
|
|
||||||
|
|
||||||
# DEPRECATED: only used for subscriptions
|
|
||||||
export PENPOT_MANAGEMENT_API_KEY=super-secret-management-api-key
|
export PENPOT_MANAGEMENT_API_KEY=super-secret-management-api-key
|
||||||
|
export PENPOT_SECRET_KEY=super-secret-devenv-key
|
||||||
export PENPOT_HOST=devenv
|
export PENPOT_HOST=devenv
|
||||||
export PENPOT_PUBLIC_URI=https://localhost:3449
|
|
||||||
|
|
||||||
export PENPOT_FLAGS="\
|
export PENPOT_FLAGS="\
|
||||||
$PENPOT_FLAGS \
|
$PENPOT_FLAGS \
|
||||||
@@ -18,7 +12,6 @@ export PENPOT_FLAGS="\
|
|||||||
disable-login-with-google \
|
disable-login-with-google \
|
||||||
disable-login-with-github \
|
disable-login-with-github \
|
||||||
disable-login-with-gitlab \
|
disable-login-with-gitlab \
|
||||||
disable-telemetry \
|
|
||||||
enable-backend-worker \
|
enable-backend-worker \
|
||||||
enable-backend-asserts \
|
enable-backend-asserts \
|
||||||
disable-feature-fdata-pointer-map \
|
disable-feature-fdata-pointer-map \
|
||||||
@@ -61,8 +54,6 @@ export PENPOT_OBJECTS_STORAGE_BACKEND=s3
|
|||||||
export PENPOT_OBJECTS_STORAGE_S3_ENDPOINT=http://minio:9000
|
export PENPOT_OBJECTS_STORAGE_S3_ENDPOINT=http://minio:9000
|
||||||
export PENPOT_OBJECTS_STORAGE_S3_BUCKET=penpot
|
export PENPOT_OBJECTS_STORAGE_S3_BUCKET=penpot
|
||||||
|
|
||||||
export PENPOT_NITRATE_BACKEND_URI=http://localhost:3000/control-center
|
|
||||||
|
|
||||||
export JAVA_OPTS="\
|
export JAVA_OPTS="\
|
||||||
-Djava.util.logging.manager=org.apache.logging.log4j.jul.LogManager \
|
-Djava.util.logging.manager=org.apache.logging.log4j.jul.LogManager \
|
||||||
-Djdk.attach.allowAttachSelf \
|
-Djdk.attach.allowAttachSelf \
|
||||||
|
|||||||
@@ -3,10 +3,6 @@
|
|||||||
SCRIPT_DIR=$(dirname $0);
|
SCRIPT_DIR=$(dirname $0);
|
||||||
source $SCRIPT_DIR/_env;
|
source $SCRIPT_DIR/_env;
|
||||||
|
|
||||||
if [ -f $SCRIPT_DIR/_env.local ]; then
|
|
||||||
source $SCRIPT_DIR/_env.local;
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Initialize MINIO config
|
# Initialize MINIO config
|
||||||
setup_minio;
|
setup_minio;
|
||||||
|
|
||||||
|
|||||||
@@ -3,11 +3,6 @@
|
|||||||
SCRIPT_DIR=$(dirname $0);
|
SCRIPT_DIR=$(dirname $0);
|
||||||
|
|
||||||
source $SCRIPT_DIR/_env;
|
source $SCRIPT_DIR/_env;
|
||||||
|
|
||||||
if [ -f $SCRIPT_DIR/_env.local ]; then
|
|
||||||
source $SCRIPT_DIR/_env.local;
|
|
||||||
fi
|
|
||||||
|
|
||||||
export OPTIONS="-A:dev"
|
export OPTIONS="-A:dev"
|
||||||
|
|
||||||
entrypoint=${1:-app.main};
|
entrypoint=${1:-app.main};
|
||||||
|
|||||||
@@ -3,10 +3,6 @@
|
|||||||
SCRIPT_DIR=$(dirname $0);
|
SCRIPT_DIR=$(dirname $0);
|
||||||
source $SCRIPT_DIR/_env;
|
source $SCRIPT_DIR/_env;
|
||||||
|
|
||||||
if [ -f $SCRIPT_DIR/_env.local ]; then
|
|
||||||
source $SCRIPT_DIR/_env.local;
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Initialize MINIO config
|
# Initialize MINIO config
|
||||||
setup_minio;
|
setup_minio;
|
||||||
|
|
||||||
|
|||||||
@@ -36,6 +36,17 @@
|
|||||||
[integrant.core :as ig]
|
[integrant.core :as ig]
|
||||||
[yetti.response :as-alias yres]))
|
[yetti.response :as-alias yres]))
|
||||||
|
|
||||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
;; HELPERS
|
||||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
|
||||||
|
(defn obfuscate-string
|
||||||
|
[s]
|
||||||
|
(if (< (count s) 10)
|
||||||
|
(apply str (take (count s) (repeat "*")))
|
||||||
|
(str (subs s 0 5)
|
||||||
|
(apply str (take (- (count s) 5) (repeat "*"))))))
|
||||||
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
;; OIDC PROVIDER (GENERIC)
|
;; OIDC PROVIDER (GENERIC)
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
@@ -166,7 +177,7 @@
|
|||||||
(l/inf :hint "provider initialized"
|
(l/inf :hint "provider initialized"
|
||||||
:provider (:id provider)
|
:provider (:id provider)
|
||||||
:client-id (:client-id provider)
|
:client-id (:client-id provider)
|
||||||
:client-secret (d/obfuscate-string (:client-secret provider)))
|
:client-secret (obfuscate-string (:client-secret provider)))
|
||||||
provider)
|
provider)
|
||||||
|
|
||||||
(catch Throwable cause
|
(catch Throwable cause
|
||||||
@@ -211,7 +222,7 @@
|
|||||||
(l/inf :hint "provider initialized"
|
(l/inf :hint "provider initialized"
|
||||||
:provider (:id provider)
|
:provider (:id provider)
|
||||||
:client-id (:client-id provider)
|
:client-id (:client-id provider)
|
||||||
:client-secret (d/obfuscate-string (:client-secret provider)))
|
:client-secret (obfuscate-string (:client-secret provider)))
|
||||||
provider)
|
provider)
|
||||||
|
|
||||||
(catch Throwable cause
|
(catch Throwable cause
|
||||||
@@ -288,7 +299,7 @@
|
|||||||
(l/inf :hint "provider initialized"
|
(l/inf :hint "provider initialized"
|
||||||
:provider (:id provider)
|
:provider (:id provider)
|
||||||
:client-id (:client-id provider)
|
:client-id (:client-id provider)
|
||||||
:client-secret (d/obfuscate-string (:client-secret provider)))
|
:client-secret (obfuscate-string (:client-secret provider)))
|
||||||
provider)
|
provider)
|
||||||
|
|
||||||
(catch Throwable cause
|
(catch Throwable cause
|
||||||
@@ -330,7 +341,7 @@
|
|||||||
:provider "gitlab"
|
:provider "gitlab"
|
||||||
:base-uri (:base-uri provider)
|
:base-uri (:base-uri provider)
|
||||||
:client-id (:client-id provider)
|
:client-id (:client-id provider)
|
||||||
:client-secret (d/obfuscate-string (:client-secret provider)))
|
:client-secret (obfuscate-string (:client-secret provider)))
|
||||||
provider)
|
provider)
|
||||||
(catch Throwable cause
|
(catch Throwable cause
|
||||||
(ex/raise :type ::internal
|
(ex/raise :type ::internal
|
||||||
@@ -350,7 +361,7 @@
|
|||||||
(l/inf :hint "provider initialized"
|
(l/inf :hint "provider initialized"
|
||||||
:provider (:id provider)
|
:provider (:id provider)
|
||||||
:client-id (:client-id provider)
|
:client-id (:client-id provider)
|
||||||
:client-secret (d/obfuscate-string (:client-secret provider)))
|
:client-secret (obfuscate-string (:client-secret provider)))
|
||||||
provider)
|
provider)
|
||||||
|
|
||||||
(catch Throwable cause
|
(catch Throwable cause
|
||||||
@@ -448,7 +459,7 @@
|
|||||||
(l/trc :hint "fetch access token"
|
(l/trc :hint "fetch access token"
|
||||||
:provider (:id provider)
|
:provider (:id provider)
|
||||||
:client-id (:client-id provider)
|
:client-id (:client-id provider)
|
||||||
:client-secret (d/obfuscate-string (:client-secret provider))
|
:client-secret (obfuscate-string (:client-secret provider))
|
||||||
:grant-type (:grant_type params)
|
:grant-type (:grant_type params)
|
||||||
:redirect-uri (:redirect_uri params))
|
:redirect-uri (:redirect_uri params))
|
||||||
|
|
||||||
@@ -501,7 +512,7 @@
|
|||||||
[cfg provider tdata]
|
[cfg provider tdata]
|
||||||
(l/trc :hint "fetch user info"
|
(l/trc :hint "fetch user info"
|
||||||
:uri (:user-uri provider)
|
:uri (:user-uri provider)
|
||||||
:token (d/obfuscate-string (:token/access tdata)))
|
:token (obfuscate-string (:token/access tdata)))
|
||||||
|
|
||||||
(let [params {:uri (:user-uri provider)
|
(let [params {:uri (:user-uri provider)
|
||||||
:headers {"Authorization" (str (:token/type tdata) " " (:token/access tdata))}
|
:headers {"Authorization" (str (:token/type tdata) " " (:token/access tdata))}
|
||||||
|
|||||||
@@ -331,81 +331,6 @@
|
|||||||
(set/difference cfeat/backend-only-features))
|
(set/difference cfeat/backend-only-features))
|
||||||
#{}))))
|
#{}))))
|
||||||
|
|
||||||
(defn check-file-exists
|
|
||||||
[cfg id & {:keys [include-deleted?]
|
|
||||||
:or {include-deleted? false}
|
|
||||||
:as options}]
|
|
||||||
(db/get-with-sql cfg [sql:get-minimal-file id]
|
|
||||||
{:db/remove-deleted (not include-deleted?)}))
|
|
||||||
|
|
||||||
(def ^:private sql:file-permissions
|
|
||||||
"select fpr.is_owner,
|
|
||||||
fpr.is_admin,
|
|
||||||
fpr.can_edit
|
|
||||||
from file_profile_rel as fpr
|
|
||||||
inner join file as f on (f.id = fpr.file_id)
|
|
||||||
where fpr.file_id = ?
|
|
||||||
and fpr.profile_id = ?
|
|
||||||
union all
|
|
||||||
select tpr.is_owner,
|
|
||||||
tpr.is_admin,
|
|
||||||
tpr.can_edit
|
|
||||||
from team_profile_rel as tpr
|
|
||||||
inner join project as p on (p.team_id = tpr.team_id)
|
|
||||||
inner join file as f on (p.id = f.project_id)
|
|
||||||
where f.id = ?
|
|
||||||
and tpr.profile_id = ?
|
|
||||||
union all
|
|
||||||
select ppr.is_owner,
|
|
||||||
ppr.is_admin,
|
|
||||||
ppr.can_edit
|
|
||||||
from project_profile_rel as ppr
|
|
||||||
inner join file as f on (f.project_id = ppr.project_id)
|
|
||||||
where f.id = ?
|
|
||||||
and ppr.profile_id = ?")
|
|
||||||
|
|
||||||
(defn- get-file-permissions*
|
|
||||||
[conn profile-id file-id]
|
|
||||||
(when (and profile-id file-id)
|
|
||||||
(db/exec! conn [sql:file-permissions
|
|
||||||
file-id profile-id
|
|
||||||
file-id profile-id
|
|
||||||
file-id profile-id])))
|
|
||||||
|
|
||||||
(defn get-file-permissions
|
|
||||||
([conn profile-id file-id]
|
|
||||||
(let [rows (get-file-permissions* conn profile-id file-id)
|
|
||||||
is-owner (boolean (some :is-owner rows))
|
|
||||||
is-admin (boolean (some :is-admin rows))
|
|
||||||
can-edit (boolean (some :can-edit rows))]
|
|
||||||
(when (seq rows)
|
|
||||||
{:type :membership
|
|
||||||
:is-owner is-owner
|
|
||||||
:is-admin (or is-owner is-admin)
|
|
||||||
:can-edit (or is-owner is-admin can-edit)
|
|
||||||
:can-read true
|
|
||||||
:is-logged (some? profile-id)})))
|
|
||||||
|
|
||||||
([conn profile-id file-id share-id]
|
|
||||||
(let [perms (get-file-permissions conn profile-id file-id)
|
|
||||||
ldata (some-> (db/get* conn :share-link {:id share-id :file-id file-id})
|
|
||||||
(dissoc :flags)
|
|
||||||
(update :pages db/decode-pgarray #{}))]
|
|
||||||
|
|
||||||
;; NOTE: in a future when share-link becomes more powerful and
|
|
||||||
;; will allow us specify which parts of the app is available, we
|
|
||||||
;; will probably need to tweak this function in order to expose
|
|
||||||
;; this flags to the frontend.
|
|
||||||
(cond
|
|
||||||
(some? perms) perms
|
|
||||||
(some? ldata) {:type :share-link
|
|
||||||
:can-read true
|
|
||||||
:pages (:pages ldata)
|
|
||||||
:is-logged (some? profile-id)
|
|
||||||
:who-comment (:who-comment ldata)
|
|
||||||
:who-inspect (:who-inspect ldata)}))))
|
|
||||||
|
|
||||||
|
|
||||||
(defn get-project
|
(defn get-project
|
||||||
[cfg project-id]
|
[cfg project-id]
|
||||||
(db/get cfg :project {:id project-id}))
|
(db/get cfg :project {:id project-id}))
|
||||||
|
|||||||
@@ -821,10 +821,9 @@
|
|||||||
entries (keep (match-storage-entry-fn) entries)]
|
entries (keep (match-storage-entry-fn) entries)]
|
||||||
|
|
||||||
(doseq [{:keys [id entry]} entries]
|
(doseq [{:keys [id entry]} entries]
|
||||||
(let [object (-> (read-entry input entry)
|
(let [object (->> (read-entry input entry)
|
||||||
(decode-storage-object)
|
(decode-storage-object)
|
||||||
(update :bucket d/nilv sto/default-bucket)
|
(validate-storage-object))
|
||||||
(validate-storage-object))
|
|
||||||
|
|
||||||
ext (cmedia/mtype->extension (:content-type object))
|
ext (cmedia/mtype->extension (:content-type object))
|
||||||
path (str "objects/" id ext)
|
path (str "objects/" id ext)
|
||||||
@@ -873,8 +872,11 @@
|
|||||||
(import-storage-objects cfg)
|
(import-storage-objects cfg)
|
||||||
|
|
||||||
(let [files (get manifest :files)
|
(let [files (get manifest :files)
|
||||||
result (reduce (fn [result file]
|
result (reduce (fn [result {:keys [id] :as file}]
|
||||||
(let [name' (get file :name)
|
(let [name' (get file :name)
|
||||||
|
name' (if (map? name)
|
||||||
|
(get name id)
|
||||||
|
name')
|
||||||
file (assoc file :name name')]
|
file (assoc file :name name')]
|
||||||
(conj result (import-file cfg file))))
|
(conj result (import-file cfg file))))
|
||||||
[]
|
[]
|
||||||
|
|||||||
@@ -102,8 +102,6 @@
|
|||||||
[:http-server-io-threads {:optional true} ::sm/int]
|
[:http-server-io-threads {:optional true} ::sm/int]
|
||||||
[:http-server-max-worker-threads {:optional true} ::sm/int]
|
[:http-server-max-worker-threads {:optional true} ::sm/int]
|
||||||
|
|
||||||
[:exporter-shared-key {:optional true} :string]
|
|
||||||
[:nitrate-shared-key {:optional true} :string]
|
|
||||||
[:management-api-key {:optional true} :string]
|
[:management-api-key {:optional true} :string]
|
||||||
|
|
||||||
[:telemetry-uri {:optional true} :string]
|
[:telemetry-uri {:optional true} :string]
|
||||||
@@ -227,8 +225,6 @@
|
|||||||
[:netty-io-threads {:optional true} ::sm/int]
|
[:netty-io-threads {:optional true} ::sm/int]
|
||||||
[:executor-threads {:optional true} ::sm/int]
|
[:executor-threads {:optional true} ::sm/int]
|
||||||
|
|
||||||
[:nitrate-backend-uri {:optional true} ::sm/uri]
|
|
||||||
|
|
||||||
;; DEPRECATED
|
;; DEPRECATED
|
||||||
[:assets-storage-backend {:optional true} :keyword]
|
[:assets-storage-backend {:optional true} :keyword]
|
||||||
[:storage-assets-fs-directory {:optional true} :string]
|
[:storage-assets-fs-directory {:optional true} :string]
|
||||||
|
|||||||
@@ -106,17 +106,17 @@
|
|||||||
(let [content-part (MimeBodyPart.)
|
(let [content-part (MimeBodyPart.)
|
||||||
alternative-mpart (MimeMultipart. "alternative")]
|
alternative-mpart (MimeMultipart. "alternative")]
|
||||||
|
|
||||||
(when-let [content (get body "text/plain")]
|
|
||||||
(let [text-part (MimeBodyPart.)]
|
|
||||||
(.setText text-part ^String content ^String charset)
|
|
||||||
(.addBodyPart alternative-mpart text-part)))
|
|
||||||
|
|
||||||
(when-let [content (get body "text/html")]
|
(when-let [content (get body "text/html")]
|
||||||
(let [html-part (MimeBodyPart.)]
|
(let [html-part (MimeBodyPart.)]
|
||||||
(.setContent html-part ^String content
|
(.setContent html-part ^String content
|
||||||
(str "text/html; charset=" charset))
|
(str "text/html; charset=" charset))
|
||||||
(.addBodyPart alternative-mpart html-part)))
|
(.addBodyPart alternative-mpart html-part)))
|
||||||
|
|
||||||
|
(when-let [content (get body "text/plain")]
|
||||||
|
(let [text-part (MimeBodyPart.)]
|
||||||
|
(.setText text-part ^String content ^String charset)
|
||||||
|
(.addBodyPart alternative-mpart text-part)))
|
||||||
|
|
||||||
(.setContent content-part alternative-mpart)
|
(.setContent content-part alternative-mpart)
|
||||||
(.addBodyPart mixed-mpart content-part))
|
(.addBodyPart mixed-mpart content-part))
|
||||||
|
|
||||||
@@ -124,6 +124,8 @@
|
|||||||
(throw (IllegalArgumentException. "invalid email body provided")))
|
(throw (IllegalArgumentException. "invalid email body provided")))
|
||||||
|
|
||||||
(doseq [[name content] attachments]
|
(doseq [[name content] attachments]
|
||||||
|
|
||||||
|
(prn "attachment" name)
|
||||||
(let [attachment-part (MimeBodyPart.)]
|
(let [attachment-part (MimeBodyPart.)]
|
||||||
(.setFileName attachment-part ^String name)
|
(.setFileName attachment-part ^String name)
|
||||||
(.setContent attachment-part ^String content (str "text/plain; charset=" charset))
|
(.setContent attachment-part ^String content (str "text/plain; charset=" charset))
|
||||||
|
|||||||
@@ -30,7 +30,7 @@
|
|||||||
|
|
||||||
(defn- get-file-media-object
|
(defn- get-file-media-object
|
||||||
[pool id]
|
[pool id]
|
||||||
(db/get pool :file-media-object {:id id} {::db/remove-deleted false}))
|
(db/get pool :file-media-object {:id id}))
|
||||||
|
|
||||||
(defn- serve-object-from-s3
|
(defn- serve-object-from-s3
|
||||||
[{:keys [::sto/storage] :as cfg} obj]
|
[{:keys [::sto/storage] :as cfg} obj]
|
||||||
|
|||||||
@@ -49,16 +49,13 @@
|
|||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
|
||||||
(defn index-handler
|
(defn index-handler
|
||||||
[cfg request]
|
[_cfg _request]
|
||||||
(let [profile-id (::session/profile-id request)
|
(let [{:keys [clock offset]} @clock/current]
|
||||||
offset (clock/get-offset profile-id)
|
|
||||||
profile (profile/get-profile cfg profile-id)]
|
|
||||||
{::yres/status 200
|
{::yres/status 200
|
||||||
::yres/headers {"content-type" "text/html"}
|
::yres/headers {"content-type" "text/html"}
|
||||||
::yres/body (-> (io/resource "app/templates/debug.tmpl")
|
::yres/body (-> (io/resource "app/templates/debug.tmpl")
|
||||||
(tmpl/render {:version (:full cf/version)
|
(tmpl/render {:version (:full cf/version)
|
||||||
:profile profile
|
:current-clock (str clock)
|
||||||
:current-clock ct/*clock*
|
|
||||||
:current-offset (if offset
|
:current-offset (if offset
|
||||||
(ct/format-duration offset)
|
(ct/format-duration offset)
|
||||||
"NO OFFSET")
|
"NO OFFSET")
|
||||||
@@ -450,16 +447,15 @@
|
|||||||
|
|
||||||
(defn- set-virtual-clock
|
(defn- set-virtual-clock
|
||||||
[_ {:keys [params] :as request}]
|
[_ {:keys [params] :as request}]
|
||||||
(let [offset (some-> params :offset str/trim not-empty ct/duration)
|
(let [offset (some-> params :offset str/trim not-empty ct/duration)
|
||||||
profile-id (::session/profile-id request)
|
reset? (contains? params :reset)]
|
||||||
reset? (contains? params :reset)]
|
|
||||||
(if (= "production" (cf/get :tenant))
|
(if (= "production" (cf/get :tenant))
|
||||||
{::yres/status 501
|
{::yres/status 501
|
||||||
::yres/body "OPERATION NOT ALLOWED"}
|
::yres/body "OPERATION NOT ALLOWED"}
|
||||||
(do
|
(do
|
||||||
(if (or reset? (zero? (inst-ms offset)))
|
(if (or reset? (zero? (inst-ms offset)))
|
||||||
(clock/assign-offset profile-id nil)
|
(clock/set-offset! nil)
|
||||||
(clock/assign-offset profile-id offset))
|
(clock/set-offset! offset))
|
||||||
{::yres/status 302
|
{::yres/status 302
|
||||||
::yres/headers {"location" "/dbg"}}))))
|
::yres/headers {"location" "/dbg"}}))))
|
||||||
|
|
||||||
@@ -499,7 +495,7 @@
|
|||||||
|
|
||||||
(defn authorized?
|
(defn authorized?
|
||||||
[pool {:keys [::session/profile-id]}]
|
[pool {:keys [::session/profile-id]}]
|
||||||
(or (and (= "devenv" (cf/get :host)) profile-id)
|
(or (= "devenv" (cf/get :host))
|
||||||
(let [profile (ex/ignoring (profile/get-profile pool profile-id))
|
(let [profile (ex/ignoring (profile/get-profile pool profile-id))
|
||||||
admins (or (cf/get :admins) #{})]
|
admins (or (cf/get :admins) #{})]
|
||||||
(contains? admins (:email profile)))))
|
(contains? admins (:email profile)))))
|
||||||
|
|||||||
@@ -13,13 +13,13 @@
|
|||||||
[app.common.time :as ct]
|
[app.common.time :as ct]
|
||||||
[app.config :as cf]
|
[app.config :as cf]
|
||||||
[app.db :as db]
|
[app.db :as db]
|
||||||
|
[app.http.middleware :as mw]
|
||||||
[app.main :as-alias main]
|
[app.main :as-alias main]
|
||||||
[app.rpc.commands.profile :as cmd.profile]
|
[app.rpc.commands.profile :as cmd.profile]
|
||||||
[app.setup :as-alias setup]
|
[app.setup :as-alias setup]
|
||||||
[app.tokens :as tokens]
|
[app.tokens :as tokens]
|
||||||
[app.worker :as-alias wrk]
|
[app.worker :as-alias wrk]
|
||||||
[integrant.core :as ig]
|
[integrant.core :as ig]
|
||||||
[yetti.request :as yreq]
|
|
||||||
[yetti.response :as-alias yres]))
|
[yetti.response :as-alias yres]))
|
||||||
|
|
||||||
;; ---- ROUTES
|
;; ---- ROUTES
|
||||||
@@ -49,40 +49,28 @@
|
|||||||
(fn [cfg request]
|
(fn [cfg request]
|
||||||
(db/tx-run! cfg handler request)))))})
|
(db/tx-run! cfg handler request)))))})
|
||||||
|
|
||||||
(def ^:private shared-key-auth
|
|
||||||
{:name ::shared-key-auth
|
|
||||||
:compile
|
|
||||||
(fn [_ _]
|
|
||||||
(fn [handler key]
|
|
||||||
(if key
|
|
||||||
(fn [request]
|
|
||||||
(if-let [key' (yreq/get-header request "x-shared-key")]
|
|
||||||
(if (= key key')
|
|
||||||
(handler request)
|
|
||||||
{::yres/status 403})
|
|
||||||
{::yres/status 403}))
|
|
||||||
(fn [_ _]
|
|
||||||
{::yres/status 403}))))})
|
|
||||||
|
|
||||||
(defmethod ig/init-key ::routes
|
(defmethod ig/init-key ::routes
|
||||||
[_ cfg]
|
[_ {:keys [::setup/props] :as cfg}]
|
||||||
|
|
||||||
["" {:middleware [[shared-key-auth (cf/get :management-api-key)]
|
(let [management-key (or (cf/get :management-api-key)
|
||||||
[default-system cfg]
|
(get props :management-key))]
|
||||||
[transaction]]}
|
|
||||||
["/authenticate"
|
|
||||||
{:handler authenticate
|
|
||||||
:allowed-methods #{:post}}]
|
|
||||||
|
|
||||||
["/get-customer"
|
["" {:middleware [[mw/shared-key-auth management-key]
|
||||||
{:handler get-customer
|
[default-system cfg]
|
||||||
:transaction true
|
[transaction]]}
|
||||||
:allowed-methods #{:post}}]
|
["/authenticate"
|
||||||
|
{:handler authenticate
|
||||||
|
:allowed-methods #{:post}}]
|
||||||
|
|
||||||
["/update-customer"
|
["/get-customer"
|
||||||
{:handler update-customer
|
{:handler get-customer
|
||||||
:allowed-methods #{:post}
|
:transaction true
|
||||||
:transaction true}]])
|
:allowed-methods #{:post}}]
|
||||||
|
|
||||||
|
["/update-customer"
|
||||||
|
{:handler update-customer
|
||||||
|
:allowed-methods #{:post}
|
||||||
|
:transaction true}]]))
|
||||||
|
|
||||||
;; ---- HELPERS
|
;; ---- HELPERS
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
[app.http.errors :as errors]
|
[app.http.errors :as errors]
|
||||||
[app.tokens :as tokens]
|
[app.tokens :as tokens]
|
||||||
[app.util.pointer-map :as pmap]
|
[app.util.pointer-map :as pmap]
|
||||||
|
[buddy.core.codecs :as bc]
|
||||||
[cuerdas.core :as str]
|
[cuerdas.core :as str]
|
||||||
[yetti.adapter :as yt]
|
[yetti.adapter :as yt]
|
||||||
[yetti.middleware :as ymw]
|
[yetti.middleware :as ymw]
|
||||||
@@ -300,20 +301,16 @@
|
|||||||
:compile (constantly wrap-auth)})
|
:compile (constantly wrap-auth)})
|
||||||
|
|
||||||
(defn- wrap-shared-key-auth
|
(defn- wrap-shared-key-auth
|
||||||
[handler keys]
|
[handler shared-key]
|
||||||
(if (seq keys)
|
(if shared-key
|
||||||
(fn [request]
|
(let [shared-key (if (string? shared-key)
|
||||||
(if-let [[key-id key] (some-> (yreq/get-header request "x-shared-key")
|
shared-key
|
||||||
(str/split #"\s+" 2))]
|
(bc/bytes->b64-str shared-key true))]
|
||||||
(let [key-id (-> key-id str/lower keyword)]
|
(fn [request]
|
||||||
(if (and (string? key)
|
(let [key (yreq/get-header request "x-shared-key")]
|
||||||
(contains? keys key-id)
|
(if (= key shared-key)
|
||||||
(= key (get keys key-id)))
|
(handler request)
|
||||||
(-> request
|
{::yres/status 403}))))
|
||||||
(assoc ::http/auth-key-id key-id)
|
|
||||||
(handler))
|
|
||||||
{::yres/status 403}))
|
|
||||||
{::yres/status 403}))
|
|
||||||
(fn [_ _]
|
(fn [_ _]
|
||||||
{::yres/status 403})))
|
{::yres/status 403})))
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,6 @@
|
|||||||
[app.http.session.tasks :as-alias tasks]
|
[app.http.session.tasks :as-alias tasks]
|
||||||
[app.main :as-alias main]
|
[app.main :as-alias main]
|
||||||
[app.setup :as-alias setup]
|
[app.setup :as-alias setup]
|
||||||
[app.setup.clock :as clock]
|
|
||||||
[app.tokens :as tokens]
|
[app.tokens :as tokens]
|
||||||
[integrant.core :as ig]
|
[integrant.core :as ig]
|
||||||
[yetti.request :as yreq]
|
[yetti.request :as yreq]
|
||||||
@@ -230,22 +229,18 @@
|
|||||||
(let [{:keys [type token claims metadata]} (get request ::http/auth-data)]
|
(let [{:keys [type token claims metadata]} (get request ::http/auth-data)]
|
||||||
(cond
|
(cond
|
||||||
(= type :cookie)
|
(= type :cookie)
|
||||||
(let [session
|
(let [session (case (:ver metadata)
|
||||||
(case (:ver metadata)
|
;; BACKWARD COMPATIBILITY WITH OLD TOKENS
|
||||||
;; BACKWARD COMPATIBILITY WITH OLD TOKENS
|
0 (read-session manager token)
|
||||||
0 (read-session manager token)
|
1 (some->> (:sid claims) (read-session manager))
|
||||||
1 (some->> (:sid claims) (read-session manager))
|
nil)
|
||||||
nil)
|
|
||||||
|
|
||||||
request
|
request (cond-> request
|
||||||
(cond-> request
|
(some? session)
|
||||||
(some? session)
|
(-> (assoc ::profile-id (:profile-id session))
|
||||||
(-> (assoc ::profile-id (:profile-id session))
|
(assoc ::session session)))
|
||||||
(assoc ::session session)))
|
|
||||||
|
|
||||||
response
|
response (handler request)]
|
||||||
(binding [ct/*clock* (clock/get-clock (:profile-id session))]
|
|
||||||
(handler request))]
|
|
||||||
|
|
||||||
(if (and session (renew-session? session))
|
(if (and session (renew-session? session))
|
||||||
(let [session (->> session
|
(let [session (->> session
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
(ns app.http.sse
|
(ns app.http.sse
|
||||||
"SSE (server sent events) helpers"
|
"SSE (server sent events) helpers"
|
||||||
|
(:refer-clojure :exclude [tap])
|
||||||
(:require
|
(:require
|
||||||
[app.common.data :as d]
|
[app.common.data :as d]
|
||||||
[app.common.logging :as l]
|
[app.common.logging :as l]
|
||||||
|
|||||||
@@ -9,7 +9,6 @@
|
|||||||
(:require
|
(:require
|
||||||
[app.common.data :as d]
|
[app.common.data :as d]
|
||||||
[app.common.data.macros :as dm]
|
[app.common.data.macros :as dm]
|
||||||
[app.common.json :as json]
|
|
||||||
[app.common.logging :as l]
|
[app.common.logging :as l]
|
||||||
[app.common.schema :as sm]
|
[app.common.schema :as sm]
|
||||||
[app.common.time :as ct]
|
[app.common.time :as ct]
|
||||||
@@ -80,6 +79,18 @@
|
|||||||
(remove #(contains? reserved-props (key %))))
|
(remove #(contains? reserved-props (key %))))
|
||||||
props))
|
props))
|
||||||
|
|
||||||
|
(defn event-from-rpc-params
|
||||||
|
"Create a base event skeleton with pre-filled some important
|
||||||
|
data that can be extracted from RPC params object"
|
||||||
|
[params]
|
||||||
|
(let [context {:external-session-id (::rpc/external-session-id params)
|
||||||
|
:external-event-origin (::rpc/external-event-origin params)
|
||||||
|
:triggered-by (::rpc/handler-name params)}]
|
||||||
|
{::type "action"
|
||||||
|
::profile-id (::rpc/profile-id params)
|
||||||
|
::ip-addr (::rpc/ip-addr params)
|
||||||
|
::context (d/without-nils context)}))
|
||||||
|
|
||||||
(defn get-external-session-id
|
(defn get-external-session-id
|
||||||
[request]
|
[request]
|
||||||
(when-let [session-id (yreq/get-header request "x-external-session-id")]
|
(when-let [session-id (yreq/get-header request "x-external-session-id")]
|
||||||
@@ -88,24 +99,13 @@
|
|||||||
(str/blank? session-id))
|
(str/blank? session-id))
|
||||||
session-id)))
|
session-id)))
|
||||||
|
|
||||||
(defn- get-client-event-origin
|
(defn- get-external-event-origin
|
||||||
[request]
|
[request]
|
||||||
(when-let [origin (yreq/get-header request "x-event-origin")]
|
(when-let [origin (yreq/get-header request "x-event-origin")]
|
||||||
(when-not (or (= origin "null")
|
(when-not (or (> (count origin) 256)
|
||||||
|
(= origin "null")
|
||||||
(str/blank? origin))
|
(str/blank? origin))
|
||||||
(str/prune origin 200))))
|
origin)))
|
||||||
|
|
||||||
(defn get-client-user-agent
|
|
||||||
[request]
|
|
||||||
(when-let [user-agent (yreq/get-header request "user-agent")]
|
|
||||||
(str/prune user-agent 500)))
|
|
||||||
|
|
||||||
(defn- get-client-version
|
|
||||||
[request]
|
|
||||||
(when-let [origin (yreq/get-header request "x-frontend-version")]
|
|
||||||
(when-not (or (= origin "null")
|
|
||||||
(str/blank? origin))
|
|
||||||
(str/prune origin 100))))
|
|
||||||
|
|
||||||
;; --- SPECS
|
;; --- SPECS
|
||||||
|
|
||||||
@@ -134,37 +134,6 @@
|
|||||||
(def ^:private check-event
|
(def ^:private check-event
|
||||||
(sm/check-fn schema:event))
|
(sm/check-fn schema:event))
|
||||||
|
|
||||||
(defn- prepare-context-from-request
|
|
||||||
[request]
|
|
||||||
(let [client-event-origin (get-client-event-origin request)
|
|
||||||
client-version (get-client-version request)
|
|
||||||
client-user-agent (get-client-user-agent request)
|
|
||||||
session-id (get-external-session-id request)
|
|
||||||
key-id (::http/auth-key-id request)
|
|
||||||
token-id (::actoken/id request)
|
|
||||||
token-type (::actoken/type request)]
|
|
||||||
(d/without-nils
|
|
||||||
{:external-session-id session-id
|
|
||||||
:initiator (or key-id "app")
|
|
||||||
:access-token-id (some-> token-id str)
|
|
||||||
:access-token-type (some-> token-type str)
|
|
||||||
:client-event-origin client-event-origin
|
|
||||||
:client-user-agent client-user-agent
|
|
||||||
:client-version client-version
|
|
||||||
:version (:full cf/version)})))
|
|
||||||
|
|
||||||
(defn event-from-rpc-params
|
|
||||||
"Create a base event skeleton with pre-filled some important
|
|
||||||
data that can be extracted from RPC params object"
|
|
||||||
[params]
|
|
||||||
(let [context (some-> params meta ::http/request prepare-context-from-request)
|
|
||||||
event {::type "action"
|
|
||||||
::profile-id (or (::rpc/profile-id params) uuid/zero)
|
|
||||||
::ip-addr (::rpc/ip-addr params)}]
|
|
||||||
(cond-> event
|
|
||||||
(some? context)
|
|
||||||
(assoc ::context context))))
|
|
||||||
|
|
||||||
(defn prepare-event
|
(defn prepare-event
|
||||||
[cfg mdata params result]
|
[cfg mdata params result]
|
||||||
(let [resultm (meta result)
|
(let [resultm (meta result)
|
||||||
@@ -179,10 +148,18 @@
|
|||||||
(merge (::props resultm))
|
(merge (::props resultm))
|
||||||
(dissoc :profile-id)
|
(dissoc :profile-id)
|
||||||
(dissoc :type)))
|
(dissoc :type)))
|
||||||
|
|
||||||
(clean-props))
|
(clean-props))
|
||||||
|
|
||||||
context (merge (::context resultm)
|
token-id (::actoken/id request)
|
||||||
(prepare-context-from-request request))
|
context (-> (::context resultm)
|
||||||
|
(assoc :external-session-id
|
||||||
|
(get-external-session-id request))
|
||||||
|
(assoc :external-event-origin
|
||||||
|
(get-external-event-origin request))
|
||||||
|
(assoc :access-token-id (some-> token-id str))
|
||||||
|
(d/without-nils))
|
||||||
|
|
||||||
ip-addr (inet/parse-request request)]
|
ip-addr (inet/parse-request request)]
|
||||||
|
|
||||||
{::type (or (::type resultm)
|
{::type (or (::type resultm)
|
||||||
@@ -228,7 +205,7 @@
|
|||||||
(some? tnow)
|
(some? tnow)
|
||||||
(assoc :tracked-at tnow))))
|
(assoc :tracked-at tnow))))
|
||||||
|
|
||||||
(defn- append-audit-entry
|
(defn- append-audit-entry!
|
||||||
[cfg params]
|
[cfg params]
|
||||||
(let [params (-> params
|
(let [params (-> params
|
||||||
(update :props db/tjson)
|
(update :props db/tjson)
|
||||||
@@ -241,16 +218,6 @@
|
|||||||
(let [params (event->params event)
|
(let [params (event->params event)
|
||||||
tnow (ct/now)]
|
tnow (ct/now)]
|
||||||
|
|
||||||
(when (contains? cf/flags :audit-log-logger)
|
|
||||||
(l/log! ::l/logger "app.audit"
|
|
||||||
::l/level :info
|
|
||||||
:profile-id (str (::profile-id event))
|
|
||||||
:ip-addr (str (::ip-addr event))
|
|
||||||
:type (::type event)
|
|
||||||
:name (::name event)
|
|
||||||
:props (json/encode (::props event) :key-fn json/write-camel-key)
|
|
||||||
:context (json/encode (::context event) :key-fn json/write-camel-key)))
|
|
||||||
|
|
||||||
(when (contains? cf/flags :audit-log)
|
(when (contains? cf/flags :audit-log)
|
||||||
;; NOTE: this operation may cause primary key conflicts on inserts
|
;; NOTE: this operation may cause primary key conflicts on inserts
|
||||||
;; because of the timestamp precission (two concurrent requests), in
|
;; because of the timestamp precission (two concurrent requests), in
|
||||||
@@ -258,7 +225,7 @@
|
|||||||
(let [params (-> params
|
(let [params (-> params
|
||||||
(assoc :created-at tnow)
|
(assoc :created-at tnow)
|
||||||
(update :tracked-at #(or % tnow)))]
|
(update :tracked-at #(or % tnow)))]
|
||||||
(append-audit-entry cfg params)))
|
(append-audit-entry! cfg params)))
|
||||||
|
|
||||||
(when (and (or (contains? cf/flags :telemetry)
|
(when (and (or (contains? cf/flags :telemetry)
|
||||||
(cf/get :telemetry-enabled))
|
(cf/get :telemetry-enabled))
|
||||||
@@ -273,7 +240,7 @@
|
|||||||
(update :tracked-at #(or % tnow))
|
(update :tracked-at #(or % tnow))
|
||||||
(assoc :props {})
|
(assoc :props {})
|
||||||
(assoc :context {}))]
|
(assoc :context {}))]
|
||||||
(append-audit-entry cfg params)))
|
(append-audit-entry! cfg params)))
|
||||||
|
|
||||||
(when (and (contains? cf/flags :webhooks)
|
(when (and (contains? cf/flags :webhooks)
|
||||||
(::webhooks/event? event))
|
(::webhooks/event? event))
|
||||||
@@ -327,4 +294,4 @@
|
|||||||
params (-> (event->params event)
|
params (-> (event->params event)
|
||||||
(assoc :created-at tnow)
|
(assoc :created-at tnow)
|
||||||
(update :tracked-at #(or % tnow)))]
|
(update :tracked-at #(or % tnow)))]
|
||||||
(append-audit-entry cfg params)))))))
|
(append-audit-entry! cfg params)))))))
|
||||||
|
|||||||
@@ -275,7 +275,8 @@
|
|||||||
::email/whitelist (ig/ref ::email/whitelist)}
|
::email/whitelist (ig/ref ::email/whitelist)}
|
||||||
|
|
||||||
::mgmt/routes
|
::mgmt/routes
|
||||||
{::db/pool (ig/ref ::db/pool)}
|
{::db/pool (ig/ref ::db/pool)
|
||||||
|
::setup/props (ig/ref ::setup/props)}
|
||||||
|
|
||||||
:app.http/router
|
:app.http/router
|
||||||
{::session/manager (ig/ref ::session/manager)
|
{::session/manager (ig/ref ::session/manager)
|
||||||
@@ -322,7 +323,6 @@
|
|||||||
{::http.client/client (ig/ref ::http.client/client)
|
{::http.client/client (ig/ref ::http.client/client)
|
||||||
::db/pool (ig/ref ::db/pool)
|
::db/pool (ig/ref ::db/pool)
|
||||||
::rds/pool (ig/ref ::rds/pool)
|
::rds/pool (ig/ref ::rds/pool)
|
||||||
:app.nitrate/client (ig/ref :app.nitrate/client)
|
|
||||||
::wrk/executor (ig/ref ::wrk/netty-executor)
|
::wrk/executor (ig/ref ::wrk/netty-executor)
|
||||||
::session/manager (ig/ref ::session/manager)
|
::session/manager (ig/ref ::session/manager)
|
||||||
::ldap/provider (ig/ref ::ldap/provider)
|
::ldap/provider (ig/ref ::ldap/provider)
|
||||||
@@ -339,10 +339,6 @@
|
|||||||
::email/blacklist (ig/ref ::email/blacklist)
|
::email/blacklist (ig/ref ::email/blacklist)
|
||||||
::email/whitelist (ig/ref ::email/whitelist)}
|
::email/whitelist (ig/ref ::email/whitelist)}
|
||||||
|
|
||||||
:app.nitrate/client
|
|
||||||
{::http.client/client (ig/ref ::http.client/client)
|
|
||||||
::setup/shared-keys (ig/ref ::setup/shared-keys)}
|
|
||||||
|
|
||||||
:app.rpc/management-methods
|
:app.rpc/management-methods
|
||||||
{::http.client/client (ig/ref ::http.client/client)
|
{::http.client/client (ig/ref ::http.client/client)
|
||||||
::db/pool (ig/ref ::db/pool)
|
::db/pool (ig/ref ::db/pool)
|
||||||
@@ -352,18 +348,17 @@
|
|||||||
::sto/storage (ig/ref ::sto/storage)
|
::sto/storage (ig/ref ::sto/storage)
|
||||||
::mtx/metrics (ig/ref ::mtx/metrics)
|
::mtx/metrics (ig/ref ::mtx/metrics)
|
||||||
::mbus/msgbus (ig/ref ::mbus/msgbus)
|
::mbus/msgbus (ig/ref ::mbus/msgbus)
|
||||||
:app.nitrate/client (ig/ref :app.nitrate/client)
|
|
||||||
::rds/client (ig/ref ::rds/client)
|
::rds/client (ig/ref ::rds/client)
|
||||||
::setup/props (ig/ref ::setup/props)}
|
::setup/props (ig/ref ::setup/props)}
|
||||||
|
|
||||||
::rpc/routes
|
::rpc/routes
|
||||||
{::rpc/methods (ig/ref :app.rpc/methods)
|
{::rpc/methods (ig/ref :app.rpc/methods)
|
||||||
::rpc/management-methods (ig/ref :app.rpc/management-methods)
|
::rpc/management-methods (ig/ref :app.rpc/management-methods)
|
||||||
|
|
||||||
;; FIXME: revisit if db/pool is necessary here
|
;; FIXME: revisit if db/pool is necessary here
|
||||||
::db/pool (ig/ref ::db/pool)
|
::db/pool (ig/ref ::db/pool)
|
||||||
::session/manager (ig/ref ::session/manager)
|
::session/manager (ig/ref ::session/manager)
|
||||||
::setup/shared-keys (ig/ref ::setup/shared-keys)}
|
::setup/props (ig/ref ::setup/props)}
|
||||||
|
|
||||||
::wrk/registry
|
::wrk/registry
|
||||||
{::mtx/metrics (ig/ref ::mtx/metrics)
|
{::mtx/metrics (ig/ref ::mtx/metrics)
|
||||||
@@ -451,11 +446,6 @@
|
|||||||
;; module requires the migrations to run before initialize.
|
;; module requires the migrations to run before initialize.
|
||||||
::migrations (ig/ref :app.migrations/migrations)}
|
::migrations (ig/ref :app.migrations/migrations)}
|
||||||
|
|
||||||
::setup/shared-keys
|
|
||||||
{::setup/props (ig/ref ::setup/props)
|
|
||||||
:nitrate (cf/get :nitrate-shared-key)
|
|
||||||
:exporter (cf/get :exporter-shared-key)}
|
|
||||||
|
|
||||||
::setup/clock
|
::setup/clock
|
||||||
{}
|
{}
|
||||||
|
|
||||||
|
|||||||
@@ -1,130 +0,0 @@
|
|||||||
(ns app.nitrate
|
|
||||||
"Module that make calls to the external nitrate aplication"
|
|
||||||
(:require
|
|
||||||
[app.common.logging :as l]
|
|
||||||
[app.common.schema :as sm]
|
|
||||||
[app.config :as cf]
|
|
||||||
[app.http.client :as http]
|
|
||||||
[app.rpc :as-alias rpc]
|
|
||||||
[app.setup :as-alias setup]
|
|
||||||
[app.util.json :as json]
|
|
||||||
[clojure.core :as c]
|
|
||||||
[integrant.core :as ig]))
|
|
||||||
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
||||||
;; HELPERS
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
||||||
|
|
||||||
(defn- request-builder
|
|
||||||
[cfg method uri shared-key profile-id]
|
|
||||||
(fn []
|
|
||||||
(http/req! cfg {:method method
|
|
||||||
:headers {"content-type" "application/json"
|
|
||||||
"accept" "application/json"
|
|
||||||
"x-shared-key" shared-key
|
|
||||||
"x-profile-id" (str profile-id)}
|
|
||||||
:uri uri
|
|
||||||
:version :http1.1})))
|
|
||||||
|
|
||||||
|
|
||||||
(defn- with-retries
|
|
||||||
[handler max-retries]
|
|
||||||
(fn []
|
|
||||||
(loop [attempt 1]
|
|
||||||
(let [result (try
|
|
||||||
(handler)
|
|
||||||
(catch Exception e
|
|
||||||
(if (< attempt max-retries)
|
|
||||||
::retry
|
|
||||||
(do
|
|
||||||
;; TODO Error handling
|
|
||||||
(l/error :hint "request fail after multiple retries" :cause e)
|
|
||||||
nil))))]
|
|
||||||
(if (= result ::retry)
|
|
||||||
(recur (inc attempt))
|
|
||||||
result)))))
|
|
||||||
|
|
||||||
|
|
||||||
(defn- with-validate [handler uri schema]
|
|
||||||
(fn []
|
|
||||||
(let [coercer-http (sm/coercer schema
|
|
||||||
:type :validation
|
|
||||||
:hint (str "invalid data received calling " uri))]
|
|
||||||
(try
|
|
||||||
(coercer-http (-> (handler) :body json/decode))
|
|
||||||
(catch Exception e
|
|
||||||
;; TODO Error handling
|
|
||||||
(l/error :hint "error validating json response" :cause e)
|
|
||||||
nil)))))
|
|
||||||
|
|
||||||
(defn- request-to-nitrate
|
|
||||||
[cfg method uri schema {:keys [::rpc/profile-id] :as params}]
|
|
||||||
(let [shared-key (-> cfg ::setup/shared-keys :nitrate)
|
|
||||||
full-http-call (-> (request-builder cfg method uri shared-key profile-id)
|
|
||||||
(with-retries 3)
|
|
||||||
(with-validate uri schema))]
|
|
||||||
(full-http-call)))
|
|
||||||
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
||||||
;; API
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
||||||
|
|
||||||
(defn call
|
|
||||||
[cfg method params]
|
|
||||||
(when (contains? cf/flags :nitrate)
|
|
||||||
(let [client (get cfg ::client)
|
|
||||||
method (get client method)]
|
|
||||||
(method params))))
|
|
||||||
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
||||||
|
|
||||||
(def ^:private schema:organization
|
|
||||||
[:map
|
|
||||||
[:id ::sm/text]
|
|
||||||
[:name ::sm/text]])
|
|
||||||
|
|
||||||
(def ^:private schema:user
|
|
||||||
[:map
|
|
||||||
[:valid ::sm/boolean]])
|
|
||||||
|
|
||||||
(defn- get-team-org
|
|
||||||
[cfg {:keys [team-id] :as params}]
|
|
||||||
(let [baseuri (cf/get :nitrate-backend-uri)]
|
|
||||||
(request-to-nitrate cfg :get (str baseuri "/api/teams/" (str team-id)) schema:organization params)))
|
|
||||||
|
|
||||||
(defn- is-valid-user
|
|
||||||
[cfg {:keys [profile-id] :as params}]
|
|
||||||
(let [baseuri (cf/get :nitrate-backend-uri)]
|
|
||||||
(request-to-nitrate cfg :get (str baseuri "/api/users/" (str profile-id)) schema:user params)))
|
|
||||||
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
||||||
;; INITIALIZATION
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
||||||
|
|
||||||
(defmethod ig/init-key ::client
|
|
||||||
[_ cfg]
|
|
||||||
(when (contains? cf/flags :nitrate)
|
|
||||||
{:get-team-org (partial get-team-org cfg)
|
|
||||||
:is-valid-user (partial is-valid-user cfg)}))
|
|
||||||
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
||||||
;; UTILS
|
|
||||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
||||||
|
|
||||||
|
|
||||||
(defn add-nitrate-licence-to-profile
|
|
||||||
[cfg profile]
|
|
||||||
(try
|
|
||||||
(let [nitrate-licence (call cfg :is-valid-user {:profile-id (:id profile)})]
|
|
||||||
(assoc profile :nitrate-licence (:valid nitrate-licence)))
|
|
||||||
(catch Throwable cause
|
|
||||||
(l/error :hint "failed to get nitrate licence"
|
|
||||||
:profile-id (:id profile)
|
|
||||||
:cause cause)
|
|
||||||
profile)))
|
|
||||||
|
|
||||||
(defn add-org-to-team
|
|
||||||
[cfg team params]
|
|
||||||
(let [params (assoc (or params {}) :team-id (:id team))
|
|
||||||
org (call cfg :get-team-org params)]
|
|
||||||
(assoc team :organization-id (:id org) :organization-name (:name org))))
|
|
||||||
@@ -14,7 +14,6 @@
|
|||||||
[app.common.spec :as us]
|
[app.common.spec :as us]
|
||||||
[app.common.time :as ct]
|
[app.common.time :as ct]
|
||||||
[app.common.uri :as u]
|
[app.common.uri :as u]
|
||||||
[app.common.uuid :as uuid]
|
|
||||||
[app.config :as cf]
|
[app.config :as cf]
|
||||||
[app.db :as db]
|
[app.db :as db]
|
||||||
[app.http :as-alias http]
|
[app.http :as-alias http]
|
||||||
@@ -92,12 +91,8 @@
|
|||||||
(fn [{:keys [params path-params method] :as request}]
|
(fn [{:keys [params path-params method] :as request}]
|
||||||
(let [handler-name (:type path-params)
|
(let [handler-name (:type path-params)
|
||||||
etag (yreq/get-header request "if-none-match")
|
etag (yreq/get-header request "if-none-match")
|
||||||
|
|
||||||
key-id (get request ::http/auth-key-id)
|
|
||||||
profile-id (or (::session/profile-id request)
|
profile-id (or (::session/profile-id request)
|
||||||
(::actoken/profile-id request)
|
(::actoken/profile-id request))
|
||||||
(if key-id uuid/zero nil))
|
|
||||||
|
|
||||||
ip-addr (inet/parse-request request)
|
ip-addr (inet/parse-request request)
|
||||||
|
|
||||||
data (-> params
|
data (-> params
|
||||||
@@ -298,12 +293,10 @@
|
|||||||
|
|
||||||
(defn- resolve-management-methods
|
(defn- resolve-management-methods
|
||||||
[cfg]
|
[cfg]
|
||||||
(let [cfg (assoc cfg ::type "management" ::metrics-id :rpc-management-timing)
|
(let [cfg (assoc cfg ::type "management" ::metrics-id :rpc-management-timing)]
|
||||||
mods (cond->> (list 'app.rpc.management.exporter)
|
(->> (sv/scan-ns
|
||||||
(contains? cf/flags :nitrate)
|
'app.rpc.management.subscription
|
||||||
(cons 'app.rpc.management.nitrate))]
|
'app.rpc.management.exporter)
|
||||||
|
|
||||||
(->> (apply sv/scan-ns mods)
|
|
||||||
(map (partial process-method cfg "management" wrap-management))
|
(map (partial process-method cfg "management" wrap-management))
|
||||||
(into {}))))
|
(into {}))))
|
||||||
|
|
||||||
@@ -347,20 +340,23 @@
|
|||||||
|
|
||||||
(defmethod ig/assert-key ::routes
|
(defmethod ig/assert-key ::routes
|
||||||
[_ params]
|
[_ params]
|
||||||
(assert (map? (::setup/shared-keys params)))
|
|
||||||
(assert (db/pool? (::db/pool params)) "expect valid database pool")
|
(assert (db/pool? (::db/pool params)) "expect valid database pool")
|
||||||
|
(assert (some? (::setup/props params)))
|
||||||
(assert (session/manager? (::session/manager params)) "expect valid session manager")
|
(assert (session/manager? (::session/manager params)) "expect valid session manager")
|
||||||
(assert (valid-methods? (::methods params)) "expect valid methods map")
|
(assert (valid-methods? (::methods params)) "expect valid methods map")
|
||||||
(assert (valid-methods? (::management-methods params)) "expect valid methods map"))
|
(assert (valid-methods? (::management-methods params)) "expect valid methods map"))
|
||||||
|
|
||||||
(defmethod ig/init-key ::routes
|
(defmethod ig/init-key ::routes
|
||||||
[_ {:keys [::methods ::management-methods ::setup/shared-keys] :as cfg}]
|
[_ {:keys [::methods ::management-methods ::setup/props] :as cfg}]
|
||||||
|
|
||||||
|
(let [public-uri (cf/get :public-uri)
|
||||||
|
management-key (or (cf/get :management-api-key)
|
||||||
|
(get props :management-key))]
|
||||||
|
|
||||||
(let [public-uri (cf/get :public-uri)]
|
|
||||||
["/api"
|
["/api"
|
||||||
["/management"
|
["/management"
|
||||||
["/methods/:type"
|
["/methods/:type"
|
||||||
{:middleware [[mw/shared-key-auth shared-keys]
|
{:middleware [[mw/shared-key-auth management-key]
|
||||||
[session/authz cfg]]
|
[session/authz cfg]]
|
||||||
:handler (make-rpc-handler management-methods)}]
|
:handler (make-rpc-handler management-methods)}]
|
||||||
|
|
||||||
|
|||||||
@@ -307,8 +307,7 @@
|
|||||||
:content-type (:mtype input)})]
|
:content-type (:mtype input)})]
|
||||||
(:id sobject))
|
(:id sobject))
|
||||||
(catch Throwable cause
|
(catch Throwable cause
|
||||||
(l/wrn :hint "unable to import profile picture"
|
(l/err :hint "unable to import profile picture"
|
||||||
:uri uri
|
|
||||||
:cause cause)
|
:cause cause)
|
||||||
nil)))
|
nil)))
|
||||||
|
|
||||||
|
|||||||
@@ -79,14 +79,85 @@
|
|||||||
|
|
||||||
;; --- FILE PERMISSIONS
|
;; --- FILE PERMISSIONS
|
||||||
|
|
||||||
|
|
||||||
|
(def ^:private sql:file-permissions
|
||||||
|
"select fpr.is_owner,
|
||||||
|
fpr.is_admin,
|
||||||
|
fpr.can_edit
|
||||||
|
from file_profile_rel as fpr
|
||||||
|
inner join file as f on (f.id = fpr.file_id)
|
||||||
|
where fpr.file_id = ?
|
||||||
|
and fpr.profile_id = ?
|
||||||
|
and f.deleted_at is null
|
||||||
|
union all
|
||||||
|
select tpr.is_owner,
|
||||||
|
tpr.is_admin,
|
||||||
|
tpr.can_edit
|
||||||
|
from team_profile_rel as tpr
|
||||||
|
inner join project as p on (p.team_id = tpr.team_id)
|
||||||
|
inner join file as f on (p.id = f.project_id)
|
||||||
|
where f.id = ?
|
||||||
|
and tpr.profile_id = ?
|
||||||
|
and f.deleted_at is null
|
||||||
|
union all
|
||||||
|
select ppr.is_owner,
|
||||||
|
ppr.is_admin,
|
||||||
|
ppr.can_edit
|
||||||
|
from project_profile_rel as ppr
|
||||||
|
inner join file as f on (f.project_id = ppr.project_id)
|
||||||
|
where f.id = ?
|
||||||
|
and ppr.profile_id = ?
|
||||||
|
and f.deleted_at is null")
|
||||||
|
|
||||||
|
(defn get-file-permissions
|
||||||
|
[conn profile-id file-id]
|
||||||
|
(when (and profile-id file-id)
|
||||||
|
(db/exec! conn [sql:file-permissions
|
||||||
|
file-id profile-id
|
||||||
|
file-id profile-id
|
||||||
|
file-id profile-id])))
|
||||||
|
|
||||||
|
(defn get-permissions
|
||||||
|
([conn profile-id file-id]
|
||||||
|
(let [rows (get-file-permissions conn profile-id file-id)
|
||||||
|
is-owner (boolean (some :is-owner rows))
|
||||||
|
is-admin (boolean (some :is-admin rows))
|
||||||
|
can-edit (boolean (some :can-edit rows))]
|
||||||
|
(when (seq rows)
|
||||||
|
{:type :membership
|
||||||
|
:is-owner is-owner
|
||||||
|
:is-admin (or is-owner is-admin)
|
||||||
|
:can-edit (or is-owner is-admin can-edit)
|
||||||
|
:can-read true
|
||||||
|
:is-logged (some? profile-id)})))
|
||||||
|
|
||||||
|
([conn profile-id file-id share-id]
|
||||||
|
(let [perms (get-permissions conn profile-id file-id)
|
||||||
|
ldata (some-> (db/get* conn :share-link {:id share-id :file-id file-id})
|
||||||
|
(dissoc :flags)
|
||||||
|
(update :pages db/decode-pgarray #{}))]
|
||||||
|
|
||||||
|
;; NOTE: in a future when share-link becomes more powerful and
|
||||||
|
;; will allow us specify which parts of the app is available, we
|
||||||
|
;; will probably need to tweak this function in order to expose
|
||||||
|
;; this flags to the frontend.
|
||||||
|
(cond
|
||||||
|
(some? perms) perms
|
||||||
|
(some? ldata) {:type :share-link
|
||||||
|
:can-read true
|
||||||
|
:pages (:pages ldata)
|
||||||
|
:is-logged (some? profile-id)
|
||||||
|
:who-comment (:who-comment ldata)
|
||||||
|
:who-inspect (:who-inspect ldata)}))))
|
||||||
|
|
||||||
(def has-edit-permissions?
|
(def has-edit-permissions?
|
||||||
(perms/make-edition-predicate-fn bfc/get-file-permissions))
|
(perms/make-edition-predicate-fn get-permissions))
|
||||||
|
|
||||||
(def has-read-permissions?
|
(def has-read-permissions?
|
||||||
(perms/make-read-predicate-fn bfc/get-file-permissions))
|
(perms/make-read-predicate-fn get-permissions))
|
||||||
|
|
||||||
(def has-comment-permissions?
|
(def has-comment-permissions?
|
||||||
(perms/make-comment-predicate-fn bfc/get-file-permissions))
|
(perms/make-comment-predicate-fn get-permissions))
|
||||||
|
|
||||||
(def check-edition-permissions!
|
(def check-edition-permissions!
|
||||||
(perms/make-check-fn has-edit-permissions?))
|
(perms/make-check-fn has-edit-permissions?))
|
||||||
@@ -99,7 +170,7 @@
|
|||||||
|
|
||||||
(defn check-comment-permissions!
|
(defn check-comment-permissions!
|
||||||
[conn profile-id file-id share-id]
|
[conn profile-id file-id share-id]
|
||||||
(let [perms (bfc/get-file-permissions conn profile-id file-id share-id)
|
(let [perms (get-permissions conn profile-id file-id share-id)
|
||||||
can-read (has-read-permissions? perms)
|
can-read (has-read-permissions? perms)
|
||||||
can-comment (has-comment-permissions? perms)]
|
can-comment (has-comment-permissions? perms)]
|
||||||
(when-not (or can-read can-comment)
|
(when-not (or can-read can-comment)
|
||||||
@@ -151,7 +222,7 @@
|
|||||||
(defn- get-minimal-file-with-perms
|
(defn- get-minimal-file-with-perms
|
||||||
[cfg {:keys [:id ::rpc/profile-id]}]
|
[cfg {:keys [:id ::rpc/profile-id]}]
|
||||||
(let [mfile (get-minimal-file cfg id)
|
(let [mfile (get-minimal-file cfg id)
|
||||||
perms (bfc/get-file-permissions cfg profile-id id)]
|
perms (get-permissions cfg profile-id id)]
|
||||||
(assoc mfile :permissions perms)))
|
(assoc mfile :permissions perms)))
|
||||||
|
|
||||||
(defn get-file-etag
|
(defn get-file-etag
|
||||||
@@ -177,7 +248,7 @@
|
|||||||
;; will be already prefetched and we just reuse them instead
|
;; will be already prefetched and we just reuse them instead
|
||||||
;; of making an additional database queries.
|
;; of making an additional database queries.
|
||||||
(let [perms (or (:permissions (::cond/object params))
|
(let [perms (or (:permissions (::cond/object params))
|
||||||
(bfc/get-file-permissions conn profile-id id))]
|
(get-permissions conn profile-id id))]
|
||||||
(check-read-permissions! perms)
|
(check-read-permissions! perms)
|
||||||
|
|
||||||
(let [team (teams/get-team conn
|
(let [team (teams/get-team conn
|
||||||
@@ -240,7 +311,7 @@
|
|||||||
::sm/result schema:file-fragment}
|
::sm/result schema:file-fragment}
|
||||||
[cfg {:keys [::rpc/profile-id file-id fragment-id share-id]}]
|
[cfg {:keys [::rpc/profile-id file-id fragment-id share-id]}]
|
||||||
(db/run! cfg (fn [cfg]
|
(db/run! cfg (fn [cfg]
|
||||||
(let [perms (bfc/get-file-permissions cfg profile-id file-id share-id)]
|
(let [perms (get-permissions cfg profile-id file-id share-id)]
|
||||||
(check-read-permissions! perms)
|
(check-read-permissions! perms)
|
||||||
(-> (get-file-fragment cfg file-id fragment-id)
|
(-> (get-file-fragment cfg file-id fragment-id)
|
||||||
(rph/with-http-cache long-cache-duration))))))
|
(rph/with-http-cache long-cache-duration))))))
|
||||||
@@ -385,7 +456,8 @@
|
|||||||
:code :params-validation
|
:code :params-validation
|
||||||
:hint "page-id is required when object-id is provided"))
|
:hint "page-id is required when object-id is provided"))
|
||||||
|
|
||||||
(let [perms (bfc/get-file-permissions conn profile-id file-id share-id)
|
(let [perms (get-permissions conn profile-id file-id share-id)
|
||||||
|
|
||||||
file (bfc/get-file cfg file-id :read-only? true)
|
file (bfc/get-file cfg file-id :read-only? true)
|
||||||
|
|
||||||
proj (db/get conn :project {:id (:project-id file)})
|
proj (db/get conn :project {:id (:project-id file)})
|
||||||
@@ -616,10 +688,11 @@
|
|||||||
"Get libraries used by the specified file."
|
"Get libraries used by the specified file."
|
||||||
{::doc/added "1.17"
|
{::doc/added "1.17"
|
||||||
::sm/params schema:get-file-libraries}
|
::sm/params schema:get-file-libraries}
|
||||||
[cfg {:keys [::rpc/profile-id file-id]}]
|
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id file-id]}]
|
||||||
(bfc/check-file-exists cfg file-id)
|
(dm/with-open [conn (db/open pool)]
|
||||||
(check-read-permissions! cfg profile-id file-id)
|
(check-read-permissions! conn profile-id file-id)
|
||||||
(bfc/get-file-libraries cfg file-id))
|
(bfc/get-file-libraries conn file-id)))
|
||||||
|
|
||||||
|
|
||||||
;; --- COMMAND QUERY: Files that use this File library
|
;; --- COMMAND QUERY: Files that use this File library
|
||||||
|
|
||||||
@@ -704,6 +777,7 @@
|
|||||||
f.created_at,
|
f.created_at,
|
||||||
f.modified_at,
|
f.modified_at,
|
||||||
f.name,
|
f.name,
|
||||||
|
f.is_shared,
|
||||||
f.deleted_at AS will_be_deleted_at,
|
f.deleted_at AS will_be_deleted_at,
|
||||||
ft.media_id AS thumbnail_id,
|
ft.media_id AS thumbnail_id,
|
||||||
row_number() OVER w AS row_num,
|
row_number() OVER w AS row_num,
|
||||||
@@ -711,7 +785,8 @@
|
|||||||
FROM file AS f
|
FROM file AS f
|
||||||
INNER JOIN project AS p ON (p.id = f.project_id)
|
INNER JOIN project AS p ON (p.id = f.project_id)
|
||||||
LEFT JOIN file_thumbnail AS ft on (ft.file_id = f.id
|
LEFT JOIN file_thumbnail AS ft on (ft.file_id = f.id
|
||||||
AND ft.revn = f.revn)
|
AND ft.revn = f.revn
|
||||||
|
AND ft.deleted_at is null)
|
||||||
WHERE p.team_id = ?
|
WHERE p.team_id = ?
|
||||||
AND (p.deleted_at > ?::timestamptz OR
|
AND (p.deleted_at > ?::timestamptz OR
|
||||||
f.deleted_at > ?::timestamptz)
|
f.deleted_at > ?::timestamptz)
|
||||||
@@ -813,7 +888,7 @@
|
|||||||
AND (f.deleted_at IS NULL OR f.deleted_at > now())
|
AND (f.deleted_at IS NULL OR f.deleted_at > now())
|
||||||
ORDER BY f.created_at ASC;")
|
ORDER BY f.created_at ASC;")
|
||||||
|
|
||||||
(defn- absorb-library-by-file
|
(defn- absorb-library-by-file!
|
||||||
[cfg ldata file-id]
|
[cfg ldata file-id]
|
||||||
|
|
||||||
(assert (db/connection-map? cfg)
|
(assert (db/connection-map? cfg)
|
||||||
@@ -837,7 +912,7 @@
|
|||||||
:modified-at (ct/now)
|
:modified-at (ct/now)
|
||||||
:has-media-trimmed false}))))
|
:has-media-trimmed false}))))
|
||||||
|
|
||||||
(defn- absorb-library*
|
(defn- absorb-library
|
||||||
"Find all files using a shared library, and absorb all library assets
|
"Find all files using a shared library, and absorb all library assets
|
||||||
into the file local libraries"
|
into the file local libraries"
|
||||||
[cfg {:keys [id data] :as library}]
|
[cfg {:keys [id data] :as library}]
|
||||||
@@ -852,10 +927,10 @@
|
|||||||
:library-id (str id)
|
:library-id (str id)
|
||||||
:files (str/join "," (map str ids)))
|
:files (str/join "," (map str ids)))
|
||||||
|
|
||||||
(run! (partial absorb-library-by-file cfg data) ids)
|
(run! (partial absorb-library-by-file! cfg data) ids)
|
||||||
library))
|
library))
|
||||||
|
|
||||||
(defn absorb-library
|
(defn absorb-library!
|
||||||
[{:keys [::db/conn] :as cfg} id]
|
[{:keys [::db/conn] :as cfg} id]
|
||||||
(let [file (-> (bfc/get-file cfg id
|
(let [file (-> (bfc/get-file cfg id
|
||||||
:realize? true
|
:realize? true
|
||||||
@@ -872,7 +947,7 @@
|
|||||||
(-> (cfeat/get-team-enabled-features cf/flags team)
|
(-> (cfeat/get-team-enabled-features cf/flags team)
|
||||||
(cfeat/check-file-features! (:features file)))
|
(cfeat/check-file-features! (:features file)))
|
||||||
|
|
||||||
(absorb-library* cfg file)))
|
(absorb-library cfg file)))
|
||||||
|
|
||||||
(defn- set-file-shared
|
(defn- set-file-shared
|
||||||
[{:keys [::db/conn] :as cfg} {:keys [profile-id id] :as params}]
|
[{:keys [::db/conn] :as cfg} {:keys [profile-id id] :as params}]
|
||||||
@@ -885,14 +960,14 @@
|
|||||||
;; file, we need to perform more complex operation,
|
;; file, we need to perform more complex operation,
|
||||||
;; so in this case we retrieve the complete file and
|
;; so in this case we retrieve the complete file and
|
||||||
;; perform all required validations.
|
;; perform all required validations.
|
||||||
(let [file (-> (absorb-library cfg id)
|
(let [file (-> (absorb-library! cfg id)
|
||||||
(assoc :is-shared false))]
|
(assoc :is-shared false))]
|
||||||
(db/delete! conn :file-library-rel {:library-file-id id})
|
(db/delete! conn :file-library-rel {:library-file-id id})
|
||||||
(db/update! conn :file
|
(db/update! conn :file
|
||||||
{:is-shared false
|
{:is-shared false
|
||||||
:modified-at (ct/now)}
|
:modified-at (ct/now)}
|
||||||
{:id id})
|
{:id id})
|
||||||
file)
|
(select-keys file [:id :name :is-shared]))
|
||||||
|
|
||||||
(and (false? (:is-shared file))
|
(and (false? (:is-shared file))
|
||||||
(true? (:is-shared params)))
|
(true? (:is-shared params)))
|
||||||
@@ -939,11 +1014,6 @@
|
|||||||
{:id file-id}
|
{:id file-id}
|
||||||
{::db/return-keys [:id :name :is-shared :deleted-at
|
{::db/return-keys [:id :name :is-shared :deleted-at
|
||||||
:project-id :created-at :modified-at]})]
|
:project-id :created-at :modified-at]})]
|
||||||
|
|
||||||
;; Remove all possible relations for that file
|
|
||||||
(db/delete! conn :file-library-rel
|
|
||||||
{:library-file-id file-id})
|
|
||||||
|
|
||||||
(wrk/submit! {::db/conn conn
|
(wrk/submit! {::db/conn conn
|
||||||
::wrk/task :delete-object
|
::wrk/task :delete-object
|
||||||
::wrk/params {:object :file
|
::wrk/params {:object :file
|
||||||
@@ -1094,53 +1164,47 @@
|
|||||||
|
|
||||||
;; --- MUTATION COMMAND: delete-files-immediatelly
|
;; --- MUTATION COMMAND: delete-files-immediatelly
|
||||||
|
|
||||||
(def ^:private sql:get-delete-team-files-candidates
|
(def ^:private sql:delete-team-files
|
||||||
"SELECT f.id
|
"UPDATE file AS uf SET deleted_at = ?::timestamptz
|
||||||
FROM file AS f
|
FROM (
|
||||||
JOIN project AS p ON (p.id = f.project_id)
|
SELECT f.id
|
||||||
JOIN team AS t ON (t.id = p.team_id)
|
FROM file AS f
|
||||||
WHERE t.deleted_at IS NULL
|
JOIN project AS p ON (p.id = f.project_id)
|
||||||
AND t.id = ?
|
JOIN team AS t ON (t.id = p.team_id)
|
||||||
AND f.id = ANY(?::uuid[])")
|
WHERE t.deleted_at IS NULL
|
||||||
|
AND t.id = ?
|
||||||
|
AND f.id = ANY(?::uuid[])
|
||||||
|
) AS subquery
|
||||||
|
WHERE uf.id = subquery.id
|
||||||
|
RETURNING uf.id, uf.deleted_at;")
|
||||||
|
|
||||||
(def ^:private schema:permanently-delete-team-files
|
(def ^:private schema:permanently-delete-team-files
|
||||||
[:map {:title "permanently-delete-team-files"}
|
[:map {:title "permanently-delete-team-files"}
|
||||||
[:team-id ::sm/uuid]
|
[:team-id ::sm/uuid]
|
||||||
[:ids [::sm/set ::sm/uuid]]])
|
[:ids [::sm/set ::sm/uuid]]])
|
||||||
|
|
||||||
(defn- permanently-delete-team-files
|
|
||||||
[{:keys [::db/conn]} {:keys [::rpc/request-at team-id ids]}]
|
|
||||||
(let [ids (into #{}
|
|
||||||
d/xf:map-id
|
|
||||||
(db/exec! conn [sql:get-delete-team-files-candidates team-id
|
|
||||||
(db/create-array conn "uuid" ids)]))]
|
|
||||||
|
|
||||||
(reduce (fn [acc id]
|
|
||||||
(events/tap :progress {:file-id id :index (inc (count acc)) :total (count ids)})
|
|
||||||
(db/update! conn :file
|
|
||||||
{:deleted-at request-at}
|
|
||||||
{:id id}
|
|
||||||
{::db/return-keys false})
|
|
||||||
(wrk/submit! {::db/conn conn
|
|
||||||
::wrk/task :delete-object
|
|
||||||
::wrk/params {:object :file
|
|
||||||
:deleted-at request-at
|
|
||||||
:id id}})
|
|
||||||
(conj acc id))
|
|
||||||
#{}
|
|
||||||
ids)))
|
|
||||||
|
|
||||||
(sv/defmethod ::permanently-delete-team-files
|
(sv/defmethod ::permanently-delete-team-files
|
||||||
"Mark the specified files to be deleted immediatelly on the
|
"Mark the specified files to be deleted immediatelly on the
|
||||||
specified team. The team-id on params will be used to filter and
|
specified team. The team-id on params will be used to filter and
|
||||||
check writable permissons on team."
|
check writable permissons on team."
|
||||||
|
|
||||||
{::doc/added "2.13"
|
{::doc/added "2.12"
|
||||||
::sm/params schema:permanently-delete-team-files}
|
::sm/params schema:permanently-delete-team-files
|
||||||
|
::db/transaction true}
|
||||||
|
|
||||||
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id team-id] :as params}]
|
[{:keys [::db/conn]} {:keys [::rpc/profile-id ::rpc/request-at team-id ids]}]
|
||||||
(teams/check-edition-permissions! pool profile-id team-id)
|
(teams/check-edition-permissions! conn profile-id team-id)
|
||||||
(sse/response #(db/tx-run! cfg permanently-delete-team-files params)))
|
|
||||||
|
(reduce (fn [acc {:keys [id deleted-at]}]
|
||||||
|
(wrk/submit! {::db/conn conn
|
||||||
|
::wrk/task :delete-object
|
||||||
|
::wrk/params {:object :file
|
||||||
|
:deleted-at deleted-at
|
||||||
|
:id id}})
|
||||||
|
(conj acc id))
|
||||||
|
#{}
|
||||||
|
(db/plan conn [sql:delete-team-files request-at team-id
|
||||||
|
(db/create-array conn "uuid" ids)])))
|
||||||
|
|
||||||
;; --- MUTATION COMMAND: restore-files-immediatelly
|
;; --- MUTATION COMMAND: restore-files-immediatelly
|
||||||
|
|
||||||
@@ -1204,7 +1268,7 @@
|
|||||||
{:keys [files projects]}
|
{:keys [files projects]}
|
||||||
(reduce (fn [result {:keys [id project-id]}]
|
(reduce (fn [result {:keys [id project-id]}]
|
||||||
(let [index (-> result :files count)]
|
(let [index (-> result :files count)]
|
||||||
(events/tap :progress {:file-id id :index (inc index) :total total-files})
|
(events/tap :progress {:file-id id :index index :total total-files})
|
||||||
(restore-file conn id)
|
(restore-file conn id)
|
||||||
|
|
||||||
(-> result
|
(-> result
|
||||||
@@ -1227,7 +1291,7 @@
|
|||||||
(sv/defmethod ::restore-deleted-team-files
|
(sv/defmethod ::restore-deleted-team-files
|
||||||
"Removes the deletion mark from the specified files (and respective
|
"Removes the deletion mark from the specified files (and respective
|
||||||
projects) on the specified team."
|
projects) on the specified team."
|
||||||
{::doc/added "2.13"
|
{::doc/added "2.12"
|
||||||
::sse/stream? true
|
::sse/stream? true
|
||||||
::sm/params schema:restore-deleted-team-files}
|
::sm/params schema:restore-deleted-team-files}
|
||||||
[cfg params]
|
[cfg params]
|
||||||
|
|||||||
@@ -199,13 +199,15 @@
|
|||||||
[cfg {:keys [::rpc/profile-id file-id strip-frames-with-thumbnails] :as params}]
|
[cfg {:keys [::rpc/profile-id file-id strip-frames-with-thumbnails] :as params}]
|
||||||
(db/run! cfg (fn [{:keys [::db/conn] :as cfg}]
|
(db/run! cfg (fn [{:keys [::db/conn] :as cfg}]
|
||||||
(files/check-read-permissions! conn profile-id file-id)
|
(files/check-read-permissions! conn profile-id file-id)
|
||||||
|
|
||||||
(let [team (teams/get-team conn
|
(let [team (teams/get-team conn
|
||||||
:profile-id profile-id
|
:profile-id profile-id
|
||||||
:file-id file-id)
|
:file-id file-id)
|
||||||
|
|
||||||
file (bfc/get-file cfg file-id
|
file (bfc/get-file cfg file-id
|
||||||
:include-deleted? true
|
|
||||||
:realize? true
|
:realize? true
|
||||||
:read-only? true)
|
:read-only? true)
|
||||||
|
|
||||||
strip-frames-with-thumbnails
|
strip-frames-with-thumbnails
|
||||||
(or (nil? strip-frames-with-thumbnails) ;; if not present, default to true
|
(or (nil? strip-frames-with-thumbnails) ;; if not present, default to true
|
||||||
(true? strip-frames-with-thumbnails))]
|
(true? strip-frames-with-thumbnails))]
|
||||||
@@ -331,16 +333,12 @@
|
|||||||
|
|
||||||
;; --- MUTATION COMMAND: create-file-thumbnail
|
;; --- MUTATION COMMAND: create-file-thumbnail
|
||||||
|
|
||||||
(defn- create-file-thumbnail
|
(defn- create-file-thumbnail!
|
||||||
[{:keys [::db/conn ::sto/storage] :as cfg} {:keys [file-id revn props media] :as params}]
|
[{:keys [::db/conn ::sto/storage]} {:keys [file-id revn props media] :as params}]
|
||||||
(media/validate-media-type! media)
|
(media/validate-media-type! media)
|
||||||
(media/validate-media-size! media)
|
(media/validate-media-size! media)
|
||||||
|
|
||||||
(let [file (bfc/get-file cfg file-id
|
(let [props (db/tjson (or props {}))
|
||||||
:include-deleted? true
|
|
||||||
:load-data? false)
|
|
||||||
|
|
||||||
props (db/tjson (or props {}))
|
|
||||||
path (:path media)
|
path (:path media)
|
||||||
mtype (:mtype media)
|
mtype (:mtype media)
|
||||||
hash (sto/calculate-hash path)
|
hash (sto/calculate-hash path)
|
||||||
@@ -369,7 +367,7 @@
|
|||||||
|
|
||||||
(db/update! conn :file-thumbnail
|
(db/update! conn :file-thumbnail
|
||||||
{:media-id (:id media)
|
{:media-id (:id media)
|
||||||
:deleted-at (:deleted-at file)
|
:deleted-at nil
|
||||||
:updated-at tnow
|
:updated-at tnow
|
||||||
:props props}
|
:props props}
|
||||||
{:file-id file-id
|
{:file-id file-id
|
||||||
@@ -380,7 +378,6 @@
|
|||||||
:revn revn
|
:revn revn
|
||||||
:created-at tnow
|
:created-at tnow
|
||||||
:updated-at tnow
|
:updated-at tnow
|
||||||
:deleted-at (:deleted-at file)
|
|
||||||
:props props
|
:props props
|
||||||
:media-id (:id media)}))
|
:media-id (:id media)}))
|
||||||
|
|
||||||
@@ -405,8 +402,6 @@
|
|||||||
::rtry/when rtry/conflict-exception?
|
::rtry/when rtry/conflict-exception?
|
||||||
::sm/params schema:create-file-thumbnail}
|
::sm/params schema:create-file-thumbnail}
|
||||||
|
|
||||||
;; FIXME: do not run the thumbnail upload inside a transaction
|
|
||||||
|
|
||||||
[cfg {:keys [::rpc/profile-id file-id] :as params}]
|
[cfg {:keys [::rpc/profile-id file-id] :as params}]
|
||||||
(db/tx-run! cfg (fn [{:keys [::db/conn] :as cfg}]
|
(db/tx-run! cfg (fn [{:keys [::db/conn] :as cfg}]
|
||||||
;; TODO For now we check read permissions instead of write,
|
;; TODO For now we check read permissions instead of write,
|
||||||
@@ -414,6 +409,6 @@
|
|||||||
;; review this approach on the future.
|
;; review this approach on the future.
|
||||||
(files/check-read-permissions! conn profile-id file-id)
|
(files/check-read-permissions! conn profile-id file-id)
|
||||||
(when-not (db/read-only? conn)
|
(when-not (db/read-only? conn)
|
||||||
(let [media (create-file-thumbnail cfg params)]
|
(let [media (create-file-thumbnail! cfg params)]
|
||||||
{:uri (files/resolve-public-uri (:id media))
|
{:uri (files/resolve-public-uri (:id media))
|
||||||
:id (:id media)})))))
|
:id (:id media)})))))
|
||||||
|
|||||||
@@ -6,7 +6,6 @@
|
|||||||
|
|
||||||
(ns app.rpc.commands.fonts
|
(ns app.rpc.commands.fonts
|
||||||
(:require
|
(:require
|
||||||
[app.binfile.common :as bfc]
|
|
||||||
[app.common.data.macros :as dm]
|
[app.common.data.macros :as dm]
|
||||||
[app.common.exceptions :as ex]
|
[app.common.exceptions :as ex]
|
||||||
[app.common.schema :as sm]
|
[app.common.schema :as sm]
|
||||||
@@ -27,17 +26,7 @@
|
|||||||
[app.rpc.helpers :as rph]
|
[app.rpc.helpers :as rph]
|
||||||
[app.rpc.quotes :as quotes]
|
[app.rpc.quotes :as quotes]
|
||||||
[app.storage :as sto]
|
[app.storage :as sto]
|
||||||
[app.storage.tmp :as tmp]
|
[app.util.services :as sv]))
|
||||||
[app.util.services :as sv]
|
|
||||||
[datoteka.io :as io])
|
|
||||||
(:import
|
|
||||||
java.io.InputStream
|
|
||||||
java.io.OutputStream
|
|
||||||
java.io.SequenceInputStream
|
|
||||||
java.util.Collections))
|
|
||||||
|
|
||||||
(set! *warn-on-reflection* true)
|
|
||||||
|
|
||||||
|
|
||||||
(def valid-weight #{100 200 300 400 500 600 700 800 900 950})
|
(def valid-weight #{100 200 300 400 500 600 700 800 900 950})
|
||||||
(def valid-style #{"normal" "italic"})
|
(def valid-style #{"normal" "italic"})
|
||||||
@@ -77,7 +66,7 @@
|
|||||||
(uuid? file-id)
|
(uuid? file-id)
|
||||||
(let [file (db/get-by-id conn :file file-id {:columns [:id :project-id]})
|
(let [file (db/get-by-id conn :file file-id {:columns [:id :project-id]})
|
||||||
project (db/get-by-id conn :project (:project-id file) {:columns [:id :team-id]})
|
project (db/get-by-id conn :project (:project-id file) {:columns [:id :team-id]})
|
||||||
perms (bfc/get-file-permissions conn profile-id file-id share-id)]
|
perms (files/get-permissions conn profile-id file-id share-id)]
|
||||||
(files/check-read-permissions! perms)
|
(files/check-read-permissions! perms)
|
||||||
(db/query conn :team-font-variant
|
(db/query conn :team-font-variant
|
||||||
{:team-id (:team-id project)
|
{:team-id (:team-id project)
|
||||||
@@ -115,7 +104,7 @@
|
|||||||
|
|
||||||
(defn create-font-variant
|
(defn create-font-variant
|
||||||
[{:keys [::sto/storage ::db/conn]} {:keys [data] :as params}]
|
[{:keys [::sto/storage ::db/conn]} {:keys [data] :as params}]
|
||||||
(letfn [(generate-missing [data]
|
(letfn [(generate-missing! [data]
|
||||||
(let [data (media/run {:cmd :generate-fonts :input data})]
|
(let [data (media/run {:cmd :generate-fonts :input data})]
|
||||||
(when (and (not (contains? data "font/otf"))
|
(when (and (not (contains? data "font/otf"))
|
||||||
(not (contains? data "font/ttf"))
|
(not (contains? data "font/ttf"))
|
||||||
@@ -126,26 +115,8 @@
|
|||||||
:hint "invalid font upload, unable to generate missing font assets"))
|
:hint "invalid font upload, unable to generate missing font assets"))
|
||||||
data))
|
data))
|
||||||
|
|
||||||
(process-chunks [chunks]
|
|
||||||
(let [tmp (tmp/tempfile :prefix "penpot.tempfont." :suffix "")
|
|
||||||
streams (map io/input-stream chunks)
|
|
||||||
streams (Collections/enumeration streams)]
|
|
||||||
(with-open [^OutputStream output (io/output-stream tmp)
|
|
||||||
^InputStream input (SequenceInputStream. streams)]
|
|
||||||
(io/copy input output))
|
|
||||||
tmp))
|
|
||||||
|
|
||||||
(join-chunks [data]
|
|
||||||
(reduce-kv (fn [data mtype content]
|
|
||||||
(if (vector? content)
|
|
||||||
(assoc data mtype (process-chunks content))
|
|
||||||
data))
|
|
||||||
data
|
|
||||||
data))
|
|
||||||
|
|
||||||
(prepare-font [data mtype]
|
(prepare-font [data mtype]
|
||||||
(when-let [resource (get data mtype)]
|
(when-let [resource (get data mtype)]
|
||||||
|
|
||||||
(let [hash (sto/calculate-hash resource)
|
(let [hash (sto/calculate-hash resource)
|
||||||
content (-> (sto/content resource)
|
content (-> (sto/content resource)
|
||||||
(sto/wrap-with-hash hash))]
|
(sto/wrap-with-hash hash))]
|
||||||
@@ -184,8 +155,7 @@
|
|||||||
:otf-file-id (:id otf)
|
:otf-file-id (:id otf)
|
||||||
:ttf-file-id (:id ttf)}))]
|
:ttf-file-id (:id ttf)}))]
|
||||||
|
|
||||||
(let [data (join-chunks data)
|
(let [data (generate-missing! data)
|
||||||
data (generate-missing data)
|
|
||||||
assets (persist-fonts-files! data)
|
assets (persist-fonts-files! data)
|
||||||
result (insert-font-variant! assets)]
|
result (insert-font-variant! assets)]
|
||||||
(vary-meta result assoc ::audit/replace-props (update params :data (comp vec keys))))))
|
(vary-meta result assoc ::audit/replace-props (update params :data (comp vec keys))))))
|
||||||
|
|||||||
@@ -21,7 +21,6 @@
|
|||||||
[app.loggers.audit :as audit]
|
[app.loggers.audit :as audit]
|
||||||
[app.main :as-alias main]
|
[app.main :as-alias main]
|
||||||
[app.media :as media]
|
[app.media :as media]
|
||||||
[app.nitrate :as nitrate]
|
|
||||||
[app.rpc :as-alias rpc]
|
[app.rpc :as-alias rpc]
|
||||||
[app.rpc.climit :as climit]
|
[app.rpc.climit :as climit]
|
||||||
[app.rpc.doc :as-alias doc]
|
[app.rpc.doc :as-alias doc]
|
||||||
@@ -89,8 +88,6 @@
|
|||||||
|
|
||||||
;; --- QUERY: Get profile (own)
|
;; --- QUERY: Get profile (own)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
(sv/defmethod ::get-profile
|
(sv/defmethod ::get-profile
|
||||||
{::rpc/auth false
|
{::rpc/auth false
|
||||||
::doc/added "1.18"
|
::doc/added "1.18"
|
||||||
@@ -101,13 +98,9 @@
|
|||||||
;; no profile-id is in session, and when db call raises not found. In all other
|
;; no profile-id is in session, and when db call raises not found. In all other
|
||||||
;; cases we need to reraise the exception.
|
;; cases we need to reraise the exception.
|
||||||
(try
|
(try
|
||||||
(let [profile (-> (get-profile pool profile-id)
|
(-> (get-profile pool profile-id)
|
||||||
(strip-private-attrs)
|
(strip-private-attrs)
|
||||||
(update :props filter-props))]
|
(update :props filter-props))
|
||||||
(if (contains? cf/flags :nitrate)
|
|
||||||
(nitrate/add-nitrate-licence-to-profile cfg profile)
|
|
||||||
profile))
|
|
||||||
|
|
||||||
(catch Throwable _
|
(catch Throwable _
|
||||||
{:id uuid/zero :fullname "Anonymous User"})))
|
{:id uuid/zero :fullname "Anonymous User"})))
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
inner join team_profile_rel as tpr on (tpr.team_id = p.team_id)
|
inner join team_profile_rel as tpr on (tpr.team_id = p.team_id)
|
||||||
where tpr.profile_id = ?
|
where tpr.profile_id = ?
|
||||||
and p.team_id = ?
|
and p.team_id = ?
|
||||||
and (p.deleted_at is null)
|
and (p.deleted_at is null or p.deleted_at > now())
|
||||||
and (tpr.is_admin = true or
|
and (tpr.is_admin = true or
|
||||||
tpr.is_owner = true or
|
tpr.is_owner = true or
|
||||||
tpr.can_edit = true)
|
tpr.can_edit = true)
|
||||||
@@ -29,7 +29,7 @@
|
|||||||
inner join project_profile_rel as ppr on (ppr.project_id = p.id)
|
inner join project_profile_rel as ppr on (ppr.project_id = p.id)
|
||||||
where ppr.profile_id = ?
|
where ppr.profile_id = ?
|
||||||
and p.team_id = ?
|
and p.team_id = ?
|
||||||
and (p.deleted_at is null)
|
and (p.deleted_at is null or p.deleted_at > now())
|
||||||
and (ppr.is_admin = true or
|
and (ppr.is_admin = true or
|
||||||
ppr.is_owner = true or
|
ppr.is_owner = true or
|
||||||
ppr.can_edit = true)
|
ppr.can_edit = true)
|
||||||
@@ -47,7 +47,7 @@
|
|||||||
left join file_thumbnail as ft on (ft.file_id = f.id and ft.revn = f.revn)
|
left join file_thumbnail as ft on (ft.file_id = f.id and ft.revn = f.revn)
|
||||||
inner join projects as pr on (f.project_id = pr.id)
|
inner join projects as pr on (f.project_id = pr.id)
|
||||||
where f.name ilike ('%' || ? || '%')
|
where f.name ilike ('%' || ? || '%')
|
||||||
and (f.deleted_at is null)
|
and (f.deleted_at is null or f.deleted_at > now())
|
||||||
order by f.created_at asc")
|
order by f.created_at asc")
|
||||||
|
|
||||||
(defn search-files
|
(defn search-files
|
||||||
|
|||||||
@@ -23,7 +23,6 @@
|
|||||||
[app.main :as-alias main]
|
[app.main :as-alias main]
|
||||||
[app.media :as media]
|
[app.media :as media]
|
||||||
[app.msgbus :as mbus]
|
[app.msgbus :as mbus]
|
||||||
[app.nitrate :as nitrate]
|
|
||||||
[app.rpc :as-alias rpc]
|
[app.rpc :as-alias rpc]
|
||||||
[app.rpc.commands.profile :as profile]
|
[app.rpc.commands.profile :as profile]
|
||||||
[app.rpc.doc :as-alias doc]
|
[app.rpc.doc :as-alias doc]
|
||||||
@@ -191,9 +190,7 @@
|
|||||||
::sm/params schema:get-teams}
|
::sm/params schema:get-teams}
|
||||||
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id] :as params}]
|
[{:keys [::db/pool] :as cfg} {:keys [::rpc/profile-id] :as params}]
|
||||||
(dm/with-open [conn (db/open pool)]
|
(dm/with-open [conn (db/open pool)]
|
||||||
(cond->> (get-teams conn profile-id)
|
(get-teams conn profile-id)))
|
||||||
(contains? cf/flags :nitrate)
|
|
||||||
(map #(nitrate/add-org-to-team cfg % params)))))
|
|
||||||
|
|
||||||
(def ^:private sql:get-owned-teams
|
(def ^:private sql:get-owned-teams
|
||||||
"SELECT t.id, t.name,
|
"SELECT t.id, t.name,
|
||||||
|
|||||||
@@ -248,11 +248,11 @@
|
|||||||
|
|
||||||
invitations (into #{}
|
invitations (into #{}
|
||||||
(comp
|
(comp
|
||||||
;; We don't re-send invitations to
|
;; We don't re-send invitations to
|
||||||
;; already existing members
|
;; already existing members
|
||||||
(remove #(contains? team-members (:email %)))
|
(remove #(contains? team-members (:email %)))
|
||||||
;; We don't send invitations to
|
;; We don't send invitations to
|
||||||
;; join-requested members
|
;; join-requested members
|
||||||
(remove #(contains? join-requests (:email %)))
|
(remove #(contains? join-requests (:email %)))
|
||||||
(map (fn [{:keys [email role]}]
|
(map (fn [{:keys [email role]}]
|
||||||
(create-invitation cfg
|
(create-invitation cfg
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
[app.config :as cf]
|
[app.config :as cf]
|
||||||
[app.db :as db]
|
[app.db :as db]
|
||||||
[app.rpc :as-alias rpc]
|
[app.rpc :as-alias rpc]
|
||||||
|
[app.rpc.commands.files :as files]
|
||||||
[app.rpc.commands.teams :as teams]
|
[app.rpc.commands.teams :as teams]
|
||||||
[app.rpc.cond :as-alias cond]
|
[app.rpc.cond :as-alias cond]
|
||||||
[app.rpc.doc :as-alias doc]
|
[app.rpc.doc :as-alias doc]
|
||||||
@@ -120,7 +121,7 @@
|
|||||||
[system {:keys [::rpc/profile-id file-id share-id] :as params}]
|
[system {:keys [::rpc/profile-id file-id share-id] :as params}]
|
||||||
(db/run! system
|
(db/run! system
|
||||||
(fn [{:keys [::db/conn] :as system}]
|
(fn [{:keys [::db/conn] :as system}]
|
||||||
(let [perms (bfc/get-file-permissions conn profile-id file-id share-id)
|
(let [perms (files/get-permissions conn profile-id file-id share-id)
|
||||||
params (-> params
|
params (-> params
|
||||||
(assoc ::perms perms)
|
(assoc ::perms perms)
|
||||||
(assoc :profile-id profile-id))]
|
(assoc :profile-id profile-id))]
|
||||||
|
|||||||
@@ -1,82 +0,0 @@
|
|||||||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
||||||
;;
|
|
||||||
;; Copyright (c) KALEIDOS INC
|
|
||||||
|
|
||||||
(ns app.rpc.management.nitrate
|
|
||||||
"Internal Nitrate HTTP RPC API. Provides authenticated access to
|
|
||||||
organization management and token validation endpoints."
|
|
||||||
(:require
|
|
||||||
[app.common.schema :as sm]
|
|
||||||
[app.common.types.profile :refer [schema:profile]]
|
|
||||||
[app.common.types.team :refer [schema:team]]
|
|
||||||
[app.common.uuid :as uuid]
|
|
||||||
[app.db :as db]
|
|
||||||
[app.msgbus :as mbus]
|
|
||||||
[app.rpc :as-alias rpc]
|
|
||||||
[app.rpc.commands.files :as files]
|
|
||||||
[app.rpc.commands.profile :as profile]
|
|
||||||
[app.rpc.doc :as doc]
|
|
||||||
[app.util.services :as sv]))
|
|
||||||
|
|
||||||
;; ---- API: authenticate
|
|
||||||
|
|
||||||
(sv/defmethod ::authenticate
|
|
||||||
"Authenticate the current user"
|
|
||||||
{::doc/added "2.14"
|
|
||||||
::sm/params [:map]
|
|
||||||
::sm/result schema:profile}
|
|
||||||
[cfg {:keys [::rpc/profile-id] :as params}]
|
|
||||||
(let [profile (profile/get-profile cfg profile-id)]
|
|
||||||
{:id (get profile :id)
|
|
||||||
:name (get profile :fullname)
|
|
||||||
:email (get profile :email)
|
|
||||||
:photo-url (files/resolve-public-uri (get profile :photo-id))}))
|
|
||||||
|
|
||||||
;; ---- API: get-teams
|
|
||||||
|
|
||||||
(def ^:private sql:get-teams
|
|
||||||
"SELECT t.*
|
|
||||||
FROM team AS t
|
|
||||||
JOIN team_profile_rel AS tpr ON t.id = tpr.team_id
|
|
||||||
WHERE tpr.profile_id = ?
|
|
||||||
AND tpr.is_owner IS TRUE
|
|
||||||
AND t.is_default IS FALSE
|
|
||||||
AND t.deleted_at IS NULL;")
|
|
||||||
|
|
||||||
(def ^:private schema:get-teams-result
|
|
||||||
[:vector schema:team])
|
|
||||||
|
|
||||||
(sv/defmethod ::get-teams
|
|
||||||
"List teams for which current user is owner"
|
|
||||||
{::doc/added "2.14"
|
|
||||||
::sm/params [:map]
|
|
||||||
::sm/result schema:get-teams-result}
|
|
||||||
[cfg {:keys [::rpc/profile-id]}]
|
|
||||||
(let [current-user-id (-> (profile/get-profile cfg profile-id) :id)]
|
|
||||||
(->> (db/exec! cfg [sql:get-teams current-user-id])
|
|
||||||
(map #(select-keys % [:id :name])))))
|
|
||||||
|
|
||||||
;; ---- API: notify-team-change
|
|
||||||
|
|
||||||
(def ^:private schema:notify-team-change
|
|
||||||
[:map
|
|
||||||
[:id ::sm/uuid]
|
|
||||||
[:organization-id ::sm/text]])
|
|
||||||
|
|
||||||
(sv/defmethod ::notify-team-change
|
|
||||||
"Notify to Penpot a team change from nitrate"
|
|
||||||
{::doc/added "2.14"
|
|
||||||
::sm/params schema:notify-team-change
|
|
||||||
::rpc/auth false}
|
|
||||||
[cfg {:keys [id organization-id organization-name]}]
|
|
||||||
(let [msgbus (::mbus/msgbus cfg)]
|
|
||||||
(mbus/pub! msgbus
|
|
||||||
;;TODO There is a bug on dashboard with teams notifications.
|
|
||||||
;;For now we send it to uuid/zero instead of team-id
|
|
||||||
:topic uuid/zero
|
|
||||||
:message {:type :team-org-change
|
|
||||||
:team-id id
|
|
||||||
:organization-id organization-id
|
|
||||||
:organization-name organization-name})))
|
|
||||||
183
backend/src/app/rpc/management/subscription.clj
Normal file
183
backend/src/app/rpc/management/subscription.clj
Normal file
@@ -0,0 +1,183 @@
|
|||||||
|
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
;;
|
||||||
|
;; Copyright (c) KALEIDOS INC
|
||||||
|
|
||||||
|
(ns app.rpc.management.subscription
|
||||||
|
(:require
|
||||||
|
[app.common.logging :as l]
|
||||||
|
[app.common.schema :as sm]
|
||||||
|
[app.common.schema.generators :as sg]
|
||||||
|
[app.common.time :as ct]
|
||||||
|
[app.db :as db]
|
||||||
|
[app.rpc :as-alias rpc]
|
||||||
|
[app.rpc.commands.profile :as profile]
|
||||||
|
[app.rpc.doc :as doc]
|
||||||
|
[app.util.services :as sv]))
|
||||||
|
|
||||||
|
;; ---- RPC METHOD: AUTHENTICATE
|
||||||
|
|
||||||
|
(def ^:private
|
||||||
|
schema:authenticate-params
|
||||||
|
[:map {:title "authenticate-params"}])
|
||||||
|
|
||||||
|
(def ^:private
|
||||||
|
schema:authenticate-result
|
||||||
|
[:map {:title "authenticate-result"}
|
||||||
|
[:profile-id ::sm/uuid]])
|
||||||
|
|
||||||
|
(sv/defmethod ::auth
|
||||||
|
{::doc/added "2.12"
|
||||||
|
::sm/params schema:authenticate-params
|
||||||
|
::sm/result schema:authenticate-result}
|
||||||
|
[_ {:keys [::rpc/profile-id]}]
|
||||||
|
{:profile-id profile-id})
|
||||||
|
|
||||||
|
;; ---- RPC METHOD: GET-CUSTOMER
|
||||||
|
|
||||||
|
;; FIXME: move to app.common.time
|
||||||
|
(def ^:private schema:timestamp
|
||||||
|
(sm/type-schema
|
||||||
|
{:type ::timestamp
|
||||||
|
:pred ct/inst?
|
||||||
|
:type-properties
|
||||||
|
{:title "inst"
|
||||||
|
:description "The same as :app.common.time/inst but encodes to epoch"
|
||||||
|
:error/message "should be an instant"
|
||||||
|
:gen/gen (->> (sg/small-int)
|
||||||
|
(sg/fmap (fn [v] (ct/inst v))))
|
||||||
|
:decode/string #(some-> % ct/inst)
|
||||||
|
:encode/string #(some-> % inst-ms)
|
||||||
|
:decode/json #(some-> % ct/inst)
|
||||||
|
:encode/json #(some-> % inst-ms)}}))
|
||||||
|
|
||||||
|
(def ^:private schema:subscription
|
||||||
|
[:map {:title "Subscription"}
|
||||||
|
[:id ::sm/text]
|
||||||
|
[:customer-id ::sm/text]
|
||||||
|
[:type [:enum
|
||||||
|
"unlimited"
|
||||||
|
"professional"
|
||||||
|
"enterprise"]]
|
||||||
|
[:status [:enum
|
||||||
|
"active"
|
||||||
|
"canceled"
|
||||||
|
"incomplete"
|
||||||
|
"incomplete_expired"
|
||||||
|
"past_due"
|
||||||
|
"paused"
|
||||||
|
"trialing"
|
||||||
|
"unpaid"]]
|
||||||
|
|
||||||
|
[:billing-period [:enum
|
||||||
|
"month"
|
||||||
|
"day"
|
||||||
|
"week"
|
||||||
|
"year"]]
|
||||||
|
[:quantity :int]
|
||||||
|
[:description [:maybe ::sm/text]]
|
||||||
|
[:created-at schema:timestamp]
|
||||||
|
[:start-date [:maybe schema:timestamp]]
|
||||||
|
[:ended-at [:maybe schema:timestamp]]
|
||||||
|
[:trial-end [:maybe schema:timestamp]]
|
||||||
|
[:trial-start [:maybe schema:timestamp]]
|
||||||
|
[:cancel-at [:maybe schema:timestamp]]
|
||||||
|
[:canceled-at [:maybe schema:timestamp]]
|
||||||
|
[:current-period-end [:maybe schema:timestamp]]
|
||||||
|
[:current-period-start [:maybe schema:timestamp]]
|
||||||
|
[:cancel-at-period-end :boolean]
|
||||||
|
|
||||||
|
[:cancellation-details
|
||||||
|
[:map {:title "CancellationDetails"}
|
||||||
|
[:comment [:maybe ::sm/text]]
|
||||||
|
[:reason [:maybe ::sm/text]]
|
||||||
|
[:feedback [:maybe
|
||||||
|
[:enum
|
||||||
|
"customer_service"
|
||||||
|
"low_quality"
|
||||||
|
"missing_feature"
|
||||||
|
"other"
|
||||||
|
"switched_service"
|
||||||
|
"too_complex"
|
||||||
|
"too_expensive"
|
||||||
|
"unused"]]]]]])
|
||||||
|
|
||||||
|
(def ^:private sql:get-customer-slots
|
||||||
|
"WITH teams AS (
|
||||||
|
SELECT tpr.team_id AS id,
|
||||||
|
tpr.profile_id AS profile_id
|
||||||
|
FROM team_profile_rel AS tpr
|
||||||
|
WHERE tpr.is_owner IS true
|
||||||
|
AND tpr.profile_id = ?
|
||||||
|
), teams_with_slots AS (
|
||||||
|
SELECT tpr.team_id AS id,
|
||||||
|
count(*) AS total
|
||||||
|
FROM team_profile_rel AS tpr
|
||||||
|
WHERE tpr.team_id IN (SELECT id FROM teams)
|
||||||
|
AND tpr.can_edit IS true
|
||||||
|
GROUP BY 1
|
||||||
|
ORDER BY 2
|
||||||
|
)
|
||||||
|
SELECT max(total) AS total FROM teams_with_slots;")
|
||||||
|
|
||||||
|
(defn- get-customer-slots
|
||||||
|
[cfg profile-id]
|
||||||
|
(let [result (db/exec-one! cfg [sql:get-customer-slots profile-id])]
|
||||||
|
(:total result)))
|
||||||
|
|
||||||
|
(def ^:private schema:get-customer-params
|
||||||
|
[:map])
|
||||||
|
|
||||||
|
(def ^:private schema:get-customer-result
|
||||||
|
[:map
|
||||||
|
[:id ::sm/uuid]
|
||||||
|
[:name :string]
|
||||||
|
[:num-editors ::sm/int]
|
||||||
|
[:subscription {:optional true} schema:subscription]])
|
||||||
|
|
||||||
|
(sv/defmethod ::get-customer
|
||||||
|
{::doc/added "2.12"
|
||||||
|
::sm/params schema:get-customer-params
|
||||||
|
::sm/result schema:get-customer-result}
|
||||||
|
[cfg {:keys [::rpc/profile-id]}]
|
||||||
|
(let [profile (profile/get-profile cfg profile-id)]
|
||||||
|
{:id (get profile :id)
|
||||||
|
:name (get profile :fullname)
|
||||||
|
:email (get profile :email)
|
||||||
|
:num-editors (get-customer-slots cfg profile-id)
|
||||||
|
:subscription (-> profile :props :subscription)}))
|
||||||
|
|
||||||
|
|
||||||
|
;; ---- RPC METHOD: GET-CUSTOMER
|
||||||
|
|
||||||
|
(def ^:private schema:update-customer-params
|
||||||
|
[:map
|
||||||
|
[:subscription [:maybe schema:subscription]]])
|
||||||
|
|
||||||
|
(def ^:private schema:update-customer-result
|
||||||
|
[:map])
|
||||||
|
|
||||||
|
(sv/defmethod ::update-customer
|
||||||
|
{::doc/added "2.12"
|
||||||
|
::sm/params schema:update-customer-params
|
||||||
|
::sm/result schema:update-customer-result}
|
||||||
|
[cfg {:keys [::rpc/profile-id subscription]}]
|
||||||
|
(let [{:keys [props] :as profile}
|
||||||
|
(profile/get-profile cfg profile-id ::db/for-update true)
|
||||||
|
|
||||||
|
props
|
||||||
|
(assoc props :subscription subscription)]
|
||||||
|
|
||||||
|
(l/dbg :hint "update customer"
|
||||||
|
:profile-id (str profile-id)
|
||||||
|
:subscription-type (get subscription :type)
|
||||||
|
:subscription-status (get subscription :status)
|
||||||
|
:subscription-quantity (get subscription :quantity))
|
||||||
|
|
||||||
|
(db/update! cfg :profile
|
||||||
|
{:props (db/tjson props)}
|
||||||
|
{:id profile-id}
|
||||||
|
{::db/return-keys false})
|
||||||
|
|
||||||
|
nil))
|
||||||
@@ -104,29 +104,28 @@
|
|||||||
(def ^:private schema:limit
|
(def ^:private schema:limit
|
||||||
[:and
|
[:and
|
||||||
[:map
|
[:map
|
||||||
[::name :keyword]
|
[::name :any]
|
||||||
[::strategy schema:strategy]
|
[::strategy schema:strategy]
|
||||||
[::key :string]
|
[::key :string]
|
||||||
[::opts :string]
|
[::opts :string]]
|
||||||
[::capacity {:optional true} ::sm/int]
|
[:or
|
||||||
[::rate {:optional true} ::sm/int]
|
[:map
|
||||||
[::interval {:optional true} ::ct/duration]
|
[::capacity ::sm/int]
|
||||||
[::params {:optional true} [::sm/vec :any]]
|
[::rate ::sm/int]
|
||||||
[::permits {:optional true} ::sm/int]
|
[::internal ::ct/duration]
|
||||||
[::unit {:optional true} [:enum :days :hours :minutes :seconds :weeks]]]
|
[::params [::sm/vec :any]]]
|
||||||
[:fn (fn [attrs]
|
[:map
|
||||||
(let [contains-fn (partial contains? attrs)]
|
[::nreq ::sm/int]
|
||||||
(or (every? contains-fn [::capacity ::rate ::interval])
|
[::unit [:enum :days :hours :minutes :seconds :weeks]]]]])
|
||||||
(every? contains-fn [::permits ::unit]))))]])
|
|
||||||
|
|
||||||
(def ^:private schema:limits
|
(def ^:private schema:limits
|
||||||
[:map-of :keyword [::sm/vec schema:limit]])
|
[:map-of :keyword [::sm/vec schema:limit]])
|
||||||
|
|
||||||
(def ^:private valid-limit-tuple?
|
(def ^:private valid-limit-tuple?
|
||||||
(sm/validator schema:limit-tuple))
|
(sm/lazy-validator schema:limit-tuple))
|
||||||
|
|
||||||
(def ^:private valid-rlimit-instance?
|
(def ^:private valid-rlimit-instance?
|
||||||
(sm/validator ::rpc/rlimit))
|
(sm/lazy-validator ::rpc/rlimit))
|
||||||
|
|
||||||
(defmethod parse-limit :window
|
(defmethod parse-limit :window
|
||||||
[[name strategy opts :as vlimit]]
|
[[name strategy opts :as vlimit]]
|
||||||
@@ -135,16 +134,16 @@
|
|||||||
(merge
|
(merge
|
||||||
{::name name
|
{::name name
|
||||||
::strategy strategy}
|
::strategy strategy}
|
||||||
(if-let [[_ permits unit] (re-find window-opts-re opts)]
|
(if-let [[_ nreq unit] (re-find window-opts-re opts)]
|
||||||
(let [permits (parse-long permits)]
|
(let [nreq (parse-long nreq)]
|
||||||
{::permits permits
|
{::nreq nreq
|
||||||
::unit (case unit
|
::unit (case unit
|
||||||
"d" :days
|
"d" :days
|
||||||
"h" :hours
|
"h" :hours
|
||||||
"m" :minutes
|
"m" :minutes
|
||||||
"s" :seconds
|
"s" :seconds
|
||||||
"w" :weeks)
|
"w" :weeks)
|
||||||
::key (str "penpot.rlimit." (cf/get :tenant) ".window." (d/name name))
|
::key (str "ratelimit.window." (d/name name))
|
||||||
::opts opts})
|
::opts opts})
|
||||||
(ex/raise :type :validation
|
(ex/raise :type :validation
|
||||||
:code :invalid-window-limit-opts
|
:code :invalid-window-limit-opts
|
||||||
@@ -165,15 +164,15 @@
|
|||||||
::interval interval
|
::interval interval
|
||||||
::opts opts
|
::opts opts
|
||||||
::params [(->seconds interval) rate capacity]
|
::params [(->seconds interval) rate capacity]
|
||||||
::key (str "penpot.rlimit." (cf/get :tenant) ".bucket." (d/name name))})
|
::key (str "ratelimit.bucket." (d/name name))})
|
||||||
(ex/raise :type :validation
|
(ex/raise :type :validation
|
||||||
:code :invalid-bucket-limit-opts
|
:code :invalid-bucket-limit-opts
|
||||||
:hint (str/ffmt "looks like '%' does not have a valid format" opts))))
|
:hint (str/ffmt "looks like '%' does not have a valid format" opts))))
|
||||||
|
|
||||||
(defmethod process-limit :bucket
|
(defmethod process-limit :bucket
|
||||||
[rconn profile-id now {:keys [::key ::params ::service ::capacity ::interval ::rate] :as limit}]
|
[rconn user-id now {:keys [::key ::params ::service ::capacity ::interval ::rate] :as limit}]
|
||||||
(let [script (-> bucket-rate-limit-script
|
(let [script (-> bucket-rate-limit-script
|
||||||
(assoc ::rscript/keys [(str key "." service "." profile-id)])
|
(assoc ::rscript/keys [(str key "." service "." user-id)])
|
||||||
(assoc ::rscript/vals (conj params (->seconds now))))
|
(assoc ::rscript/vals (conj params (->seconds now))))
|
||||||
result (rds/eval rconn script)
|
result (rds/eval rconn script)
|
||||||
allowed? (boolean (nth result 0))
|
allowed? (boolean (nth result 0))
|
||||||
@@ -193,18 +192,18 @@
|
|||||||
(assoc ::lresult/remaining remaining))))
|
(assoc ::lresult/remaining remaining))))
|
||||||
|
|
||||||
(defmethod process-limit :window
|
(defmethod process-limit :window
|
||||||
[rconn profile-id now {:keys [::permits ::unit ::key ::service] :as limit}]
|
[rconn user-id now {:keys [::nreq ::unit ::key ::service] :as limit}]
|
||||||
(let [ts (ct/truncate now unit)
|
(let [ts (ct/truncate now unit)
|
||||||
ttl (ct/diff now (ct/plus ts {unit 1}))
|
ttl (ct/diff now (ct/plus ts {unit 1}))
|
||||||
script (-> window-rate-limit-script
|
script (-> window-rate-limit-script
|
||||||
(assoc ::rscript/keys [(str key "." service "." profile-id "." (ct/format-inst ts))])
|
(assoc ::rscript/keys [(str key "." service "." user-id "." (ct/format-inst ts))])
|
||||||
(assoc ::rscript/vals [permits (->seconds ttl)]))
|
(assoc ::rscript/vals [nreq (->seconds ttl)]))
|
||||||
result (rds/eval rconn script)
|
result (rds/eval rconn script)
|
||||||
allowed? (boolean (nth result 0))
|
allowed? (boolean (nth result 0))
|
||||||
remaining (nth result 1)]
|
remaining (nth result 1)]
|
||||||
(l/trace :hint "limit processed"
|
(l/trace :hint "limit processed"
|
||||||
:service service
|
:service service
|
||||||
:name (name (::name limit))
|
:limit (name (::name limit))
|
||||||
:strategy (name (::strategy limit))
|
:strategy (name (::strategy limit))
|
||||||
:opts (::opts limit)
|
:opts (::opts limit)
|
||||||
:allowed allowed?
|
:allowed allowed?
|
||||||
@@ -215,8 +214,8 @@
|
|||||||
(assoc ::lresult/reset (ct/plus ts {unit 1})))))
|
(assoc ::lresult/reset (ct/plus ts {unit 1})))))
|
||||||
|
|
||||||
(defn- process-limits
|
(defn- process-limits
|
||||||
[rconn profile-id limits now]
|
[rconn user-id limits now]
|
||||||
(let [results (into [] (map (partial process-limit rconn profile-id now)) limits)
|
(let [results (into [] (map (partial process-limit rconn user-id now)) limits)
|
||||||
remaining (->> results
|
remaining (->> results
|
||||||
(d/index-by ::name ::lresult/remaining)
|
(d/index-by ::name ::lresult/remaining)
|
||||||
(uri/map->query-string))
|
(uri/map->query-string))
|
||||||
@@ -228,7 +227,7 @@
|
|||||||
|
|
||||||
(when rejected
|
(when rejected
|
||||||
(l/warn :hint "rejected rate limit"
|
(l/warn :hint "rejected rate limit"
|
||||||
:profile-id (str profile-id)
|
:user-id (str user-id)
|
||||||
:limit-service (-> rejected ::service name)
|
:limit-service (-> rejected ::service name)
|
||||||
:limit-name (-> rejected ::name name)
|
:limit-name (-> rejected ::name name)
|
||||||
:limit-strategy (-> rejected ::strategy name)))
|
:limit-strategy (-> rejected ::strategy name)))
|
||||||
@@ -372,9 +371,12 @@
|
|||||||
(defn- on-refresh-error
|
(defn- on-refresh-error
|
||||||
[_ cause]
|
[_ cause]
|
||||||
(when-not (instance? java.util.concurrent.RejectedExecutionException cause)
|
(when-not (instance? java.util.concurrent.RejectedExecutionException cause)
|
||||||
(l/warn :hint "unexpected exception on loading config"
|
(if-let [explain (-> cause ex-data ex/explain)]
|
||||||
:cause cause
|
(l/warn ::l/raw (str "unable to refresh config, invalid format:\n" explain)
|
||||||
::l/sync? true)))
|
::l/sync? true)
|
||||||
|
(l/warn :hint "unexpected exception on loading config"
|
||||||
|
:cause cause
|
||||||
|
::l/sync? true))))
|
||||||
|
|
||||||
(defn- get-config-path
|
(defn- get-config-path
|
||||||
[]
|
[]
|
||||||
|
|||||||
@@ -25,9 +25,9 @@ local allowed = filled >= requested
|
|||||||
local newTokens = filled
|
local newTokens = filled
|
||||||
if allowed then
|
if allowed then
|
||||||
newTokens = filled - requested
|
newTokens = filled - requested
|
||||||
redis.call("hset", tokensKey, "tokens", newTokens, "timestamp", timestamp)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
redis.call("hset", tokensKey, "tokens", newTokens, "timestamp", timestamp)
|
||||||
redis.call("expire", tokensKey, ttl)
|
redis.call("expire", tokensKey, ttl)
|
||||||
|
|
||||||
return { allowed, newTokens }
|
return { allowed, newTokens }
|
||||||
|
|||||||
@@ -17,7 +17,6 @@
|
|||||||
[app.setup.templates]
|
[app.setup.templates]
|
||||||
[buddy.core.codecs :as bc]
|
[buddy.core.codecs :as bc]
|
||||||
[buddy.core.nonce :as bn]
|
[buddy.core.nonce :as bn]
|
||||||
[cuerdas.core :as str]
|
|
||||||
[integrant.core :as ig]))
|
[integrant.core :as ig]))
|
||||||
|
|
||||||
(defn- generate-random-key
|
(defn- generate-random-key
|
||||||
@@ -89,38 +88,7 @@
|
|||||||
(-> (get-all-props conn)
|
(-> (get-all-props conn)
|
||||||
(assoc :secret-key secret)
|
(assoc :secret-key secret)
|
||||||
(assoc :tokens-key (keys/derive secret :salt "tokens"))
|
(assoc :tokens-key (keys/derive secret :salt "tokens"))
|
||||||
|
(assoc :management-key (keys/derive secret :salt "management"))
|
||||||
(update :instance-id handle-instance-id conn (db/read-only? pool)))))))
|
(update :instance-id handle-instance-id conn (db/read-only? pool)))))))
|
||||||
|
|
||||||
(sm/register! ::props [:map-of :keyword ::sm/any])
|
(sm/register! ::props [:map-of :keyword ::sm/any])
|
||||||
|
|
||||||
|
|
||||||
(defmethod ig/init-key ::shared-keys
|
|
||||||
[_ {:keys [::props] :as cfg}]
|
|
||||||
(let [secret (get props :secret-key)]
|
|
||||||
(d/without-nils
|
|
||||||
{:exporter
|
|
||||||
(let [key (or (get cfg :exporter)
|
|
||||||
(-> (keys/derive secret :salt "exporter")
|
|
||||||
(bc/bytes->b64-str true)))]
|
|
||||||
(if (or (str/empty? key)
|
|
||||||
(str/blank? key))
|
|
||||||
(do
|
|
||||||
(l/wrn :hint "exporter key is disabled because empty string found")
|
|
||||||
nil)
|
|
||||||
(do
|
|
||||||
(l/inf :hint "exporter key initialized" :key (d/obfuscate-string key))
|
|
||||||
key)))
|
|
||||||
|
|
||||||
:nitrate
|
|
||||||
(let [key (or (get cfg :nitrate)
|
|
||||||
(-> (keys/derive secret :salt "nitrate")
|
|
||||||
(bc/bytes->b64-str true)))]
|
|
||||||
(if (or (str/empty? key)
|
|
||||||
(str/blank? key))
|
|
||||||
(do
|
|
||||||
(l/wrn :hint "nitrate key is disabled because empty string found")
|
|
||||||
nil)
|
|
||||||
(do
|
|
||||||
(l/inf :hint "nitrate key initialized" :key (d/obfuscate-string key))
|
|
||||||
key)))})))
|
|
||||||
|
|
||||||
|
|||||||
@@ -9,35 +9,48 @@
|
|||||||
modification of time offset (useful for testing and time adjustments)."
|
modification of time offset (useful for testing and time adjustments)."
|
||||||
(:require
|
(:require
|
||||||
[app.common.logging :as l]
|
[app.common.logging :as l]
|
||||||
[app.common.time :as ct]))
|
[app.common.time :as ct]
|
||||||
|
[app.setup :as-alias setup]
|
||||||
|
[integrant.core :as ig])
|
||||||
|
(:import
|
||||||
|
java.time.Clock
|
||||||
|
java.time.Duration
|
||||||
|
java.time.Instant
|
||||||
|
java.time.ZoneId))
|
||||||
|
|
||||||
(defonce state
|
(defonce current
|
||||||
(atom {}))
|
(atom {:clock (Clock/systemDefaultZone)
|
||||||
|
:offset nil}))
|
||||||
|
|
||||||
(defn assign-offset
|
(defmethod ig/init-key ::setup/clock
|
||||||
"Assign virtual clock offset to a specific user. Is the responsability
|
[_ _]
|
||||||
of RPC module to properly bind the correct clock to the user
|
(add-watch current ::common
|
||||||
request."
|
(fn [_ _ _ {:keys [clock offset]}]
|
||||||
[profile-id duration]
|
(let [clock (if (ct/duration? offset)
|
||||||
(swap! state (fn [state]
|
(Clock/offset ^Clock clock
|
||||||
(if (nil? duration)
|
^Duration offset)
|
||||||
(dissoc state profile-id)
|
clock)]
|
||||||
(assoc state profile-id duration)))))
|
(l/wrn :hint "altering clock" :clock (str clock))
|
||||||
|
(alter-var-root #'ct/*clock* (constantly clock))))))
|
||||||
|
|
||||||
(defn get-offset
|
|
||||||
[profile-id]
|
|
||||||
(get @state profile-id))
|
|
||||||
|
|
||||||
(defn get-clock
|
(defmethod ig/halt-key! ::setup/clock
|
||||||
[profile-id]
|
[_ _]
|
||||||
(if-let [offset (get-offset profile-id)]
|
(remove-watch current ::common))
|
||||||
(ct/offset-clock offset)
|
|
||||||
(ct/get-system-clock)))
|
|
||||||
|
|
||||||
(defn set-global-clock
|
(defn fixed
|
||||||
|
"Get fixed clock, mainly used in tests"
|
||||||
|
[instant]
|
||||||
|
(Clock/fixed ^Instant (ct/inst instant)
|
||||||
|
^ZoneId (ZoneId/of "Z")))
|
||||||
|
|
||||||
|
(defn set-offset!
|
||||||
|
[duration]
|
||||||
|
(swap! current assoc :offset (some-> duration ct/duration)))
|
||||||
|
|
||||||
|
(defn set-clock!
|
||||||
([]
|
([]
|
||||||
(set-global-clock (ct/get-system-clock)))
|
(swap! current assoc :clock (Clock/systemDefaultZone)))
|
||||||
([clock]
|
([clock]
|
||||||
(assert (ct/clock? clock) "expected valid clock instance")
|
(when (instance? Clock clock)
|
||||||
(l/wrn :hint "altering clock" :clock (str clock))
|
(swap! current assoc :clock clock))))
|
||||||
(alter-var-root #'ct/*clock* (constantly clock))))
|
|
||||||
|
|||||||
@@ -35,9 +35,6 @@
|
|||||||
:assets-s3 :s3
|
:assets-s3 :s3
|
||||||
nil)))
|
nil)))
|
||||||
|
|
||||||
(def default-bucket
|
|
||||||
"file-media-object")
|
|
||||||
|
|
||||||
(def valid-buckets
|
(def valid-buckets
|
||||||
#{"file-media-object"
|
#{"file-media-object"
|
||||||
"team-font-variant"
|
"team-font-variant"
|
||||||
|
|||||||
@@ -25,7 +25,7 @@
|
|||||||
[app.common.time :as ct]
|
[app.common.time :as ct]
|
||||||
[app.config :as cf]
|
[app.config :as cf]
|
||||||
[app.db :as db]
|
[app.db :as db]
|
||||||
[app.storage :as sto]
|
[app.storage :as-alias sto]
|
||||||
[app.storage.impl :as impl]
|
[app.storage.impl :as impl]
|
||||||
[integrant.core :as ig]))
|
[integrant.core :as ig]))
|
||||||
|
|
||||||
@@ -130,7 +130,7 @@
|
|||||||
[{:keys [metadata]}]
|
[{:keys [metadata]}]
|
||||||
(or (some-> metadata :bucket)
|
(or (some-> metadata :bucket)
|
||||||
(some-> metadata :reference d/name)
|
(some-> metadata :reference d/name)
|
||||||
sto/default-bucket))
|
"file-media-object"))
|
||||||
|
|
||||||
(defn- process-objects!
|
(defn- process-objects!
|
||||||
[conn has-refs? bucket objects]
|
[conn has-refs? bucket objects]
|
||||||
|
|||||||
@@ -45,8 +45,7 @@
|
|||||||
:deleted-at (ct/format-inst deleted-at))
|
:deleted-at (ct/format-inst deleted-at))
|
||||||
|
|
||||||
(db/update! conn :file
|
(db/update! conn :file
|
||||||
{:deleted-at deleted-at
|
{:deleted-at deleted-at}
|
||||||
:is-shared false}
|
|
||||||
{:id id}
|
{:id id}
|
||||||
{::db/return-keys false})
|
{::db/return-keys false})
|
||||||
|
|
||||||
@@ -54,7 +53,7 @@
|
|||||||
(not *team-deletion*))
|
(not *team-deletion*))
|
||||||
;; NOTE: we don't prevent file deletion on absorb operation failure
|
;; NOTE: we don't prevent file deletion on absorb operation failure
|
||||||
(try
|
(try
|
||||||
(db/tx-run! cfg files/absorb-library id)
|
(db/tx-run! cfg files/absorb-library! id)
|
||||||
(catch Throwable cause
|
(catch Throwable cause
|
||||||
(l/warn :hint "error on absorbing library"
|
(l/warn :hint "error on absorbing library"
|
||||||
:file-id id
|
:file-id id
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
"A generic asynchronous events notifications subsystem; used mainly
|
"A generic asynchronous events notifications subsystem; used mainly
|
||||||
for mark event points in functions and be able to attach listeners
|
for mark event points in functions and be able to attach listeners
|
||||||
to them. Mainly used in http.sse for progress reporting."
|
to them. Mainly used in http.sse for progress reporting."
|
||||||
(:refer-clojure :exclude [run!])
|
(:refer-clojure :exclude [tap run!])
|
||||||
(:require
|
(:require
|
||||||
[app.common.exceptions :as ex]
|
[app.common.exceptions :as ex]
|
||||||
[app.common.logging :as l]
|
[app.common.logging :as l]
|
||||||
|
|||||||
@@ -7,18 +7,10 @@
|
|||||||
(ns app.util.template
|
(ns app.util.template
|
||||||
(:require
|
(:require
|
||||||
[app.common.exceptions :as ex]
|
[app.common.exceptions :as ex]
|
||||||
[cuerdas.core :as str]
|
|
||||||
[selmer.filters :as sf]
|
|
||||||
[selmer.parser :as sp]))
|
[selmer.parser :as sp]))
|
||||||
|
|
||||||
;; (sp/cache-off!)
|
;; (sp/cache-off!)
|
||||||
|
|
||||||
(sf/add-filter! :abbreviate
|
|
||||||
(fn [s n]
|
|
||||||
(let [n (parse-long n)]
|
|
||||||
(str/abbreviate s n))))
|
|
||||||
|
|
||||||
|
|
||||||
(defn render
|
(defn render
|
||||||
[path context]
|
[path context]
|
||||||
(try
|
(try
|
||||||
|
|||||||
@@ -137,34 +137,33 @@ RETURNING task.id, task.queue")
|
|||||||
::wait)))
|
::wait)))
|
||||||
|
|
||||||
(run-batch []
|
(run-batch []
|
||||||
(try
|
(let [rconn (rds/connect cfg)]
|
||||||
(let [rconn (rds/connect cfg)]
|
(try
|
||||||
(try
|
(-> cfg
|
||||||
(-> cfg
|
(assoc ::rds/conn rconn)
|
||||||
(assoc ::rds/conn rconn)
|
(db/tx-run! run-batch'))
|
||||||
(db/tx-run! run-batch'))
|
|
||||||
(finally
|
|
||||||
(.close ^AutoCloseable rconn))))
|
|
||||||
|
|
||||||
(catch InterruptedException cause
|
(catch InterruptedException cause
|
||||||
(throw cause))
|
(throw cause))
|
||||||
|
(catch Exception cause
|
||||||
|
(cond
|
||||||
|
(rds/exception? cause)
|
||||||
|
(do
|
||||||
|
(l/wrn :hint "redis exception (will retry in an instant)" :cause cause)
|
||||||
|
(px/sleep timeout))
|
||||||
|
|
||||||
(catch Exception cause
|
(db/sql-exception? cause)
|
||||||
(cond
|
(do
|
||||||
(rds/exception? cause)
|
(l/wrn :hint "database exception (will retry in an instant)" :cause cause)
|
||||||
(do
|
(px/sleep timeout))
|
||||||
(l/wrn :hint "redis exception (will retry in an instant)" :cause cause)
|
|
||||||
(px/sleep timeout))
|
|
||||||
|
|
||||||
(db/sql-exception? cause)
|
:else
|
||||||
(do
|
(do
|
||||||
(l/wrn :hint "database exception (will retry in an instant)" :cause cause)
|
(l/err :hint "unhandled exception (will retry in an instant)" :cause cause)
|
||||||
(px/sleep timeout))
|
(px/sleep timeout))))
|
||||||
|
|
||||||
:else
|
(finally
|
||||||
(do
|
(.close ^AutoCloseable rconn)))))
|
||||||
(l/err :hint "unhandled exception (will retry in an instant)" :cause cause)
|
|
||||||
(px/sleep timeout))))))
|
|
||||||
|
|
||||||
(dispatcher []
|
(dispatcher []
|
||||||
(l/inf :hint "started")
|
(l/inf :hint "started")
|
||||||
@@ -177,7 +176,7 @@ RETURNING task.id, task.queue")
|
|||||||
(catch InterruptedException _
|
(catch InterruptedException _
|
||||||
(l/trc :hint "interrupted"))
|
(l/trc :hint "interrupted"))
|
||||||
(catch Throwable cause
|
(catch Throwable cause
|
||||||
(l/err :hint "unexpected exception" :cause cause))
|
(l/err :hint " unexpected exception" :cause cause))
|
||||||
(finally
|
(finally
|
||||||
(l/inf :hint "terminated"))))]
|
(l/inf :hint "terminated"))))]
|
||||||
|
|
||||||
|
|||||||
@@ -595,8 +595,8 @@
|
|||||||
(px/exec! :virtual #(rcp/write-body-to-stream body nil output))
|
(px/exec! :virtual #(rcp/write-body-to-stream body nil output))
|
||||||
(into []
|
(into []
|
||||||
(map (fn [{:keys [event data]}]
|
(map (fn [{:keys [event data]}]
|
||||||
(d/vec2 (keyword event)
|
[(keyword event)
|
||||||
(tr/decode-str data))))
|
(tr/decode-str data)]))
|
||||||
(parse-sse (slurp' input)))
|
(parse-sse (slurp' input)))
|
||||||
(finally
|
(finally
|
||||||
(.close input)))))
|
(.close input)))))
|
||||||
|
|||||||
@@ -86,7 +86,7 @@
|
|||||||
(t/deftest shared-key-auth
|
(t/deftest shared-key-auth
|
||||||
(let [handler (#'app.http.middleware/wrap-shared-key-auth
|
(let [handler (#'app.http.middleware/wrap-shared-key-auth
|
||||||
(fn [req] {::yres/status 200})
|
(fn [req] {::yres/status 200})
|
||||||
{:test1 "secret-key"})]
|
"secret-key")]
|
||||||
|
|
||||||
(let [response (handler (->DummyRequest {} {}))]
|
(let [response (handler (->DummyRequest {} {}))]
|
||||||
(t/is (= 403 (::yres/status response))))
|
(t/is (= 403 (::yres/status response))))
|
||||||
@@ -95,9 +95,6 @@
|
|||||||
(t/is (= 403 (::yres/status response))))
|
(t/is (= 403 (::yres/status response))))
|
||||||
|
|
||||||
(let [response (handler (->DummyRequest {"x-shared-key" "secret-key"} {}))]
|
(let [response (handler (->DummyRequest {"x-shared-key" "secret-key"} {}))]
|
||||||
(t/is (= 403 (::yres/status response))))
|
|
||||||
|
|
||||||
(let [response (handler (->DummyRequest {"x-shared-key" "test1 secret-key"} {}))]
|
|
||||||
(t/is (= 200 (::yres/status response))))))
|
(t/is (= 200 (::yres/status response))))))
|
||||||
|
|
||||||
(t/deftest access-token-authz
|
(t/deftest access-token-authz
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
[app.db.sql :as sql]
|
[app.db.sql :as sql]
|
||||||
[app.http :as http]
|
[app.http :as http]
|
||||||
[app.rpc :as-alias rpc]
|
[app.rpc :as-alias rpc]
|
||||||
|
[app.setup.clock :as clock]
|
||||||
[app.storage :as sto]
|
[app.storage :as sto]
|
||||||
[backend-tests.helpers :as th]
|
[backend-tests.helpers :as th]
|
||||||
[clojure.test :as t]
|
[clojure.test :as t]
|
||||||
@@ -133,7 +134,7 @@
|
|||||||
;; this will run pending task triggered by deleting user snapshot
|
;; this will run pending task triggered by deleting user snapshot
|
||||||
(th/run-pending-tasks!)
|
(th/run-pending-tasks!)
|
||||||
|
|
||||||
(binding [ct/*clock* (ct/fixed-clock (ct/in-future {:days 8}))]
|
(binding [ct/*clock* (clock/fixed (ct/in-future {:days 8}))]
|
||||||
(let [res (th/run-task! :objects-gc {})]
|
(let [res (th/run-task! :objects-gc {})]
|
||||||
;; delete 2 snapshots and 2 file data entries
|
;; delete 2 snapshots and 2 file data entries
|
||||||
(t/is (= 4 (:processed res)))))))))
|
(t/is (= 4 (:processed res)))))))))
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
[app.http :as http]
|
[app.http :as http]
|
||||||
[app.rpc :as-alias rpc]
|
[app.rpc :as-alias rpc]
|
||||||
[app.rpc.commands.files :as files]
|
[app.rpc.commands.files :as files]
|
||||||
|
[app.setup.clock :as clock]
|
||||||
[app.storage :as sto]
|
[app.storage :as sto]
|
||||||
[backend-tests.helpers :as th]
|
[backend-tests.helpers :as th]
|
||||||
[clojure.test :as t]
|
[clojure.test :as t]
|
||||||
@@ -841,7 +842,7 @@
|
|||||||
out (th/command! data)
|
out (th/command! data)
|
||||||
error (:error out)]
|
error (:error out)]
|
||||||
|
|
||||||
;; (th/print-result! out)
|
;; (th/print-result! out)
|
||||||
(t/is (th/ex-info? error))
|
(t/is (th/ex-info? error))
|
||||||
(t/is (th/ex-of-type? error :not-found))))
|
(t/is (th/ex-of-type? error :not-found))))
|
||||||
|
|
||||||
@@ -863,7 +864,7 @@
|
|||||||
out (th/command! data)
|
out (th/command! data)
|
||||||
error (:error out)]
|
error (:error out)]
|
||||||
|
|
||||||
;; (th/print-result! out)
|
;; (th/print-result! out)
|
||||||
(t/is (th/ex-info? error))
|
(t/is (th/ex-info? error))
|
||||||
(t/is (th/ex-of-type? error :not-found))))
|
(t/is (th/ex-of-type? error :not-found))))
|
||||||
|
|
||||||
@@ -921,7 +922,7 @@
|
|||||||
(t/is (= 0 (:processed result))))
|
(t/is (= 0 (:processed result))))
|
||||||
|
|
||||||
;; run permanent deletion
|
;; run permanent deletion
|
||||||
(binding [ct/*clock* (ct/fixed-clock (ct/in-future {:days 8}))]
|
(binding [ct/*clock* (clock/fixed (ct/in-future {:days 8}))]
|
||||||
(let [result (th/run-task! :objects-gc {})]
|
(let [result (th/run-task! :objects-gc {})]
|
||||||
(t/is (= 3 (:processed result)))))
|
(t/is (= 3 (:processed result)))))
|
||||||
|
|
||||||
@@ -1261,7 +1262,7 @@
|
|||||||
(t/is (= 1 (count rows)))
|
(t/is (= 1 (count rows)))
|
||||||
(t/is (every? #(some? (:data %)) rows)))
|
(t/is (every? #(some? (:data %)) rows)))
|
||||||
|
|
||||||
;; Mark the file ellegible again for GC
|
;; Mark the file ellegible again for GC
|
||||||
(th/db-update! :file
|
(th/db-update! :file
|
||||||
{:has-media-trimmed false}
|
{:has-media-trimmed false}
|
||||||
{:id (:id file)})
|
{:id (:id file)})
|
||||||
@@ -1318,7 +1319,7 @@
|
|||||||
{:file-id (:id file)
|
{:file-id (:id file)
|
||||||
:type "fragment"}
|
:type "fragment"}
|
||||||
{:order-by [:created-at]})]
|
{:order-by [:created-at]})]
|
||||||
;; (pp/pprint rows)
|
;; (pp/pprint rows)
|
||||||
(t/is (= 2 (count rows)))
|
(t/is (= 2 (count rows)))
|
||||||
(t/is (nil? (:data row1)))
|
(t/is (nil? (:data row1)))
|
||||||
(t/is (= "storage" (:backend row1)))
|
(t/is (= "storage" (:backend row1)))
|
||||||
@@ -1874,7 +1875,7 @@
|
|||||||
file-id (uuid/next)
|
file-id (uuid/next)
|
||||||
now (ct/inst "2025-10-31T00:00:00Z")]
|
now (ct/inst "2025-10-31T00:00:00Z")]
|
||||||
|
|
||||||
(binding [ct/*clock* (ct/fixed-clock now)]
|
(binding [ct/*clock* (clock/fixed now)]
|
||||||
(let [data {::th/type :create-file
|
(let [data {::th/type :create-file
|
||||||
::rpc/profile-id (:id prof)
|
::rpc/profile-id (:id prof)
|
||||||
:project-id proj-id
|
:project-id proj-id
|
||||||
@@ -1920,11 +1921,7 @@
|
|||||||
;; (th/print-result! out)
|
;; (th/print-result! out)
|
||||||
(t/is (nil? (:error out)))
|
(t/is (nil? (:error out)))
|
||||||
(let [result (:result out)]
|
(let [result (:result out)]
|
||||||
(t/is (fn? result))
|
(t/is (= (:ids data) result)))
|
||||||
|
|
||||||
(let [[ev1 ev2 :as events] (th/consume-sse result)]
|
|
||||||
(t/is (= 2 (count events)))
|
|
||||||
(t/is (= (:ids data) (val ev2)))))
|
|
||||||
|
|
||||||
(let [row (th/db-exec-one! ["select * from file where id = ?" file-id])]
|
(let [row (th/db-exec-one! ["select * from file where id = ?" file-id])]
|
||||||
(t/is (= (:deleted-at row) now)))))))
|
(t/is (= (:deleted-at row) now)))))))
|
||||||
@@ -1936,7 +1933,7 @@
|
|||||||
file-id (uuid/next)
|
file-id (uuid/next)
|
||||||
now (ct/inst "2025-10-31T00:00:00Z")]
|
now (ct/inst "2025-10-31T00:00:00Z")]
|
||||||
|
|
||||||
(binding [ct/*clock* (ct/fixed-clock now)]
|
(binding [ct/*clock* (clock/fixed now)]
|
||||||
(let [data {::th/type :create-file
|
(let [data {::th/type :create-file
|
||||||
::rpc/profile-id (:id prof)
|
::rpc/profile-id (:id prof)
|
||||||
:project-id proj-id
|
:project-id proj-id
|
||||||
@@ -1999,7 +1996,7 @@
|
|||||||
team-id (:default-team-id profile)
|
team-id (:default-team-id profile)
|
||||||
now (ct/inst "2025-10-31T00:00:00Z")]
|
now (ct/inst "2025-10-31T00:00:00Z")]
|
||||||
|
|
||||||
(binding [ct/*clock* (ct/fixed-clock now)]
|
(binding [ct/*clock* (clock/fixed now)]
|
||||||
(let [project (th/create-project* 1 {:profile-id (:id profile)
|
(let [project (th/create-project* 1 {:profile-id (:id profile)
|
||||||
:team-id team-id})
|
:team-id team-id})
|
||||||
file (th/create-file* 1 {:profile-id (:id profile)
|
file (th/create-file* 1 {:profile-id (:id profile)
|
||||||
|
|||||||
@@ -85,7 +85,7 @@
|
|||||||
(t/is (map? (:result out))))
|
(t/is (map? (:result out))))
|
||||||
|
|
||||||
;; run the task again
|
;; run the task again
|
||||||
(let [res (binding [ct/*clock* (ct/fixed-clock (ct/in-future {:minutes 31}))]
|
(let [res (binding [ct/*clock* (clock/fixed (ct/in-future {:minutes 31}))]
|
||||||
(th/run-task! "storage-gc-touched" {}))]
|
(th/run-task! "storage-gc-touched" {}))]
|
||||||
(t/is (= 2 (:freeze res))))
|
(t/is (= 2 (:freeze res))))
|
||||||
|
|
||||||
@@ -136,7 +136,7 @@
|
|||||||
(t/is (some? (sto/get-object storage (:media-id row2))))
|
(t/is (some? (sto/get-object storage (:media-id row2))))
|
||||||
|
|
||||||
;; run the task again
|
;; run the task again
|
||||||
(let [res (binding [ct/*clock* (ct/fixed-clock (ct/in-future {:minutes 31}))]
|
(let [res (binding [ct/*clock* (clock/fixed (ct/in-future {:minutes 31}))]
|
||||||
(th/run-task! :storage-gc-touched {}))]
|
(th/run-task! :storage-gc-touched {}))]
|
||||||
(t/is (= 1 (:delete res)))
|
(t/is (= 1 (:delete res)))
|
||||||
(t/is (= 0 (:freeze res))))
|
(t/is (= 0 (:freeze res))))
|
||||||
@@ -147,7 +147,7 @@
|
|||||||
|
|
||||||
;; Run the storage gc deleted task, it should permanently delete
|
;; Run the storage gc deleted task, it should permanently delete
|
||||||
;; all storage objects related to the deleted thumbnails
|
;; all storage objects related to the deleted thumbnails
|
||||||
(binding [ct/*clock* (ct/fixed-clock (ct/in-future {:days 8}))]
|
(binding [ct/*clock* (clock/fixed (ct/in-future {:days 8}))]
|
||||||
(let [res (th/run-task! :storage-gc-deleted {})]
|
(let [res (th/run-task! :storage-gc-deleted {})]
|
||||||
(t/is (= 1 (:deleted res)))))
|
(t/is (= 1 (:deleted res)))))
|
||||||
|
|
||||||
@@ -247,7 +247,7 @@
|
|||||||
|
|
||||||
;; Run the storage gc deleted task, it should permanently delete
|
;; Run the storage gc deleted task, it should permanently delete
|
||||||
;; all storage objects related to the deleted thumbnails
|
;; all storage objects related to the deleted thumbnails
|
||||||
(binding [ct/*clock* (ct/fixed-clock (ct/in-future {:days 8}))]
|
(binding [ct/*clock* (clock/fixed (ct/in-future {:days 8}))]
|
||||||
(let [result (th/run-task! :storage-gc-deleted {})]
|
(let [result (th/run-task! :storage-gc-deleted {})]
|
||||||
(t/is (= 1 (:deleted result)))))
|
(t/is (= 1 (:deleted result)))))
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
[app.db :as db]
|
[app.db :as db]
|
||||||
[app.http :as http]
|
[app.http :as http]
|
||||||
[app.rpc :as-alias rpc]
|
[app.rpc :as-alias rpc]
|
||||||
|
[app.setup.clock :as clock]
|
||||||
[app.storage :as sto]
|
[app.storage :as sto]
|
||||||
[backend-tests.helpers :as th]
|
[backend-tests.helpers :as th]
|
||||||
[clojure.test :as t]
|
[clojure.test :as t]
|
||||||
@@ -146,7 +147,7 @@
|
|||||||
(t/is (= 0 (:freeze res)))
|
(t/is (= 0 (:freeze res)))
|
||||||
(t/is (= 0 (:delete res))))
|
(t/is (= 0 (:delete res))))
|
||||||
|
|
||||||
(binding [ct/*clock* (ct/fixed-clock (ct/in-future {:days 8}))]
|
(binding [ct/*clock* (clock/fixed (ct/in-future {:days 8}))]
|
||||||
(let [res (th/run-task! :objects-gc {})]
|
(let [res (th/run-task! :objects-gc {})]
|
||||||
(t/is (= 2 (:processed res))))
|
(t/is (= 2 (:processed res))))
|
||||||
|
|
||||||
@@ -207,7 +208,7 @@
|
|||||||
(t/is (= 0 (:freeze res)))
|
(t/is (= 0 (:freeze res)))
|
||||||
(t/is (= 0 (:delete res))))
|
(t/is (= 0 (:delete res))))
|
||||||
|
|
||||||
(binding [ct/*clock* (ct/fixed-clock (ct/in-future {:days 8}))]
|
(binding [ct/*clock* (clock/fixed (ct/in-future {:days 8}))]
|
||||||
(let [res (th/run-task! :objects-gc {})]
|
(let [res (th/run-task! :objects-gc {})]
|
||||||
(t/is (= 1 (:processed res))))
|
(t/is (= 1 (:processed res))))
|
||||||
|
|
||||||
@@ -267,7 +268,7 @@
|
|||||||
(t/is (= 0 (:freeze res)))
|
(t/is (= 0 (:freeze res)))
|
||||||
(t/is (= 0 (:delete res))))
|
(t/is (= 0 (:delete res))))
|
||||||
|
|
||||||
(binding [ct/*clock* (ct/fixed-clock (ct/in-future {:days 8}))]
|
(binding [ct/*clock* (clock/fixed (ct/in-future {:days 8}))]
|
||||||
(let [res (th/run-task! :objects-gc {})]
|
(let [res (th/run-task! :objects-gc {})]
|
||||||
(t/is (= 1 (:processed res))))
|
(t/is (= 1 (:processed res))))
|
||||||
|
|
||||||
|
|||||||
@@ -536,7 +536,7 @@
|
|||||||
:token rtoken}
|
:token rtoken}
|
||||||
|
|
||||||
{:keys [result error] :as out} (th/command! data)]
|
{:keys [result error] :as out} (th/command! data)]
|
||||||
;; (th/print-result! out)
|
;; (th/print-result! out)
|
||||||
(t/is (nil? error))
|
(t/is (nil? error))
|
||||||
(t/is (map? result))
|
(t/is (map? result))
|
||||||
(t/is (string? (:invitation-token result))))))
|
(t/is (string? (:invitation-token result))))))
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
[app.db :as db]
|
[app.db :as db]
|
||||||
[app.http :as http]
|
[app.http :as http]
|
||||||
[app.rpc :as-alias rpc]
|
[app.rpc :as-alias rpc]
|
||||||
|
[app.setup.clock :as clock]
|
||||||
[backend-tests.helpers :as th]
|
[backend-tests.helpers :as th]
|
||||||
[clojure.test :as t]))
|
[clojure.test :as t]))
|
||||||
|
|
||||||
@@ -30,7 +31,7 @@
|
|||||||
:team-id (:id team)
|
:team-id (:id team)
|
||||||
:name "test project"}
|
:name "test project"}
|
||||||
out (th/command! data)]
|
out (th/command! data)]
|
||||||
;; (th/print-result! out)
|
;; (th/print-result! out)
|
||||||
|
|
||||||
(t/is (nil? (:error out)))
|
(t/is (nil? (:error out)))
|
||||||
(let [result (:result out)]
|
(let [result (:result out)]
|
||||||
@@ -93,7 +94,7 @@
|
|||||||
:id project-id}
|
:id project-id}
|
||||||
out (th/command! data)]
|
out (th/command! data)]
|
||||||
|
|
||||||
;; (th/print-result! out)
|
;; (th/print-result! out)
|
||||||
(t/is (nil? (:error out)))
|
(t/is (nil? (:error out)))
|
||||||
(t/is (nil? (:result out))))
|
(t/is (nil? (:result out))))
|
||||||
|
|
||||||
@@ -227,7 +228,7 @@
|
|||||||
(t/is (= 0 (count result)))))
|
(t/is (= 0 (count result)))))
|
||||||
|
|
||||||
;; run permanent deletion
|
;; run permanent deletion
|
||||||
(binding [ct/*clock* (ct/fixed-clock (ct/in-future {:days 8}))]
|
(binding [ct/*clock* (clock/fixed (ct/in-future {:days 8}))]
|
||||||
(let [result (th/run-task! :objects-gc {})]
|
(let [result (th/run-task! :objects-gc {})]
|
||||||
(t/is (= 1 (:processed result)))))
|
(t/is (= 1 (:processed result)))))
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
[app.db :as db]
|
[app.db :as db]
|
||||||
[app.http :as http]
|
[app.http :as http]
|
||||||
[app.rpc :as-alias rpc]
|
[app.rpc :as-alias rpc]
|
||||||
|
[app.setup.clock :as clock]
|
||||||
[app.storage :as sto]
|
[app.storage :as sto]
|
||||||
[app.tokens :as tokens]
|
[app.tokens :as tokens]
|
||||||
[backend-tests.helpers :as th]
|
[backend-tests.helpers :as th]
|
||||||
@@ -525,7 +526,7 @@
|
|||||||
(t/is (= :not-found (:type edata)))))
|
(t/is (= :not-found (:type edata)))))
|
||||||
|
|
||||||
;; run permanent deletion
|
;; run permanent deletion
|
||||||
(binding [ct/*clock* (ct/fixed-clock (ct/in-future {:days 8}))]
|
(binding [ct/*clock* (clock/fixed (ct/in-future {:days 8}))]
|
||||||
(let [result (th/run-task! :objects-gc {})]
|
(let [result (th/run-task! :objects-gc {})]
|
||||||
(t/is (= 2 (:processed result)))))
|
(t/is (= 2 (:processed result)))))
|
||||||
|
|
||||||
@@ -582,7 +583,7 @@
|
|||||||
(t/is (= 1 (count rows)))
|
(t/is (= 1 (count rows)))
|
||||||
(t/is (ct/inst? (:deleted-at (first rows)))))
|
(t/is (ct/inst? (:deleted-at (first rows)))))
|
||||||
|
|
||||||
(binding [ct/*clock* (ct/fixed-clock (ct/in-future {:days 8}))]
|
(binding [ct/*clock* (clock/fixed (ct/in-future {:days 8}))]
|
||||||
(let [result (th/run-task! :objects-gc {})]
|
(let [result (th/run-task! :objects-gc {})]
|
||||||
(t/is (= 7 (:processed result)))))))
|
(t/is (= 7 (:processed result)))))))
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
[app.common.uuid :as uuid]
|
[app.common.uuid :as uuid]
|
||||||
[app.db :as db]
|
[app.db :as db]
|
||||||
[app.rpc :as-alias rpc]
|
[app.rpc :as-alias rpc]
|
||||||
|
[app.setup.clock :as clock]
|
||||||
[app.storage :as sto]
|
[app.storage :as sto]
|
||||||
[backend-tests.helpers :as th]
|
[backend-tests.helpers :as th]
|
||||||
[clojure.test :as t]
|
[clojure.test :as t]
|
||||||
@@ -98,14 +99,14 @@
|
|||||||
::sto/expired-at (ct/in-future {:hours 1})
|
::sto/expired-at (ct/in-future {:hours 1})
|
||||||
:content-type "text/plain"})]
|
:content-type "text/plain"})]
|
||||||
|
|
||||||
(binding [ct/*clock* (ct/fixed-clock (ct/in-future {:minutes 0}))]
|
(binding [ct/*clock* (clock/fixed (ct/in-future {:minutes 0}))]
|
||||||
(let [res (th/run-task! :storage-gc-deleted {})]
|
(let [res (th/run-task! :storage-gc-deleted {})]
|
||||||
(t/is (= 1 (:deleted res)))))
|
(t/is (= 1 (:deleted res)))))
|
||||||
|
|
||||||
(let [res (th/db-exec-one! ["select count(*) from storage_object;"])]
|
(let [res (th/db-exec-one! ["select count(*) from storage_object;"])]
|
||||||
(t/is (= 2 (:count res))))
|
(t/is (= 2 (:count res))))
|
||||||
|
|
||||||
(binding [ct/*clock* (ct/fixed-clock (ct/in-future {:minutes 61}))]
|
(binding [ct/*clock* (clock/fixed (ct/in-future {:minutes 61}))]
|
||||||
(let [res (th/run-task! :storage-gc-deleted {})]
|
(let [res (th/run-task! :storage-gc-deleted {})]
|
||||||
(t/is (= 1 (:deleted res)))))
|
(t/is (= 1 (:deleted res)))))
|
||||||
|
|
||||||
@@ -330,22 +331,22 @@
|
|||||||
:content-type "text/plain"})]
|
:content-type "text/plain"})]
|
||||||
|
|
||||||
|
|
||||||
(binding [ct/*clock* (ct/fixed-clock now)]
|
(binding [ct/*clock* (clock/fixed now)]
|
||||||
(let [res (th/run-task! :storage-gc-touched {})]
|
(let [res (th/run-task! :storage-gc-touched {})]
|
||||||
(t/is (= 0 (:freeze res)))
|
(t/is (= 0 (:freeze res)))
|
||||||
(t/is (= 0 (:delete res)))))
|
(t/is (= 0 (:delete res)))))
|
||||||
|
|
||||||
|
|
||||||
(binding [ct/*clock* (ct/fixed-clock (ct/plus now {:minutes 1}))]
|
(binding [ct/*clock* (clock/fixed (ct/plus now {:minutes 1}))]
|
||||||
(let [res (th/run-task! :storage-gc-touched {})]
|
(let [res (th/run-task! :storage-gc-touched {})]
|
||||||
(t/is (= 0 (:freeze res)))
|
(t/is (= 0 (:freeze res)))
|
||||||
(t/is (= 1 (:delete res)))))
|
(t/is (= 1 (:delete res)))))
|
||||||
|
|
||||||
|
|
||||||
(binding [ct/*clock* (ct/fixed-clock (ct/plus now {:hours 1}))]
|
(binding [ct/*clock* (clock/fixed (ct/plus now {:hours 1}))]
|
||||||
(let [res (th/run-task! :storage-gc-deleted {})]
|
(let [res (th/run-task! :storage-gc-deleted {})]
|
||||||
(t/is (= 0 (:deleted res)))))
|
(t/is (= 0 (:deleted res)))))
|
||||||
|
|
||||||
(binding [ct/*clock* (ct/fixed-clock (ct/plus now {:hours 2}))]
|
(binding [ct/*clock* (clock/fixed (ct/plus now {:hours 2}))]
|
||||||
(let [res (th/run-task! :storage-gc-deleted {})]
|
(let [res (th/run-task! :storage-gc-deleted {})]
|
||||||
(t/is (= 0 (:deleted res)))))))
|
(t/is (= 0 (:deleted res)))))))
|
||||||
|
|||||||
1145
backend/yarn.lock
Normal file
1145
backend/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
7
common/.gitignore
vendored
Normal file
7
common/.gitignore
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
.pnp.*
|
||||||
|
.yarn/*
|
||||||
|
!.yarn/patches
|
||||||
|
!.yarn/plugins
|
||||||
|
!.yarn/releases
|
||||||
|
!.yarn/sdks
|
||||||
|
!.yarn/versions
|
||||||
@@ -29,7 +29,8 @@
|
|||||||
java-http-clj/java-http-clj {:mvn/version "0.4.3"}
|
java-http-clj/java-http-clj {:mvn/version "0.4.3"}
|
||||||
integrant/integrant {:mvn/version "1.0.0"}
|
integrant/integrant {:mvn/version "1.0.0"}
|
||||||
|
|
||||||
funcool/cuerdas {:mvn/version "2026.415"}
|
funcool/tubax {:mvn/version "2021.05.20-0"}
|
||||||
|
funcool/cuerdas {:mvn/version "2025.06.16-414"}
|
||||||
funcool/promesa
|
funcool/promesa
|
||||||
{:git/sha "46048fc0d4bf5466a2a4121f5d52aefa6337f2e8"
|
{:git/sha "46048fc0d4bf5466a2a4121f5d52aefa6337f2e8"
|
||||||
:git/url "https://github.com/funcool/promesa"}
|
:git/url "https://github.com/funcool/promesa"}
|
||||||
@@ -59,7 +60,7 @@
|
|||||||
thheller/shadow-cljs {:mvn/version "3.2.0"}
|
thheller/shadow-cljs {:mvn/version "3.2.0"}
|
||||||
com.clojure-goes-fast/clj-async-profiler {:mvn/version "RELEASE"}
|
com.clojure-goes-fast/clj-async-profiler {:mvn/version "RELEASE"}
|
||||||
com.bhauman/rebel-readline {:mvn/version "RELEASE"}
|
com.bhauman/rebel-readline {:mvn/version "RELEASE"}
|
||||||
criterium/criterium {:mvn/version "0.4.6"}
|
criterium/criterium {:mvn/version "RELEASE"}
|
||||||
mockery/mockery {:mvn/version "RELEASE"}}
|
mockery/mockery {:mvn/version "RELEASE"}}
|
||||||
:extra-paths ["test" "dev"]}
|
:extra-paths ["test" "dev"]}
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"author": "Kaleidos INC",
|
"author": "Kaleidos INC",
|
||||||
"private": true,
|
"private": true,
|
||||||
"packageManager": "pnpm@10.28.2+sha512.41872f037ad22f7348e3b1debbaf7e867cfd448f2726d9cf74c08f19507c31d2c8e7a11525b983febc2df640b5438dee6023ebb1f84ed43cc2d654d2bc326264",
|
"packageManager": "yarn@4.9.2+sha512.1fc009bc09d13cfd0e19efa44cbfc2b9cf6ca61482725eb35bbc5e257e093ebf4130db6dfe15d604ff4b79efd8e1e8e99b25fa7d0a6197c9f9826358d4d65c3c",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
@@ -23,9 +23,9 @@
|
|||||||
"fmt:clj:check": "cljfmt check --parallel=false src/ test/",
|
"fmt:clj:check": "cljfmt check --parallel=false src/ test/",
|
||||||
"fmt:clj": "cljfmt fix --parallel=true src/ test/",
|
"fmt:clj": "cljfmt fix --parallel=true src/ test/",
|
||||||
"lint:clj": "clj-kondo --parallel=true --lint src/",
|
"lint:clj": "clj-kondo --parallel=true --lint src/",
|
||||||
"lint": "pnpm run lint:clj",
|
"lint": "yarn run lint:clj",
|
||||||
"watch:test": "concurrently \"clojure -M:dev:shadow-cljs watch test\" \"nodemon -C -d 2 -w target/tests/ --exec 'node target/tests/test.js'\"",
|
"watch:test": "concurrently \"clojure -M:dev:shadow-cljs watch test\" \"nodemon -C -d 2 -w target/tests/ --exec 'node target/tests/test.js'\"",
|
||||||
"build:test": "clojure -M:dev:shadow-cljs compile test",
|
"build:test": "clojure -M:dev:shadow-cljs compile test",
|
||||||
"test": "pnpm run build:test && node target/tests/test.js"
|
"test": "yarn run build:test && node target/tests/test.js"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
489
common/pnpm-lock.yaml
generated
489
common/pnpm-lock.yaml
generated
@@ -1,489 +0,0 @@
|
|||||||
lockfileVersion: '9.0'
|
|
||||||
|
|
||||||
settings:
|
|
||||||
autoInstallPeers: true
|
|
||||||
excludeLinksFromLockfile: false
|
|
||||||
|
|
||||||
importers:
|
|
||||||
|
|
||||||
.:
|
|
||||||
dependencies:
|
|
||||||
date-fns:
|
|
||||||
specifier: ^4.1.0
|
|
||||||
version: 4.1.0
|
|
||||||
devDependencies:
|
|
||||||
concurrently:
|
|
||||||
specifier: ^9.1.2
|
|
||||||
version: 9.2.1
|
|
||||||
nodemon:
|
|
||||||
specifier: ^3.1.10
|
|
||||||
version: 3.1.11
|
|
||||||
source-map-support:
|
|
||||||
specifier: ^0.5.21
|
|
||||||
version: 0.5.21
|
|
||||||
ws:
|
|
||||||
specifier: ^8.18.2
|
|
||||||
version: 8.18.3
|
|
||||||
|
|
||||||
packages:
|
|
||||||
|
|
||||||
ansi-regex@5.0.1:
|
|
||||||
resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
|
|
||||||
engines: {node: '>=8'}
|
|
||||||
|
|
||||||
ansi-styles@4.3.0:
|
|
||||||
resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
|
|
||||||
engines: {node: '>=8'}
|
|
||||||
|
|
||||||
anymatch@3.1.3:
|
|
||||||
resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==}
|
|
||||||
engines: {node: '>= 8'}
|
|
||||||
|
|
||||||
balanced-match@1.0.2:
|
|
||||||
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
|
|
||||||
|
|
||||||
binary-extensions@2.3.0:
|
|
||||||
resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==}
|
|
||||||
engines: {node: '>=8'}
|
|
||||||
|
|
||||||
brace-expansion@1.1.12:
|
|
||||||
resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==}
|
|
||||||
|
|
||||||
braces@3.0.3:
|
|
||||||
resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==}
|
|
||||||
engines: {node: '>=8'}
|
|
||||||
|
|
||||||
buffer-from@1.1.2:
|
|
||||||
resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
|
|
||||||
|
|
||||||
chalk@4.1.2:
|
|
||||||
resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
|
|
||||||
engines: {node: '>=10'}
|
|
||||||
|
|
||||||
chokidar@3.6.0:
|
|
||||||
resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==}
|
|
||||||
engines: {node: '>= 8.10.0'}
|
|
||||||
|
|
||||||
cliui@8.0.1:
|
|
||||||
resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==}
|
|
||||||
engines: {node: '>=12'}
|
|
||||||
|
|
||||||
color-convert@2.0.1:
|
|
||||||
resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
|
|
||||||
engines: {node: '>=7.0.0'}
|
|
||||||
|
|
||||||
color-name@1.1.4:
|
|
||||||
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
|
|
||||||
|
|
||||||
concat-map@0.0.1:
|
|
||||||
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
|
|
||||||
|
|
||||||
concurrently@9.2.1:
|
|
||||||
resolution: {integrity: sha512-fsfrO0MxV64Znoy8/l1vVIjjHa29SZyyqPgQBwhiDcaW8wJc2W3XWVOGx4M3oJBnv/zdUZIIp1gDeS98GzP8Ng==}
|
|
||||||
engines: {node: '>=18'}
|
|
||||||
hasBin: true
|
|
||||||
|
|
||||||
date-fns@4.1.0:
|
|
||||||
resolution: {integrity: sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==}
|
|
||||||
|
|
||||||
debug@4.4.3:
|
|
||||||
resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==}
|
|
||||||
engines: {node: '>=6.0'}
|
|
||||||
peerDependencies:
|
|
||||||
supports-color: '*'
|
|
||||||
peerDependenciesMeta:
|
|
||||||
supports-color:
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
emoji-regex@8.0.0:
|
|
||||||
resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
|
|
||||||
|
|
||||||
escalade@3.2.0:
|
|
||||||
resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}
|
|
||||||
engines: {node: '>=6'}
|
|
||||||
|
|
||||||
fill-range@7.1.1:
|
|
||||||
resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
|
|
||||||
engines: {node: '>=8'}
|
|
||||||
|
|
||||||
fsevents@2.3.3:
|
|
||||||
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
|
|
||||||
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
|
|
||||||
os: [darwin]
|
|
||||||
|
|
||||||
get-caller-file@2.0.5:
|
|
||||||
resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==}
|
|
||||||
engines: {node: 6.* || 8.* || >= 10.*}
|
|
||||||
|
|
||||||
glob-parent@5.1.2:
|
|
||||||
resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
|
|
||||||
engines: {node: '>= 6'}
|
|
||||||
|
|
||||||
has-flag@3.0.0:
|
|
||||||
resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==}
|
|
||||||
engines: {node: '>=4'}
|
|
||||||
|
|
||||||
has-flag@4.0.0:
|
|
||||||
resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
|
|
||||||
engines: {node: '>=8'}
|
|
||||||
|
|
||||||
ignore-by-default@1.0.1:
|
|
||||||
resolution: {integrity: sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==}
|
|
||||||
|
|
||||||
is-binary-path@2.1.0:
|
|
||||||
resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==}
|
|
||||||
engines: {node: '>=8'}
|
|
||||||
|
|
||||||
is-extglob@2.1.1:
|
|
||||||
resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
|
|
||||||
engines: {node: '>=0.10.0'}
|
|
||||||
|
|
||||||
is-fullwidth-code-point@3.0.0:
|
|
||||||
resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==}
|
|
||||||
engines: {node: '>=8'}
|
|
||||||
|
|
||||||
is-glob@4.0.3:
|
|
||||||
resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
|
|
||||||
engines: {node: '>=0.10.0'}
|
|
||||||
|
|
||||||
is-number@7.0.0:
|
|
||||||
resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
|
|
||||||
engines: {node: '>=0.12.0'}
|
|
||||||
|
|
||||||
minimatch@3.1.2:
|
|
||||||
resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
|
|
||||||
|
|
||||||
ms@2.1.3:
|
|
||||||
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
|
|
||||||
|
|
||||||
nodemon@3.1.11:
|
|
||||||
resolution: {integrity: sha512-is96t8F/1//UHAjNPHpbsNY46ELPpftGUoSVNXwUfMk/qdjSylYrWSu1XavVTBOn526kFiOR733ATgNBCQyH0g==}
|
|
||||||
engines: {node: '>=10'}
|
|
||||||
hasBin: true
|
|
||||||
|
|
||||||
normalize-path@3.0.0:
|
|
||||||
resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
|
|
||||||
engines: {node: '>=0.10.0'}
|
|
||||||
|
|
||||||
picomatch@2.3.1:
|
|
||||||
resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
|
|
||||||
engines: {node: '>=8.6'}
|
|
||||||
|
|
||||||
pstree.remy@1.1.8:
|
|
||||||
resolution: {integrity: sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==}
|
|
||||||
|
|
||||||
readdirp@3.6.0:
|
|
||||||
resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
|
|
||||||
engines: {node: '>=8.10.0'}
|
|
||||||
|
|
||||||
require-directory@2.1.1:
|
|
||||||
resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==}
|
|
||||||
engines: {node: '>=0.10.0'}
|
|
||||||
|
|
||||||
rxjs@7.8.2:
|
|
||||||
resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==}
|
|
||||||
|
|
||||||
semver@7.7.3:
|
|
||||||
resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==}
|
|
||||||
engines: {node: '>=10'}
|
|
||||||
hasBin: true
|
|
||||||
|
|
||||||
shell-quote@1.8.3:
|
|
||||||
resolution: {integrity: sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==}
|
|
||||||
engines: {node: '>= 0.4'}
|
|
||||||
|
|
||||||
simple-update-notifier@2.0.0:
|
|
||||||
resolution: {integrity: sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==}
|
|
||||||
engines: {node: '>=10'}
|
|
||||||
|
|
||||||
source-map-support@0.5.21:
|
|
||||||
resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==}
|
|
||||||
|
|
||||||
source-map@0.6.1:
|
|
||||||
resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==}
|
|
||||||
engines: {node: '>=0.10.0'}
|
|
||||||
|
|
||||||
string-width@4.2.3:
|
|
||||||
resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
|
|
||||||
engines: {node: '>=8'}
|
|
||||||
|
|
||||||
strip-ansi@6.0.1:
|
|
||||||
resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==}
|
|
||||||
engines: {node: '>=8'}
|
|
||||||
|
|
||||||
supports-color@5.5.0:
|
|
||||||
resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==}
|
|
||||||
engines: {node: '>=4'}
|
|
||||||
|
|
||||||
supports-color@7.2.0:
|
|
||||||
resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
|
|
||||||
engines: {node: '>=8'}
|
|
||||||
|
|
||||||
supports-color@8.1.1:
|
|
||||||
resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==}
|
|
||||||
engines: {node: '>=10'}
|
|
||||||
|
|
||||||
to-regex-range@5.0.1:
|
|
||||||
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
|
|
||||||
engines: {node: '>=8.0'}
|
|
||||||
|
|
||||||
touch@3.1.1:
|
|
||||||
resolution: {integrity: sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==}
|
|
||||||
hasBin: true
|
|
||||||
|
|
||||||
tree-kill@1.2.2:
|
|
||||||
resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==}
|
|
||||||
hasBin: true
|
|
||||||
|
|
||||||
tslib@2.8.1:
|
|
||||||
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
|
|
||||||
|
|
||||||
undefsafe@2.0.5:
|
|
||||||
resolution: {integrity: sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==}
|
|
||||||
|
|
||||||
wrap-ansi@7.0.0:
|
|
||||||
resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==}
|
|
||||||
engines: {node: '>=10'}
|
|
||||||
|
|
||||||
ws@8.18.3:
|
|
||||||
resolution: {integrity: sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==}
|
|
||||||
engines: {node: '>=10.0.0'}
|
|
||||||
peerDependencies:
|
|
||||||
bufferutil: ^4.0.1
|
|
||||||
utf-8-validate: '>=5.0.2'
|
|
||||||
peerDependenciesMeta:
|
|
||||||
bufferutil:
|
|
||||||
optional: true
|
|
||||||
utf-8-validate:
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
y18n@5.0.8:
|
|
||||||
resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==}
|
|
||||||
engines: {node: '>=10'}
|
|
||||||
|
|
||||||
yargs-parser@21.1.1:
|
|
||||||
resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==}
|
|
||||||
engines: {node: '>=12'}
|
|
||||||
|
|
||||||
yargs@17.7.2:
|
|
||||||
resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==}
|
|
||||||
engines: {node: '>=12'}
|
|
||||||
|
|
||||||
snapshots:
|
|
||||||
|
|
||||||
ansi-regex@5.0.1: {}
|
|
||||||
|
|
||||||
ansi-styles@4.3.0:
|
|
||||||
dependencies:
|
|
||||||
color-convert: 2.0.1
|
|
||||||
|
|
||||||
anymatch@3.1.3:
|
|
||||||
dependencies:
|
|
||||||
normalize-path: 3.0.0
|
|
||||||
picomatch: 2.3.1
|
|
||||||
|
|
||||||
balanced-match@1.0.2: {}
|
|
||||||
|
|
||||||
binary-extensions@2.3.0: {}
|
|
||||||
|
|
||||||
brace-expansion@1.1.12:
|
|
||||||
dependencies:
|
|
||||||
balanced-match: 1.0.2
|
|
||||||
concat-map: 0.0.1
|
|
||||||
|
|
||||||
braces@3.0.3:
|
|
||||||
dependencies:
|
|
||||||
fill-range: 7.1.1
|
|
||||||
|
|
||||||
buffer-from@1.1.2: {}
|
|
||||||
|
|
||||||
chalk@4.1.2:
|
|
||||||
dependencies:
|
|
||||||
ansi-styles: 4.3.0
|
|
||||||
supports-color: 7.2.0
|
|
||||||
|
|
||||||
chokidar@3.6.0:
|
|
||||||
dependencies:
|
|
||||||
anymatch: 3.1.3
|
|
||||||
braces: 3.0.3
|
|
||||||
glob-parent: 5.1.2
|
|
||||||
is-binary-path: 2.1.0
|
|
||||||
is-glob: 4.0.3
|
|
||||||
normalize-path: 3.0.0
|
|
||||||
readdirp: 3.6.0
|
|
||||||
optionalDependencies:
|
|
||||||
fsevents: 2.3.3
|
|
||||||
|
|
||||||
cliui@8.0.1:
|
|
||||||
dependencies:
|
|
||||||
string-width: 4.2.3
|
|
||||||
strip-ansi: 6.0.1
|
|
||||||
wrap-ansi: 7.0.0
|
|
||||||
|
|
||||||
color-convert@2.0.1:
|
|
||||||
dependencies:
|
|
||||||
color-name: 1.1.4
|
|
||||||
|
|
||||||
color-name@1.1.4: {}
|
|
||||||
|
|
||||||
concat-map@0.0.1: {}
|
|
||||||
|
|
||||||
concurrently@9.2.1:
|
|
||||||
dependencies:
|
|
||||||
chalk: 4.1.2
|
|
||||||
rxjs: 7.8.2
|
|
||||||
shell-quote: 1.8.3
|
|
||||||
supports-color: 8.1.1
|
|
||||||
tree-kill: 1.2.2
|
|
||||||
yargs: 17.7.2
|
|
||||||
|
|
||||||
date-fns@4.1.0: {}
|
|
||||||
|
|
||||||
debug@4.4.3(supports-color@5.5.0):
|
|
||||||
dependencies:
|
|
||||||
ms: 2.1.3
|
|
||||||
optionalDependencies:
|
|
||||||
supports-color: 5.5.0
|
|
||||||
|
|
||||||
emoji-regex@8.0.0: {}
|
|
||||||
|
|
||||||
escalade@3.2.0: {}
|
|
||||||
|
|
||||||
fill-range@7.1.1:
|
|
||||||
dependencies:
|
|
||||||
to-regex-range: 5.0.1
|
|
||||||
|
|
||||||
fsevents@2.3.3:
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
get-caller-file@2.0.5: {}
|
|
||||||
|
|
||||||
glob-parent@5.1.2:
|
|
||||||
dependencies:
|
|
||||||
is-glob: 4.0.3
|
|
||||||
|
|
||||||
has-flag@3.0.0: {}
|
|
||||||
|
|
||||||
has-flag@4.0.0: {}
|
|
||||||
|
|
||||||
ignore-by-default@1.0.1: {}
|
|
||||||
|
|
||||||
is-binary-path@2.1.0:
|
|
||||||
dependencies:
|
|
||||||
binary-extensions: 2.3.0
|
|
||||||
|
|
||||||
is-extglob@2.1.1: {}
|
|
||||||
|
|
||||||
is-fullwidth-code-point@3.0.0: {}
|
|
||||||
|
|
||||||
is-glob@4.0.3:
|
|
||||||
dependencies:
|
|
||||||
is-extglob: 2.1.1
|
|
||||||
|
|
||||||
is-number@7.0.0: {}
|
|
||||||
|
|
||||||
minimatch@3.1.2:
|
|
||||||
dependencies:
|
|
||||||
brace-expansion: 1.1.12
|
|
||||||
|
|
||||||
ms@2.1.3: {}
|
|
||||||
|
|
||||||
nodemon@3.1.11:
|
|
||||||
dependencies:
|
|
||||||
chokidar: 3.6.0
|
|
||||||
debug: 4.4.3(supports-color@5.5.0)
|
|
||||||
ignore-by-default: 1.0.1
|
|
||||||
minimatch: 3.1.2
|
|
||||||
pstree.remy: 1.1.8
|
|
||||||
semver: 7.7.3
|
|
||||||
simple-update-notifier: 2.0.0
|
|
||||||
supports-color: 5.5.0
|
|
||||||
touch: 3.1.1
|
|
||||||
undefsafe: 2.0.5
|
|
||||||
|
|
||||||
normalize-path@3.0.0: {}
|
|
||||||
|
|
||||||
picomatch@2.3.1: {}
|
|
||||||
|
|
||||||
pstree.remy@1.1.8: {}
|
|
||||||
|
|
||||||
readdirp@3.6.0:
|
|
||||||
dependencies:
|
|
||||||
picomatch: 2.3.1
|
|
||||||
|
|
||||||
require-directory@2.1.1: {}
|
|
||||||
|
|
||||||
rxjs@7.8.2:
|
|
||||||
dependencies:
|
|
||||||
tslib: 2.8.1
|
|
||||||
|
|
||||||
semver@7.7.3: {}
|
|
||||||
|
|
||||||
shell-quote@1.8.3: {}
|
|
||||||
|
|
||||||
simple-update-notifier@2.0.0:
|
|
||||||
dependencies:
|
|
||||||
semver: 7.7.3
|
|
||||||
|
|
||||||
source-map-support@0.5.21:
|
|
||||||
dependencies:
|
|
||||||
buffer-from: 1.1.2
|
|
||||||
source-map: 0.6.1
|
|
||||||
|
|
||||||
source-map@0.6.1: {}
|
|
||||||
|
|
||||||
string-width@4.2.3:
|
|
||||||
dependencies:
|
|
||||||
emoji-regex: 8.0.0
|
|
||||||
is-fullwidth-code-point: 3.0.0
|
|
||||||
strip-ansi: 6.0.1
|
|
||||||
|
|
||||||
strip-ansi@6.0.1:
|
|
||||||
dependencies:
|
|
||||||
ansi-regex: 5.0.1
|
|
||||||
|
|
||||||
supports-color@5.5.0:
|
|
||||||
dependencies:
|
|
||||||
has-flag: 3.0.0
|
|
||||||
|
|
||||||
supports-color@7.2.0:
|
|
||||||
dependencies:
|
|
||||||
has-flag: 4.0.0
|
|
||||||
|
|
||||||
supports-color@8.1.1:
|
|
||||||
dependencies:
|
|
||||||
has-flag: 4.0.0
|
|
||||||
|
|
||||||
to-regex-range@5.0.1:
|
|
||||||
dependencies:
|
|
||||||
is-number: 7.0.0
|
|
||||||
|
|
||||||
touch@3.1.1: {}
|
|
||||||
|
|
||||||
tree-kill@1.2.2: {}
|
|
||||||
|
|
||||||
tslib@2.8.1: {}
|
|
||||||
|
|
||||||
undefsafe@2.0.5: {}
|
|
||||||
|
|
||||||
wrap-ansi@7.0.0:
|
|
||||||
dependencies:
|
|
||||||
ansi-styles: 4.3.0
|
|
||||||
string-width: 4.2.3
|
|
||||||
strip-ansi: 6.0.1
|
|
||||||
|
|
||||||
ws@8.18.3: {}
|
|
||||||
|
|
||||||
y18n@5.0.8: {}
|
|
||||||
|
|
||||||
yargs-parser@21.1.1: {}
|
|
||||||
|
|
||||||
yargs@17.7.2:
|
|
||||||
dependencies:
|
|
||||||
cliui: 8.0.1
|
|
||||||
escalade: 3.2.0
|
|
||||||
get-caller-file: 2.0.5
|
|
||||||
require-directory: 2.1.1
|
|
||||||
string-width: 4.2.3
|
|
||||||
y18n: 5.0.8
|
|
||||||
yargs-parser: 21.1.1
|
|
||||||
@@ -3,5 +3,5 @@
|
|||||||
set -ex
|
set -ex
|
||||||
corepack enable;
|
corepack enable;
|
||||||
corepack install;
|
corepack install;
|
||||||
pnpm install;
|
yarn install;
|
||||||
pnpm run test;
|
yarn run test;
|
||||||
|
|||||||
@@ -1024,26 +1024,6 @@
|
|||||||
:clj
|
:clj
|
||||||
(sort comp-fn items))))
|
(sort comp-fn items))))
|
||||||
|
|
||||||
(defn obfuscate-string
|
|
||||||
"Obfuscates potentially sensitive values.
|
|
||||||
|
|
||||||
- One-arg arity:
|
|
||||||
* For strings shorter than 10 characters, all characters are replaced by `*`.
|
|
||||||
* For longer strings, the first 5 characters are preserved and the rest obfuscated.
|
|
||||||
- Two-arg arity accepts a boolean `full?` that, when true, replaces the whole value
|
|
||||||
by `*`, preserving only the length."
|
|
||||||
([v]
|
|
||||||
(obfuscate-string v false))
|
|
||||||
([v full?]
|
|
||||||
(let [s (str v)
|
|
||||||
n (count s)]
|
|
||||||
(cond
|
|
||||||
(zero? n) s
|
|
||||||
full? (apply str (repeat n "*"))
|
|
||||||
(< n 10) (apply str (repeat n "*"))
|
|
||||||
:else (str (subs s 0 5)
|
|
||||||
(apply str (repeat (- n 5) "*")))))))
|
|
||||||
|
|
||||||
(defn reorder
|
(defn reorder
|
||||||
"Reorder a vector by moving one of their items from some position to some space between positions.
|
"Reorder a vector by moving one of their items from some position to some space between positions.
|
||||||
It clamps the position numbers to a valid range."
|
It clamps the position numbers to a valid range."
|
||||||
@@ -1092,9 +1072,9 @@
|
|||||||
(if (number? num)
|
(if (number? num)
|
||||||
(try
|
(try
|
||||||
(let [num-str (mth/to-fixed num precision)
|
(let [num-str (mth/to-fixed num precision)
|
||||||
;; Remove all trailing zeros after the comma 100.00000
|
;; Remove all trailing zeros after the comma 100.00000
|
||||||
num-str (str/replace num-str trail-zeros-regex-1 "")]
|
num-str (str/replace num-str trail-zeros-regex-1 "")]
|
||||||
;; Remove trailing zeros after a decimal number: 0.001|00|
|
;; Remove trailing zeros after a decimal number: 0.001|00|
|
||||||
(if-let [m (re-find trail-zeros-regex-2 num-str)]
|
(if-let [m (re-find trail-zeros-regex-2 num-str)]
|
||||||
(str/replace num-str (first m) (second m))
|
(str/replace num-str (first m) (second m))
|
||||||
num-str))
|
num-str))
|
||||||
|
|||||||
@@ -10,7 +10,6 @@
|
|||||||
(:refer-clojure :exclude [instance?])
|
(:refer-clojure :exclude [instance?])
|
||||||
(:require
|
(:require
|
||||||
#?(:clj [clojure.stacktrace :as strace])
|
#?(:clj [clojure.stacktrace :as strace])
|
||||||
[app.common.data :refer [obfuscate-string]]
|
|
||||||
[app.common.pprint :as pp]
|
[app.common.pprint :as pp]
|
||||||
[app.common.schema :as sm]
|
[app.common.schema :as sm]
|
||||||
[clojure.core :as c]
|
[clojure.core :as c]
|
||||||
@@ -20,10 +19,6 @@
|
|||||||
(:import
|
(:import
|
||||||
clojure.lang.IPersistentMap)))
|
clojure.lang.IPersistentMap)))
|
||||||
|
|
||||||
(def ^:private sensitive-fields
|
|
||||||
"Keys whose values must be obfuscated in validation explains."
|
|
||||||
#{:password :old-password :token :invitation-token})
|
|
||||||
|
|
||||||
#?(:clj (set! *warn-on-reflection* true))
|
#?(:clj (set! *warn-on-reflection* true))
|
||||||
|
|
||||||
(def ^:dynamic *data-length* 8)
|
(def ^:dynamic *data-length* 8)
|
||||||
@@ -115,25 +110,7 @@
|
|||||||
(explain (:explain data) opts)
|
(explain (:explain data) opts)
|
||||||
|
|
||||||
(contains? data ::sm/explain)
|
(contains? data ::sm/explain)
|
||||||
(let [exp (::sm/explain data)
|
(sm/humanize-explain (::sm/explain data) opts)))
|
||||||
sanitize-map (fn sanitize-map [m]
|
|
||||||
(reduce-kv
|
|
||||||
(fn [acc k v]
|
|
||||||
(let [k* (if (string? k) (keyword k) k)]
|
|
||||||
(cond
|
|
||||||
(contains? sensitive-fields k*)
|
|
||||||
(assoc acc k (if (map? v)
|
|
||||||
(sanitize-map v)
|
|
||||||
(obfuscate-string v true)))
|
|
||||||
|
|
||||||
(map? v) (assoc acc k (sanitize-map v))
|
|
||||||
:else (assoc acc k v))))
|
|
||||||
{}
|
|
||||||
m))
|
|
||||||
sanitize-explain (fn [exp]
|
|
||||||
(cond-> exp
|
|
||||||
(:value exp) (update :value sanitize-map)))]
|
|
||||||
(sm/humanize-explain (sanitize-explain exp) opts))))
|
|
||||||
|
|
||||||
#?(:clj
|
#?(:clj
|
||||||
(defn format-throwable
|
(defn format-throwable
|
||||||
@@ -241,20 +218,7 @@
|
|||||||
(with-out-str
|
(with-out-str
|
||||||
(print-all cause)))))
|
(print-all cause)))))
|
||||||
|
|
||||||
(defn print-throwable
|
#?(:clj
|
||||||
[cause & {:as opts}]
|
(defn print-throwable
|
||||||
#?(:clj
|
[cause & {:as opts}]
|
||||||
(println (format-throwable cause opts))
|
(println (format-throwable cause opts))))
|
||||||
:cljs
|
|
||||||
(let [prefix (get opts :prefix "exception")
|
|
||||||
title (str prefix ": " (ex-message cause))
|
|
||||||
exdata (ex-data cause)]
|
|
||||||
(js/console.group title)
|
|
||||||
(when-let [explain (get exdata ::sm/explain)]
|
|
||||||
(println (sm/humanize-explain explain)))
|
|
||||||
|
|
||||||
(js/console.log "\nData:")
|
|
||||||
(pp/pprint (dissoc exdata ::sm/explain))
|
|
||||||
|
|
||||||
(js/console.log "\nTrace:")
|
|
||||||
(js/console.error (.-stack cause)))))
|
|
||||||
|
|||||||
@@ -526,25 +526,20 @@
|
|||||||
ids))
|
ids))
|
||||||
|
|
||||||
(defn clean-loops
|
(defn clean-loops
|
||||||
"Clean a list of ids from circular references. Optimized fast-path for single selections."
|
"Clean a list of ids from circular references."
|
||||||
[objects ids]
|
[objects ids]
|
||||||
(if (<= (count ids) 1)
|
(let [parent-selected?
|
||||||
;; For single selection, there can't be circularity; return as ordered-set.
|
(fn [id]
|
||||||
(into (d/ordered-set) ids)
|
(let [parents (get-parent-ids objects id)]
|
||||||
(let [ids-set (if (set? ids) ids (set ids))
|
(some ids parents)))
|
||||||
parent-selected?
|
|
||||||
(fn [id]
|
|
||||||
;; Stop early as soon as we find any selected parent
|
|
||||||
(let [parents (get-parent-ids objects id)]
|
|
||||||
(some #(contains? ids-set %) parents)))
|
|
||||||
|
|
||||||
add-element
|
add-element
|
||||||
(fn [result id]
|
(fn [result id]
|
||||||
(cond-> result
|
(cond-> result
|
||||||
(not (parent-selected? id))
|
(not (parent-selected? id))
|
||||||
(conj id)))]
|
(conj id)))]
|
||||||
|
|
||||||
(reduce add-element (d/ordered-set) ids))))
|
(reduce add-element (d/ordered-set) ids)))
|
||||||
|
|
||||||
(defn- indexed-shapes
|
(defn- indexed-shapes
|
||||||
"Retrieves a vector with the indexes for each element in the layer
|
"Retrieves a vector with the indexes for each element in the layer
|
||||||
|
|||||||
@@ -44,7 +44,7 @@
|
|||||||
[_ {:keys [shape page-id] :as error} file-data _]
|
[_ {:keys [shape page-id] :as error} file-data _]
|
||||||
(let [repair-shape
|
(let [repair-shape
|
||||||
(fn [shape]
|
(fn [shape]
|
||||||
;; Set parent to root frame.
|
; Set parent to root frame.
|
||||||
(log/debug :hint " -> set to " :parent-id uuid/zero)
|
(log/debug :hint " -> set to " :parent-id uuid/zero)
|
||||||
(assoc shape :parent-id uuid/zero))]
|
(assoc shape :parent-id uuid/zero))]
|
||||||
|
|
||||||
@@ -57,7 +57,7 @@
|
|||||||
[_ {:keys [shape page-id] :as error} file-data _]
|
[_ {:keys [shape page-id] :as error} file-data _]
|
||||||
(let [repair-shape
|
(let [repair-shape
|
||||||
(fn [parent-shape]
|
(fn [parent-shape]
|
||||||
;; Add shape to parent's children list
|
; Add shape to parent's children list
|
||||||
(log/debug :hint " -> add children to" :parent-id (:id parent-shape))
|
(log/debug :hint " -> add children to" :parent-id (:id parent-shape))
|
||||||
(update parent-shape :shapes conj (:id shape)))]
|
(update parent-shape :shapes conj (:id shape)))]
|
||||||
|
|
||||||
@@ -70,7 +70,7 @@
|
|||||||
[_ {:keys [shape page-id] :as error} file-data _]
|
[_ {:keys [shape page-id] :as error} file-data _]
|
||||||
(let [repair-shape
|
(let [repair-shape
|
||||||
(fn [shape]
|
(fn [shape]
|
||||||
;; Remove duplicated
|
; Remove duplicated
|
||||||
(log/debug :hint " -> remove duplicated children")
|
(log/debug :hint " -> remove duplicated children")
|
||||||
(update shape :shapes distinct))]
|
(update shape :shapes distinct))]
|
||||||
|
|
||||||
@@ -102,7 +102,7 @@
|
|||||||
[_ {:keys [shape page-id] :as error} file-data _]
|
[_ {:keys [shape page-id] :as error} file-data _]
|
||||||
(let [repair-shape
|
(let [repair-shape
|
||||||
(fn [shape]
|
(fn [shape]
|
||||||
;; Locate the first frame in parents and set frame-id to it.
|
; Locate the first frame in parents and set frame-id to it.
|
||||||
(let [page (ctpl/get-page file-data page-id)
|
(let [page (ctpl/get-page file-data page-id)
|
||||||
frame (cfh/get-frame (:objects page) (:parent-id shape))
|
frame (cfh/get-frame (:objects page) (:parent-id shape))
|
||||||
frame-id (or (:id frame) uuid/zero)]
|
frame-id (or (:id frame) uuid/zero)]
|
||||||
@@ -118,7 +118,7 @@
|
|||||||
[_ {:keys [shape page-id] :as error} file-data _]
|
[_ {:keys [shape page-id] :as error} file-data _]
|
||||||
(let [repair-shape
|
(let [repair-shape
|
||||||
(fn [shape]
|
(fn [shape]
|
||||||
;; Locate the first frame in parents and set frame-id to it.
|
; Locate the first frame in parents and set frame-id to it.
|
||||||
(let [page (ctpl/get-page file-data page-id)
|
(let [page (ctpl/get-page file-data page-id)
|
||||||
frame (cfh/get-frame (:objects page) (:parent-id shape))
|
frame (cfh/get-frame (:objects page) (:parent-id shape))
|
||||||
frame-id (or (:id frame) uuid/zero)]
|
frame-id (or (:id frame) uuid/zero)]
|
||||||
@@ -134,7 +134,7 @@
|
|||||||
[_ {:keys [shape page-id] :as error} file-data _]
|
[_ {:keys [shape page-id] :as error} file-data _]
|
||||||
(let [repair-shape
|
(let [repair-shape
|
||||||
(fn [shape]
|
(fn [shape]
|
||||||
;; Set the :shape as main instance root
|
; Set the :shape as main instance root
|
||||||
(log/debug :hint " -> set :main-instance")
|
(log/debug :hint " -> set :main-instance")
|
||||||
(assoc shape :main-instance true))]
|
(assoc shape :main-instance true))]
|
||||||
|
|
||||||
@@ -147,13 +147,12 @@
|
|||||||
[_ {:keys [shape page-id] :as error} file-data _]
|
[_ {:keys [shape page-id] :as error} file-data _]
|
||||||
(let [repair-shape
|
(let [repair-shape
|
||||||
(fn [shape]
|
(fn [shape]
|
||||||
;; Set :component-file to local file
|
; Set :component-file to local file
|
||||||
(log/debug :hint " -> set :component-file to local file")
|
(log/debug :hint " -> set :component-file to local file")
|
||||||
(assoc shape :component-file (:id file-data)))]
|
(assoc shape :component-file (:id file-data)))]
|
||||||
|
; There is no solution that may recover it with confidence
|
||||||
;; There is no solution that may recover it with confidence
|
;; (log/warn :hint " -> CANNOT REPAIR THIS AUTOMATICALLY.")
|
||||||
;; (log/warn :hint " -> CANNOT REPAIR THIS AUTOMATICALLY.")
|
;; shape)]
|
||||||
;; shape)]
|
|
||||||
|
|
||||||
(log/dbg :hint "repairing shape :component-main-external" :id (:id shape) :name (:name shape) :page-id page-id)
|
(log/dbg :hint "repairing shape :component-main-external" :id (:id shape) :name (:name shape) :page-id page-id)
|
||||||
(-> (pcb/empty-changes nil page-id)
|
(-> (pcb/empty-changes nil page-id)
|
||||||
@@ -167,12 +166,12 @@
|
|||||||
|
|
||||||
repair-shape
|
repair-shape
|
||||||
(fn [shape]
|
(fn [shape]
|
||||||
;; Detach the shape and convert it to non instance.
|
; Detach the shape and convert it to non instance.
|
||||||
(log/debug :hint " -> detach shape" :shape-id (:id shape))
|
(log/debug :hint " -> detach shape" :shape-id (:id shape))
|
||||||
(ctk/detach-shape shape))]
|
(ctk/detach-shape shape))]
|
||||||
;; There is no solution that may recover it with confidence
|
; There is no solution that may recover it with confidence
|
||||||
;; (log/warn :hint " -> CANNOT REPAIR THIS AUTOMATICALLY.")
|
;; (log/warn :hint " -> CANNOT REPAIR THIS AUTOMATICALLY.")
|
||||||
;; shape)]
|
;; shape)]
|
||||||
|
|
||||||
(log/dbg :hint "repairing shape :component-not-found" :id (:id shape) :name (:name shape) :page-id page-id)
|
(log/dbg :hint "repairing shape :component-not-found" :id (:id shape) :name (:name shape) :page-id page-id)
|
||||||
(-> (pcb/empty-changes nil page-id)
|
(-> (pcb/empty-changes nil page-id)
|
||||||
@@ -185,7 +184,7 @@
|
|||||||
|
|
||||||
repair-component
|
repair-component
|
||||||
(fn [component]
|
(fn [component]
|
||||||
;; Assign main instance in the component to current shape
|
; Assign main instance in the component to current shape
|
||||||
(log/debug :hint " -> assign main-instance-id" :component-id (:id component))
|
(log/debug :hint " -> assign main-instance-id" :component-id (:id component))
|
||||||
(assoc component :main-instance-id (:id shape)))
|
(assoc component :main-instance-id (:id shape)))
|
||||||
|
|
||||||
@@ -208,7 +207,7 @@
|
|||||||
[_ {:keys [shape page-id] :as error} file-data _]
|
[_ {:keys [shape page-id] :as error} file-data _]
|
||||||
(let [repair-component
|
(let [repair-component
|
||||||
(fn [component]
|
(fn [component]
|
||||||
;; Assign main instance in the component to current shape
|
; Assign main instance in the component to current shape
|
||||||
(log/debug :hint " -> assign main-instance-page" :component-id (:id component))
|
(log/debug :hint " -> assign main-instance-page" :component-id (:id component))
|
||||||
(assoc component :main-instance-page page-id))]
|
(assoc component :main-instance-page page-id))]
|
||||||
(log/dbg :hint "repairing shape :invalid-main-instance-page" :id (:id shape) :name (:name shape) :page-id page-id)
|
(log/dbg :hint "repairing shape :invalid-main-instance-page" :id (:id shape) :name (:name shape) :page-id page-id)
|
||||||
@@ -220,7 +219,7 @@
|
|||||||
[_ {:keys [shape page-id] :as error} file-data _]
|
[_ {:keys [shape page-id] :as error} file-data _]
|
||||||
(let [repair-shape
|
(let [repair-shape
|
||||||
(fn [shape]
|
(fn [shape]
|
||||||
;; There is no solution that may recover it with confidence
|
; There is no solution that may recover it with confidence
|
||||||
(log/warn :hint " -> CANNOT REPAIR THIS AUTOMATICALLY.")
|
(log/warn :hint " -> CANNOT REPAIR THIS AUTOMATICALLY.")
|
||||||
shape)]
|
shape)]
|
||||||
|
|
||||||
@@ -233,7 +232,7 @@
|
|||||||
[_ {:keys [shape page-id] :as error} file-data _]
|
[_ {:keys [shape page-id] :as error} file-data _]
|
||||||
(let [repair-shape
|
(let [repair-shape
|
||||||
(fn [shape]
|
(fn [shape]
|
||||||
;; Unset the :shape as main instance root
|
; Unset the :shape as main instance root
|
||||||
(log/debug :hint " -> unset :main-instance")
|
(log/debug :hint " -> unset :main-instance")
|
||||||
(dissoc shape :main-instance))]
|
(dissoc shape :main-instance))]
|
||||||
|
|
||||||
@@ -246,7 +245,7 @@
|
|||||||
[_ {:keys [shape page-id] :as error} file-data _]
|
[_ {:keys [shape page-id] :as error} file-data _]
|
||||||
(let [repair-shape
|
(let [repair-shape
|
||||||
(fn [shape]
|
(fn [shape]
|
||||||
;; Convert the shape in a top copy root.
|
; Convert the shape in a top copy root.
|
||||||
(log/debug :hint " -> set :component-root")
|
(log/debug :hint " -> set :component-root")
|
||||||
(assoc shape :component-root true))]
|
(assoc shape :component-root true))]
|
||||||
|
|
||||||
@@ -259,7 +258,7 @@
|
|||||||
[_ {:keys [shape page-id] :as error} file-data _]
|
[_ {:keys [shape page-id] :as error} file-data _]
|
||||||
(let [repair-shape
|
(let [repair-shape
|
||||||
(fn [shape]
|
(fn [shape]
|
||||||
;; Convert the shape in a nested copy root.
|
; Convert the shape in a nested copy root.
|
||||||
(log/debug :hint " -> unset :component-root")
|
(log/debug :hint " -> unset :component-root")
|
||||||
(dissoc shape :component-root))]
|
(dissoc shape :component-root))]
|
||||||
|
|
||||||
@@ -308,8 +307,8 @@
|
|||||||
(log/debug :hint " -> detach shape" :shape-id (:id shape))
|
(log/debug :hint " -> detach shape" :shape-id (:id shape))
|
||||||
(ctk/detach-shape shape))]
|
(ctk/detach-shape shape))]
|
||||||
|
|
||||||
;; If the shape still refers to the remote component, try to find the corresponding near one
|
; If the shape still refers to the remote component, try to find the corresponding near one
|
||||||
;; and link to it. If not, detach the shape.
|
; and link to it. If not, detach the shape.
|
||||||
(log/dbg :hint "repairing shape :ref-shape-not-found" :id (:id shape) :name (:name shape) :page-id page-id)
|
(log/dbg :hint "repairing shape :ref-shape-not-found" :id (:id shape) :name (:name shape) :page-id page-id)
|
||||||
(if (some? matching-shape)
|
(if (some? matching-shape)
|
||||||
(-> (pcb/empty-changes nil page-id)
|
(-> (pcb/empty-changes nil page-id)
|
||||||
@@ -325,7 +324,7 @@
|
|||||||
[_ {:keys [shape page-id] :as error} file-data _]
|
[_ {:keys [shape page-id] :as error} file-data _]
|
||||||
(let [repair-shape
|
(let [repair-shape
|
||||||
(fn [shape]
|
(fn [shape]
|
||||||
;; Convert shape in a normal copy, removing nested copy status
|
; Convert shape in a normal copy, removing nested copy status
|
||||||
(log/debug :hint " -> unhead shape")
|
(log/debug :hint " -> unhead shape")
|
||||||
(ctk/unhead-shape shape))]
|
(ctk/unhead-shape shape))]
|
||||||
|
|
||||||
@@ -338,7 +337,7 @@
|
|||||||
[_ {:keys [shape page-id args] :as error} file-data _]
|
[_ {:keys [shape page-id args] :as error} file-data _]
|
||||||
(let [repair-shape
|
(let [repair-shape
|
||||||
(fn [shape]
|
(fn [shape]
|
||||||
;; Convert shape in a nested head, adding component info
|
; Convert shape in a nested head, adding component info
|
||||||
(log/debug :hint " -> reroot shape")
|
(log/debug :hint " -> reroot shape")
|
||||||
(ctk/rehead-shape shape (:component-file args) (:component-id args)))]
|
(ctk/rehead-shape shape (:component-file args) (:component-id args)))]
|
||||||
|
|
||||||
@@ -351,9 +350,8 @@
|
|||||||
[_ {:keys [shape args] :as error} file-data _]
|
[_ {:keys [shape args] :as error} file-data _]
|
||||||
(let [repair-component
|
(let [repair-component
|
||||||
(fn [component]
|
(fn [component]
|
||||||
(let [objects (:objects component)
|
(let [objects (:objects component) ;; we only have encounter this on deleted components,
|
||||||
;; we only have encounter this on deleted components,
|
;; so the relevant objects are inside the component
|
||||||
;; so the relevant objects are inside the component
|
|
||||||
to-detach (->> (:cycles-ids args)
|
to-detach (->> (:cycles-ids args)
|
||||||
(map #(get objects %))
|
(map #(get objects %))
|
||||||
(map #(ctn/get-head-shape objects %))
|
(map #(ctn/get-head-shape objects %))
|
||||||
@@ -380,7 +378,7 @@
|
|||||||
[_ {:keys [shape page-id] :as error} file-data _]
|
[_ {:keys [shape page-id] :as error} file-data _]
|
||||||
(let [repair-shape
|
(let [repair-shape
|
||||||
(fn [shape]
|
(fn [shape]
|
||||||
;; Remove shape-ref
|
; Remove shape-ref
|
||||||
(log/debug :hint " -> unset :shape-ref")
|
(log/debug :hint " -> unset :shape-ref")
|
||||||
(dissoc shape :shape-ref))]
|
(dissoc shape :shape-ref))]
|
||||||
|
|
||||||
@@ -393,7 +391,7 @@
|
|||||||
[_ {:keys [shape page-id] :as error} file-data _]
|
[_ {:keys [shape page-id] :as error} file-data _]
|
||||||
(let [repair-shape
|
(let [repair-shape
|
||||||
(fn [shape]
|
(fn [shape]
|
||||||
;; Convert the shape in a nested main head.
|
; Convert the shape in a nested main head.
|
||||||
(log/debug :hint " -> unset :component-root")
|
(log/debug :hint " -> unset :component-root")
|
||||||
(dissoc shape :component-root))]
|
(dissoc shape :component-root))]
|
||||||
|
|
||||||
@@ -406,7 +404,7 @@
|
|||||||
[_ {:keys [shape page-id] :as error} file-data _]
|
[_ {:keys [shape page-id] :as error} file-data _]
|
||||||
(let [repair-shape
|
(let [repair-shape
|
||||||
(fn [shape]
|
(fn [shape]
|
||||||
;; Convert the shape in a top main head.
|
; Convert the shape in a top main head.
|
||||||
(log/debug :hint " -> set :component-root")
|
(log/debug :hint " -> set :component-root")
|
||||||
(assoc shape :component-root true))]
|
(assoc shape :component-root true))]
|
||||||
|
|
||||||
@@ -420,7 +418,7 @@
|
|||||||
[_ {:keys [shape page-id] :as error} file-data _]
|
[_ {:keys [shape page-id] :as error} file-data _]
|
||||||
(let [repair-shape
|
(let [repair-shape
|
||||||
(fn [shape]
|
(fn [shape]
|
||||||
;; Convert the shape in a nested copy head.
|
; Convert the shape in a nested copy head.
|
||||||
(log/debug :hint " -> unset :component-root")
|
(log/debug :hint " -> unset :component-root")
|
||||||
(dissoc shape :component-root))]
|
(dissoc shape :component-root))]
|
||||||
|
|
||||||
@@ -433,7 +431,7 @@
|
|||||||
[_ {:keys [shape page-id] :as error} file-data _]
|
[_ {:keys [shape page-id] :as error} file-data _]
|
||||||
(let [repair-shape
|
(let [repair-shape
|
||||||
(fn [shape]
|
(fn [shape]
|
||||||
;; Convert the shape in a top copy root.
|
; Convert the shape in a top copy root.
|
||||||
(log/debug :hint " -> set :component-root")
|
(log/debug :hint " -> set :component-root")
|
||||||
(assoc shape :component-root true))]
|
(assoc shape :component-root true))]
|
||||||
|
|
||||||
@@ -446,7 +444,7 @@
|
|||||||
[_ {:keys [shape page-id] :as error} file-data _]
|
[_ {:keys [shape page-id] :as error} file-data _]
|
||||||
(let [repair-shape
|
(let [repair-shape
|
||||||
(fn [shape]
|
(fn [shape]
|
||||||
;; Detach the shape and convert it to non instance.
|
; Detach the shape and convert it to non instance.
|
||||||
(log/debug :hint " -> detach shape" :shape-id (:id shape))
|
(log/debug :hint " -> detach shape" :shape-id (:id shape))
|
||||||
(ctk/detach-shape shape))]
|
(ctk/detach-shape shape))]
|
||||||
|
|
||||||
@@ -459,7 +457,7 @@
|
|||||||
[_ {:keys [shape page-id] :as error} file-data _]
|
[_ {:keys [shape page-id] :as error} file-data _]
|
||||||
(let [repair-shape
|
(let [repair-shape
|
||||||
(fn [shape]
|
(fn [shape]
|
||||||
;; Detach the shape and convert it to non instance.
|
; Detach the shape and convert it to non instance.
|
||||||
(log/debug :hint " -> detach shape" :shape-id (:id shape))
|
(log/debug :hint " -> detach shape" :shape-id (:id shape))
|
||||||
(ctk/detach-shape shape))]
|
(ctk/detach-shape shape))]
|
||||||
|
|
||||||
@@ -472,7 +470,7 @@
|
|||||||
[_ {:keys [shape page-id] :as error} file-data _]
|
[_ {:keys [shape page-id] :as error} file-data _]
|
||||||
(let [repair-shape
|
(let [repair-shape
|
||||||
(fn [shape]
|
(fn [shape]
|
||||||
;; There is no solution that may recover it with confidence
|
; There is no solution that may recover it with confidence
|
||||||
(log/warn :hint " -> CANNOT REPAIR THIS AUTOMATICALLY.")
|
(log/warn :hint " -> CANNOT REPAIR THIS AUTOMATICALLY.")
|
||||||
shape)]
|
shape)]
|
||||||
|
|
||||||
@@ -485,7 +483,7 @@
|
|||||||
[_ {:keys [shape page-id] :as error} file-data _]
|
[_ {:keys [shape page-id] :as error} file-data _]
|
||||||
(let [repair-shape
|
(let [repair-shape
|
||||||
(fn [shape]
|
(fn [shape]
|
||||||
;; Convert the shape in a frame.
|
; Convert the shape in a frame.
|
||||||
(log/debug :hint " -> set :type :frame")
|
(log/debug :hint " -> set :type :frame")
|
||||||
(assoc shape :type :frame
|
(assoc shape :type :frame
|
||||||
:fills []
|
:fills []
|
||||||
@@ -504,7 +502,7 @@
|
|||||||
[_ {:keys [shape] :as error} file-data _]
|
[_ {:keys [shape] :as error} file-data _]
|
||||||
(let [repair-component
|
(let [repair-component
|
||||||
(fn [component]
|
(fn [component]
|
||||||
;; Remove the objects key, or set it to {} if the component is deleted
|
; Remove the objects key, or set it to {} if the component is deleted
|
||||||
(if (:deleted component)
|
(if (:deleted component)
|
||||||
(do
|
(do
|
||||||
(log/debug :hint " -> set :objects {}")
|
(log/debug :hint " -> set :objects {}")
|
||||||
|
|||||||
@@ -82,113 +82,6 @@
|
|||||||
(declare create-svg-children)
|
(declare create-svg-children)
|
||||||
(declare parse-svg-element)
|
(declare parse-svg-element)
|
||||||
|
|
||||||
(defn- process-gradient-stops
|
|
||||||
"Processes gradient stops to extract stop-color and stop-opacity from style attributes
|
|
||||||
and convert them to direct attributes. This ensures stops with style='stop-color:#...;stop-opacity:1'
|
|
||||||
are properly converted to stop-color and stop-opacity attributes."
|
|
||||||
[stops]
|
|
||||||
(mapv (fn [stop]
|
|
||||||
(let [stop-attrs (:attrs stop)
|
|
||||||
stop-style (get stop-attrs :style)
|
|
||||||
;; Parse style if it's a string using csvg/parse-style utility
|
|
||||||
parsed-style (when (and (string? stop-style) (seq stop-style))
|
|
||||||
(csvg/parse-style stop-style))
|
|
||||||
;; Extract stop-color and stop-opacity from style
|
|
||||||
style-stop-color (when parsed-style (:stop-color parsed-style))
|
|
||||||
style-stop-opacity (when parsed-style (:stop-opacity parsed-style))
|
|
||||||
;; Merge: use direct attributes first, then style values as fallback
|
|
||||||
final-attrs (cond-> stop-attrs
|
|
||||||
(and style-stop-color (not (contains? stop-attrs :stop-color)))
|
|
||||||
(assoc :stop-color style-stop-color)
|
|
||||||
|
|
||||||
(and style-stop-opacity (not (contains? stop-attrs :stop-opacity)))
|
|
||||||
(assoc :stop-opacity style-stop-opacity)
|
|
||||||
|
|
||||||
;; Remove style attribute if we've extracted its values
|
|
||||||
(or style-stop-color style-stop-opacity)
|
|
||||||
(dissoc :style))]
|
|
||||||
(assoc stop :attrs final-attrs)))
|
|
||||||
stops))
|
|
||||||
|
|
||||||
(defn- resolve-gradient-href
|
|
||||||
"Resolves xlink:href references in gradients by merging the referenced gradient's
|
|
||||||
stops and attributes with the referencing gradient. This ensures gradients that
|
|
||||||
reference other gradients (like linearGradient3550 referencing linearGradient3536)
|
|
||||||
inherit the stops from the base gradient.
|
|
||||||
|
|
||||||
According to SVG spec, when a gradient has xlink:href:
|
|
||||||
- It inherits all attributes from the referenced gradient
|
|
||||||
- It inherits all stops from the referenced gradient
|
|
||||||
- The referencing gradient's attributes override the base ones
|
|
||||||
- If the referencing gradient has stops, they replace the base stops
|
|
||||||
|
|
||||||
Returns the defs map with all gradient href references resolved."
|
|
||||||
[defs]
|
|
||||||
(letfn [(resolve-gradient [gradient-id gradient-node defs visited]
|
|
||||||
(if (contains? visited gradient-id)
|
|
||||||
(do
|
|
||||||
#?(:cljs (js/console.warn "[resolve-gradient] Circular reference detected for" gradient-id)
|
|
||||||
:clj nil)
|
|
||||||
gradient-node) ;; Avoid circular references
|
|
||||||
(let [attrs (:attrs gradient-node)
|
|
||||||
href-id (or (:href attrs) (:xlink:href attrs))
|
|
||||||
href-id (when (and (string? href-id) (pos? (count href-id)))
|
|
||||||
(subs href-id 1)) ;; Remove leading #
|
|
||||||
|
|
||||||
base-gradient (when (and href-id (contains? defs href-id))
|
|
||||||
(get defs href-id))
|
|
||||||
|
|
||||||
resolved-base (when base-gradient (resolve-gradient href-id base-gradient defs (conj visited gradient-id)))]
|
|
||||||
|
|
||||||
(if resolved-base
|
|
||||||
;; Merge: base gradient attributes + referencing gradient attributes
|
|
||||||
;; Use referencing gradient's stops if present, otherwise use base stops
|
|
||||||
(let [base-attrs (:attrs resolved-base)
|
|
||||||
ref-attrs (:attrs gradient-node)
|
|
||||||
|
|
||||||
;; Start with base attributes (without id), then merge with ref attributes
|
|
||||||
;; This ensures ref attributes override base ones
|
|
||||||
base-attrs-clean (dissoc base-attrs :id)
|
|
||||||
ref-attrs-clean (dissoc ref-attrs :href :xlink:href :id)
|
|
||||||
|
|
||||||
;; Special handling for gradientTransform: if both have it, combine them
|
|
||||||
base-transform (get base-attrs :gradientTransform)
|
|
||||||
ref-transform (get ref-attrs :gradientTransform)
|
|
||||||
combined-transform (cond
|
|
||||||
(and base-transform ref-transform)
|
|
||||||
(str base-transform " " ref-transform) ;; Apply base first, then ref
|
|
||||||
:else (or ref-transform base-transform))
|
|
||||||
|
|
||||||
;; Merge attributes: base first, then ref (ref overrides)
|
|
||||||
merged-attrs (-> (d/deep-merge base-attrs-clean ref-attrs-clean)
|
|
||||||
(cond-> combined-transform
|
|
||||||
(assoc :gradientTransform combined-transform)))
|
|
||||||
|
|
||||||
;; If referencing gradient has content (stops), use it; otherwise use base content
|
|
||||||
final-content (if (seq (:content gradient-node))
|
|
||||||
(:content gradient-node)
|
|
||||||
(:content resolved-base))
|
|
||||||
|
|
||||||
;; Process stops to extract stop-color and stop-opacity from style attributes
|
|
||||||
processed-content (process-gradient-stops final-content)
|
|
||||||
|
|
||||||
result {:tag (:tag gradient-node)
|
|
||||||
:attrs (assoc merged-attrs :id gradient-id)
|
|
||||||
:content processed-content}]
|
|
||||||
result)
|
|
||||||
;; Process stops even for gradients without references to extract style attributes
|
|
||||||
(let [processed-content (process-gradient-stops (:content gradient-node))]
|
|
||||||
(assoc gradient-node :content processed-content))))))]
|
|
||||||
(let [gradient-tags #{:linearGradient :radialGradient}
|
|
||||||
result (reduce-kv
|
|
||||||
(fn [acc id node]
|
|
||||||
(if (contains? gradient-tags (:tag node))
|
|
||||||
(assoc acc id (resolve-gradient id node defs #{}))
|
|
||||||
(assoc acc id node)))
|
|
||||||
{}
|
|
||||||
defs)]
|
|
||||||
result)))
|
|
||||||
|
|
||||||
(defn create-svg-shapes
|
(defn create-svg-shapes
|
||||||
([svg-data pos objects frame-id parent-id selected center?]
|
([svg-data pos objects frame-id parent-id selected center?]
|
||||||
(create-svg-shapes (uuid/next) svg-data pos objects frame-id parent-id selected center?))
|
(create-svg-shapes (uuid/next) svg-data pos objects frame-id parent-id selected center?))
|
||||||
@@ -219,9 +112,6 @@
|
|||||||
(csvg/fix-percents)
|
(csvg/fix-percents)
|
||||||
(csvg/extract-defs))
|
(csvg/extract-defs))
|
||||||
|
|
||||||
;; Resolve gradient href references in all defs before processing shapes
|
|
||||||
def-nodes (resolve-gradient-href def-nodes)
|
|
||||||
|
|
||||||
;; In penpot groups have the size of their children. To
|
;; In penpot groups have the size of their children. To
|
||||||
;; respect the imported svg size and empty space let's create
|
;; respect the imported svg size and empty space let's create
|
||||||
;; a transparent shape as background to respect the imported
|
;; a transparent shape as background to respect the imported
|
||||||
@@ -252,23 +142,12 @@
|
|||||||
(reduce (partial create-svg-children objects selected frame-id root-id svg-data)
|
(reduce (partial create-svg-children objects selected frame-id root-id svg-data)
|
||||||
[unames []]
|
[unames []]
|
||||||
(d/enumerate (->> (:content svg-data)
|
(d/enumerate (->> (:content svg-data)
|
||||||
(mapv #(csvg/inherit-attributes root-attrs %)))))
|
(mapv #(csvg/inherit-attributes root-attrs %)))))]
|
||||||
|
|
||||||
;; Collect all defs from children and merge into root shape
|
[root-shape children])))
|
||||||
all-defs-from-children (reduce (fn [acc child]
|
|
||||||
(if-let [child-defs (:svg-defs child)]
|
|
||||||
(merge acc child-defs)
|
|
||||||
acc))
|
|
||||||
{}
|
|
||||||
children)
|
|
||||||
|
|
||||||
;; Merge defs from svg-data and children into root shape
|
|
||||||
root-shape-with-defs (assoc root-shape :svg-defs (merge def-nodes all-defs-from-children))]
|
|
||||||
|
|
||||||
[root-shape-with-defs children])))
|
|
||||||
|
|
||||||
(defn create-raw-svg
|
(defn create-raw-svg
|
||||||
[name frame-id {:keys [x y width height offset-x offset-y defs] :as svg-data} {:keys [attrs] :as data}]
|
[name frame-id {:keys [x y width height offset-x offset-y]} {:keys [attrs] :as data}]
|
||||||
(let [props (csvg/attrs->props attrs)
|
(let [props (csvg/attrs->props attrs)
|
||||||
vbox (grc/make-rect offset-x offset-y width height)]
|
vbox (grc/make-rect offset-x offset-y width height)]
|
||||||
(cts/setup-shape
|
(cts/setup-shape
|
||||||
@@ -281,11 +160,10 @@
|
|||||||
:y y
|
:y y
|
||||||
:content data
|
:content data
|
||||||
:svg-attrs props
|
:svg-attrs props
|
||||||
:svg-viewbox vbox
|
:svg-viewbox vbox})))
|
||||||
:svg-defs defs})))
|
|
||||||
|
|
||||||
(defn create-svg-root
|
(defn create-svg-root
|
||||||
[id frame-id parent-id {:keys [name x y width height offset-x offset-y attrs defs] :as svg-data}]
|
[id frame-id parent-id {:keys [name x y width height offset-x offset-y attrs]}]
|
||||||
(let [props (-> (dissoc attrs :viewBox :view-box :xmlns)
|
(let [props (-> (dissoc attrs :viewBox :view-box :xmlns)
|
||||||
(d/without-keys csvg/inheritable-props)
|
(d/without-keys csvg/inheritable-props)
|
||||||
(csvg/attrs->props))]
|
(csvg/attrs->props))]
|
||||||
@@ -299,8 +177,7 @@
|
|||||||
:height height
|
:height height
|
||||||
:x (+ x offset-x)
|
:x (+ x offset-x)
|
||||||
:y (+ y offset-y)
|
:y (+ y offset-y)
|
||||||
:svg-attrs props
|
:svg-attrs props})))
|
||||||
:svg-defs defs})))
|
|
||||||
|
|
||||||
(defn create-svg-children
|
(defn create-svg-children
|
||||||
[objects selected frame-id parent-id svg-data [unames children] [_index svg-element]]
|
[objects selected frame-id parent-id svg-data [unames children] [_index svg-element]]
|
||||||
@@ -321,7 +198,7 @@
|
|||||||
|
|
||||||
|
|
||||||
(defn create-group
|
(defn create-group
|
||||||
[name frame-id {:keys [x y width height offset-x offset-y defs] :as svg-data} {:keys [attrs]}]
|
[name frame-id {:keys [x y width height offset-x offset-y] :as svg-data} {:keys [attrs]}]
|
||||||
(let [transform (csvg/parse-transform (:transform attrs))
|
(let [transform (csvg/parse-transform (:transform attrs))
|
||||||
attrs (-> attrs
|
attrs (-> attrs
|
||||||
(d/without-keys csvg/inheritable-props)
|
(d/without-keys csvg/inheritable-props)
|
||||||
@@ -337,8 +214,7 @@
|
|||||||
:height height
|
:height height
|
||||||
:svg-transform transform
|
:svg-transform transform
|
||||||
:svg-attrs attrs
|
:svg-attrs attrs
|
||||||
:svg-viewbox vbox
|
:svg-viewbox vbox})))
|
||||||
:svg-defs defs})))
|
|
||||||
|
|
||||||
(defn create-path-shape [name frame-id svg-data {:keys [attrs] :as data}]
|
(defn create-path-shape [name frame-id svg-data {:keys [attrs] :as data}]
|
||||||
(when (and (contains? attrs :d) (seq (:d attrs)))
|
(when (and (contains? attrs :d) (seq (:d attrs)))
|
||||||
@@ -430,8 +306,8 @@
|
|||||||
(assoc :frame-id frame-id)
|
(assoc :frame-id frame-id)
|
||||||
(assoc :svg-viewbox vbox)
|
(assoc :svg-viewbox vbox)
|
||||||
(assoc :svg-attrs props)
|
(assoc :svg-attrs props)
|
||||||
;; We need to ensure fills are empty on import process
|
;; We need to ensure fills are empty on import process
|
||||||
;; because setup-shape assings one by default.
|
;; because setup-shape assings one by default.
|
||||||
(assoc :fills [])
|
(assoc :fills [])
|
||||||
(merge radius-attrs)))))
|
(merge radius-attrs)))))
|
||||||
|
|
||||||
@@ -647,21 +523,6 @@
|
|||||||
:else (dm/str tag))]
|
:else (dm/str tag))]
|
||||||
(dm/str "svg-" suffix)))
|
(dm/str "svg-" suffix)))
|
||||||
|
|
||||||
(defn- filter-valid-def-references
|
|
||||||
"Filters out false positive references that are not valid def IDs.
|
|
||||||
Filters out:
|
|
||||||
- Colors in style attributes (hex colors like #f9dd67)
|
|
||||||
- Style fragments that contain CSS keywords (like stop-opacity)
|
|
||||||
- References that don't exist in defs"
|
|
||||||
[ref-ids defs]
|
|
||||||
(let [is-style-fragment? (fn [ref-id]
|
|
||||||
(or (clr/hex-color-string? (str "#" ref-id))
|
|
||||||
(str/includes? ref-id ";") ;; Contains CSS separator
|
|
||||||
(str/includes? ref-id "stop-opacity") ;; CSS keyword
|
|
||||||
(str/includes? ref-id "stop-color")))] ;; CSS keyword
|
|
||||||
(->> ref-ids
|
|
||||||
(remove is-style-fragment?) ;; Filter style fragments and hex colors
|
|
||||||
(filter #(contains? defs %))))) ;; Only existing defs
|
|
||||||
|
|
||||||
(defn parse-svg-element
|
(defn parse-svg-element
|
||||||
[frame-id svg-data {:keys [tag attrs hidden] :as element} unames]
|
[frame-id svg-data {:keys [tag attrs hidden] :as element} unames]
|
||||||
@@ -673,11 +534,7 @@
|
|||||||
(let [name (or (:id attrs) (tag->name tag))
|
(let [name (or (:id attrs) (tag->name tag))
|
||||||
att-refs (csvg/find-attr-references attrs)
|
att-refs (csvg/find-attr-references attrs)
|
||||||
defs (get svg-data :defs)
|
defs (get svg-data :defs)
|
||||||
valid-refs (filter-valid-def-references att-refs defs)
|
references (csvg/find-def-references defs att-refs)
|
||||||
all-refs (csvg/find-def-references defs valid-refs)
|
|
||||||
;; Filter the final result to ensure all references are valid defs
|
|
||||||
;; This prevents false positives from style attributes in gradient stops
|
|
||||||
references (filter-valid-def-references all-refs defs)
|
|
||||||
|
|
||||||
href-id (or (:href attrs) (:xlink:href attrs) " ")
|
href-id (or (:href attrs) (:xlink:href attrs) " ")
|
||||||
href-id (if (and (string? href-id)
|
href-id (if (and (string? href-id)
|
||||||
|
|||||||
@@ -62,7 +62,6 @@
|
|||||||
#{:audit-log
|
#{:audit-log
|
||||||
:audit-log-archive
|
:audit-log-archive
|
||||||
:audit-log-gc
|
:audit-log-gc
|
||||||
:audit-log-logger
|
|
||||||
:auto-file-snapshot
|
:auto-file-snapshot
|
||||||
;; enables the `/api/doc` endpoint that lists all the rpc methods available.
|
;; enables the `/api/doc` endpoint that lists all the rpc methods available.
|
||||||
:backend-api-doc
|
:backend-api-doc
|
||||||
@@ -135,8 +134,6 @@
|
|||||||
:subscriptions
|
:subscriptions
|
||||||
:subscriptions-old
|
:subscriptions-old
|
||||||
:inspect-styles
|
:inspect-styles
|
||||||
;; Enable performance logs in devconsole (disabled by default)
|
|
||||||
:perf-logs
|
|
||||||
|
|
||||||
;; Security layer middleware that filters request by fetch
|
;; Security layer middleware that filters request by fetch
|
||||||
;; metadata headers
|
;; metadata headers
|
||||||
@@ -148,10 +145,7 @@
|
|||||||
|
|
||||||
;; A temporal flag, enables backend code use more extensivelly
|
;; A temporal flag, enables backend code use more extensivelly
|
||||||
;; redis for caching data
|
;; redis for caching data
|
||||||
:redis-cache
|
:redis-cache})
|
||||||
|
|
||||||
;; Activates the nitrate module
|
|
||||||
:nitrate})
|
|
||||||
|
|
||||||
(def all-flags
|
(def all-flags
|
||||||
(set/union email login varia))
|
(set/union email login varia))
|
||||||
@@ -175,7 +169,6 @@
|
|||||||
:enable-component-thumbnails
|
:enable-component-thumbnails
|
||||||
:enable-render-wasm-dpr
|
:enable-render-wasm-dpr
|
||||||
:enable-token-color
|
:enable-token-color
|
||||||
:enable-token-shadow
|
|
||||||
:enable-inspect-styles
|
:enable-inspect-styles
|
||||||
:enable-feature-fdata-objects-map])
|
:enable-feature-fdata-objects-map])
|
||||||
|
|
||||||
|
|||||||
@@ -124,51 +124,33 @@
|
|||||||
|
|
||||||
(defn adjust-to-viewport
|
(defn adjust-to-viewport
|
||||||
([viewport srect] (adjust-to-viewport viewport srect nil))
|
([viewport srect] (adjust-to-viewport viewport srect nil))
|
||||||
([viewport srect {:keys [padding min-zoom] :or {padding 0 min-zoom nil}}]
|
([viewport srect {:keys [padding] :or {padding 0}}]
|
||||||
(let [gprop (/ (:width viewport)
|
(let [gprop (/ (:width viewport)
|
||||||
(:height viewport))
|
(:height viewport))
|
||||||
srect-padded (-> srect
|
srect (-> srect
|
||||||
(update :x #(- % padding))
|
(update :x #(- % padding))
|
||||||
(update :y #(- % padding))
|
(update :y #(- % padding))
|
||||||
(update :width #(+ % padding padding))
|
(update :width #(+ % padding padding))
|
||||||
(update :height #(+ % padding padding)))
|
(update :height #(+ % padding padding)))
|
||||||
width (:width srect-padded)
|
width (:width srect)
|
||||||
height (:height srect-padded)
|
height (:height srect)
|
||||||
lprop (/ width height)
|
lprop (/ width height)]
|
||||||
adjusted-rect
|
(cond
|
||||||
(cond
|
(> gprop lprop)
|
||||||
(> gprop lprop)
|
(let [width' (* (/ width lprop) gprop)
|
||||||
(let [width' (* (/ width lprop) gprop)
|
padding (/ (- width' width) 2)]
|
||||||
padding (/ (- width' width) 2)]
|
(-> srect
|
||||||
(-> srect-padded
|
(update :x #(- % padding))
|
||||||
(update :x #(- % padding))
|
(assoc :width width')
|
||||||
(assoc :width width')
|
|
||||||
(grc/update-rect :position)))
|
|
||||||
|
|
||||||
(< gprop lprop)
|
|
||||||
(let [height' (/ (* height lprop) gprop)
|
|
||||||
padding (/ (- height' height) 2)]
|
|
||||||
(-> srect-padded
|
|
||||||
(update :y #(- % padding))
|
|
||||||
(assoc :height height')
|
|
||||||
(grc/update-rect :position)))
|
|
||||||
|
|
||||||
:else
|
|
||||||
(grc/update-rect srect-padded :position))]
|
|
||||||
;; If min-zoom is specified and the resulting zoom would be below it,
|
|
||||||
;; return a rect with the original top-left corner centered in the viewport
|
|
||||||
;; instead of using the aspect-ratio-adjusted rect (which can push coords
|
|
||||||
;; extremely far with extreme aspect ratios).
|
|
||||||
(if (and (some? min-zoom)
|
|
||||||
(< (/ (:width viewport) (:width adjusted-rect)) min-zoom))
|
|
||||||
(let [anchor-x (:x srect)
|
|
||||||
anchor-y (:y srect)
|
|
||||||
vbox-width (/ (:width viewport) min-zoom)
|
|
||||||
vbox-height (/ (:height viewport) min-zoom)]
|
|
||||||
(-> adjusted-rect
|
|
||||||
(assoc :x (- anchor-x (/ vbox-width 2))
|
|
||||||
:y (- anchor-y (/ vbox-height 2))
|
|
||||||
:width vbox-width
|
|
||||||
:height vbox-height)
|
|
||||||
(grc/update-rect :position)))
|
(grc/update-rect :position)))
|
||||||
adjusted-rect))))
|
|
||||||
|
(< gprop lprop)
|
||||||
|
(let [height' (/ (* height lprop) gprop)
|
||||||
|
padding (/ (- height' height) 2)]
|
||||||
|
(-> srect
|
||||||
|
(update :y #(- % padding))
|
||||||
|
(assoc :height height')
|
||||||
|
(grc/update-rect :position)))
|
||||||
|
|
||||||
|
:else
|
||||||
|
(grc/update-rect srect :position)))))
|
||||||
|
|||||||
@@ -75,25 +75,20 @@
|
|||||||
|
|
||||||
#?(:cljs
|
#?(:cljs
|
||||||
(defn ->clj
|
(defn ->clj
|
||||||
[o & {:keys [key-fn val-fn recursive] :or {key-fn read-kebab-key val-fn identity recursive true}}]
|
[o & {:keys [key-fn val-fn] :or {key-fn read-kebab-key val-fn identity}}]
|
||||||
(let [f (fn this-fn [x]
|
(let [f (fn this-fn [x]
|
||||||
(let [x (val-fn x)]
|
(let [x (val-fn x)]
|
||||||
(cond
|
(cond
|
||||||
(array? x)
|
(array? x)
|
||||||
(persistent!
|
(persistent!
|
||||||
(.reduce ^js/Array x
|
(.reduce ^js/Array x
|
||||||
#(conj! %1 (if recursive
|
#(conj! %1 (this-fn %2))
|
||||||
(this-fn %2)
|
|
||||||
%2))
|
|
||||||
(transient [])))
|
(transient [])))
|
||||||
|
|
||||||
(identical? (type x) js/Object)
|
(identical? (type x) js/Object)
|
||||||
(persistent!
|
(persistent!
|
||||||
(.reduce ^js/Array (js-keys x)
|
(.reduce ^js/Array (js-keys x)
|
||||||
#(assoc! %1 (key-fn %2)
|
#(assoc! %1 (key-fn %2) (this-fn (unchecked-get x %2)))
|
||||||
(if recursive
|
|
||||||
(this-fn (unchecked-get x %2))
|
|
||||||
(unchecked-get x %2)))
|
|
||||||
(transient {})))
|
(transient {})))
|
||||||
|
|
||||||
:else
|
:else
|
||||||
|
|||||||
@@ -12,11 +12,8 @@
|
|||||||
[app.common.files.changes-builder :as pcb]
|
[app.common.files.changes-builder :as pcb]
|
||||||
[app.common.files.helpers :as cfh]
|
[app.common.files.helpers :as cfh]
|
||||||
[app.common.files.variant :as cfv]
|
[app.common.files.variant :as cfv]
|
||||||
[app.common.geom.matrix :as gmt]
|
|
||||||
[app.common.geom.point :as gpt]
|
[app.common.geom.point :as gpt]
|
||||||
[app.common.geom.rect :as grc]
|
|
||||||
[app.common.geom.shapes :as gsh]
|
[app.common.geom.shapes :as gsh]
|
||||||
[app.common.geom.shapes.common :as gco]
|
|
||||||
[app.common.logging :as log]
|
[app.common.logging :as log]
|
||||||
[app.common.logic.shapes :as cls]
|
[app.common.logic.shapes :as cls]
|
||||||
[app.common.logic.variant-properties :as clvp]
|
[app.common.logic.variant-properties :as clvp]
|
||||||
@@ -28,7 +25,6 @@
|
|||||||
[app.common.types.library :as ctl]
|
[app.common.types.library :as ctl]
|
||||||
[app.common.types.page :as ctp]
|
[app.common.types.page :as ctp]
|
||||||
[app.common.types.pages-list :as ctpl]
|
[app.common.types.pages-list :as ctpl]
|
||||||
[app.common.types.path.segment :as segment]
|
|
||||||
[app.common.types.shape :as cts]
|
[app.common.types.shape :as cts]
|
||||||
[app.common.types.shape-tree :as ctst]
|
[app.common.types.shape-tree :as ctst]
|
||||||
[app.common.types.shape.interactions :as ctsi]
|
[app.common.types.shape.interactions :as ctsi]
|
||||||
@@ -49,9 +45,9 @@
|
|||||||
(def log-container-ids #{})
|
(def log-container-ids #{})
|
||||||
|
|
||||||
(def updatable-attrs (->> (seq (keys ctk/sync-attrs))
|
(def updatable-attrs (->> (seq (keys ctk/sync-attrs))
|
||||||
;; We don't update the flex-child attrs
|
;; We don't update the flex-child attrs
|
||||||
(remove ctk/swap-keep-attrs)
|
(remove ctk/swap-keep-attrs)
|
||||||
;; We don't do automatic update of the `layout-grid-cells` property.
|
;; We don't do automatic update of the `layout-grid-cells` property.
|
||||||
(remove #(= :layout-grid-cells %))))
|
(remove #(= :layout-grid-cells %))))
|
||||||
|
|
||||||
(defn enabled-shape?
|
(defn enabled-shape?
|
||||||
@@ -1878,49 +1874,12 @@
|
|||||||
roperations'
|
roperations'
|
||||||
uoperations')))))))
|
uoperations')))))))
|
||||||
|
|
||||||
(defn- set-path-new-values
|
|
||||||
[current-shape prev-shape transform]
|
|
||||||
(let [new-content (segment/transform-content
|
|
||||||
(:content current-shape)
|
|
||||||
(gmt/transform-in (gpt/point 0 0) transform))
|
|
||||||
new-points (-> (segment/content->selrect new-content)
|
|
||||||
(grc/rect->points))
|
|
||||||
points-center (gco/points->center new-points)
|
|
||||||
new-selrect (gsh/calculate-selrect new-points points-center)
|
|
||||||
shape (assoc current-shape
|
|
||||||
:content new-content
|
|
||||||
:points new-points
|
|
||||||
:selrect new-selrect)
|
|
||||||
|
|
||||||
prev-center (segment/content-center (:content prev-shape))
|
|
||||||
delta (gpt/subtract points-center (first new-points))
|
|
||||||
new-pos (gpt/subtract prev-center delta)]
|
|
||||||
(gsh/absolute-move shape new-pos)))
|
|
||||||
|
|
||||||
(defn- switch-path-change-value
|
|
||||||
[prev-shape ; The shape before the switch
|
|
||||||
current-shape ; The shape after the switch (a clean copy)
|
|
||||||
ref-shape ; The referenced shape on the main component
|
|
||||||
; before the switch
|
|
||||||
attr]
|
|
||||||
(let [old-width (-> ref-shape :selrect :width)
|
|
||||||
new-width (-> prev-shape :selrect :width)
|
|
||||||
|
|
||||||
old-height (-> ref-shape :selrect :height)
|
|
||||||
new-height (-> prev-shape :selrect :height)
|
|
||||||
|
|
||||||
transform (-> (gpt/point (/ new-width old-width)
|
|
||||||
(/ new-height old-height))
|
|
||||||
(gmt/scale-matrix))
|
|
||||||
|
|
||||||
shape (set-path-new-values current-shape prev-shape transform)]
|
|
||||||
(get shape attr)))
|
|
||||||
|
|
||||||
|
|
||||||
(defn- switch-text-change-value
|
(defn- switch-text-change-value
|
||||||
[prev-content ; The :content of the text before the switch
|
[prev-content ;; The :content of the text before the switch
|
||||||
current-content ; The :content of the text after the switch (a clean copy)
|
current-content ;; The :content of the text after the switch (a clean copy)
|
||||||
ref-content touched] ; The :content of the referenced text on the main component before the switch
|
ref-content touched] ;; The :content of the referenced text on the main component
|
||||||
|
;; before the switch
|
||||||
(let [;; We need the differences between the contents on the main
|
(let [;; We need the differences between the contents on the main
|
||||||
;; components. current-content is the content of a clean copy,
|
;; components. current-content is the content of a clean copy,
|
||||||
;; so for all effects its the same as the content on its main
|
;; so for all effects its the same as the content on its main
|
||||||
@@ -2066,10 +2025,6 @@
|
|||||||
(= :content attr)
|
(= :content attr)
|
||||||
(touched attr-group))
|
(touched attr-group))
|
||||||
|
|
||||||
path-change?
|
|
||||||
(and (= :path (:type current-shape))
|
|
||||||
(contains? #{:points :selrect :content} attr))
|
|
||||||
|
|
||||||
;; position-data is a special case because can be affected by :geometry-group and :content-group
|
;; position-data is a special case because can be affected by :geometry-group and :content-group
|
||||||
;; so, if the position-data changes but the geometry is touched we need to reset the position-data
|
;; so, if the position-data changes but the geometry is touched we need to reset the position-data
|
||||||
;; so it's calculated again
|
;; so it's calculated again
|
||||||
@@ -2098,12 +2053,6 @@
|
|||||||
(:content origin-ref-shape)
|
(:content origin-ref-shape)
|
||||||
touched)
|
touched)
|
||||||
|
|
||||||
path-change?
|
|
||||||
(switch-path-change-value previous-shape
|
|
||||||
current-shape
|
|
||||||
origin-ref-shape
|
|
||||||
attr)
|
|
||||||
|
|
||||||
:else
|
:else
|
||||||
(get previous-shape attr)))
|
(get previous-shape attr)))
|
||||||
|
|
||||||
@@ -2490,13 +2439,11 @@
|
|||||||
(ctk/get-swap-slot))
|
(ctk/get-swap-slot))
|
||||||
(constantly false))
|
(constantly false))
|
||||||
|
|
||||||
;; In the cases where the swapped shape was the first element of the masked group it would make the group to loose the
|
|
||||||
;; mask property as part of the sanitization check on generate-delete-shapes, passing "ignore-mask" to prevent this
|
|
||||||
[all-parents changes]
|
[all-parents changes]
|
||||||
(-> changes
|
(-> changes
|
||||||
(cls/generate-delete-shapes
|
(cls/generate-delete-shapes
|
||||||
file page objects (d/ordered-set (:id shape))
|
file page objects (d/ordered-set (:id shape))
|
||||||
{:allow-altering-copies true :ignore-children-fn ignore-swapped-fn :ignore-mask true :ignore-flows-for #{(:id shape)}}))
|
{:allow-altering-copies true :ignore-children-fn ignore-swapped-fn}))
|
||||||
[new-shape changes]
|
[new-shape changes]
|
||||||
(-> changes
|
(-> changes
|
||||||
(generate-new-shape-for-swap shape file page libraries id-new-component index target-cell keep-props-values))]
|
(generate-new-shape-for-swap shape file page libraries id-new-component index target-cell keep-props-values))]
|
||||||
@@ -2844,8 +2791,8 @@
|
|||||||
duplicating-component?
|
duplicating-component?
|
||||||
true
|
true
|
||||||
(and remove-swap-slot?
|
(and remove-swap-slot?
|
||||||
;; only remove swap slot of children when the current shape
|
;; only remove swap slot of children when the current shape
|
||||||
;; is not a subinstance head nor a instance root
|
;; is not a subinstance head nor a instance root
|
||||||
(not subinstance-head?)
|
(not subinstance-head?)
|
||||||
(not instance-root?))
|
(not instance-root?))
|
||||||
variant-props))
|
variant-props))
|
||||||
@@ -2901,7 +2848,7 @@
|
|||||||
variant-props)
|
variant-props)
|
||||||
changes))
|
changes))
|
||||||
|
|
||||||
;; We need to check the changes to get the ids-map
|
;; We need to check the changes to get the ids-map
|
||||||
ids-map
|
ids-map
|
||||||
(into {}
|
(into {}
|
||||||
(comp
|
(comp
|
||||||
|
|||||||
@@ -123,12 +123,8 @@
|
|||||||
;; ignore-children-fn is used to ignore some descendants
|
;; ignore-children-fn is used to ignore some descendants
|
||||||
;; on the deletion process. It should receive a shape and
|
;; on the deletion process. It should receive a shape and
|
||||||
;; return a boolean
|
;; return a boolean
|
||||||
ignore-children-fn
|
ignore-children-fn]
|
||||||
ignore-mask
|
:or {ignore-children-fn (constantly false)}}]
|
||||||
ignore-flows-for]
|
|
||||||
:or {ignore-children-fn (constantly false)
|
|
||||||
ignore-mask false
|
|
||||||
ignore-flows-for #{}}}]
|
|
||||||
(let [objects (pcb/get-objects changes)
|
(let [objects (pcb/get-objects changes)
|
||||||
data (pcb/get-library-data changes)
|
data (pcb/get-library-data changes)
|
||||||
page-id (pcb/get-page-id changes)
|
page-id (pcb/get-page-id changes)
|
||||||
@@ -138,12 +134,12 @@
|
|||||||
ids (cfh/clean-loops objects ids)
|
ids (cfh/clean-loops objects ids)
|
||||||
in-component-copy?
|
in-component-copy?
|
||||||
(fn [shape-id]
|
(fn [shape-id]
|
||||||
;; Look for shapes that are inside a component copy, but are
|
;; Look for shapes that are inside a component copy, but are
|
||||||
;; not the root. In this case, they must not be deleted,
|
;; not the root. In this case, they must not be deleted,
|
||||||
;; but hidden (to be able to recover them more easily).
|
;; but hidden (to be able to recover them more easily).
|
||||||
;; If we want to specifically allow altering the copies, this is
|
;; If we want to specifically allow altering the copies, this is
|
||||||
;; a special case, like a component swap, in which case we want
|
;; a special case, like a component swap, in which case we want
|
||||||
;; to delete the old shape
|
;; to delete the old shape
|
||||||
(let [shape (get objects shape-id)]
|
(let [shape (get objects shape-id)]
|
||||||
(and (ctn/has-any-copy-parent? objects shape)
|
(and (ctn/has-any-copy-parent? objects shape)
|
||||||
(not allow-altering-copies))))
|
(not allow-altering-copies))))
|
||||||
@@ -166,25 +162,23 @@
|
|||||||
lookup (d/getf objects)
|
lookup (d/getf objects)
|
||||||
|
|
||||||
groups-to-unmask
|
groups-to-unmask
|
||||||
(when-not ignore-mask
|
(reduce (fn [group-ids id]
|
||||||
(reduce (fn [group-ids id]
|
;; When the shape to delete is the mask of a masked group,
|
||||||
;; When the shape to delete is the mask of a masked group,
|
;; the mask condition must be removed, and it must be
|
||||||
;; the mask condition must be removed, and it must be
|
;; converted to a normal group.
|
||||||
;; converted to a normal group.
|
(let [obj (lookup id)
|
||||||
(let [obj (lookup id)
|
parent (lookup (:parent-id obj))]
|
||||||
parent (lookup (:parent-id obj))]
|
(if (and (:masked-group parent)
|
||||||
(if (and (:masked-group parent)
|
(= id (first (:shapes parent))))
|
||||||
(= id (first (:shapes parent))))
|
(conj group-ids (:id parent))
|
||||||
(conj group-ids (:id parent))
|
group-ids)))
|
||||||
group-ids)))
|
#{}
|
||||||
#{}
|
ids-to-delete)
|
||||||
ids-to-delete)
|
|
||||||
[])
|
|
||||||
|
|
||||||
interacting-shapes
|
interacting-shapes
|
||||||
(filter (fn [shape]
|
(filter (fn [shape]
|
||||||
;; If any of the deleted shapes is the destination of
|
;; If any of the deleted shapes is the destination of
|
||||||
;; some interaction, this must be deleted, too.
|
;; some interaction, this must be deleted, too.
|
||||||
(let [interactions (:interactions shape)]
|
(let [interactions (:interactions shape)]
|
||||||
(some #(and (ctsi/has-destination %)
|
(some #(and (ctsi/has-destination %)
|
||||||
(contains? ids-to-delete (:destination %)))
|
(contains? ids-to-delete (:destination %)))
|
||||||
@@ -196,8 +190,7 @@
|
|||||||
(->> (:flows page)
|
(->> (:flows page)
|
||||||
(reduce
|
(reduce
|
||||||
(fn [changes [id flow]]
|
(fn [changes [id flow]]
|
||||||
(if (and (id-to-delete? (:starting-frame flow))
|
(if (id-to-delete? (:starting-frame flow))
|
||||||
(not (contains? ignore-flows-for (:starting-frame flow))))
|
|
||||||
(-> changes
|
(-> changes
|
||||||
(pcb/with-page page)
|
(pcb/with-page page)
|
||||||
(pcb/set-flow id nil))
|
(pcb/set-flow id nil))
|
||||||
@@ -207,7 +200,7 @@
|
|||||||
|
|
||||||
all-parents
|
all-parents
|
||||||
(reduce (fn [res id]
|
(reduce (fn [res id]
|
||||||
;; All parents of any deleted shape must be resized.
|
;; All parents of any deleted shape must be resized.
|
||||||
(into res (cfh/get-parent-ids objects id)))
|
(into res (cfh/get-parent-ids objects id)))
|
||||||
(d/ordered-set)
|
(d/ordered-set)
|
||||||
(concat ids-to-delete ids-to-hide))
|
(concat ids-to-delete ids-to-hide))
|
||||||
@@ -239,10 +232,10 @@
|
|||||||
(recursive-find-empty-parents parents))))
|
(recursive-find-empty-parents parents))))
|
||||||
|
|
||||||
empty-parents
|
empty-parents
|
||||||
;; Any parent whose children are all deleted, must be deleted too.
|
;; Any parent whose children are all deleted, must be deleted too.
|
||||||
;; If we want to specifically allow altering the copies, this is a special case,
|
;; If we want to specifically allow altering the copies, this is a special case,
|
||||||
;; for example during a component swap. in this case we are replacing a shape by
|
;; for example during a component swap. in this case we are replacing a shape by
|
||||||
;; other one, so must not delete empty parents.
|
;; other one, so must not delete empty parents.
|
||||||
(if-not allow-altering-copies
|
(if-not allow-altering-copies
|
||||||
(into (d/ordered-set) (find-all-empty-parents #{}))
|
(into (d/ordered-set) (find-all-empty-parents #{}))
|
||||||
#{})
|
#{})
|
||||||
@@ -274,8 +267,8 @@
|
|||||||
guides-to-delete)
|
guides-to-delete)
|
||||||
|
|
||||||
changes (reduce (fn [changes component-id]
|
changes (reduce (fn [changes component-id]
|
||||||
;; It's important to delete the component before the main instance, because we
|
;; It's important to delete the component before the main instance, because we
|
||||||
;; need to store the instance position if we want to restore it later.
|
;; need to store the instance position if we want to restore it later.
|
||||||
(pcb/delete-component changes component-id (:id page)))
|
(pcb/delete-component changes component-id (:id page)))
|
||||||
changes
|
changes
|
||||||
components-to-delete)
|
components-to-delete)
|
||||||
@@ -327,7 +320,7 @@
|
|||||||
result #{}]
|
result #{}]
|
||||||
|
|
||||||
(if-not current-id
|
(if-not current-id
|
||||||
;; Base case, no next element
|
;; Base case, no next element
|
||||||
result
|
result
|
||||||
|
|
||||||
(let [group (get objects current-id)]
|
(let [group (get objects current-id)]
|
||||||
@@ -335,14 +328,14 @@
|
|||||||
(not= current-id parent-id)
|
(not= current-id parent-id)
|
||||||
(empty? (remove removed-id? (:shapes group))))
|
(empty? (remove removed-id? (:shapes group))))
|
||||||
|
|
||||||
;; Adds group to the remove and check its parent
|
;; Adds group to the remove and check its parent
|
||||||
(let [to-check (concat to-check [(cfh/get-parent-id objects current-id)])]
|
(let [to-check (concat to-check [(cfh/get-parent-id objects current-id)])]
|
||||||
(recur (first to-check)
|
(recur (first to-check)
|
||||||
(rest to-check)
|
(rest to-check)
|
||||||
(conj removed-id? current-id)
|
(conj removed-id? current-id)
|
||||||
(conj result current-id)))
|
(conj result current-id)))
|
||||||
|
|
||||||
;; otherwise recur
|
;; otherwise recur
|
||||||
(recur (first to-check)
|
(recur (first to-check)
|
||||||
(rest to-check)
|
(rest to-check)
|
||||||
removed-id?
|
removed-id?
|
||||||
|
|||||||
@@ -111,7 +111,7 @@
|
|||||||
"Check if any ancestor of a shape (between base-parent-id and shape) was swapped"
|
"Check if any ancestor of a shape (between base-parent-id and shape) was swapped"
|
||||||
[shape objects base-parent-id]
|
[shape objects base-parent-id]
|
||||||
(let [ancestors (->> (ctn/get-parent-heads objects shape)
|
(let [ancestors (->> (ctn/get-parent-heads objects shape)
|
||||||
;; Ignore ancestors ahead of base-parent
|
;; Ignore ancestors ahead of base-parent
|
||||||
(drop-while #(not= base-parent-id (:id %)))
|
(drop-while #(not= base-parent-id (:id %)))
|
||||||
seq)
|
seq)
|
||||||
num-ancestors (count ancestors)
|
num-ancestors (count ancestors)
|
||||||
|
|||||||
@@ -132,94 +132,3 @@ Some naming conventions:
|
|||||||
(if-let [last-period (str/last-index-of s ".")]
|
(if-let [last-period (str/last-index-of s ".")]
|
||||||
[(subs s 0 (inc last-period)) (subs s (inc last-period))]
|
[(subs s 0 (inc last-period)) (subs s (inc last-period))]
|
||||||
[s ""]))
|
[s ""]))
|
||||||
|
|
||||||
;; Tree building functions --------------------------------------------------
|
|
||||||
|
|
||||||
"Build tree structure from flat list of paths"
|
|
||||||
|
|
||||||
"`build-tree-root` is the main function to build the tree."
|
|
||||||
|
|
||||||
"Receives a list of segments with 'name' properties representing paths,
|
|
||||||
and a separator string."
|
|
||||||
"E.g segments = [{... :name 'one/two/three'} {... :name 'one/two/four'} {... :name 'one/five'}]"
|
|
||||||
|
|
||||||
"Transforms into a tree structure like:
|
|
||||||
[{:name 'one'
|
|
||||||
:path 'one'
|
|
||||||
:depth 0
|
|
||||||
:leaf nil
|
|
||||||
:children-fn (fn [] [{:name 'two'
|
|
||||||
:path 'one.two'
|
|
||||||
:depth 1
|
|
||||||
:leaf nil
|
|
||||||
:children-fn (fn [] [{... :name 'three'} {... :name 'four'}])}
|
|
||||||
{:name 'five'
|
|
||||||
:path 'one.five'
|
|
||||||
:depth 1
|
|
||||||
:leaf {... :name 'five'}
|
|
||||||
...}])}]"
|
|
||||||
|
|
||||||
(defn- sort-by-children
|
|
||||||
"Sorts segments so that those with children come first."
|
|
||||||
[segments separator]
|
|
||||||
(sort-by (fn [segment]
|
|
||||||
(let [path (split-path (:name segment) :separator separator)
|
|
||||||
path-length (count path)]
|
|
||||||
(if (= path-length 1)
|
|
||||||
1
|
|
||||||
0)))
|
|
||||||
segments))
|
|
||||||
|
|
||||||
(defn- group-by-first-segment
|
|
||||||
"Groups segments by their first path segment and update segment name."
|
|
||||||
[segments separator]
|
|
||||||
(reduce (fn [acc segment]
|
|
||||||
(let [[first-segment & remaining-segments] (split-path (:name segment) :separator separator)
|
|
||||||
rest-path (when (seq remaining-segments) (join-path remaining-segments :separator separator :with-spaces? false))]
|
|
||||||
(update acc first-segment (fnil conj [])
|
|
||||||
(if rest-path
|
|
||||||
(assoc segment :name rest-path)
|
|
||||||
segment))))
|
|
||||||
{}
|
|
||||||
segments))
|
|
||||||
|
|
||||||
(defn- sort-and-group-segments
|
|
||||||
"Sorts elements and groups them by their first path segment."
|
|
||||||
[segments separator]
|
|
||||||
(let [sorted (sort-by-children segments separator)
|
|
||||||
grouped (group-by-first-segment sorted separator)]
|
|
||||||
grouped))
|
|
||||||
|
|
||||||
(defn- build-tree-node
|
|
||||||
"Builds a single tree node with lazy children."
|
|
||||||
[segment-name remaining-segments separator parent-path depth]
|
|
||||||
(let [current-path (if parent-path
|
|
||||||
(str parent-path "." segment-name)
|
|
||||||
segment-name)
|
|
||||||
|
|
||||||
is-leaf? (and (seq remaining-segments)
|
|
||||||
(every? (fn [segment]
|
|
||||||
(let [remaining-segment-name (first (split-path (:name segment) :separator separator))]
|
|
||||||
(= segment-name remaining-segment-name)))
|
|
||||||
remaining-segments))
|
|
||||||
|
|
||||||
leaf-segment (when is-leaf? (first remaining-segments))
|
|
||||||
node {:name segment-name
|
|
||||||
:path current-path
|
|
||||||
:depth depth
|
|
||||||
:leaf leaf-segment
|
|
||||||
:children-fn (when-not is-leaf?
|
|
||||||
(fn []
|
|
||||||
(let [grouped-elements (sort-and-group-segments remaining-segments separator)]
|
|
||||||
(mapv (fn [[child-segment-name remaining-child-segments]]
|
|
||||||
(build-tree-node child-segment-name remaining-child-segments separator current-path (inc depth)))
|
|
||||||
grouped-elements))))}]
|
|
||||||
node))
|
|
||||||
|
|
||||||
(defn build-tree-root
|
|
||||||
"Builds the root level of the tree."
|
|
||||||
[segments separator]
|
|
||||||
(let [grouped-elements (sort-and-group-segments segments separator)]
|
|
||||||
(mapv (fn [[segment-name remaining-segments]]
|
|
||||||
(build-tree-node segment-name remaining-segments separator nil 0))
|
|
||||||
grouped-elements)))
|
|
||||||
|
|||||||
@@ -284,20 +284,7 @@
|
|||||||
(defn check-fn
|
(defn check-fn
|
||||||
"Create a predefined check function"
|
"Create a predefined check function"
|
||||||
[s & {:keys [hint type code]}]
|
[s & {:keys [hint type code]}]
|
||||||
(let [s #?(:clj
|
(let [s (schema s)
|
||||||
(schema s)
|
|
||||||
:cljs
|
|
||||||
(try
|
|
||||||
(schema s)
|
|
||||||
(catch :default cause
|
|
||||||
(let [data (ex-data cause)]
|
|
||||||
(if (= :malli.core/invalid-schema (:type data))
|
|
||||||
(throw (ex-info
|
|
||||||
(str "Invalid schema\n"
|
|
||||||
(pp/pprint-str (:data data)))
|
|
||||||
{}))
|
|
||||||
(throw cause))))))
|
|
||||||
|
|
||||||
validator* (delay (m/validator s))
|
validator* (delay (m/validator s))
|
||||||
explainer* (delay (m/explainer s))
|
explainer* (delay (m/explainer s))
|
||||||
hint (or ^boolean hint "check error")
|
hint (or ^boolean hint "check error")
|
||||||
@@ -317,7 +304,7 @@
|
|||||||
|
|
||||||
(defn coercer
|
(defn coercer
|
||||||
[schema & {:as opts}]
|
[schema & {:as opts}]
|
||||||
(let [decode-fn (lazy-decoder schema json-transformer)
|
(let [decode-fn (decoder schema json-transformer)
|
||||||
check-fn (check-fn schema opts)]
|
check-fn (check-fn schema opts)]
|
||||||
(fn [data]
|
(fn [data]
|
||||||
(-> data decode-fn check-fn))))
|
(-> data decode-fn check-fn))))
|
||||||
|
|||||||
@@ -546,19 +546,9 @@
|
|||||||
filter-values)))
|
filter-values)))
|
||||||
|
|
||||||
(defn extract-ids [val]
|
(defn extract-ids [val]
|
||||||
;; Extract referenced ids from string values like "url(#myId)".
|
(when (some? val)
|
||||||
;; Non-string values (maps, numbers, nil, etc.) return an empty seq
|
|
||||||
;; to avoid re-seq type errors when attributes carry nested structures.
|
|
||||||
(cond
|
|
||||||
(string? val)
|
|
||||||
(->> (re-seq xml-id-regex val)
|
(->> (re-seq xml-id-regex val)
|
||||||
(mapv second))
|
(mapv second))))
|
||||||
|
|
||||||
(sequential? val)
|
|
||||||
(mapcat extract-ids val)
|
|
||||||
|
|
||||||
:else
|
|
||||||
[]))
|
|
||||||
|
|
||||||
(defn fix-dot-number
|
(defn fix-dot-number
|
||||||
"Fixes decimal numbers starting in dot but without leading 0"
|
"Fixes decimal numbers starting in dot but without leading 0"
|
||||||
|
|||||||
@@ -222,7 +222,7 @@
|
|||||||
:else
|
:else
|
||||||
(cons [node-style (dm/str head-text "" (:text node))] (rest acc)))
|
(cons [node-style (dm/str head-text "" (:text node))] (rest acc)))
|
||||||
|
|
||||||
;; We add an end-of-line when finish a paragraph
|
;; We add an end-of-line when finish a paragraph
|
||||||
new-acc
|
new-acc
|
||||||
(if (= (:type node) "paragraph")
|
(if (= (:type node) "paragraph")
|
||||||
(let [[hs ht] (first new-acc)]
|
(let [[hs ht] (first new-acc)]
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user