mirror of
https://github.com/Stirling-Tools/Stirling-PDF.git
synced 2025-08-22 04:09:22 +00:00
Merge remote-tracking branch 'origin/V2' into mainClone
This commit is contained in:
commit
c1082d9e42
@ -5,7 +5,11 @@
|
|||||||
"Bash(mkdir:*)",
|
"Bash(mkdir:*)",
|
||||||
"Bash(./gradlew:*)",
|
"Bash(./gradlew:*)",
|
||||||
"Bash(grep:*)",
|
"Bash(grep:*)",
|
||||||
"Bash(cat:*)"
|
"Bash(cat:*)",
|
||||||
|
"Bash(find:*)",
|
||||||
|
"Bash(npm test)",
|
||||||
|
"Bash(npm test:*)",
|
||||||
|
"Bash(ls:*)"
|
||||||
],
|
],
|
||||||
"deny": []
|
"deny": []
|
||||||
}
|
}
|
||||||
|
@ -24,7 +24,7 @@ indent_size = 2
|
|||||||
insert_final_newline = false
|
insert_final_newline = false
|
||||||
trim_trailing_whitespace = false
|
trim_trailing_whitespace = false
|
||||||
|
|
||||||
[*.js]
|
[{*.js,*.jsx,*.ts,*.tsx}]
|
||||||
indent_size = 2
|
indent_size = 2
|
||||||
|
|
||||||
[*.css]
|
[*.css]
|
||||||
|
504
.github/workflows/PR-Auto-Deploy-V2.yml
vendored
Normal file
504
.github/workflows/PR-Auto-Deploy-V2.yml
vendored
Normal file
@ -0,0 +1,504 @@
|
|||||||
|
name: Auto PR V2 Deployment
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
types: [opened, synchronize, reopened, closed]
|
||||||
|
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
issues: write
|
||||||
|
pull-requests: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
check-pr:
|
||||||
|
if: github.event.action != 'closed'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
outputs:
|
||||||
|
should_deploy: ${{ steps.check-conditions.outputs.should_deploy }}
|
||||||
|
pr_number: ${{ github.event.number }}
|
||||||
|
pr_repository: ${{ steps.get-pr-info.outputs.repository }}
|
||||||
|
pr_ref: ${{ steps.get-pr-info.outputs.ref }}
|
||||||
|
steps:
|
||||||
|
- name: Harden Runner
|
||||||
|
uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1
|
||||||
|
with:
|
||||||
|
egress-policy: audit
|
||||||
|
|
||||||
|
- name: Check deployment conditions
|
||||||
|
id: check-conditions
|
||||||
|
env:
|
||||||
|
PR_TITLE: ${{ github.event.pull_request.title }}
|
||||||
|
PR_AUTHOR: ${{ github.event.pull_request.user.login }}
|
||||||
|
PR_BRANCH: ${{ github.event.pull_request.head.ref }}
|
||||||
|
run: |
|
||||||
|
echo "PR Title: $PR_TITLE"
|
||||||
|
echo "PR Author: $PR_AUTHOR"
|
||||||
|
echo "PR Branch: $PR_BRANCH"
|
||||||
|
echo "PR Base Branch: ${{ github.event.pull_request.base.ref }}"
|
||||||
|
|
||||||
|
# Define authorized users
|
||||||
|
authorized_users=(
|
||||||
|
"Frooodle"
|
||||||
|
"sf298"
|
||||||
|
"Ludy87"
|
||||||
|
"LaserKaspar"
|
||||||
|
"sbplat"
|
||||||
|
"reecebrowne"
|
||||||
|
"DarioGii"
|
||||||
|
"ConnorYoh"
|
||||||
|
"EthanHealy01"
|
||||||
|
"jbrunton96"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Check if author is in the authorized list
|
||||||
|
is_authorized=false
|
||||||
|
for user in "${authorized_users[@]}"; do
|
||||||
|
if [[ "$PR_AUTHOR" == "$user" ]]; then
|
||||||
|
is_authorized=true
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
# If PR is targeting V2 and user is authorized, deploy unconditionally
|
||||||
|
PR_BASE_BRANCH="${{ github.event.pull_request.base.ref }}"
|
||||||
|
if [[ "$PR_BASE_BRANCH" == "V2" && "$is_authorized" == "true" ]]; then
|
||||||
|
echo "✅ Deployment forced: PR targets V2 and author is authorized."
|
||||||
|
echo "should_deploy=true" >> $GITHUB_OUTPUT
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Otherwise, continue with original keyword checks
|
||||||
|
has_v2_keyword=false
|
||||||
|
if [[ "$PR_TITLE" =~ [Vv]2 ]] || [[ "$PR_TITLE" =~ [Vv]ersion.?2 ]] || [[ "$PR_TITLE" =~ [Vv]ersion.?[Tt]wo ]]; then
|
||||||
|
has_v2_keyword=true
|
||||||
|
fi
|
||||||
|
|
||||||
|
has_branch_keyword=false
|
||||||
|
if [[ "$PR_BRANCH" =~ [Vv]2 ]] || [[ "$PR_BRANCH" =~ [Rr]eact ]]; then
|
||||||
|
has_branch_keyword=true
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$is_authorized" == "true" && ( "$has_v2_keyword" == "true" || "$has_branch_keyword" == "true" ) ]]; then
|
||||||
|
echo "✅ Deployment conditions met"
|
||||||
|
echo "should_deploy=true" >> $GITHUB_OUTPUT
|
||||||
|
else
|
||||||
|
echo "❌ Deployment conditions not met"
|
||||||
|
echo " - Authorized user: $is_authorized"
|
||||||
|
echo " - Has V2 keyword in title: $has_v2_keyword"
|
||||||
|
echo " - Has V2/React keyword in branch: $has_branch_keyword"
|
||||||
|
echo "should_deploy=false" >> $GITHUB_OUTPUT
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Get PR repository and ref
|
||||||
|
id: get-pr-info
|
||||||
|
if: steps.check-conditions.outputs.should_deploy == 'true'
|
||||||
|
run: |
|
||||||
|
# For forks, use the full repository name, for internal PRs use the current repo
|
||||||
|
if [[ "${{ github.event.pull_request.head.repo.fork }}" == "true" ]]; then
|
||||||
|
repository="${{ github.event.pull_request.head.repo.full_name }}"
|
||||||
|
else
|
||||||
|
repository="${{ github.repository }}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "repository=$repository" >> $GITHUB_OUTPUT
|
||||||
|
echo "ref=${{ github.event.pull_request.head.ref }}" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
deploy-v2-pr:
|
||||||
|
needs: check-pr
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: needs.check-pr.outputs.should_deploy == 'true'
|
||||||
|
# Concurrency control - only one deployment per PR at a time
|
||||||
|
concurrency:
|
||||||
|
group: v2-deploy-pr-${{ needs.check-pr.outputs.pr_number }}
|
||||||
|
cancel-in-progress: true
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
issues: write
|
||||||
|
pull-requests: write
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Harden Runner
|
||||||
|
uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1
|
||||||
|
with:
|
||||||
|
egress-policy: audit
|
||||||
|
|
||||||
|
- name: Checkout main repository
|
||||||
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
|
with:
|
||||||
|
repository: ${{ github.repository }}
|
||||||
|
ref: main
|
||||||
|
|
||||||
|
- name: Setup GitHub App Bot
|
||||||
|
if: github.actor != 'dependabot[bot]'
|
||||||
|
id: setup-bot
|
||||||
|
uses: ./.github/actions/setup-bot
|
||||||
|
continue-on-error: true
|
||||||
|
with:
|
||||||
|
app-id: ${{ secrets.GH_APP_ID }}
|
||||||
|
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
|
||||||
|
|
||||||
|
- name: Add deployment started comment
|
||||||
|
id: deployment-started
|
||||||
|
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
|
||||||
|
with:
|
||||||
|
github-token: ${{ steps.setup-bot.outputs.token }}
|
||||||
|
script: |
|
||||||
|
const { owner, repo } = context.repo;
|
||||||
|
const prNumber = ${{ needs.check-pr.outputs.pr_number }};
|
||||||
|
|
||||||
|
// Delete previous V2 deployment comments to avoid clutter
|
||||||
|
const { data: comments } = await github.rest.issues.listComments({
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
issue_number: prNumber,
|
||||||
|
per_page: 100
|
||||||
|
});
|
||||||
|
|
||||||
|
const v2Comments = comments.filter(comment =>
|
||||||
|
comment.body.includes('🚀 **Auto-deploying V2 version**') ||
|
||||||
|
comment.body.includes('## 🚀 V2 Auto-Deployment Complete!') ||
|
||||||
|
comment.body.includes('❌ **V2 Auto-deployment failed**')
|
||||||
|
);
|
||||||
|
|
||||||
|
for (const comment of v2Comments) {
|
||||||
|
console.log(`Deleting old V2 comment: ${comment.id}`);
|
||||||
|
await github.rest.issues.deleteComment({
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
comment_id: comment.id
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create new deployment started comment
|
||||||
|
const { data: newComment } = await github.rest.issues.createComment({
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
issue_number: prNumber,
|
||||||
|
body: `🚀 **Auto-deploying V2 version** for PR #${prNumber}...\n\n_This is an automated deployment triggered by V2/version2 keywords in the PR title or V2/React keywords in the branch name._\n\n⚠️ **Note:** If new commits are pushed during deployment, this build will be cancelled and replaced with the latest version.`
|
||||||
|
});
|
||||||
|
|
||||||
|
return newComment.id;
|
||||||
|
|
||||||
|
- name: Checkout PR
|
||||||
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
|
with:
|
||||||
|
repository: ${{ needs.check-pr.outputs.pr_repository }}
|
||||||
|
ref: ${{ needs.check-pr.outputs.pr_ref }}
|
||||||
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
fetch-depth: 0 # Fetch full history for commit hash detection
|
||||||
|
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
|
||||||
|
|
||||||
|
- name: Get version number
|
||||||
|
id: versionNumber
|
||||||
|
run: |
|
||||||
|
VERSION=$(grep "^version =" build.gradle | awk -F'"' '{print $2}')
|
||||||
|
echo "versionNumber=$VERSION" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Login to Docker Hub
|
||||||
|
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKER_HUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKER_HUB_API }}
|
||||||
|
|
||||||
|
- name: Get commit hashes for frontend and backend
|
||||||
|
id: commit-hashes
|
||||||
|
run: |
|
||||||
|
# Get last commit that touched the frontend folder, docker/frontend, or docker/compose
|
||||||
|
FRONTEND_HASH=$(git log -1 --format="%H" -- frontend/ docker/frontend/ docker/compose/ 2>/dev/null || echo "")
|
||||||
|
if [ -z "$FRONTEND_HASH" ]; then
|
||||||
|
FRONTEND_HASH="no-frontend-changes"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Get last commit that touched backend code, docker/backend, or docker/compose
|
||||||
|
BACKEND_HASH=$(git log -1 --format="%H" -- app/ docker/backend/ docker/compose/ 2>/dev/null || echo "")
|
||||||
|
if [ -z "$BACKEND_HASH" ]; then
|
||||||
|
BACKEND_HASH="no-backend-changes"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Frontend hash: $FRONTEND_HASH"
|
||||||
|
echo "Backend hash: $BACKEND_HASH"
|
||||||
|
|
||||||
|
echo "frontend_hash=$FRONTEND_HASH" >> $GITHUB_OUTPUT
|
||||||
|
echo "backend_hash=$BACKEND_HASH" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
# Short hashes for tags
|
||||||
|
if [ "$FRONTEND_HASH" = "no-frontend-changes" ]; then
|
||||||
|
echo "frontend_short=no-frontend" >> $GITHUB_OUTPUT
|
||||||
|
else
|
||||||
|
echo "frontend_short=${FRONTEND_HASH:0:8}" >> $GITHUB_OUTPUT
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$BACKEND_HASH" = "no-backend-changes" ]; then
|
||||||
|
echo "backend_short=no-backend" >> $GITHUB_OUTPUT
|
||||||
|
else
|
||||||
|
echo "backend_short=${BACKEND_HASH:0:8}" >> $GITHUB_OUTPUT
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Check if frontend image exists
|
||||||
|
id: check-frontend
|
||||||
|
run: |
|
||||||
|
if docker manifest inspect ${{ secrets.DOCKER_HUB_USERNAME }}/test:v2-frontend-${{ steps.commit-hashes.outputs.frontend_short }} >/dev/null 2>&1; then
|
||||||
|
echo "exists=true" >> $GITHUB_OUTPUT
|
||||||
|
echo "Frontend image already exists, skipping build"
|
||||||
|
else
|
||||||
|
echo "exists=false" >> $GITHUB_OUTPUT
|
||||||
|
echo "Frontend image needs to be built"
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Check if backend image exists
|
||||||
|
id: check-backend
|
||||||
|
run: |
|
||||||
|
if docker manifest inspect ${{ secrets.DOCKER_HUB_USERNAME }}/test:v2-backend-${{ steps.commit-hashes.outputs.backend_short }} >/dev/null 2>&1; then
|
||||||
|
echo "exists=true" >> $GITHUB_OUTPUT
|
||||||
|
echo "Backend image already exists, skipping build"
|
||||||
|
else
|
||||||
|
echo "exists=false" >> $GITHUB_OUTPUT
|
||||||
|
echo "Backend image needs to be built"
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Build and push V2 frontend image
|
||||||
|
if: steps.check-frontend.outputs.exists == 'false'
|
||||||
|
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
file: ./docker/frontend/Dockerfile
|
||||||
|
push: true
|
||||||
|
tags: ${{ secrets.DOCKER_HUB_USERNAME }}/test:v2-frontend-${{ steps.commit-hashes.outputs.frontend_short }}
|
||||||
|
build-args: VERSION_TAG=v2-alpha
|
||||||
|
platforms: linux/amd64
|
||||||
|
|
||||||
|
- name: Build and push V2 backend image
|
||||||
|
if: steps.check-backend.outputs.exists == 'false'
|
||||||
|
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
file: ./docker/backend/Dockerfile
|
||||||
|
push: true
|
||||||
|
tags: ${{ secrets.DOCKER_HUB_USERNAME }}/test:v2-backend-${{ steps.commit-hashes.outputs.backend_short }}
|
||||||
|
build-args: VERSION_TAG=v2-alpha
|
||||||
|
platforms: linux/amd64
|
||||||
|
|
||||||
|
- name: Set up SSH
|
||||||
|
run: |
|
||||||
|
mkdir -p ~/.ssh/
|
||||||
|
echo "${{ secrets.VPS_SSH_KEY }}" > ../private.key
|
||||||
|
sudo chmod 600 ../private.key
|
||||||
|
|
||||||
|
- name: Deploy V2 to VPS
|
||||||
|
id: deploy
|
||||||
|
run: |
|
||||||
|
# Use same port strategy as regular PRs - just the PR number
|
||||||
|
V2_PORT=${{ needs.check-pr.outputs.pr_number }}
|
||||||
|
BACKEND_PORT=$((V2_PORT + 10000)) # Backend on higher port to avoid conflicts
|
||||||
|
|
||||||
|
# Create docker-compose for V2 with separate frontend and backend
|
||||||
|
cat > docker-compose.yml << EOF
|
||||||
|
version: '3.3'
|
||||||
|
services:
|
||||||
|
stirling-pdf-v2-backend:
|
||||||
|
container_name: stirling-pdf-v2-backend-pr-${{ needs.check-pr.outputs.pr_number }}
|
||||||
|
image: ${{ secrets.DOCKER_HUB_USERNAME }}/test:v2-backend-${{ steps.commit-hashes.outputs.backend_short }}
|
||||||
|
ports:
|
||||||
|
- "${BACKEND_PORT}:8080" # Backend API port
|
||||||
|
volumes:
|
||||||
|
- /stirling/V2-PR-${{ needs.check-pr.outputs.pr_number }}/data:/usr/share/tessdata:rw
|
||||||
|
- /stirling/V2-PR-${{ needs.check-pr.outputs.pr_number }}/config:/configs:rw
|
||||||
|
- /stirling/V2-PR-${{ needs.check-pr.outputs.pr_number }}/logs:/logs:rw
|
||||||
|
environment:
|
||||||
|
DISABLE_ADDITIONAL_FEATURES: "true"
|
||||||
|
SECURITY_ENABLELOGIN: "false"
|
||||||
|
SYSTEM_DEFAULTLOCALE: en-GB
|
||||||
|
UI_APPNAME: "Stirling-PDF V2 PR#${{ needs.check-pr.outputs.pr_number }}"
|
||||||
|
UI_HOMEDESCRIPTION: "V2 PR#${{ needs.check-pr.outputs.pr_number }} - Frontend/Backend Split Architecture"
|
||||||
|
UI_APPNAMENAVBAR: "V2 PR#${{ needs.check-pr.outputs.pr_number }}"
|
||||||
|
SYSTEM_MAXFILESIZE: "100"
|
||||||
|
METRICS_ENABLED: "true"
|
||||||
|
SYSTEM_GOOGLEVISIBILITY: "false"
|
||||||
|
SWAGGER_SERVER_URL: "http://${{ secrets.VPS_HOST }}:${V2_PORT}"
|
||||||
|
restart: on-failure:5
|
||||||
|
|
||||||
|
stirling-pdf-v2-frontend:
|
||||||
|
container_name: stirling-pdf-v2-frontend-pr-${{ needs.check-pr.outputs.pr_number }}
|
||||||
|
image: ${{ secrets.DOCKER_HUB_USERNAME }}/test:v2-frontend-${{ steps.commit-hashes.outputs.frontend_short }}
|
||||||
|
ports:
|
||||||
|
- "${V2_PORT}:80" # Frontend port (same as regular PRs)
|
||||||
|
environment:
|
||||||
|
VITE_API_BASE_URL: "http://${{ secrets.VPS_HOST }}:${BACKEND_PORT}"
|
||||||
|
depends_on:
|
||||||
|
- stirling-pdf-v2-backend
|
||||||
|
restart: on-failure:5
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Deploy to VPS
|
||||||
|
scp -i ../private.key -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null docker-compose.yml ${{ secrets.VPS_USERNAME }}@${{ secrets.VPS_HOST }}:/tmp/docker-compose-v2.yml
|
||||||
|
|
||||||
|
ssh -i ../private.key -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -T ${{ secrets.VPS_USERNAME }}@${{ secrets.VPS_HOST }} << ENDSSH
|
||||||
|
# Create V2 PR-specific directories
|
||||||
|
mkdir -p /stirling/V2-PR-${{ needs.check-pr.outputs.pr_number }}/{data,config,logs}
|
||||||
|
|
||||||
|
# Move docker-compose file to correct location
|
||||||
|
mv /tmp/docker-compose-v2.yml /stirling/V2-PR-${{ needs.check-pr.outputs.pr_number }}/docker-compose.yml
|
||||||
|
|
||||||
|
# Stop any existing container and clean up
|
||||||
|
cd /stirling/V2-PR-${{ needs.check-pr.outputs.pr_number }}
|
||||||
|
docker-compose down --remove-orphans 2>/dev/null || true
|
||||||
|
|
||||||
|
# Start the new container
|
||||||
|
docker-compose pull
|
||||||
|
docker-compose up -d
|
||||||
|
|
||||||
|
# Clean up unused Docker resources to save space
|
||||||
|
docker system prune -af --volumes || true
|
||||||
|
|
||||||
|
# Clean up old backend/frontend images (older than 2 weeks)
|
||||||
|
docker image prune -af --filter "until=336h" --filter "label!=keep=true" || true
|
||||||
|
ENDSSH
|
||||||
|
|
||||||
|
# Set port for output
|
||||||
|
echo "v2_port=${V2_PORT}" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Post V2 deployment URL to PR
|
||||||
|
if: success()
|
||||||
|
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
|
||||||
|
with:
|
||||||
|
github-token: ${{ steps.setup-bot.outputs.token }}
|
||||||
|
script: |
|
||||||
|
const { owner, repo } = context.repo;
|
||||||
|
const prNumber = ${{ needs.check-pr.outputs.pr_number }};
|
||||||
|
const v2Port = ${{ steps.deploy.outputs.v2_port }};
|
||||||
|
|
||||||
|
// Delete the "deploying..." comment since we're posting the final result
|
||||||
|
const deploymentStartedId = ${{ steps.deployment-started.outputs.result }};
|
||||||
|
if (deploymentStartedId) {
|
||||||
|
console.log(`Deleting deployment started comment: ${deploymentStartedId}`);
|
||||||
|
try {
|
||||||
|
await github.rest.issues.deleteComment({
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
comment_id: deploymentStartedId
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.log(`Could not delete deployment started comment: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const deploymentUrl = `http://${{ secrets.VPS_HOST }}:${v2Port}`;
|
||||||
|
const httpsUrl = `https://${v2Port}.ssl.stirlingpdf.cloud`;
|
||||||
|
|
||||||
|
const commentBody = `## 🚀 V2 Auto-Deployment Complete!\n\n` +
|
||||||
|
`Your V2 PR with the new frontend/backend split architecture has been deployed!\n\n` +
|
||||||
|
`🔗 **Direct Test URL (non-SSL)** [${deploymentUrl}](${deploymentUrl})\n\n` +
|
||||||
|
`🔐 **Secure HTTPS URL**: [${httpsUrl}](${httpsUrl})\n\n` +
|
||||||
|
`_This deployment will be automatically cleaned up when the PR is closed._\n\n` +
|
||||||
|
`🔄 **Auto-deployed** because PR title or branch name contains V2/version2/React keywords.`;
|
||||||
|
|
||||||
|
await github.rest.issues.createComment({
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
issue_number: prNumber,
|
||||||
|
body: commentBody
|
||||||
|
});
|
||||||
|
|
||||||
|
cleanup-v2-deployment:
|
||||||
|
if: github.event.action == 'closed'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
issues: write
|
||||||
|
pull-requests: write
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Harden Runner
|
||||||
|
uses: step-security/harden-runner@6c439dc8bdf85cadbbce9ed30d1c7b959517bc49 # v2.12.2
|
||||||
|
with:
|
||||||
|
egress-policy: audit
|
||||||
|
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
|
|
||||||
|
- name: Setup GitHub App Bot
|
||||||
|
if: github.actor != 'dependabot[bot]'
|
||||||
|
id: setup-bot
|
||||||
|
uses: ./.github/actions/setup-bot
|
||||||
|
continue-on-error: true
|
||||||
|
with:
|
||||||
|
app-id: ${{ secrets.GH_APP_ID }}
|
||||||
|
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
|
||||||
|
|
||||||
|
- name: Clean up V2 deployment comments
|
||||||
|
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
|
||||||
|
with:
|
||||||
|
github-token: ${{ steps.setup-bot.outputs.token }}
|
||||||
|
script: |
|
||||||
|
const { owner, repo } = context.repo;
|
||||||
|
const prNumber = ${{ github.event.pull_request.number }};
|
||||||
|
|
||||||
|
// Find and delete V2 deployment comments
|
||||||
|
const { data: comments } = await github.rest.issues.listComments({
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
issue_number: prNumber
|
||||||
|
});
|
||||||
|
|
||||||
|
const v2Comments = comments.filter(c =>
|
||||||
|
c.body?.includes("## 🚀 V2 Auto-Deployment Complete!") &&
|
||||||
|
c.user?.type === "Bot"
|
||||||
|
);
|
||||||
|
|
||||||
|
for (const comment of v2Comments) {
|
||||||
|
await github.rest.issues.deleteComment({
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
comment_id: comment.id
|
||||||
|
});
|
||||||
|
console.log(`Deleted V2 deployment comment (ID: ${comment.id})`);
|
||||||
|
}
|
||||||
|
|
||||||
|
- name: Set up SSH
|
||||||
|
run: |
|
||||||
|
mkdir -p ~/.ssh/
|
||||||
|
echo "${{ secrets.VPS_SSH_KEY }}" > ../private.key
|
||||||
|
sudo chmod 600 ../private.key
|
||||||
|
|
||||||
|
- name: Cleanup V2 deployment
|
||||||
|
run: |
|
||||||
|
ssh -i ../private.key -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -T ${{ secrets.VPS_USERNAME }}@${{ secrets.VPS_HOST }} << 'ENDSSH'
|
||||||
|
if [ -d "/stirling/V2-PR-${{ github.event.pull_request.number }}" ]; then
|
||||||
|
echo "Found V2 PR directory, proceeding with cleanup..."
|
||||||
|
|
||||||
|
# Stop and remove V2 containers
|
||||||
|
cd /stirling/V2-PR-${{ github.event.pull_request.number }}
|
||||||
|
docker-compose down || true
|
||||||
|
|
||||||
|
# Go back to root before removal
|
||||||
|
cd /
|
||||||
|
|
||||||
|
# Remove V2 PR-specific directories
|
||||||
|
rm -rf /stirling/V2-PR-${{ github.event.pull_request.number }}
|
||||||
|
|
||||||
|
# Clean up V2 containers by name (in case compose cleanup missed them)
|
||||||
|
docker rm -f stirling-pdf-v2-frontend-pr-${{ github.event.pull_request.number }} || true
|
||||||
|
docker rm -f stirling-pdf-v2-backend-pr-${{ github.event.pull_request.number }} || true
|
||||||
|
|
||||||
|
echo "V2 cleanup completed"
|
||||||
|
else
|
||||||
|
echo "V2 PR directory not found, nothing to clean up"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Clean up old unused images (older than 2 weeks) but keep recent ones for reuse
|
||||||
|
docker image prune -af --filter "until=336h" --filter "label!=keep=true" || true
|
||||||
|
|
||||||
|
# Note: We don't remove the commit-based images since they can be reused across PRs
|
||||||
|
# Only remove PR-specific containers and directories
|
||||||
|
ENDSSH
|
||||||
|
|
||||||
|
- name: Cleanup temporary files
|
||||||
|
if: always()
|
||||||
|
run: |
|
||||||
|
rm -f ../private.key
|
||||||
|
continue-on-error: true
|
||||||
|
|
@ -29,6 +29,7 @@ jobs:
|
|||||||
github.event.comment.user.login == 'reecebrowne' ||
|
github.event.comment.user.login == 'reecebrowne' ||
|
||||||
github.event.comment.user.login == 'DarioGii' ||
|
github.event.comment.user.login == 'DarioGii' ||
|
||||||
github.event.comment.user.login == 'EthanHealy01' ||
|
github.event.comment.user.login == 'EthanHealy01' ||
|
||||||
|
github.event.comment.user.login == 'jbrunton96' ||
|
||||||
github.event.comment.user.login == 'ConnorYoh'
|
github.event.comment.user.login == 'ConnorYoh'
|
||||||
)
|
)
|
||||||
outputs:
|
outputs:
|
||||||
|
130
.github/workflows/build.yml
vendored
130
.github/workflows/build.yml
vendored
@ -1,11 +1,9 @@
|
|||||||
name: Build and Test Workflow
|
name: Build and Test Workflow
|
||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_dispatch:
|
|
||||||
# push:
|
|
||||||
# branches: ["main"]
|
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: ["main"]
|
branches: ["main", "V2", "V2-gha"]
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
# cancel in-progress jobs if a new job is triggered
|
# cancel in-progress jobs if a new job is triggered
|
||||||
# This is useful to avoid running multiple builds for the same branch if a new commit is pushed
|
# This is useful to avoid running multiple builds for the same branch if a new commit is pushed
|
||||||
@ -27,60 +25,56 @@ jobs:
|
|||||||
name: detect what files changed
|
name: detect what files changed
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
timeout-minutes: 3
|
timeout-minutes: 3
|
||||||
# Map a step output to a job output
|
|
||||||
outputs:
|
outputs:
|
||||||
build: ${{ steps.changes.outputs.build }}
|
build: ${{ steps.changes.outputs.build }}
|
||||||
app: ${{ steps.changes.outputs.app }}
|
app: ${{ steps.changes.outputs.app }}
|
||||||
project: ${{ steps.changes.outputs.project }}
|
project: ${{ steps.changes.outputs.project }}
|
||||||
openapi: ${{ steps.changes.outputs.openapi }}
|
openapi: ${{ steps.changes.outputs.openapi }}
|
||||||
steps:
|
steps:
|
||||||
|
|
||||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
|
|
||||||
- name: Check for file changes
|
- name: Check for file changes
|
||||||
uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2
|
uses: dorny/paths-filter@v3.0.2
|
||||||
id: changes
|
id: changes
|
||||||
with:
|
with:
|
||||||
filters: ".github/config/.files.yaml"
|
filters: .github/config/.files.yaml
|
||||||
|
|
||||||
build:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
actions: read
|
actions: read
|
||||||
security-events: write
|
security-events: write
|
||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
jdk-version: [17, 21]
|
jdk-version: [17, 21]
|
||||||
spring-security: [true, false]
|
spring-security: [true, false]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
|
<<<<<<< HEAD
|
||||||
uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.0
|
uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.0
|
||||||
|
=======
|
||||||
|
uses: step-security/harden-runner@v2.12.2
|
||||||
|
>>>>>>> refs/remotes/origin/V2
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
uses: actions/checkout@v4.2.2
|
||||||
|
|
||||||
- name: Set up JDK ${{ matrix.jdk-version }}
|
- name: Set up JDK ${{ matrix.jdk-version }}
|
||||||
uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1
|
uses: actions/setup-java@v4.7.1
|
||||||
with:
|
with:
|
||||||
java-version: ${{ matrix.jdk-version }}
|
java-version: ${{ matrix.jdk-version }}
|
||||||
distribution: "temurin"
|
distribution: "temurin"
|
||||||
|
|
||||||
- name: Setup Gradle
|
- name: Setup Gradle
|
||||||
uses: gradle/actions/setup-gradle@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1
|
uses: gradle/actions/setup-gradle@v4.4.1
|
||||||
with:
|
with:
|
||||||
gradle-version: 8.14
|
gradle-version: 8.14
|
||||||
|
|
||||||
- name: Build with Gradle and spring security ${{ matrix.spring-security }}
|
- name: Build with Gradle and spring security ${{ matrix.spring-security }}
|
||||||
run: ./gradlew clean build
|
run: ./gradlew clean build -PnoSpotless
|
||||||
env:
|
env:
|
||||||
DISABLE_ADDITIONAL_FEATURES: ${{ matrix.spring-security }}
|
DISABLE_ADDITIONAL_FEATURES: ${{ matrix.spring-security }}
|
||||||
|
|
||||||
- name: Check Test Reports Exist
|
- name: Check Test Reports Exist
|
||||||
id: check-reports
|
|
||||||
if: always()
|
if: always()
|
||||||
run: |
|
run: |
|
||||||
declare -a dirs=(
|
declare -a dirs=(
|
||||||
@ -91,98 +85,113 @@ jobs:
|
|||||||
"app/proprietary/build/reports/tests/"
|
"app/proprietary/build/reports/tests/"
|
||||||
"app/proprietary/build/test-results/"
|
"app/proprietary/build/test-results/"
|
||||||
)
|
)
|
||||||
missing_reports=()
|
|
||||||
for dir in "${dirs[@]}"; do
|
for dir in "${dirs[@]}"; do
|
||||||
if [ ! -d "$dir" ]; then
|
if [ ! -d "$dir" ]; then
|
||||||
missing_reports+=("$dir")
|
echo "Missing $dir"
|
||||||
fi
|
|
||||||
done
|
|
||||||
if [ ${#missing_reports[@]} -gt 0 ]; then
|
|
||||||
echo "ERROR: The following required test report directories are missing:"
|
|
||||||
printf '%s\n' "${missing_reports[@]}"
|
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
echo "All required test report directories are present"
|
done
|
||||||
|
|
||||||
- name: Upload Test Reports
|
- name: Upload Test Reports
|
||||||
if: always()
|
if: always()
|
||||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
uses: actions/upload-artifact@v4.6.2
|
||||||
with:
|
with:
|
||||||
name: test-reports-jdk-${{ matrix.jdk-version }}-spring-security-${{ matrix.spring-security }}
|
name: test-reports-jdk-${{ matrix.jdk-version }}-spring-security-${{ matrix.spring-security }}
|
||||||
path: |
|
path: |
|
||||||
app/core/build/reports/tests/
|
app/**/build/reports/tests/
|
||||||
app/core/build/test-results/
|
app/**/build/test-results/
|
||||||
app/core/build/reports/problems/
|
app/**/build/reports/problems/
|
||||||
app/common/build/reports/tests/
|
|
||||||
app/common/build/test-results/
|
|
||||||
app/common/build/reports/problems/
|
|
||||||
app/proprietary/build/reports/tests/
|
|
||||||
app/proprietary/build/test-results/
|
|
||||||
app/proprietary/build/reports/problems/
|
|
||||||
build/reports/problems/
|
build/reports/problems/
|
||||||
retention-days: 3
|
retention-days: 3
|
||||||
if-no-files-found: warn
|
if-no-files-found: warn
|
||||||
|
|
||||||
check-generateOpenApiDocs:
|
check-generateOpenApiDocs:
|
||||||
if: needs.files-changed.outputs.openapi == 'true'
|
if: needs.files-changed.outputs.openapi == 'true'
|
||||||
needs: [files-changed, build]
|
needs: [files-changed]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
|
<<<<<<< HEAD
|
||||||
uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.0
|
uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.0
|
||||||
|
=======
|
||||||
|
uses: step-security/harden-runner@v2.12.2
|
||||||
|
>>>>>>> refs/remotes/origin/V2
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
- uses: actions/checkout@v4.2.2
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
|
||||||
|
|
||||||
- name: Set up JDK 17
|
- name: Set up JDK 17
|
||||||
uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1
|
uses: actions/setup-java@v4.7.1
|
||||||
with:
|
with:
|
||||||
java-version: "17"
|
java-version: "17"
|
||||||
distribution: "temurin"
|
distribution: "temurin"
|
||||||
|
- uses: gradle/actions/setup-gradle@v4.4.1
|
||||||
- name: Setup Gradle
|
|
||||||
uses: gradle/actions/setup-gradle@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1
|
|
||||||
|
|
||||||
- name: Generate OpenAPI documentation
|
- name: Generate OpenAPI documentation
|
||||||
run: ./gradlew :stirling-pdf:generateOpenApiDocs
|
run: ./gradlew :stirling-pdf:generateOpenApiDocs
|
||||||
|
<<<<<<< HEAD
|
||||||
|
|
||||||
|
=======
|
||||||
|
>>>>>>> refs/remotes/origin/V2
|
||||||
- name: Upload OpenAPI Documentation
|
- name: Upload OpenAPI Documentation
|
||||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
uses: actions/upload-artifact@v4.6.2
|
||||||
with:
|
with:
|
||||||
name: openapi-docs
|
name: openapi-docs
|
||||||
path: ./SwaggerDoc.json
|
path: ./SwaggerDoc.json
|
||||||
|
|
||||||
|
frontend-validation:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Harden Runner
|
||||||
|
uses: step-security/harden-runner@v2.12.2
|
||||||
|
with:
|
||||||
|
egress-policy: audit
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4.2.2
|
||||||
|
- name: Set up Node.js
|
||||||
|
uses: actions/setup-node@v4.1.0
|
||||||
|
with:
|
||||||
|
node-version: '20'
|
||||||
|
cache: 'npm'
|
||||||
|
cache-dependency-path: frontend/package-lock.json
|
||||||
|
- name: Install frontend dependencies
|
||||||
|
run: cd frontend && npm ci
|
||||||
|
- name: Build frontend
|
||||||
|
run: cd frontend && npm run build
|
||||||
|
- name: Run frontend tests
|
||||||
|
run: cd frontend && npm run test -- --run
|
||||||
|
- name: Upload frontend build artifacts
|
||||||
|
uses: actions/upload-artifact@v4.6.2
|
||||||
|
with:
|
||||||
|
name: frontend-build
|
||||||
|
path: frontend/dist/
|
||||||
|
retention-days: 3
|
||||||
|
|
||||||
check-licence:
|
check-licence:
|
||||||
if: needs.files-changed.outputs.build == 'true'
|
if: needs.files-changed.outputs.build == 'true'
|
||||||
needs: [files-changed, build]
|
needs: [files-changed, build]
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Harden Runner
|
- name: Harden Runner
|
||||||
|
<<<<<<< HEAD
|
||||||
uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.0
|
uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.0
|
||||||
|
=======
|
||||||
|
uses: step-security/harden-runner@v2.12.2
|
||||||
|
>>>>>>> refs/remotes/origin/V2
|
||||||
with:
|
with:
|
||||||
egress-policy: audit
|
egress-policy: audit
|
||||||
|
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
uses: actions/checkout@v4.2.2
|
||||||
|
|
||||||
- name: Set up JDK 17
|
- name: Set up JDK 17
|
||||||
uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1
|
uses: actions/setup-java@v4.7.1
|
||||||
with:
|
with:
|
||||||
java-version: "17"
|
java-version: "17"
|
||||||
distribution: "temurin"
|
distribution: "temurin"
|
||||||
|
|
||||||
- name: check the licenses for compatibility
|
- name: check the licenses for compatibility
|
||||||
run: ./gradlew clean checkLicense
|
run: ./gradlew clean checkLicense
|
||||||
|
|
||||||
- name: FAILED - check the licenses for compatibility
|
- name: FAILED - check the licenses for compatibility
|
||||||
if: failure()
|
if: failure()
|
||||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
uses: actions/upload-artifact@v4.6.2
|
||||||
with:
|
with:
|
||||||
name: dependencies-without-allowed-license.json
|
name: dependencies-without-allowed-license.json
|
||||||
path: |
|
path: build/reports/dependency-license/dependencies-without-allowed-license.json
|
||||||
build/reports/dependency-license/dependencies-without-allowed-license.json
|
|
||||||
retention-days: 3
|
retention-days: 3
|
||||||
|
|
||||||
docker-compose-tests:
|
docker-compose-tests:
|
||||||
@ -244,6 +253,7 @@ jobs:
|
|||||||
chmod +x ./testing/test.sh
|
chmod +x ./testing/test.sh
|
||||||
chmod +x ./testing/test_disabledEndpoints.sh
|
chmod +x ./testing/test_disabledEndpoints.sh
|
||||||
./testing/test.sh
|
./testing/test.sh
|
||||||
|
<<<<<<< HEAD
|
||||||
|
|
||||||
test-build-docker-images:
|
test-build-docker-images:
|
||||||
if: github.event_name == 'pull_request' && needs.files-changed.outputs.project == 'true'
|
if: github.event_name == 'pull_request' && needs.files-changed.outputs.project == 'true'
|
||||||
@ -310,3 +320,5 @@ jobs:
|
|||||||
build/reports/problems/
|
build/reports/problems/
|
||||||
retention-days: 3
|
retention-days: 3
|
||||||
if-no-files-found: warn
|
if-no-files-found: warn
|
||||||
|
=======
|
||||||
|
>>>>>>> refs/remotes/origin/V2
|
||||||
|
188
.github/workflows/deploy-on-v2-commit.yml
vendored
Normal file
188
.github/workflows/deploy-on-v2-commit.yml
vendored
Normal file
@ -0,0 +1,188 @@
|
|||||||
|
name: Auto V2 Deploy on Push
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- V2
|
||||||
|
- deploy-on-v2-commit
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
deploy-v2-on-push:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
concurrency:
|
||||||
|
group: deploy-v2-push-V2
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Harden Runner
|
||||||
|
uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1
|
||||||
|
with:
|
||||||
|
egress-policy: audit
|
||||||
|
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
- name: Get commit hashes for frontend and backend
|
||||||
|
id: commit-hashes
|
||||||
|
run: |
|
||||||
|
# Get last commit that touched the frontend folder, docker/frontend, or docker/compose
|
||||||
|
FRONTEND_HASH=$(git log -1 --format="%H" -- frontend/ docker/frontend/ docker/compose/ 2>/dev/null || echo "")
|
||||||
|
if [ -z "$FRONTEND_HASH" ]; then
|
||||||
|
FRONTEND_HASH="no-frontend-changes"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Get last commit that touched backend code, docker/backend, or docker/compose
|
||||||
|
BACKEND_HASH=$(git log -1 --format="%H" -- app/ docker/backend/ docker/compose/ 2>/dev/null || echo "")
|
||||||
|
if [ -z "$BACKEND_HASH" ]; then
|
||||||
|
BACKEND_HASH="no-backend-changes"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Frontend hash: $FRONTEND_HASH"
|
||||||
|
echo "Backend hash: $BACKEND_HASH"
|
||||||
|
|
||||||
|
echo "frontend_hash=$FRONTEND_HASH" >> $GITHUB_OUTPUT
|
||||||
|
echo "backend_hash=$BACKEND_HASH" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
# Short hashes for tags
|
||||||
|
if [ "$FRONTEND_HASH" = "no-frontend-changes" ]; then
|
||||||
|
echo "frontend_short=no-frontend" >> $GITHUB_OUTPUT
|
||||||
|
else
|
||||||
|
echo "frontend_short=${FRONTEND_HASH:0:8}" >> $GITHUB_OUTPUT
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$BACKEND_HASH" = "no-backend-changes" ]; then
|
||||||
|
echo "backend_short=no-backend" >> $GITHUB_OUTPUT
|
||||||
|
else
|
||||||
|
echo "backend_short=${BACKEND_HASH:0:8}" >> $GITHUB_OUTPUT
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Check if frontend image exists
|
||||||
|
id: check-frontend
|
||||||
|
run: |
|
||||||
|
if docker manifest inspect ${{ secrets.DOCKER_HUB_USERNAME }}/test:v2-frontend-${{ steps.commit-hashes.outputs.frontend_short }} >/dev/null 2>&1; then
|
||||||
|
echo "exists=true" >> $GITHUB_OUTPUT
|
||||||
|
echo "Frontend image already exists, skipping build"
|
||||||
|
else
|
||||||
|
echo "exists=false" >> $GITHUB_OUTPUT
|
||||||
|
echo "Frontend image needs to be built"
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Check if backend image exists
|
||||||
|
id: check-backend
|
||||||
|
run: |
|
||||||
|
if docker manifest inspect ${{ secrets.DOCKER_HUB_USERNAME }}/test:v2-backend-${{ steps.commit-hashes.outputs.backend_short }} >/dev/null 2>&1; then
|
||||||
|
echo "exists=true" >> $GITHUB_OUTPUT
|
||||||
|
echo "Backend image already exists, skipping build"
|
||||||
|
else
|
||||||
|
echo "exists=false" >> $GITHUB_OUTPUT
|
||||||
|
echo "Backend image needs to be built"
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
- name: Login to Docker Hub
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKER_HUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKER_HUB_API }}
|
||||||
|
|
||||||
|
- name: Build and push frontend image
|
||||||
|
if: steps.check-frontend.outputs.exists == 'false'
|
||||||
|
uses: docker/build-push-action@v6
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
file: ./docker/frontend/Dockerfile
|
||||||
|
push: true
|
||||||
|
tags: |
|
||||||
|
${{ secrets.DOCKER_HUB_USERNAME }}/test:v2-frontend-${{ steps.commit-hashes.outputs.frontend_short }}
|
||||||
|
${{ secrets.DOCKER_HUB_USERNAME }}/test:v2-frontend-latest
|
||||||
|
build-args: VERSION_TAG=v2-alpha
|
||||||
|
platforms: linux/amd64
|
||||||
|
|
||||||
|
- name: Build and push backend image
|
||||||
|
if: steps.check-backend.outputs.exists == 'false'
|
||||||
|
uses: docker/build-push-action@v6
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
file: ./docker/backend/Dockerfile
|
||||||
|
push: true
|
||||||
|
tags: |
|
||||||
|
${{ secrets.DOCKER_HUB_USERNAME }}/test:v2-backend-${{ steps.commit-hashes.outputs.backend_short }}
|
||||||
|
${{ secrets.DOCKER_HUB_USERNAME }}/test:v2-backend-latest
|
||||||
|
build-args: VERSION_TAG=v2-alpha
|
||||||
|
platforms: linux/amd64
|
||||||
|
|
||||||
|
|
||||||
|
- name: Set up SSH
|
||||||
|
run: |
|
||||||
|
mkdir -p ~/.ssh/
|
||||||
|
echo "${{ secrets.VPS_SSH_KEY }}" > ../private.key
|
||||||
|
chmod 600 ../private.key
|
||||||
|
|
||||||
|
|
||||||
|
- name: Deploy to VPS on port 3000
|
||||||
|
run: |
|
||||||
|
export UNIQUE_NAME=docker-compose-v2-$GITHUB_RUN_ID.yml
|
||||||
|
|
||||||
|
cat > $UNIQUE_NAME << EOF
|
||||||
|
version: '3.3'
|
||||||
|
services:
|
||||||
|
backend:
|
||||||
|
container_name: stirling-v2-backend
|
||||||
|
image: ${{ secrets.DOCKER_HUB_USERNAME }}/test:v2-backend-${{ steps.commit-hashes.outputs.backend_short }}
|
||||||
|
ports:
|
||||||
|
- "13000:8080"
|
||||||
|
volumes:
|
||||||
|
- /stirling/V2/data:/usr/share/tessdata:rw
|
||||||
|
- /stirling/V2/config:/configs:rw
|
||||||
|
- /stirling/V2/logs:/logs:rw
|
||||||
|
environment:
|
||||||
|
DISABLE_ADDITIONAL_FEATURES: "true"
|
||||||
|
SECURITY_ENABLELOGIN: "false"
|
||||||
|
SYSTEM_DEFAULTLOCALE: en-GB
|
||||||
|
UI_APPNAME: "Stirling-PDF V2"
|
||||||
|
UI_HOMEDESCRIPTION: "V2 Frontend/Backend Split"
|
||||||
|
UI_APPNAMENAVBAR: "V2 Deployment"
|
||||||
|
SYSTEM_MAXFILESIZE: "100"
|
||||||
|
METRICS_ENABLED: "true"
|
||||||
|
SYSTEM_GOOGLEVISIBILITY: "false"
|
||||||
|
SWAGGER_SERVER_URL: "http://${{ secrets.VPS_HOST }}:3000"
|
||||||
|
restart: on-failure:5
|
||||||
|
|
||||||
|
frontend:
|
||||||
|
container_name: stirling-v2-frontend
|
||||||
|
image: ${{ secrets.DOCKER_HUB_USERNAME }}/test:v2-frontend-${{ steps.commit-hashes.outputs.frontend_short }}
|
||||||
|
ports:
|
||||||
|
- "3000:80"
|
||||||
|
environment:
|
||||||
|
VITE_API_BASE_URL: "http://${{ secrets.VPS_HOST }}:13000"
|
||||||
|
depends_on:
|
||||||
|
- backend
|
||||||
|
restart: on-failure:5
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Copy to remote with unique name
|
||||||
|
scp -i ../private.key -o StrictHostKeyChecking=no $UNIQUE_NAME ${{ secrets.VPS_USERNAME }}@${{ secrets.VPS_HOST }}:/tmp/$UNIQUE_NAME
|
||||||
|
|
||||||
|
# SSH and rename/move atomically to avoid interference
|
||||||
|
ssh -i ../private.key -o StrictHostKeyChecking=no ${{ secrets.VPS_USERNAME }}@${{ secrets.VPS_HOST }} << ENDSSH
|
||||||
|
mkdir -p /stirling/V2/{data,config,logs}
|
||||||
|
mv /tmp/$UNIQUE_NAME /stirling/V2/docker-compose.yml
|
||||||
|
cd /stirling/V2
|
||||||
|
docker-compose down || true
|
||||||
|
docker-compose pull
|
||||||
|
docker-compose up -d
|
||||||
|
docker system prune -af --volumes || true
|
||||||
|
docker image prune -af --filter "until=336h" --filter "label!=keep=true" || true
|
||||||
|
ENDSSH
|
||||||
|
|
||||||
|
- name: Cleanup temporary files
|
||||||
|
if: always()
|
||||||
|
run: |
|
||||||
|
rm -f ../private.key
|
||||||
|
|
217
.github/workflows/frontend-licenses-update.yml
vendored
Normal file
217
.github/workflows/frontend-licenses-update.yml
vendored
Normal file
@ -0,0 +1,217 @@
|
|||||||
|
name: Frontend License Report Workflow
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- V2
|
||||||
|
paths:
|
||||||
|
- "frontend/package.json"
|
||||||
|
- "frontend/package-lock.json"
|
||||||
|
- "frontend/scripts/generate-licenses.js"
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- V2
|
||||||
|
paths:
|
||||||
|
- "frontend/package.json"
|
||||||
|
- "frontend/package-lock.json"
|
||||||
|
- "frontend/scripts/generate-licenses.js"
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
generate-frontend-license-report:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
pull-requests: write
|
||||||
|
repository-projects: write # Required for enabling automerge
|
||||||
|
steps:
|
||||||
|
- name: Harden Runner
|
||||||
|
uses: step-security/harden-runner@6c439dc8bdf85cadbbce9ed30d1c7b959517bc49 # v2.12.2
|
||||||
|
with:
|
||||||
|
egress-policy: audit
|
||||||
|
|
||||||
|
- name: Check out code
|
||||||
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Setup GitHub App Bot
|
||||||
|
id: setup-bot
|
||||||
|
uses: ./.github/actions/setup-bot
|
||||||
|
with:
|
||||||
|
app-id: ${{ secrets.GH_APP_ID }}
|
||||||
|
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
|
||||||
|
|
||||||
|
- name: Set up Node.js
|
||||||
|
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
|
||||||
|
with:
|
||||||
|
node-version: '18'
|
||||||
|
cache: 'npm'
|
||||||
|
cache-dependency-path: frontend/package-lock.json
|
||||||
|
|
||||||
|
- name: Install frontend dependencies
|
||||||
|
working-directory: frontend
|
||||||
|
run: npm ci
|
||||||
|
|
||||||
|
- name: Generate frontend license report
|
||||||
|
working-directory: frontend
|
||||||
|
run: npm run generate-licenses
|
||||||
|
|
||||||
|
- name: Check for license warnings
|
||||||
|
run: |
|
||||||
|
if [ -f "frontend/src/assets/license-warnings.json" ]; then
|
||||||
|
echo "LICENSE_WARNINGS_EXIST=true" >> $GITHUB_ENV
|
||||||
|
else
|
||||||
|
echo "LICENSE_WARNINGS_EXIST=false" >> $GITHUB_ENV
|
||||||
|
fi
|
||||||
|
|
||||||
|
# PR Event: Check licenses and comment on PR
|
||||||
|
- name: Delete previous license check comments
|
||||||
|
if: github.event_name == 'pull_request'
|
||||||
|
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
|
||||||
|
with:
|
||||||
|
github-token: ${{ steps.setup-bot.outputs.token }}
|
||||||
|
script: |
|
||||||
|
const { owner, repo } = context.repo;
|
||||||
|
const prNumber = context.issue.number;
|
||||||
|
|
||||||
|
// Get all comments on the PR
|
||||||
|
const { data: comments } = await github.rest.issues.listComments({
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
issue_number: prNumber,
|
||||||
|
per_page: 100
|
||||||
|
});
|
||||||
|
|
||||||
|
// Filter for license check comments
|
||||||
|
const licenseComments = comments.filter(comment =>
|
||||||
|
comment.body.includes('## ✅ Frontend License Check Passed') ||
|
||||||
|
comment.body.includes('## ❌ Frontend License Check Failed')
|
||||||
|
);
|
||||||
|
|
||||||
|
// Delete old license check comments
|
||||||
|
for (const comment of licenseComments) {
|
||||||
|
console.log(`Deleting old license check comment: ${comment.id}`);
|
||||||
|
await github.rest.issues.deleteComment({
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
comment_id: comment.id
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
- name: Comment on PR - License Check Results
|
||||||
|
if: github.event_name == 'pull_request'
|
||||||
|
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
|
||||||
|
with:
|
||||||
|
github-token: ${{ steps.setup-bot.outputs.token }}
|
||||||
|
script: |
|
||||||
|
const { owner, repo } = context.repo;
|
||||||
|
const prNumber = context.issue.number;
|
||||||
|
const hasWarnings = process.env.LICENSE_WARNINGS_EXIST === 'true';
|
||||||
|
|
||||||
|
let commentBody;
|
||||||
|
|
||||||
|
if (hasWarnings) {
|
||||||
|
// Read warnings file to get specific issues
|
||||||
|
const fs = require('fs');
|
||||||
|
let warningDetails = '';
|
||||||
|
try {
|
||||||
|
const warnings = JSON.parse(fs.readFileSync('frontend/src/assets/license-warnings.json', 'utf8'));
|
||||||
|
warningDetails = warnings.warnings.map(w => `- ${w.message}`).join('\n');
|
||||||
|
} catch (e) {
|
||||||
|
warningDetails = 'Unable to read warning details';
|
||||||
|
}
|
||||||
|
|
||||||
|
commentBody = `## ❌ Frontend License Check Failed
|
||||||
|
|
||||||
|
The frontend license check has detected compatibility warnings that require review:
|
||||||
|
|
||||||
|
${warningDetails}
|
||||||
|
|
||||||
|
**Action Required:** Please review these licenses to ensure they are acceptable for your use case before merging.
|
||||||
|
|
||||||
|
_This check will fail the PR until license issues are resolved._`;
|
||||||
|
} else {
|
||||||
|
commentBody = `## ✅ Frontend License Check Passed
|
||||||
|
|
||||||
|
All frontend licenses have been validated and no compatibility warnings were detected.
|
||||||
|
|
||||||
|
The frontend license report has been updated successfully.`;
|
||||||
|
}
|
||||||
|
|
||||||
|
await github.rest.issues.createComment({
|
||||||
|
owner,
|
||||||
|
repo,
|
||||||
|
issue_number: prNumber,
|
||||||
|
body: commentBody
|
||||||
|
});
|
||||||
|
|
||||||
|
- name: Fail workflow if license warnings exist (PR only)
|
||||||
|
if: github.event_name == 'pull_request' && env.LICENSE_WARNINGS_EXIST == 'true'
|
||||||
|
run: |
|
||||||
|
echo "❌ License warnings detected. Failing the workflow."
|
||||||
|
exit 1
|
||||||
|
|
||||||
|
# Push Event: Commit license files and create PR
|
||||||
|
- name: Commit changes (Push only)
|
||||||
|
if: github.event_name == 'push'
|
||||||
|
run: |
|
||||||
|
git add frontend/src/assets/3rdPartyLicenses.json
|
||||||
|
# Note: Do NOT commit license-warnings.json - it's only for PR review
|
||||||
|
git diff --staged --quiet || echo "CHANGES_DETECTED=true" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Prepare PR body (Push only)
|
||||||
|
if: github.event_name == 'push'
|
||||||
|
run: |
|
||||||
|
PR_BODY="Auto-generated by ${{ steps.setup-bot.outputs.app-slug }}[bot]
|
||||||
|
|
||||||
|
This PR updates the frontend license report based on changes to package.json dependencies."
|
||||||
|
|
||||||
|
if [ "${{ env.LICENSE_WARNINGS_EXIST }}" = "true" ]; then
|
||||||
|
PR_BODY="$PR_BODY
|
||||||
|
|
||||||
|
## ⚠️ License Compatibility Warnings
|
||||||
|
|
||||||
|
The following licenses may require review for corporate compatibility:
|
||||||
|
|
||||||
|
$(cat frontend/src/assets/license-warnings.json | jq -r '.warnings[].message')
|
||||||
|
|
||||||
|
Please review these licenses to ensure they are acceptable for your use case."
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "PR_BODY<<EOF" >> $GITHUB_ENV
|
||||||
|
echo "$PR_BODY" >> $GITHUB_ENV
|
||||||
|
echo "EOF" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
- name: Create Pull Request (Push only)
|
||||||
|
id: cpr
|
||||||
|
if: github.event_name == 'push' && env.CHANGES_DETECTED == 'true'
|
||||||
|
uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8
|
||||||
|
with:
|
||||||
|
token: ${{ steps.setup-bot.outputs.token }}
|
||||||
|
commit-message: "Update Frontend 3rd Party Licenses"
|
||||||
|
committer: ${{ steps.setup-bot.outputs.committer }}
|
||||||
|
author: ${{ steps.setup-bot.outputs.committer }}
|
||||||
|
signoff: true
|
||||||
|
branch: update-frontend-3rd-party-licenses
|
||||||
|
base: V2
|
||||||
|
title: "Update Frontend 3rd Party Licenses"
|
||||||
|
body: ${{ env.PR_BODY }}
|
||||||
|
labels: Licenses,github-actions,frontend
|
||||||
|
draft: false
|
||||||
|
delete-branch: true
|
||||||
|
sign-commits: true
|
||||||
|
|
||||||
|
- name: Enable Pull Request Automerge (Push only)
|
||||||
|
if: github.event_name == 'push' && steps.cpr.outputs.pull-request-operation == 'created' && env.LICENSE_WARNINGS_EXIST == 'false'
|
||||||
|
run: gh pr merge --squash --auto "${{ steps.cpr.outputs.pull-request-number }}"
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ steps.setup-bot.outputs.token }}
|
||||||
|
|
||||||
|
- name: Add review required label (Push only)
|
||||||
|
if: github.event_name == 'push' && steps.cpr.outputs.pull-request-operation == 'created' && env.LICENSE_WARNINGS_EXIST == 'true'
|
||||||
|
run: gh pr edit "${{ steps.cpr.outputs.pull-request-number }}" --add-label "license-review-required"
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ steps.setup-bot.outputs.token }}
|
3
.gitignore
vendored
3
.gitignore
vendored
@ -27,6 +27,7 @@ clientWebUI/
|
|||||||
!cucumber/exampleFiles/
|
!cucumber/exampleFiles/
|
||||||
!cucumber/exampleFiles/example_html.zip
|
!cucumber/exampleFiles/example_html.zip
|
||||||
exampleYmlFiles/stirling/
|
exampleYmlFiles/stirling/
|
||||||
|
/stirling/
|
||||||
/testing/file_snapshots
|
/testing/file_snapshots
|
||||||
SwaggerDoc.json
|
SwaggerDoc.json
|
||||||
|
|
||||||
@ -196,6 +197,8 @@ id_ed25519.pub
|
|||||||
.pytest_cache
|
.pytest_cache
|
||||||
.ipynb_checkpoints
|
.ipynb_checkpoints
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
**/jcef-bundle/
|
**/jcef-bundle/
|
||||||
|
|
||||||
# node_modules
|
# node_modules
|
||||||
|
224
CLAUDE.md
Normal file
224
CLAUDE.md
Normal file
@ -0,0 +1,224 @@
|
|||||||
|
# CLAUDE.md
|
||||||
|
|
||||||
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||||
|
|
||||||
|
## Common Development Commands
|
||||||
|
|
||||||
|
### Build and Test
|
||||||
|
- **Build project**: `./gradlew clean build`
|
||||||
|
- **Run locally**: `./gradlew bootRun`
|
||||||
|
- **Full test suite**: `./test.sh` (builds all Docker variants and runs comprehensive tests)
|
||||||
|
- **Code formatting**: `./gradlew spotlessApply` (runs automatically before compilation)
|
||||||
|
|
||||||
|
### Docker Development
|
||||||
|
- **Build ultra-lite**: `docker build -t stirlingtools/stirling-pdf:latest-ultra-lite -f ./Dockerfile.ultra-lite .`
|
||||||
|
- **Build standard**: `docker build -t stirlingtools/stirling-pdf:latest -f ./Dockerfile .`
|
||||||
|
- **Build fat version**: `docker build -t stirlingtools/stirling-pdf:latest-fat -f ./Dockerfile.fat .`
|
||||||
|
- **Example compose files**: Located in `exampleYmlFiles/` directory
|
||||||
|
|
||||||
|
### Security Mode Development
|
||||||
|
Set `DOCKER_ENABLE_SECURITY=true` environment variable to enable security features during development. This is required for testing the full version locally.
|
||||||
|
|
||||||
|
### Frontend Development
|
||||||
|
- **Frontend dev server**: `cd frontend && npm run dev` (requires backend on localhost:8080)
|
||||||
|
- **Tech Stack**: Vite + React + TypeScript + Mantine UI + TailwindCSS
|
||||||
|
- **Proxy Configuration**: Vite proxies `/api/*` calls to backend (localhost:8080)
|
||||||
|
- **Build Process**: DO NOT run build scripts manually - builds are handled by CI/CD pipelines
|
||||||
|
- **Package Installation**: DO NOT run npm install commands - package management handled separately
|
||||||
|
- **Deployment Options**:
|
||||||
|
- **Desktop App**: `npm run tauri-build` (native desktop application)
|
||||||
|
- **Web Server**: `npm run build` then serve dist/ folder
|
||||||
|
- **Development**: `npm run tauri-dev` for desktop dev mode
|
||||||
|
|
||||||
|
#### Multi-Tool Workflow Architecture
|
||||||
|
Frontend designed for **stateful document processing**:
|
||||||
|
- Users upload PDFs once, then chain tools (split → merge → compress → view)
|
||||||
|
- File state and processing results persist across tool switches
|
||||||
|
- No file reloading between tools - performance critical for large PDFs (up to 100GB+)
|
||||||
|
|
||||||
|
#### FileContext - Central State Management
|
||||||
|
**Location**: `src/contexts/FileContext.tsx`
|
||||||
|
- **Active files**: Currently loaded PDFs and their variants
|
||||||
|
- **Tool navigation**: Current mode (viewer/pageEditor/fileEditor/toolName)
|
||||||
|
- **Memory management**: PDF document cleanup, blob URL lifecycle, Web Worker management
|
||||||
|
- **IndexedDB persistence**: File storage with thumbnail caching
|
||||||
|
- **Preview system**: Tools can preview results (e.g., Split → Viewer → back to Split) without context pollution
|
||||||
|
|
||||||
|
**Critical**: All file operations go through FileContext. Don't bypass with direct file handling.
|
||||||
|
|
||||||
|
#### Processing Services
|
||||||
|
- **enhancedPDFProcessingService**: Background PDF parsing and manipulation
|
||||||
|
- **thumbnailGenerationService**: Web Worker-based with main-thread fallback
|
||||||
|
- **fileStorage**: IndexedDB with LRU cache management
|
||||||
|
|
||||||
|
#### Memory Management Strategy
|
||||||
|
**Why manual cleanup exists**: Large PDFs (up to 100GB+) through multiple tools accumulate:
|
||||||
|
- PDF.js documents that need explicit .destroy() calls
|
||||||
|
- Blob URLs from tool outputs that need revocation
|
||||||
|
- Web Workers that need termination
|
||||||
|
Without cleanup: browser crashes with memory leaks.
|
||||||
|
|
||||||
|
#### Tool Development
|
||||||
|
|
||||||
|
**Architecture**: Modular hook-based system with clear separation of concerns:
|
||||||
|
|
||||||
|
- **useToolOperation** (`frontend/src/hooks/tools/shared/useToolOperation.ts`): Main orchestrator hook
|
||||||
|
- Coordinates all tool operations with consistent interface
|
||||||
|
- Integrates with FileContext for operation tracking
|
||||||
|
- Handles validation, error handling, and UI state management
|
||||||
|
|
||||||
|
- **Supporting Hooks**:
|
||||||
|
- **useToolState**: UI state management (loading, progress, error, files)
|
||||||
|
- **useToolApiCalls**: HTTP requests and file processing
|
||||||
|
- **useToolResources**: Blob URLs, thumbnails, ZIP downloads
|
||||||
|
|
||||||
|
- **Utilities**:
|
||||||
|
- **toolErrorHandler**: Standardized error extraction and i18n support
|
||||||
|
- **toolResponseProcessor**: API response handling (single/zip/custom)
|
||||||
|
- **toolOperationTracker**: FileContext integration utilities
|
||||||
|
|
||||||
|
**Three Tool Patterns**:
|
||||||
|
|
||||||
|
**Pattern 1: Single-File Tools** (Individual processing)
|
||||||
|
- Backend processes one file per API call
|
||||||
|
- Set `multiFileEndpoint: false`
|
||||||
|
- Examples: Compress, Rotate
|
||||||
|
```typescript
|
||||||
|
return useToolOperation({
|
||||||
|
operationType: 'compress',
|
||||||
|
endpoint: '/api/v1/misc/compress-pdf',
|
||||||
|
buildFormData: (params, file: File) => { /* single file */ },
|
||||||
|
multiFileEndpoint: false,
|
||||||
|
filePrefix: 'compressed_'
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
**Pattern 2: Multi-File Tools** (Batch processing)
|
||||||
|
- Backend accepts `MultipartFile[]` arrays in single API call
|
||||||
|
- Set `multiFileEndpoint: true`
|
||||||
|
- Examples: Split, Merge, Overlay
|
||||||
|
```typescript
|
||||||
|
return useToolOperation({
|
||||||
|
operationType: 'split',
|
||||||
|
endpoint: '/api/v1/general/split-pages',
|
||||||
|
buildFormData: (params, files: File[]) => { /* all files */ },
|
||||||
|
multiFileEndpoint: true,
|
||||||
|
filePrefix: 'split_'
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
**Pattern 3: Complex Tools** (Custom processing)
|
||||||
|
- Tools with complex routing logic or non-standard processing
|
||||||
|
- Provide `customProcessor` for full control
|
||||||
|
- Examples: Convert, OCR
|
||||||
|
```typescript
|
||||||
|
return useToolOperation({
|
||||||
|
operationType: 'convert',
|
||||||
|
customProcessor: async (params, files) => { /* custom logic */ },
|
||||||
|
filePrefix: 'converted_'
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
**Benefits**:
|
||||||
|
- **No Timeouts**: Operations run until completion (supports 100GB+ files)
|
||||||
|
- **Consistent**: All tools follow same pattern and interface
|
||||||
|
- **Maintainable**: Single responsibility hooks, easy to test and modify
|
||||||
|
- **i18n Ready**: Built-in internationalization support
|
||||||
|
- **Type Safe**: Full TypeScript support with generic interfaces
|
||||||
|
- **Memory Safe**: Automatic resource cleanup and blob URL management
|
||||||
|
|
||||||
|
## Architecture Overview
|
||||||
|
|
||||||
|
### Project Structure
|
||||||
|
- **Backend**: Spring Boot application with Thymeleaf templating
|
||||||
|
- **Frontend**: React-based SPA in `/frontend` directory (Thymeleaf templates fully replaced)
|
||||||
|
- **File Storage**: IndexedDB for client-side file persistence and thumbnails
|
||||||
|
- **Internationalization**: JSON-based translations (converted from backend .properties)
|
||||||
|
- **PDF Processing**: PDFBox for core PDF operations, LibreOffice for conversions, PDF.js for client-side rendering
|
||||||
|
- **Security**: Spring Security with optional authentication (controlled by `DOCKER_ENABLE_SECURITY`)
|
||||||
|
- **Configuration**: YAML-based configuration with environment variable overrides
|
||||||
|
|
||||||
|
### Controller Architecture
|
||||||
|
- **API Controllers** (`src/main/java/.../controller/api/`): REST endpoints for PDF operations
|
||||||
|
- Organized by function: converters, security, misc, pipeline
|
||||||
|
- Follow pattern: `@RestController` + `@RequestMapping("/api/v1/...")`
|
||||||
|
- **Web Controllers** (`src/main/java/.../controller/web/`): Serve Thymeleaf templates
|
||||||
|
- Pattern: `@Controller` + return template names
|
||||||
|
|
||||||
|
### Key Components
|
||||||
|
- **SPDFApplication.java**: Main application class with desktop UI and browser launching logic
|
||||||
|
- **ConfigInitializer**: Handles runtime configuration and settings files
|
||||||
|
- **Pipeline System**: Automated PDF processing workflows via `PipelineController`
|
||||||
|
- **Security Layer**: Authentication, authorization, and user management (when enabled)
|
||||||
|
|
||||||
|
### Component Architecture
|
||||||
|
- **React Components**: Located in `frontend/src/components/` and `frontend/src/tools/`
|
||||||
|
- **Static Assets**: CSS, JS, and resources in `src/main/resources/static/` (legacy) + `frontend/public/` (modern)
|
||||||
|
- **Internationalization**:
|
||||||
|
- Backend: `messages_*.properties` files
|
||||||
|
- Frontend: JSON files in `frontend/public/locales/` (converted from .properties)
|
||||||
|
- Conversion Script: `scripts/convert_properties_to_json.py`
|
||||||
|
|
||||||
|
### Configuration Modes
|
||||||
|
- **Ultra-lite**: Basic PDF operations only
|
||||||
|
- **Standard**: Full feature set
|
||||||
|
- **Fat**: Pre-downloaded dependencies for air-gapped environments
|
||||||
|
- **Security Mode**: Adds authentication, user management, and enterprise features
|
||||||
|
|
||||||
|
### Testing Strategy
|
||||||
|
- **Integration Tests**: Cucumber tests in `testing/cucumber/`
|
||||||
|
- **Docker Testing**: `test.sh` validates all Docker variants
|
||||||
|
- **Manual Testing**: No unit tests currently - relies on UI and API testing
|
||||||
|
|
||||||
|
## Development Workflow
|
||||||
|
|
||||||
|
1. **Local Development**:
|
||||||
|
- Backend: `./gradlew bootRun` (runs on localhost:8080)
|
||||||
|
- Frontend: `cd frontend && npm run dev` (runs on localhost:5173, proxies to backend)
|
||||||
|
2. **Docker Testing**: Use `./test.sh` before submitting PRs
|
||||||
|
3. **Code Style**: Spotless enforces Google Java Format automatically
|
||||||
|
4. **Translations**:
|
||||||
|
- Backend: Use helper scripts in `/scripts` for multi-language updates
|
||||||
|
- Frontend: Update JSON files in `frontend/public/locales/` or use conversion script
|
||||||
|
5. **Documentation**: API docs auto-generated and available at `/swagger-ui/index.html`
|
||||||
|
|
||||||
|
## Frontend Architecture Status
|
||||||
|
|
||||||
|
- **Core Status**: React SPA architecture complete with multi-tool workflow support
|
||||||
|
- **State Management**: FileContext handles all file operations and tool navigation
|
||||||
|
- **File Processing**: Production-ready with memory management for large PDF workflows (up to 100GB+)
|
||||||
|
- **Tool Integration**: Modular hook architecture with `useToolOperation` orchestrator
|
||||||
|
- Individual hooks: `useToolState`, `useToolApiCalls`, `useToolResources`
|
||||||
|
- Utilities: `toolErrorHandler`, `toolResponseProcessor`, `toolOperationTracker`
|
||||||
|
- Pattern: Each tool creates focused operation hook, UI consumes state/actions
|
||||||
|
- **Preview System**: Tool results can be previewed without polluting file context (Split tool example)
|
||||||
|
- **Performance**: Web Worker thumbnails, IndexedDB persistence, background processing
|
||||||
|
|
||||||
|
## Important Notes
|
||||||
|
|
||||||
|
- **Java Version**: Minimum JDK 17, supports and recommends JDK 21
|
||||||
|
- **Lombok**: Used extensively - ensure IDE plugin is installed
|
||||||
|
- **Desktop Mode**: Set `STIRLING_PDF_DESKTOP_UI=true` for desktop application mode
|
||||||
|
- **File Persistence**:
|
||||||
|
- **Backend**: Designed to be stateless - files are processed in memory/temp locations only
|
||||||
|
- **Frontend**: Uses IndexedDB for client-side file storage and caching (with thumbnails)
|
||||||
|
- **Security**: When `DOCKER_ENABLE_SECURITY=false`, security-related classes are excluded from compilation
|
||||||
|
- **FileContext**: All file operations MUST go through FileContext - never bypass with direct File handling
|
||||||
|
- **Memory Management**: Manual cleanup required for PDF.js documents and blob URLs - don't remove cleanup code
|
||||||
|
- **Tool Development**: New tools should follow `useToolOperation` hook pattern (see `useCompressOperation.ts`)
|
||||||
|
- **Performance Target**: Must handle PDFs up to 100GB+ without browser crashes
|
||||||
|
- **Preview System**: Tools can preview results without polluting main file context (see Split tool implementation)
|
||||||
|
|
||||||
|
## Communication Style
|
||||||
|
- Be direct and to the point
|
||||||
|
- No apologies or conversational filler
|
||||||
|
- Answer questions directly without preamble
|
||||||
|
- Explain reasoning concisely when asked
|
||||||
|
- Avoid unnecessary elaboration
|
||||||
|
|
||||||
|
## Decision Making
|
||||||
|
- Ask clarifying questions before making assumptions
|
||||||
|
- Stop and ask when uncertain about project-specific details
|
||||||
|
- Confirm approach before making structural changes
|
||||||
|
- Request guidance on preferences (cross-platform vs specific tools, etc.)
|
||||||
|
- Verify understanding of requirements before proceeding
|
692
DeveloperGuide.md
Normal file
692
DeveloperGuide.md
Normal file
@ -0,0 +1,692 @@
|
|||||||
|
# Stirling-PDF Developer Guide
|
||||||
|
|
||||||
|
## 1. Introduction
|
||||||
|
|
||||||
|
Stirling-PDF is a robust, locally hosted, web-based PDF manipulation tool. **Stirling 2.0** represents a complete frontend rewrite, replacing the legacy Thymeleaf-based UI with a modern React SPA (Single Page Application).
|
||||||
|
|
||||||
|
This guide focuses on developing for Stirling 2.0, including both the React frontend and Spring Boot backend development workflows.
|
||||||
|
|
||||||
|
## 2. Project Overview
|
||||||
|
|
||||||
|
**Stirling 2.0** is built using:
|
||||||
|
|
||||||
|
**Backend:**
|
||||||
|
- Spring Boot (Java 17+, JDK 21 recommended)
|
||||||
|
- PDFBox for core PDF operations
|
||||||
|
- LibreOffice for document conversions
|
||||||
|
- qpdf for PDF optimization
|
||||||
|
- Spring Security (optional, controlled by `DOCKER_ENABLE_SECURITY`)
|
||||||
|
- Lombok for reducing boilerplate code
|
||||||
|
|
||||||
|
**Frontend (React SPA):**
|
||||||
|
- React + TypeScript
|
||||||
|
- Vite for build tooling and development server
|
||||||
|
- Mantine UI component library
|
||||||
|
- TailwindCSS for styling
|
||||||
|
- PDF.js for client-side PDF rendering
|
||||||
|
- PDF-LIB.js for client-side PDF manipulation
|
||||||
|
- IndexedDB for client-side file storage and thumbnails
|
||||||
|
- i18next for internationalization
|
||||||
|
|
||||||
|
**Infrastructure:**
|
||||||
|
- Docker for containerization
|
||||||
|
- Gradle for build management
|
||||||
|
|
||||||
|
**Legacy (reference only during development):**
|
||||||
|
- Thymeleaf templates (being completely replaced in 2.0)
|
||||||
|
|
||||||
|
## 3. Development Environment Setup
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
|
||||||
|
- Docker
|
||||||
|
- Git
|
||||||
|
- Java JDK 17 or later (JDK 21 recommended)
|
||||||
|
- Node.js 18+ and npm (required for frontend development)
|
||||||
|
- Gradle 7.0 or later (Included within the repo)
|
||||||
|
|
||||||
|
### Setup Steps
|
||||||
|
|
||||||
|
1. Clone the repository:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/Stirling-Tools/Stirling-PDF.git
|
||||||
|
cd Stirling-PDF
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Install Docker and JDK17 if not already installed.
|
||||||
|
|
||||||
|
3. Install a recommended Java IDE such as Eclipse, IntelliJ, or VSCode
|
||||||
|
1. Only VSCode
|
||||||
|
1. Open VS Code.
|
||||||
|
2. When prompted, install the recommended extensions.
|
||||||
|
3. Alternatively, open the command palette (`Ctrl + Shift + P` or `Cmd + Shift + P` on macOS) and run:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
Extensions: Show Recommended Extensions
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Install the required extensions from the list.
|
||||||
|
|
||||||
|
4. Lombok Setup
|
||||||
|
Stirling-PDF uses Lombok to reduce boilerplate code. Some IDEs, like Eclipse, don't support Lombok out of the box. To set up Lombok in your development environment:
|
||||||
|
Visit the [Lombok website](https://projectlombok.org/setup/) for installation instructions specific to your IDE.
|
||||||
|
|
||||||
|
5. Add environment variable
|
||||||
|
For local testing, you should generally be testing the full 'Security' version of Stirling PDF. To do this, you must add the environment flag DISABLE_ADDITIONAL_FEATURES=false to your system and/or IDE build/run step.
|
||||||
|
5. **Frontend Setup (Required for Stirling 2.0)**
|
||||||
|
Navigate to the frontend directory and install dependencies using npm.
|
||||||
|
|
||||||
|
## 4. Stirling 2.0 Development Workflow
|
||||||
|
|
||||||
|
### Frontend Development (React)
|
||||||
|
The frontend is a React SPA that runs independently during development:
|
||||||
|
|
||||||
|
1. **Start the backend**: Run the Spring Boot application (serves API endpoints on localhost:8080)
|
||||||
|
2. **Start the frontend dev server**: Navigate to the frontend directory and run the development server (serves UI on localhost:5173)
|
||||||
|
3. **Development flow**: The Vite dev server automatically proxies API calls to the backend
|
||||||
|
|
||||||
|
### File Storage Architecture
|
||||||
|
Stirling 2.0 uses client-side file storage:
|
||||||
|
- **IndexedDB**: Stores files locally in the browser with automatic thumbnail generation
|
||||||
|
- **PDF.js**: Handles client-side PDF rendering and processing
|
||||||
|
- **URL Parameters**: Support for deep linking and tool state persistence
|
||||||
|
|
||||||
|
### Legacy Code Reference
|
||||||
|
The existing Thymeleaf templates remain in the codebase during development as reference material but will be completely removed for the 2.0 release.
|
||||||
|
|
||||||
|
## 5. Project Structure
|
||||||
|
|
||||||
|
```bash
|
||||||
|
Stirling-PDF/
|
||||||
|
├── .github/ # GitHub-specific files (workflows, issue templates)
|
||||||
|
├── configs/ # Configuration files used by stirling at runtime (generated at runtime)
|
||||||
|
├── frontend/ # React SPA frontend (Stirling 2.0)
|
||||||
|
│ ├── src/
|
||||||
|
│ │ ├── components/ # React components
|
||||||
|
│ │ ├── tools/ # Tool-specific React components
|
||||||
|
│ │ ├── hooks/ # Custom React hooks
|
||||||
|
│ │ ├── services/ # API and utility services
|
||||||
|
│ │ ├── types/ # TypeScript type definitions
|
||||||
|
│ │ └── utils/ # Utility functions
|
||||||
|
│ ├── public/
|
||||||
|
│ │ └── locales/ # Internationalization files (JSON)
|
||||||
|
│ ├── package.json # Frontend dependencies
|
||||||
|
│ └── vite.config.ts # Vite configuration
|
||||||
|
├── customFiles/ # Custom static files and templates (generated at runtime used to replace existing files)
|
||||||
|
├── docs/ # Documentation files
|
||||||
|
├── exampleYmlFiles/ # Example YAML configuration files
|
||||||
|
├── images/ # Image assets
|
||||||
|
├── pipeline/ # Pipeline-related files (generated at runtime)
|
||||||
|
├── scripts/ # Utility scripts
|
||||||
|
├── src/ # Source code
|
||||||
|
│ ├── main/
|
||||||
|
│ │ ├── java/
|
||||||
|
│ │ │ └── stirling/
|
||||||
|
│ │ │ └── software/
|
||||||
|
│ │ │ └── SPDF/
|
||||||
|
│ │ │ ├── config/
|
||||||
|
│ │ │ ├── controller/
|
||||||
|
│ │ │ ├── model/
|
||||||
|
│ │ │ ├── repository/
|
||||||
|
│ │ │ ├── service/
|
||||||
|
│ │ │ └── utils/
|
||||||
|
│ │ └── resources/
|
||||||
|
│ │ ├── static/ # Legacy static assets (reference only)
|
||||||
|
│ │ │ ├── css/
|
||||||
|
│ │ │ ├── js/
|
||||||
|
│ │ │ └── pdfjs/
|
||||||
|
│ │ └── templates/ # Legacy Thymeleaf templates (reference only)
|
||||||
|
│ └── test/
|
||||||
|
├── testing/ # Cucumber and integration tests
|
||||||
|
│ └── cucumber/ # Cucumber test files
|
||||||
|
├── build.gradle # Gradle build configuration
|
||||||
|
├── Dockerfile # Main Dockerfile
|
||||||
|
├── Dockerfile.ultra-lite # Dockerfile for ultra-lite version
|
||||||
|
├── Dockerfile.fat # Dockerfile for fat version
|
||||||
|
├── docker-compose.yml # Docker Compose configuration
|
||||||
|
└── test.sh # Test script to deploy all docker versions and run cuke tests
|
||||||
|
```
|
||||||
|
|
||||||
|
## 6. Docker-based Development
|
||||||
|
|
||||||
|
Stirling-PDF offers several Docker versions:
|
||||||
|
|
||||||
|
- Full: All features included
|
||||||
|
- Ultra-Lite: Basic PDF operations only
|
||||||
|
- Fat: Includes additional libraries and fonts predownloaded
|
||||||
|
|
||||||
|
### Example Docker Compose Files
|
||||||
|
|
||||||
|
Stirling-PDF provides several example Docker Compose files in the `exampleYmlFiles` directory, such as:
|
||||||
|
|
||||||
|
- `docker-compose-latest.yml`: Latest version without login and security features
|
||||||
|
- `docker-compose-latest-security.yml`: Latest version with login and security features enabled
|
||||||
|
- `docker-compose-latest-fat-security.yml`: Fat version with login and security features enabled
|
||||||
|
|
||||||
|
These files provide pre-configured setups for different scenarios. For example, here's a snippet from `docker-compose-latest-security.yml`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
services:
|
||||||
|
stirling-pdf:
|
||||||
|
container_name: Stirling-PDF-Security
|
||||||
|
image: docker.stirlingpdf.com/stirlingtools/stirling-pdf:latest
|
||||||
|
deploy:
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
memory: 4G
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "curl -f http://localhost:8080/api/v1/info/status | grep -q 'UP' && curl -fL http://localhost:8080/ | grep -q 'Please sign in'"]
|
||||||
|
interval: 5s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 16
|
||||||
|
ports:
|
||||||
|
- "8080:8080"
|
||||||
|
volumes:
|
||||||
|
- ./stirling/latest/data:/usr/share/tessdata:rw
|
||||||
|
- ./stirling/latest/config:/configs:rw
|
||||||
|
- ./stirling/latest/logs:/logs:rw
|
||||||
|
environment:
|
||||||
|
DISABLE_ADDITIONAL_FEATURES: "false"
|
||||||
|
SECURITY_ENABLELOGIN: "true"
|
||||||
|
PUID: 1002
|
||||||
|
PGID: 1002
|
||||||
|
UMASK: "022"
|
||||||
|
SYSTEM_DEFAULTLOCALE: en-US
|
||||||
|
UI_APPNAME: Stirling-PDF
|
||||||
|
UI_HOMEDESCRIPTION: Demo site for Stirling-PDF Latest with Security
|
||||||
|
UI_APPNAMENAVBAR: Stirling-PDF Latest
|
||||||
|
SYSTEM_MAXFILESIZE: "100"
|
||||||
|
METRICS_ENABLED: "true"
|
||||||
|
SYSTEM_GOOGLEVISIBILITY: "true"
|
||||||
|
SHOW_SURVEY: "true"
|
||||||
|
restart: on-failure:5
|
||||||
|
```
|
||||||
|
|
||||||
|
To use these example files, copy the desired file to your project root and rename it to `docker-compose.yml`, or specify the file explicitly when running Docker Compose:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker-compose -f exampleYmlFiles/docker-compose-latest-security.yml up
|
||||||
|
```
|
||||||
|
|
||||||
|
### Building Docker Images
|
||||||
|
|
||||||
|
Stirling-PDF uses different Docker images for various configurations. The build process is controlled by environment variables and uses specific Dockerfile variants. Here's how to build the Docker images:
|
||||||
|
|
||||||
|
1. Set the security environment variable:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export DISABLE_ADDITIONAL_FEATURES=true # or false for to enable login and security features for builds
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Build the project with Gradle:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./gradlew clean build
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Build the Docker images:
|
||||||
|
|
||||||
|
For the latest version:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker build --no-cache --pull --build-arg VERSION_TAG=alpha -t stirlingtools/stirling-pdf:latest -f ./Dockerfile .
|
||||||
|
```
|
||||||
|
|
||||||
|
For the ultra-lite version:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker build --no-cache --pull --build-arg VERSION_TAG=alpha -t stirlingtools/stirling-pdf:latest-ultra-lite -f ./Dockerfile.ultra-lite .
|
||||||
|
```
|
||||||
|
|
||||||
|
For the fat version (with login and security features enabled):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export DISABLE_ADDITIONAL_FEATURES=false
|
||||||
|
docker build --no-cache --pull --build-arg VERSION_TAG=alpha -t stirlingtools/stirling-pdf:latest-fat -f ./Dockerfile.fat .
|
||||||
|
```
|
||||||
|
|
||||||
|
Note: The `--no-cache` and `--pull` flags ensure that the build process uses the latest base images and doesn't use cached layers, which is useful for testing and ensuring reproducible builds. however to improve build times these can often be removed depending on your usecase
|
||||||
|
|
||||||
|
## 7. Testing
|
||||||
|
|
||||||
|
### Comprehensive Testing Script
|
||||||
|
|
||||||
|
Stirling-PDF provides a `test.sh` script in the root directory. This script builds all versions of Stirling-PDF, checks that each version works, and runs Cucumber tests. It's recommended to run this script before submitting a final pull request.
|
||||||
|
|
||||||
|
To run the test script:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./test.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
This script performs the following actions:
|
||||||
|
|
||||||
|
1. Builds all Docker images (full, ultra-lite, fat).
|
||||||
|
2. Runs each version to ensure it starts correctly.
|
||||||
|
3. Executes Cucumber tests against the main version and ensures feature compatibility. In the event these tests fail, your PR will not be merged.
|
||||||
|
|
||||||
|
Note: The `test.sh` script will run automatically when you raise a PR. However, it's recommended to run it locally first to save resources and catch any issues early.
|
||||||
|
|
||||||
|
### Full Testing with Docker
|
||||||
|
|
||||||
|
1. Build and run the Docker container per the above instructions:
|
||||||
|
|
||||||
|
2. Access the application at `http://localhost:8080` and manually test all features developed.
|
||||||
|
|
||||||
|
### Frontend Development Testing (Stirling 2.0)
|
||||||
|
|
||||||
|
For React frontend development:
|
||||||
|
|
||||||
|
1. Start the backend: Run the Spring Boot application to serve API endpoints on localhost:8080
|
||||||
|
2. Start the frontend dev server: Navigate to the frontend directory and run the development server on localhost:5173
|
||||||
|
3. The Vite dev server automatically proxies API calls to the backend
|
||||||
|
4. Test React components, UI interactions, and IndexedDB file operations using browser developer tools
|
||||||
|
|
||||||
|
### Local Testing (Java and UI Components)
|
||||||
|
|
||||||
|
For quick iterations and development of Java backend, JavaScript, and UI components, you can run and test Stirling-PDF locally without Docker. This approach allows you to work on and verify changes to:
|
||||||
|
|
||||||
|
- Java backend logic
|
||||||
|
- RESTful API endpoints
|
||||||
|
- JavaScript functionality
|
||||||
|
- User interface components and styling
|
||||||
|
- Thymeleaf templates
|
||||||
|
|
||||||
|
To run Stirling-PDF locally:
|
||||||
|
|
||||||
|
1. Compile and run the project using built-in IDE methods or by running:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./gradlew bootRun
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Access the application at `http://localhost:8080` in your web browser.
|
||||||
|
|
||||||
|
3. Manually test the features you're working on through the UI.
|
||||||
|
|
||||||
|
4. For API changes, use tools like Postman or curl to test endpoints directly.
|
||||||
|
|
||||||
|
Important notes:
|
||||||
|
|
||||||
|
- Local testing doesn't include features that depend on external tools like qpdf, LibreOffice, or Python scripts.
|
||||||
|
- There are currently no automated unit tests. All testing is done manually through the UI or API calls. (You are welcome to add JUnits!)
|
||||||
|
- Always verify your changes in the full Docker environment before submitting pull requests, as some integrations and features will only work in the complete setup.
|
||||||
|
|
||||||
|
## 8. Contributing
|
||||||
|
|
||||||
|
1. Fork the repository on GitHub.
|
||||||
|
2. Create a new branch for your feature or bug fix.
|
||||||
|
3. Make your changes and commit them with clear, descriptive messages and ensure any documentation is updated related to your changes.
|
||||||
|
4. Test your changes thoroughly in the Docker environment.
|
||||||
|
5. Run the `test.sh` script to ensure all versions build correctly and pass the Cucumber tests:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./test.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
6. Push your changes to your fork.
|
||||||
|
7. Submit a pull request to the main repository.
|
||||||
|
8. See additional [contributing guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md).
|
||||||
|
|
||||||
|
When you raise a PR:
|
||||||
|
|
||||||
|
- The `test.sh` script will run automatically against your PR.
|
||||||
|
- The PR checks will verify versioning and dependency updates.
|
||||||
|
- Documentation will be automatically updated for dependency changes.
|
||||||
|
- Security issues will be checked using Snyk and PixeeBot.
|
||||||
|
|
||||||
|
Address any issues that arise from these checks before finalizing your pull request.
|
||||||
|
|
||||||
|
## 9. API Documentation
|
||||||
|
|
||||||
|
API documentation is available at `/swagger-ui/index.html` when running the application. You can also view the latest API documentation [here](https://app.swaggerhub.com/apis-docs/Stirling-Tools/Stirling-PDF/).
|
||||||
|
|
||||||
|
## 10. Customization
|
||||||
|
|
||||||
|
Stirling-PDF can be customized through environment variables or a `settings.yml` file. Key customization options include:
|
||||||
|
|
||||||
|
- Application name and branding
|
||||||
|
- Security settings
|
||||||
|
- UI customization
|
||||||
|
- Endpoint management
|
||||||
|
|
||||||
|
When using Docker, pass environment variables using the `-e` flag or in your `docker-compose.yml` file.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker run -p 8080:8080 -e APP_NAME="My PDF Tool" stirling-pdf:full
|
||||||
|
```
|
||||||
|
|
||||||
|
Refer to the main README for a full list of customization options.
|
||||||
|
|
||||||
|
## 11. Language Translations
|
||||||
|
|
||||||
|
For managing language translations that affect multiple files, Stirling-PDF provides a helper script:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
/scripts/replace_translation_line.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
This script helps you make consistent replacements across language files.
|
||||||
|
|
||||||
|
When contributing translations:
|
||||||
|
|
||||||
|
1. Use the helper script for multi-file changes.
|
||||||
|
2. Ensure all language files are updated consistently.
|
||||||
|
3. The PR checks will verify consistency in language file updates.
|
||||||
|
|
||||||
|
Remember to test your changes thoroughly to ensure they don't break any existing functionality.
|
||||||
|
|
||||||
|
## Code examples
|
||||||
|
|
||||||
|
### React Component Development (Stirling 2.0)
|
||||||
|
|
||||||
|
For Stirling 2.0, new features are built as React components instead of Thymeleaf templates:
|
||||||
|
|
||||||
|
#### Creating a New Tool Component
|
||||||
|
|
||||||
|
1. **Create the React Component:**
|
||||||
|
```typescript
|
||||||
|
// frontend/src/tools/NewTool.tsx
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { Button, FileInput, Container } from '@mantine/core';
|
||||||
|
|
||||||
|
interface NewToolProps {
|
||||||
|
params: Record<string, any>;
|
||||||
|
updateParams: (updates: Record<string, any>) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function NewTool({ params, updateParams }: NewToolProps) {
|
||||||
|
const [files, setFiles] = useState<File[]>([]);
|
||||||
|
|
||||||
|
const handleProcess = async () => {
|
||||||
|
// Process files using API or client-side logic
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container>
|
||||||
|
<FileInput
|
||||||
|
multiple
|
||||||
|
accept="application/pdf"
|
||||||
|
onChange={setFiles}
|
||||||
|
/>
|
||||||
|
<Button onClick={handleProcess}>Process</Button>
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Add API Integration:**
|
||||||
|
```typescript
|
||||||
|
// Use existing API endpoints or create new ones
|
||||||
|
const response = await fetch('/api/v1/new-tool', {
|
||||||
|
method: 'POST',
|
||||||
|
body: formData
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Register in Tool Picker:**
|
||||||
|
Update the tool picker component to include the new tool with proper routing and URL parameter support.
|
||||||
|
|
||||||
|
### Legacy Reference: Overview of Thymeleaf
|
||||||
|
|
||||||
|
Thymeleaf is a server-side Java HTML template engine. It is used in Stirling-PDF to render dynamic web pages. Thymeleaf integrates heavily with Spring Boot.
|
||||||
|
|
||||||
|
### Thymeleaf overview
|
||||||
|
|
||||||
|
In Stirling-PDF, Thymeleaf is used to create HTML templates that are rendered on the server side. These templates are located in the `stirling-pdf/src/main/resources/templates` directory. Thymeleaf templates use a combination of HTML and special Thymeleaf attributes to dynamically generate content.
|
||||||
|
|
||||||
|
Some examples of this are:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<th:block th:insert="~{fragments/navbar.html :: navbar}"></th:block>
|
||||||
|
```
|
||||||
|
or
|
||||||
|
```html
|
||||||
|
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
|
||||||
|
```
|
||||||
|
|
||||||
|
Where it uses the `th:block`, `th:` indicating it's a special Thymeleaf element to be used server-side in generating the HTML, and block being the actual element type.
|
||||||
|
In this case, we are inserting the `navbar` entry within the `fragments/navbar.html` fragment into the `th:block` element.
|
||||||
|
|
||||||
|
They can be more complex, such as:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<th:block th:insert="~{fragments/common :: head(title=#{pageExtracter.title}, header=#{pageExtracter.header})}"></th:block>
|
||||||
|
```
|
||||||
|
|
||||||
|
Which is the same as above but passes the parameters title and header into the fragment `common.html` to be used in its HTML generation.
|
||||||
|
|
||||||
|
Thymeleaf can also be used to loop through objects or pass things from the Java side into the HTML side.
|
||||||
|
|
||||||
|
```java
|
||||||
|
@GetMapping
|
||||||
|
public String newFeaturePage(Model model) {
|
||||||
|
model.addAttribute("exampleData", exampleData);
|
||||||
|
return "new-feature";
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
In the above example, if exampleData is a list of plain java objects of class Person and within it, you had id, name, age, etc. You can reference it like so
|
||||||
|
|
||||||
|
```html
|
||||||
|
<tbody>
|
||||||
|
<!-- Use th:each to iterate over the list -->
|
||||||
|
<tr th:each="person : ${exampleData}">
|
||||||
|
<td th:text="${person.id}"></td>
|
||||||
|
<td th:text="${person.name}"></td>
|
||||||
|
<td th:text="${person.age}"></td>
|
||||||
|
<td th:text="${person.email}"></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
```
|
||||||
|
|
||||||
|
This would generate n entries of tr for each person in exampleData
|
||||||
|
|
||||||
|
### Adding a New Feature to the Backend (API)
|
||||||
|
|
||||||
|
1. **Create a New Controller:**
|
||||||
|
- Create a new Java class in the `stirling-pdf/src/main/java/stirling/software/SPDF/controller/api` directory.
|
||||||
|
- Annotate the class with `@RestController` and `@RequestMapping` to define the API endpoint.
|
||||||
|
- Ensure to add API documentation annotations like `@Tag(name = "General", description = "General APIs")` and `@Operation(summary = "Crops a PDF document", description = "This operation takes an input PDF file and crops it according to the given coordinates. Input:PDF Output:PDF Type:SISO")`.
|
||||||
|
|
||||||
|
```java
|
||||||
|
package stirling.software.SPDF.controller.api;
|
||||||
|
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/v1/new-feature")
|
||||||
|
@Tag(name = "General", description = "General APIs")
|
||||||
|
public class NewFeatureController {
|
||||||
|
|
||||||
|
@GetMapping
|
||||||
|
@Operation(summary = "New Feature", description = "This is a new feature endpoint.")
|
||||||
|
public String newFeature() {
|
||||||
|
return "NewFeatureResponse"; // This refers to the NewFeatureResponse.html template presenting the user with the generated html from that file when they navigate to /api/v1/new-feature
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Define the Service Layer:** (Not required but often useful)
|
||||||
|
- Create a new service class in the `stirling-pdf/src/main/java/stirling/software/SPDF/service` directory.
|
||||||
|
- Implement the business logic for the new feature.
|
||||||
|
|
||||||
|
```java
|
||||||
|
package stirling.software.SPDF.service;
|
||||||
|
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class NewFeatureService {
|
||||||
|
|
||||||
|
public String getNewFeatureData() {
|
||||||
|
// Implement business logic here
|
||||||
|
return "New Feature Data";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
2b. **Integrate the Service with the Controller:**
|
||||||
|
|
||||||
|
- Autowire the service class in the controller and use it to handle the API request.
|
||||||
|
|
||||||
|
```java
|
||||||
|
package stirling.software.SPDF.controller.api;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
import stirling.software.SPDF.service.NewFeatureService;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/v1/new-feature")
|
||||||
|
@Tag(name = "General", description = "General APIs")
|
||||||
|
public class NewFeatureController {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private NewFeatureService newFeatureService;
|
||||||
|
|
||||||
|
@GetMapping
|
||||||
|
@Operation(summary = "New Feature", description = "This is a new feature endpoint.")
|
||||||
|
public String newFeature() {
|
||||||
|
return newFeatureService.getNewFeatureData();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Adding a New Feature to the Frontend (UI)
|
||||||
|
|
||||||
|
1. **Create a New Thymeleaf Template:**
|
||||||
|
- Create a new HTML file in the `stirling-pdf/src/main/resources/templates` directory.
|
||||||
|
- Use Thymeleaf attributes to dynamically generate content.
|
||||||
|
- Use `extract-page.html` as a base example for the HTML template, which is useful to ensure importing of the general layout, navbar, and footer.
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html th:lang="${#locale.language}" th:dir="#{language.direction}" th:data-language="${#locale.toString()}" xmlns:th="https://www.thymeleaf.org">
|
||||||
|
<head>
|
||||||
|
<th:block th:insert="~{fragments/common :: head(title=#{newFeature.title}, header=#{newFeature.header})}"></th:block>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div id="page-container">
|
||||||
|
<div id="content-wrap">
|
||||||
|
<th:block th:insert="~{fragments/navbar.html :: navbar}"></th:block>
|
||||||
|
<br><br>
|
||||||
|
<div class="container">
|
||||||
|
<div class="row justify-content-center">
|
||||||
|
<div class="col-md-6 bg-card">
|
||||||
|
<div class="tool-header">
|
||||||
|
<span class="material-symbols-rounded tool-header-icon organize">upload</span>
|
||||||
|
<span class="tool-header-text" th:text="#{newFeature.header}"></span>
|
||||||
|
</div>
|
||||||
|
<form th:action="@{'/api/v1/new-feature'}" method="post" enctype="multipart/form-data">
|
||||||
|
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multipleInputsForSingleRequest=false, accept='application/pdf')}"></div>
|
||||||
|
<input type="hidden" id="customMode" name="customMode" value="">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="featureInput" th:text="#{newFeature.prompt}"></label>
|
||||||
|
<input type="text" class="form-control" id="featureInput" name="featureInput" th:placeholder="#{newFeature.placeholder}" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{newFeature.submit}"></button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Create a New Controller for the UI:**
|
||||||
|
- Create a new Java class in the `stirling-pdf/src/main/java/stirling/software/SPDF/controller/ui` directory.
|
||||||
|
- Annotate the class with `@Controller` and `@RequestMapping` to define the UI endpoint.
|
||||||
|
|
||||||
|
```java
|
||||||
|
package stirling.software.SPDF.controller.ui;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Controller;
|
||||||
|
import org.springframework.ui.Model;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import stirling.software.SPDF.service.NewFeatureService;
|
||||||
|
|
||||||
|
@Controller
|
||||||
|
@RequestMapping("/new-feature")
|
||||||
|
public class NewFeatureUIController {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private NewFeatureService newFeatureService;
|
||||||
|
|
||||||
|
@GetMapping
|
||||||
|
public String newFeaturePage(Model model) {
|
||||||
|
model.addAttribute("newFeatureData", newFeatureService.getNewFeatureData());
|
||||||
|
return "new-feature";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Update the Navigation Bar:**
|
||||||
|
- Add a link to the new feature page in the navigation bar.
|
||||||
|
- Update the `stirling-pdf/src/main/resources/templates/fragments/navbar.html` file.
|
||||||
|
|
||||||
|
```html
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" th:href="@{'/new-feature'}">New Feature</a>
|
||||||
|
</li>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Adding New Translations to Existing Language Files in Stirling-PDF
|
||||||
|
|
||||||
|
When adding a new feature or modifying existing ones in Stirling-PDF, you'll need to add new translation entries to the existing language files. Here's a step-by-step guide:
|
||||||
|
|
||||||
|
### 1. Locate Existing Language Files
|
||||||
|
|
||||||
|
Find the existing `messages.properties` files in the `stirling-pdf/src/main/resources` directory. You'll see files like:
|
||||||
|
|
||||||
|
- `messages.properties` (default, usually English)
|
||||||
|
- `messages_en_GB.properties`
|
||||||
|
- `messages_fr_FR.properties`
|
||||||
|
- `messages_de_DE.properties`
|
||||||
|
- etc.
|
||||||
|
|
||||||
|
### 2. Add New Translation Entries
|
||||||
|
|
||||||
|
Open each of these files and add your new translation entries. For example, if you're adding a new feature called "PDF Splitter",
|
||||||
|
Use descriptive, hierarchical keys (e.g., `feature.element.description`)
|
||||||
|
you might add:
|
||||||
|
|
||||||
|
```properties
|
||||||
|
pdfSplitter.title=PDF Splitter
|
||||||
|
pdfSplitter.description=Split your PDF into multiple documents
|
||||||
|
pdfSplitter.button.split=Split PDF
|
||||||
|
pdfSplitter.input.pages=Enter page numbers to split
|
||||||
|
```
|
||||||
|
|
||||||
|
Add these entries to the default GB language file and any others you wish, translating the values as appropriate for each language.
|
||||||
|
|
||||||
|
### 3. Use Translations in Thymeleaf Templates
|
||||||
|
|
||||||
|
In your Thymeleaf templates, use the `#{key}` syntax to reference the new translations:
|
||||||
|
|
||||||
|
```html
|
||||||
|
<h1 th:text="#{pdfSplitter.title}">PDF Splitter</h1>
|
||||||
|
<p th:text="#{pdfSplitter.description}">Split your PDF into multiple documents</p>
|
||||||
|
<input type="text" th:placeholder="#{pdfSplitter.input.pages}">
|
||||||
|
<button th:text="#{pdfSplitter.button.split}">Split PDF</button>
|
||||||
|
```
|
||||||
|
|
||||||
|
Remember, never hard-code text in your templates or Java code. Always use translation keys to ensure proper localization.
|
@ -45,6 +45,7 @@ public class CleanUrlInterceptor implements HandlerInterceptor {
|
|||||||
|
|
||||||
String queryString = request.getQueryString();
|
String queryString = request.getQueryString();
|
||||||
if (queryString != null && !queryString.isEmpty()) {
|
if (queryString != null && !queryString.isEmpty()) {
|
||||||
|
|
||||||
Map<String, String> allowedParameters = new HashMap<>();
|
Map<String, String> allowedParameters = new HashMap<>();
|
||||||
|
|
||||||
// Keep only the allowed parameters
|
// Keep only the allowed parameters
|
||||||
|
@ -10,6 +10,7 @@ import io.swagger.v3.oas.models.info.Info;
|
|||||||
import io.swagger.v3.oas.models.info.License;
|
import io.swagger.v3.oas.models.info.License;
|
||||||
import io.swagger.v3.oas.models.security.SecurityRequirement;
|
import io.swagger.v3.oas.models.security.SecurityRequirement;
|
||||||
import io.swagger.v3.oas.models.security.SecurityScheme;
|
import io.swagger.v3.oas.models.security.SecurityScheme;
|
||||||
|
import io.swagger.v3.oas.models.servers.Server;
|
||||||
|
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
|
|
||||||
@ -50,17 +51,26 @@ public class OpenApiConfig {
|
|||||||
.url("https://www.stirlingpdf.com")
|
.url("https://www.stirlingpdf.com")
|
||||||
.email("contact@stirlingpdf.com"))
|
.email("contact@stirlingpdf.com"))
|
||||||
.description(DEFAULT_DESCRIPTION);
|
.description(DEFAULT_DESCRIPTION);
|
||||||
|
|
||||||
|
OpenAPI openAPI = new OpenAPI().info(info);
|
||||||
|
|
||||||
|
// Add server configuration from environment variable
|
||||||
|
String swaggerServerUrl = System.getenv("SWAGGER_SERVER_URL");
|
||||||
|
if (swaggerServerUrl != null && !swaggerServerUrl.trim().isEmpty()) {
|
||||||
|
Server server = new Server().url(swaggerServerUrl).description("API Server");
|
||||||
|
openAPI.addServersItem(server);
|
||||||
|
}
|
||||||
|
|
||||||
if (!applicationProperties.getSecurity().getEnableLogin()) {
|
if (!applicationProperties.getSecurity().getEnableLogin()) {
|
||||||
return new OpenAPI().components(new Components()).info(info);
|
return openAPI.components(new Components());
|
||||||
} else {
|
} else {
|
||||||
SecurityScheme apiKeyScheme =
|
SecurityScheme apiKeyScheme =
|
||||||
new SecurityScheme()
|
new SecurityScheme()
|
||||||
.type(SecurityScheme.Type.APIKEY)
|
.type(SecurityScheme.Type.APIKEY)
|
||||||
.in(SecurityScheme.In.HEADER)
|
.in(SecurityScheme.In.HEADER)
|
||||||
.name("X-API-KEY");
|
.name("X-API-KEY");
|
||||||
return new OpenAPI()
|
return openAPI
|
||||||
.components(new Components().addSecuritySchemes("apiKey", apiKeyScheme))
|
.components(new Components().addSecuritySchemes("apiKey", apiKeyScheme))
|
||||||
.info(info)
|
|
||||||
.addSecurityItem(new SecurityRequirement().addList("apiKey"));
|
.addSecurityItem(new SecurityRequirement().addList("apiKey"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package stirling.software.SPDF.controller.api;
|
package stirling.software.SPDF.controller.api;
|
||||||
|
|
||||||
|
import stirling.software.common.annotations.AutoJobPostMapping;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
@ -29,7 +31,7 @@ public class AnalysisController {
|
|||||||
|
|
||||||
private final CustomPDFDocumentFactory pdfDocumentFactory;
|
private final CustomPDFDocumentFactory pdfDocumentFactory;
|
||||||
|
|
||||||
@PostMapping(value = "/page-count", consumes = "multipart/form-data")
|
@AutoJobPostMapping(value = "/page-count", consumes = "multipart/form-data")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Get PDF page count",
|
summary = "Get PDF page count",
|
||||||
description = "Returns total number of pages in PDF. Input:PDF Output:JSON Type:SISO")
|
description = "Returns total number of pages in PDF. Input:PDF Output:JSON Type:SISO")
|
||||||
@ -39,7 +41,7 @@ public class AnalysisController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping(value = "/basic-info", consumes = "multipart/form-data")
|
@AutoJobPostMapping(value = "/basic-info", consumes = "multipart/form-data")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Get basic PDF information",
|
summary = "Get basic PDF information",
|
||||||
description = "Returns page count, version, file size. Input:PDF Output:JSON Type:SISO")
|
description = "Returns page count, version, file size. Input:PDF Output:JSON Type:SISO")
|
||||||
@ -53,7 +55,7 @@ public class AnalysisController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping(value = "/document-properties", consumes = "multipart/form-data")
|
@AutoJobPostMapping(value = "/document-properties", consumes = "multipart/form-data")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Get PDF document properties",
|
summary = "Get PDF document properties",
|
||||||
description = "Returns title, author, subject, etc. Input:PDF Output:JSON Type:SISO")
|
description = "Returns title, author, subject, etc. Input:PDF Output:JSON Type:SISO")
|
||||||
@ -76,7 +78,7 @@ public class AnalysisController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping(value = "/page-dimensions", consumes = "multipart/form-data")
|
@AutoJobPostMapping(value = "/page-dimensions", consumes = "multipart/form-data")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Get page dimensions for all pages",
|
summary = "Get page dimensions for all pages",
|
||||||
description = "Returns width and height of each page. Input:PDF Output:JSON Type:SISO")
|
description = "Returns width and height of each page. Input:PDF Output:JSON Type:SISO")
|
||||||
@ -96,7 +98,7 @@ public class AnalysisController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping(value = "/form-fields", consumes = "multipart/form-data")
|
@AutoJobPostMapping(value = "/form-fields", consumes = "multipart/form-data")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Get form field information",
|
summary = "Get form field information",
|
||||||
description =
|
description =
|
||||||
@ -119,7 +121,7 @@ public class AnalysisController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping(value = "/annotation-info", consumes = "multipart/form-data")
|
@AutoJobPostMapping(value = "/annotation-info", consumes = "multipart/form-data")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Get annotation information",
|
summary = "Get annotation information",
|
||||||
description = "Returns count and types of annotations. Input:PDF Output:JSON Type:SISO")
|
description = "Returns count and types of annotations. Input:PDF Output:JSON Type:SISO")
|
||||||
@ -143,7 +145,7 @@ public class AnalysisController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping(value = "/font-info", consumes = "multipart/form-data")
|
@AutoJobPostMapping(value = "/font-info", consumes = "multipart/form-data")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Get font information",
|
summary = "Get font information",
|
||||||
description =
|
description =
|
||||||
@ -165,7 +167,7 @@ public class AnalysisController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping(value = "/security-info", consumes = "multipart/form-data")
|
@AutoJobPostMapping(value = "/security-info", consumes = "multipart/form-data")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Get security information",
|
summary = "Get security information",
|
||||||
description =
|
description =
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package stirling.software.SPDF.controller.api;
|
package stirling.software.SPDF.controller.api;
|
||||||
|
|
||||||
|
import stirling.software.common.annotations.AutoJobPostMapping;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
@ -33,7 +35,7 @@ public class CropController {
|
|||||||
|
|
||||||
private final CustomPDFDocumentFactory pdfDocumentFactory;
|
private final CustomPDFDocumentFactory pdfDocumentFactory;
|
||||||
|
|
||||||
@PostMapping(value = "/crop", consumes = "multipart/form-data")
|
@AutoJobPostMapping(value = "/crop", consumes = "multipart/form-data")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Crops a PDF document",
|
summary = "Crops a PDF document",
|
||||||
description =
|
description =
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package stirling.software.SPDF.controller.api;
|
package stirling.software.SPDF.controller.api;
|
||||||
|
|
||||||
|
import stirling.software.common.annotations.AutoJobPostMapping;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
@ -44,7 +46,7 @@ public class EditTableOfContentsController {
|
|||||||
private final CustomPDFDocumentFactory pdfDocumentFactory;
|
private final CustomPDFDocumentFactory pdfDocumentFactory;
|
||||||
private final ObjectMapper objectMapper;
|
private final ObjectMapper objectMapper;
|
||||||
|
|
||||||
@PostMapping(value = "/extract-bookmarks", consumes = "multipart/form-data")
|
@AutoJobPostMapping(value = "/extract-bookmarks", consumes = "multipart/form-data")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Extract PDF Bookmarks",
|
summary = "Extract PDF Bookmarks",
|
||||||
description = "Extracts bookmarks/table of contents from a PDF document as JSON.")
|
description = "Extracts bookmarks/table of contents from a PDF document as JSON.")
|
||||||
@ -152,7 +154,7 @@ public class EditTableOfContentsController {
|
|||||||
return bookmark;
|
return bookmark;
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping(value = "/edit-table-of-contents", consumes = "multipart/form-data")
|
@AutoJobPostMapping(value = "/edit-table-of-contents", consumes = "multipart/form-data")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Edit Table of Contents",
|
summary = "Edit Table of Contents",
|
||||||
description = "Add or edit bookmarks/table of contents in a PDF document.")
|
description = "Add or edit bookmarks/table of contents in a PDF document.")
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package stirling.software.SPDF.controller.api;
|
package stirling.software.SPDF.controller.api;
|
||||||
|
|
||||||
|
import stirling.software.common.annotations.AutoJobPostMapping;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@ -154,7 +156,7 @@ public class MergeController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/merge-pdfs")
|
@AutoJobPostMapping(consumes = "multipart/form-data", value = "/merge-pdfs")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Merge multiple PDF files into one",
|
summary = "Merge multiple PDF files into one",
|
||||||
description =
|
description =
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package stirling.software.SPDF.controller.api;
|
package stirling.software.SPDF.controller.api;
|
||||||
|
|
||||||
|
import stirling.software.common.annotations.AutoJobPostMapping;
|
||||||
|
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@ -36,7 +38,7 @@ public class MultiPageLayoutController {
|
|||||||
|
|
||||||
private final CustomPDFDocumentFactory pdfDocumentFactory;
|
private final CustomPDFDocumentFactory pdfDocumentFactory;
|
||||||
|
|
||||||
@PostMapping(value = "/multi-page-layout", consumes = "multipart/form-data")
|
@AutoJobPostMapping(value = "/multi-page-layout", consumes = "multipart/form-data")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Merge multiple pages of a PDF document into a single page",
|
summary = "Merge multiple pages of a PDF document into a single page",
|
||||||
description =
|
description =
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package stirling.software.SPDF.controller.api;
|
package stirling.software.SPDF.controller.api;
|
||||||
|
|
||||||
|
import stirling.software.common.annotations.AutoJobPostMapping;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
@ -46,7 +48,7 @@ public class PdfImageRemovalController {
|
|||||||
* content type and filename.
|
* content type and filename.
|
||||||
* @throws IOException If an error occurs while processing the PDF file.
|
* @throws IOException If an error occurs while processing the PDF file.
|
||||||
*/
|
*/
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/remove-image-pdf")
|
@AutoJobPostMapping(consumes = "multipart/form-data", value = "/remove-image-pdf")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Remove images from file to reduce the file size.",
|
summary = "Remove images from file to reduce the file size.",
|
||||||
description =
|
description =
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package stirling.software.SPDF.controller.api;
|
package stirling.software.SPDF.controller.api;
|
||||||
|
|
||||||
|
import stirling.software.common.annotations.AutoJobPostMapping;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@ -39,7 +41,7 @@ public class PdfOverlayController {
|
|||||||
|
|
||||||
private final CustomPDFDocumentFactory pdfDocumentFactory;
|
private final CustomPDFDocumentFactory pdfDocumentFactory;
|
||||||
|
|
||||||
@PostMapping(value = "/overlay-pdfs", consumes = "multipart/form-data")
|
@AutoJobPostMapping(value = "/overlay-pdfs", consumes = "multipart/form-data")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Overlay PDF files in various modes",
|
summary = "Overlay PDF files in various modes",
|
||||||
description =
|
description =
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package stirling.software.SPDF.controller.api;
|
package stirling.software.SPDF.controller.api;
|
||||||
|
|
||||||
|
import stirling.software.common.annotations.AutoJobPostMapping;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
@ -38,7 +40,7 @@ public class RearrangePagesPDFController {
|
|||||||
|
|
||||||
private final CustomPDFDocumentFactory pdfDocumentFactory;
|
private final CustomPDFDocumentFactory pdfDocumentFactory;
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/remove-pages")
|
@AutoJobPostMapping(consumes = "multipart/form-data", value = "/remove-pages")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Remove pages from a PDF file",
|
summary = "Remove pages from a PDF file",
|
||||||
description =
|
description =
|
||||||
@ -237,7 +239,7 @@ public class RearrangePagesPDFController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/rearrange-pages")
|
@AutoJobPostMapping(consumes = "multipart/form-data", value = "/rearrange-pages")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Rearrange pages in a PDF file",
|
summary = "Rearrange pages in a PDF file",
|
||||||
description =
|
description =
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package stirling.software.SPDF.controller.api;
|
package stirling.software.SPDF.controller.api;
|
||||||
|
|
||||||
|
import stirling.software.common.annotations.AutoJobPostMapping;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||||
@ -31,7 +33,7 @@ public class RotationController {
|
|||||||
|
|
||||||
private final CustomPDFDocumentFactory pdfDocumentFactory;
|
private final CustomPDFDocumentFactory pdfDocumentFactory;
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/rotate-pdf")
|
@AutoJobPostMapping(consumes = "multipart/form-data", value = "/rotate-pdf")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Rotate a PDF file",
|
summary = "Rotate a PDF file",
|
||||||
description =
|
description =
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package stirling.software.SPDF.controller.api;
|
package stirling.software.SPDF.controller.api;
|
||||||
|
|
||||||
|
import stirling.software.common.annotations.AutoJobPostMapping;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
@ -38,7 +40,7 @@ public class ScalePagesController {
|
|||||||
|
|
||||||
private final CustomPDFDocumentFactory pdfDocumentFactory;
|
private final CustomPDFDocumentFactory pdfDocumentFactory;
|
||||||
|
|
||||||
@PostMapping(value = "/scale-pages", consumes = "multipart/form-data")
|
@AutoJobPostMapping(value = "/scale-pages", consumes = "multipart/form-data")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Change the size of a PDF page/document",
|
summary = "Change the size of a PDF page/document",
|
||||||
description =
|
description =
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package stirling.software.SPDF.controller.api;
|
package stirling.software.SPDF.controller.api;
|
||||||
|
|
||||||
|
import stirling.software.common.annotations.AutoJobPostMapping;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
@ -31,7 +33,7 @@ public class SettingsController {
|
|||||||
private final ApplicationProperties applicationProperties;
|
private final ApplicationProperties applicationProperties;
|
||||||
private final EndpointConfiguration endpointConfiguration;
|
private final EndpointConfiguration endpointConfiguration;
|
||||||
|
|
||||||
@PostMapping("/update-enable-analytics")
|
@AutoJobPostMapping("/update-enable-analytics")
|
||||||
@Hidden
|
@Hidden
|
||||||
public ResponseEntity<String> updateApiKey(@RequestBody Boolean enabled) throws IOException {
|
public ResponseEntity<String> updateApiKey(@RequestBody Boolean enabled) throws IOException {
|
||||||
if (applicationProperties.getSystem().getEnableAnalytics() != null) {
|
if (applicationProperties.getSystem().getEnableAnalytics() != null) {
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package stirling.software.SPDF.controller.api;
|
package stirling.software.SPDF.controller.api;
|
||||||
|
|
||||||
|
import stirling.software.common.annotations.AutoJobPostMapping;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
@ -41,7 +43,7 @@ public class SplitPDFController {
|
|||||||
|
|
||||||
private final CustomPDFDocumentFactory pdfDocumentFactory;
|
private final CustomPDFDocumentFactory pdfDocumentFactory;
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/split-pages")
|
@AutoJobPostMapping(consumes = "multipart/form-data", value = "/split-pages")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Split a PDF file into separate documents",
|
summary = "Split a PDF file into separate documents",
|
||||||
description =
|
description =
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package stirling.software.SPDF.controller.api;
|
package stirling.software.SPDF.controller.api;
|
||||||
|
|
||||||
|
import stirling.software.common.annotations.AutoJobPostMapping;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
@ -117,7 +119,7 @@ public class SplitPdfByChaptersController {
|
|||||||
return bookmarks;
|
return bookmarks;
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping(value = "/split-pdf-by-chapters", consumes = "multipart/form-data")
|
@AutoJobPostMapping(value = "/split-pdf-by-chapters", consumes = "multipart/form-data")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Split PDFs by Chapters",
|
summary = "Split PDFs by Chapters",
|
||||||
description = "Splits a PDF into chapters and returns a ZIP file.")
|
description = "Splits a PDF into chapters and returns a ZIP file.")
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package stirling.software.SPDF.controller.api;
|
package stirling.software.SPDF.controller.api;
|
||||||
|
|
||||||
|
import stirling.software.common.annotations.AutoJobPostMapping;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
@ -43,7 +45,7 @@ public class SplitPdfBySectionsController {
|
|||||||
|
|
||||||
private final CustomPDFDocumentFactory pdfDocumentFactory;
|
private final CustomPDFDocumentFactory pdfDocumentFactory;
|
||||||
|
|
||||||
@PostMapping(value = "/split-pdf-by-sections", consumes = "multipart/form-data")
|
@AutoJobPostMapping(value = "/split-pdf-by-sections", consumes = "multipart/form-data")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Split PDF pages into smaller sections",
|
summary = "Split PDF pages into smaller sections",
|
||||||
description =
|
description =
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package stirling.software.SPDF.controller.api;
|
package stirling.software.SPDF.controller.api;
|
||||||
|
|
||||||
|
import stirling.software.common.annotations.AutoJobPostMapping;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
@ -39,7 +41,7 @@ public class SplitPdfBySizeController {
|
|||||||
|
|
||||||
private final CustomPDFDocumentFactory pdfDocumentFactory;
|
private final CustomPDFDocumentFactory pdfDocumentFactory;
|
||||||
|
|
||||||
@PostMapping(value = "/split-by-size-or-count", consumes = "multipart/form-data")
|
@AutoJobPostMapping(value = "/split-by-size-or-count", consumes = "multipart/form-data")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Auto split PDF pages into separate documents based on size or count",
|
summary = "Auto split PDF pages into separate documents based on size or count",
|
||||||
description =
|
description =
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package stirling.software.SPDF.controller.api;
|
package stirling.software.SPDF.controller.api;
|
||||||
|
|
||||||
|
import stirling.software.common.annotations.AutoJobPostMapping;
|
||||||
|
|
||||||
import java.awt.geom.AffineTransform;
|
import java.awt.geom.AffineTransform;
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@ -33,7 +35,7 @@ public class ToSinglePageController {
|
|||||||
|
|
||||||
private final CustomPDFDocumentFactory pdfDocumentFactory;
|
private final CustomPDFDocumentFactory pdfDocumentFactory;
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/pdf-to-single-page")
|
@AutoJobPostMapping(consumes = "multipart/form-data", value = "/pdf-to-single-page")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Convert a multi-page PDF into a single long page PDF",
|
summary = "Convert a multi-page PDF into a single long page PDF",
|
||||||
description =
|
description =
|
||||||
|
@ -0,0 +1,301 @@
|
|||||||
|
package stirling.software.SPDF.controller.api;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.core.io.ClassPathResource;
|
||||||
|
import org.springframework.core.io.Resource;
|
||||||
|
import org.springframework.core.io.ResourceLoader;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.core.type.TypeReference;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import stirling.software.SPDF.model.Dependency;
|
||||||
|
import stirling.software.SPDF.model.SignatureFile;
|
||||||
|
import stirling.software.SPDF.service.SignatureService;
|
||||||
|
import stirling.software.common.configuration.InstallationPathConfig;
|
||||||
|
import stirling.software.common.configuration.RuntimePathConfig;
|
||||||
|
import stirling.software.common.model.ApplicationProperties;
|
||||||
|
import stirling.software.common.service.UserServiceInterface;
|
||||||
|
import stirling.software.common.util.ExceptionUtils;
|
||||||
|
import stirling.software.common.util.GeneralUtils;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/v1/ui-data")
|
||||||
|
@Tag(name = "UI Data", description = "APIs for React UI data")
|
||||||
|
public class UIDataController {
|
||||||
|
|
||||||
|
private final ApplicationProperties applicationProperties;
|
||||||
|
private final SignatureService signatureService;
|
||||||
|
private final UserServiceInterface userService;
|
||||||
|
private final ResourceLoader resourceLoader;
|
||||||
|
private final RuntimePathConfig runtimePathConfig;
|
||||||
|
|
||||||
|
public UIDataController(
|
||||||
|
ApplicationProperties applicationProperties,
|
||||||
|
SignatureService signatureService,
|
||||||
|
@Autowired(required = false) UserServiceInterface userService,
|
||||||
|
ResourceLoader resourceLoader,
|
||||||
|
RuntimePathConfig runtimePathConfig) {
|
||||||
|
this.applicationProperties = applicationProperties;
|
||||||
|
this.signatureService = signatureService;
|
||||||
|
this.userService = userService;
|
||||||
|
this.resourceLoader = resourceLoader;
|
||||||
|
this.runtimePathConfig = runtimePathConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/home")
|
||||||
|
@Operation(summary = "Get home page data")
|
||||||
|
public ResponseEntity<HomeData> getHomeData() {
|
||||||
|
String showSurvey = System.getenv("SHOW_SURVEY");
|
||||||
|
boolean showSurveyValue = showSurvey == null || "true".equalsIgnoreCase(showSurvey);
|
||||||
|
|
||||||
|
HomeData data = new HomeData();
|
||||||
|
data.setShowSurveyFromDocker(showSurveyValue);
|
||||||
|
|
||||||
|
return ResponseEntity.ok(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/licenses")
|
||||||
|
@Operation(summary = "Get third-party licenses data")
|
||||||
|
public ResponseEntity<LicensesData> getLicensesData() {
|
||||||
|
LicensesData data = new LicensesData();
|
||||||
|
Resource resource = new ClassPathResource("static/3rdPartyLicenses.json");
|
||||||
|
|
||||||
|
try {
|
||||||
|
InputStream is = resource.getInputStream();
|
||||||
|
String json = new String(is.readAllBytes(), StandardCharsets.UTF_8);
|
||||||
|
ObjectMapper mapper = new ObjectMapper();
|
||||||
|
Map<String, List<Dependency>> licenseData =
|
||||||
|
mapper.readValue(json, new TypeReference<>() {});
|
||||||
|
data.setDependencies(licenseData.get("dependencies"));
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.error("Failed to load licenses data", e);
|
||||||
|
data.setDependencies(Collections.emptyList());
|
||||||
|
}
|
||||||
|
|
||||||
|
return ResponseEntity.ok(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/pipeline")
|
||||||
|
@Operation(summary = "Get pipeline configuration data")
|
||||||
|
public ResponseEntity<PipelineData> getPipelineData() {
|
||||||
|
PipelineData data = new PipelineData();
|
||||||
|
List<String> pipelineConfigs = new ArrayList<>();
|
||||||
|
List<Map<String, String>> pipelineConfigsWithNames = new ArrayList<>();
|
||||||
|
|
||||||
|
if (new java.io.File(runtimePathConfig.getPipelineDefaultWebUiConfigs()).exists()) {
|
||||||
|
try (Stream<Path> paths =
|
||||||
|
Files.walk(Paths.get(runtimePathConfig.getPipelineDefaultWebUiConfigs()))) {
|
||||||
|
List<Path> jsonFiles =
|
||||||
|
paths.filter(Files::isRegularFile)
|
||||||
|
.filter(p -> p.toString().endsWith(".json"))
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
for (Path jsonFile : jsonFiles) {
|
||||||
|
String content = Files.readString(jsonFile, StandardCharsets.UTF_8);
|
||||||
|
pipelineConfigs.add(content);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (String config : pipelineConfigs) {
|
||||||
|
Map<String, Object> jsonContent =
|
||||||
|
new ObjectMapper()
|
||||||
|
.readValue(config, new TypeReference<Map<String, Object>>() {});
|
||||||
|
String name = (String) jsonContent.get("name");
|
||||||
|
if (name == null || name.length() < 1) {
|
||||||
|
String filename =
|
||||||
|
jsonFiles
|
||||||
|
.get(pipelineConfigs.indexOf(config))
|
||||||
|
.getFileName()
|
||||||
|
.toString();
|
||||||
|
name = filename.substring(0, filename.lastIndexOf('.'));
|
||||||
|
}
|
||||||
|
Map<String, String> configWithName = new HashMap<>();
|
||||||
|
configWithName.put("json", config);
|
||||||
|
configWithName.put("name", name);
|
||||||
|
pipelineConfigsWithNames.add(configWithName);
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.error("Failed to load pipeline configs", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pipelineConfigsWithNames.isEmpty()) {
|
||||||
|
Map<String, String> configWithName = new HashMap<>();
|
||||||
|
configWithName.put("json", "");
|
||||||
|
configWithName.put("name", "No preloaded configs found");
|
||||||
|
pipelineConfigsWithNames.add(configWithName);
|
||||||
|
}
|
||||||
|
|
||||||
|
data.setPipelineConfigsWithNames(pipelineConfigsWithNames);
|
||||||
|
data.setPipelineConfigs(pipelineConfigs);
|
||||||
|
|
||||||
|
return ResponseEntity.ok(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/sign")
|
||||||
|
@Operation(summary = "Get signature form data")
|
||||||
|
public ResponseEntity<SignData> getSignData() {
|
||||||
|
String username = "";
|
||||||
|
if (userService != null) {
|
||||||
|
username = userService.getCurrentUsername();
|
||||||
|
}
|
||||||
|
|
||||||
|
List<SignatureFile> signatures = signatureService.getAvailableSignatures(username);
|
||||||
|
List<FontResource> fonts = getFontNames();
|
||||||
|
|
||||||
|
SignData data = new SignData();
|
||||||
|
data.setSignatures(signatures);
|
||||||
|
data.setFonts(fonts);
|
||||||
|
|
||||||
|
return ResponseEntity.ok(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/ocr-pdf")
|
||||||
|
@Operation(summary = "Get OCR PDF data")
|
||||||
|
public ResponseEntity<OcrData> getOcrPdfData() {
|
||||||
|
List<String> languages = getAvailableTesseractLanguages();
|
||||||
|
|
||||||
|
OcrData data = new OcrData();
|
||||||
|
data.setLanguages(languages);
|
||||||
|
|
||||||
|
return ResponseEntity.ok(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<String> getAvailableTesseractLanguages() {
|
||||||
|
String tessdataDir = applicationProperties.getSystem().getTessdataDir();
|
||||||
|
java.io.File[] files = new java.io.File(tessdataDir).listFiles();
|
||||||
|
if (files == null) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
return Arrays.stream(files)
|
||||||
|
.filter(file -> file.getName().endsWith(".traineddata"))
|
||||||
|
.map(file -> file.getName().replace(".traineddata", ""))
|
||||||
|
.filter(lang -> !"osd".equalsIgnoreCase(lang))
|
||||||
|
.sorted()
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<FontResource> getFontNames() {
|
||||||
|
List<FontResource> fontNames = new ArrayList<>();
|
||||||
|
fontNames.addAll(getFontNamesFromLocation("classpath:static/fonts/*.woff2"));
|
||||||
|
fontNames.addAll(
|
||||||
|
getFontNamesFromLocation(
|
||||||
|
"file:"
|
||||||
|
+ InstallationPathConfig.getStaticPath()
|
||||||
|
+ "fonts"
|
||||||
|
+ java.io.File.separator
|
||||||
|
+ "*"));
|
||||||
|
return fontNames;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<FontResource> getFontNamesFromLocation(String locationPattern) {
|
||||||
|
try {
|
||||||
|
Resource[] resources =
|
||||||
|
GeneralUtils.getResourcesFromLocationPattern(locationPattern, resourceLoader);
|
||||||
|
return Arrays.stream(resources)
|
||||||
|
.map(
|
||||||
|
resource -> {
|
||||||
|
try {
|
||||||
|
String filename = resource.getFilename();
|
||||||
|
if (filename != null) {
|
||||||
|
int lastDotIndex = filename.lastIndexOf('.');
|
||||||
|
if (lastDotIndex != -1) {
|
||||||
|
String name = filename.substring(0, lastDotIndex);
|
||||||
|
String extension = filename.substring(lastDotIndex + 1);
|
||||||
|
return new FontResource(name, extension);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw ExceptionUtils.createRuntimeException(
|
||||||
|
"error.fontLoadingFailed",
|
||||||
|
"Error processing font file",
|
||||||
|
e);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.filter(Objects::nonNull)
|
||||||
|
.toList();
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw ExceptionUtils.createRuntimeException(
|
||||||
|
"error.fontDirectoryReadFailed", "Failed to read font directory", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Data classes
|
||||||
|
@Data
|
||||||
|
public static class HomeData {
|
||||||
|
private boolean showSurveyFromDocker;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public static class LicensesData {
|
||||||
|
private List<Dependency> dependencies;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public static class PipelineData {
|
||||||
|
private List<Map<String, String>> pipelineConfigsWithNames;
|
||||||
|
private List<String> pipelineConfigs;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public static class SignData {
|
||||||
|
private List<SignatureFile> signatures;
|
||||||
|
private List<FontResource> fonts;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public static class OcrData {
|
||||||
|
private List<String> languages;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public static class FontResource {
|
||||||
|
private String name;
|
||||||
|
private String extension;
|
||||||
|
private String type;
|
||||||
|
|
||||||
|
public FontResource(String name, String extension) {
|
||||||
|
this.name = name;
|
||||||
|
this.extension = extension;
|
||||||
|
this.type = getFormatFromExtension(extension);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getFormatFromExtension(String extension) {
|
||||||
|
switch (extension) {
|
||||||
|
case "ttf":
|
||||||
|
return "truetype";
|
||||||
|
case "woff":
|
||||||
|
return "woff";
|
||||||
|
case "woff2":
|
||||||
|
return "woff2";
|
||||||
|
case "eot":
|
||||||
|
return "embedded-opentype";
|
||||||
|
case "svg":
|
||||||
|
return "svg";
|
||||||
|
default:
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,7 @@
|
|||||||
package stirling.software.SPDF.controller.api.converters;
|
package stirling.software.SPDF.controller.api.converters;
|
||||||
|
|
||||||
|
import stirling.software.common.annotations.AutoJobPostMapping;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
@ -40,7 +42,7 @@ public class ConvertEmlToPDF {
|
|||||||
private final TempFileManager tempFileManager;
|
private final TempFileManager tempFileManager;
|
||||||
private final CustomHtmlSanitizer customHtmlSanitizer;
|
private final CustomHtmlSanitizer customHtmlSanitizer;
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/eml/pdf")
|
@AutoJobPostMapping(consumes = "multipart/form-data", value = "/eml/pdf")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Convert EML to PDF",
|
summary = "Convert EML to PDF",
|
||||||
description =
|
description =
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package stirling.software.SPDF.controller.api.converters;
|
package stirling.software.SPDF.controller.api.converters;
|
||||||
|
|
||||||
|
import stirling.software.common.annotations.AutoJobPostMapping;
|
||||||
|
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
@ -36,7 +38,8 @@ public class ConvertHtmlToPDF {
|
|||||||
|
|
||||||
private final CustomHtmlSanitizer customHtmlSanitizer;
|
private final CustomHtmlSanitizer customHtmlSanitizer;
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/html/pdf")
|
@AutoJobPostMapping(consumes = "multipart/form-data", value = "/html/pdf")
|
||||||
|
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Convert an HTML or ZIP (containing HTML and CSS) to PDF",
|
summary = "Convert an HTML or ZIP (containing HTML and CSS) to PDF",
|
||||||
description =
|
description =
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package stirling.software.SPDF.controller.api.converters;
|
package stirling.software.SPDF.controller.api.converters;
|
||||||
|
|
||||||
|
import stirling.software.common.annotations.AutoJobPostMapping;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
@ -51,7 +53,7 @@ public class ConvertImgPDFController {
|
|||||||
|
|
||||||
private final CustomPDFDocumentFactory pdfDocumentFactory;
|
private final CustomPDFDocumentFactory pdfDocumentFactory;
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/pdf/img")
|
@AutoJobPostMapping(consumes = "multipart/form-data", value = "/pdf/img")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Convert PDF to image(s)",
|
summary = "Convert PDF to image(s)",
|
||||||
description =
|
description =
|
||||||
@ -211,7 +213,7 @@ public class ConvertImgPDFController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/img/pdf")
|
@AutoJobPostMapping(consumes = "multipart/form-data", value = "/img/pdf")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Convert images to a PDF file",
|
summary = "Convert images to a PDF file",
|
||||||
description =
|
description =
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package stirling.software.SPDF.controller.api.converters;
|
package stirling.software.SPDF.controller.api.converters;
|
||||||
|
|
||||||
|
import stirling.software.common.annotations.AutoJobPostMapping;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
@ -45,7 +47,8 @@ public class ConvertMarkdownToPdf {
|
|||||||
|
|
||||||
private final CustomHtmlSanitizer customHtmlSanitizer;
|
private final CustomHtmlSanitizer customHtmlSanitizer;
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/markdown/pdf")
|
@AutoJobPostMapping(consumes = "multipart/form-data", value = "/markdown/pdf")
|
||||||
|
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Convert a Markdown file to PDF",
|
summary = "Convert a Markdown file to PDF",
|
||||||
description =
|
description =
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package stirling.software.SPDF.controller.api.converters;
|
package stirling.software.SPDF.controller.api.converters;
|
||||||
|
|
||||||
|
import stirling.software.common.annotations.AutoJobPostMapping;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
@ -97,7 +99,7 @@ public class ConvertOfficeController {
|
|||||||
return fileExtension.matches(extensionPattern);
|
return fileExtension.matches(extensionPattern);
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/file/pdf")
|
@AutoJobPostMapping(consumes = "multipart/form-data", value = "/file/pdf")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Convert a file to a PDF using LibreOffice",
|
summary = "Convert a file to a PDF using LibreOffice",
|
||||||
description =
|
description =
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package stirling.software.SPDF.controller.api.converters;
|
package stirling.software.SPDF.controller.api.converters;
|
||||||
|
|
||||||
|
import stirling.software.common.annotations.AutoJobPostMapping;
|
||||||
|
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
@ -18,7 +20,7 @@ import stirling.software.common.util.PDFToFile;
|
|||||||
@RequestMapping("/api/v1/convert")
|
@RequestMapping("/api/v1/convert")
|
||||||
public class ConvertPDFToHtml {
|
public class ConvertPDFToHtml {
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/pdf/html")
|
@AutoJobPostMapping(consumes = "multipart/form-data", value = "/pdf/html")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Convert PDF to HTML",
|
summary = "Convert PDF to HTML",
|
||||||
description =
|
description =
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package stirling.software.SPDF.controller.api.converters;
|
package stirling.software.SPDF.controller.api.converters;
|
||||||
|
|
||||||
|
import stirling.software.common.annotations.AutoJobPostMapping;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||||
@ -34,7 +36,7 @@ public class ConvertPDFToOffice {
|
|||||||
|
|
||||||
private final CustomPDFDocumentFactory pdfDocumentFactory;
|
private final CustomPDFDocumentFactory pdfDocumentFactory;
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/pdf/presentation")
|
@AutoJobPostMapping(consumes = "multipart/form-data", value = "/pdf/presentation")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Convert PDF to Presentation format",
|
summary = "Convert PDF to Presentation format",
|
||||||
description =
|
description =
|
||||||
@ -49,7 +51,7 @@ public class ConvertPDFToOffice {
|
|||||||
return pdfToFile.processPdfToOfficeFormat(inputFile, outputFormat, "impress_pdf_import");
|
return pdfToFile.processPdfToOfficeFormat(inputFile, outputFormat, "impress_pdf_import");
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/pdf/text")
|
@AutoJobPostMapping(consumes = "multipart/form-data", value = "/pdf/text")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Convert PDF to Text or RTF format",
|
summary = "Convert PDF to Text or RTF format",
|
||||||
description =
|
description =
|
||||||
@ -77,7 +79,7 @@ public class ConvertPDFToOffice {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/pdf/word")
|
@AutoJobPostMapping(consumes = "multipart/form-data", value = "/pdf/word")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Convert PDF to Word document",
|
summary = "Convert PDF to Word document",
|
||||||
description =
|
description =
|
||||||
@ -91,7 +93,7 @@ public class ConvertPDFToOffice {
|
|||||||
return pdfToFile.processPdfToOfficeFormat(inputFile, outputFormat, "writer_pdf_import");
|
return pdfToFile.processPdfToOfficeFormat(inputFile, outputFormat, "writer_pdf_import");
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/pdf/xml")
|
@AutoJobPostMapping(consumes = "multipart/form-data", value = "/pdf/xml")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Convert PDF to XML",
|
summary = "Convert PDF to XML",
|
||||||
description =
|
description =
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package stirling.software.SPDF.controller.api.converters;
|
package stirling.software.SPDF.controller.api.converters;
|
||||||
|
|
||||||
|
import stirling.software.common.annotations.AutoJobPostMapping;
|
||||||
|
|
||||||
import java.awt.Color;
|
import java.awt.Color;
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
@ -78,7 +80,7 @@ import stirling.software.common.util.WebResponseUtils;
|
|||||||
@Tag(name = "Convert", description = "Convert APIs")
|
@Tag(name = "Convert", description = "Convert APIs")
|
||||||
public class ConvertPDFToPDFA {
|
public class ConvertPDFToPDFA {
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/pdf/pdfa")
|
@AutoJobPostMapping(consumes = "multipart/form-data", value = "/pdf/pdfa")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Convert a PDF to a PDF/A",
|
summary = "Convert a PDF to a PDF/A",
|
||||||
description =
|
description =
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package stirling.software.SPDF.controller.api.converters;
|
package stirling.software.SPDF.controller.api.converters;
|
||||||
|
|
||||||
|
import stirling.software.common.annotations.AutoJobPostMapping;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
@ -40,7 +42,7 @@ public class ConvertWebsiteToPDF {
|
|||||||
private final RuntimePathConfig runtimePathConfig;
|
private final RuntimePathConfig runtimePathConfig;
|
||||||
private final ApplicationProperties applicationProperties;
|
private final ApplicationProperties applicationProperties;
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/url/pdf")
|
@AutoJobPostMapping(consumes = "multipart/form-data", value = "/url/pdf")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Convert a URL to a PDF",
|
summary = "Convert a URL to a PDF",
|
||||||
description =
|
description =
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package stirling.software.SPDF.controller.api.converters;
|
package stirling.software.SPDF.controller.api.converters;
|
||||||
|
|
||||||
|
import stirling.software.common.annotations.AutoJobPostMapping;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.StringWriter;
|
import java.io.StringWriter;
|
||||||
@ -46,7 +48,7 @@ public class ExtractCSVController {
|
|||||||
|
|
||||||
private final CustomPDFDocumentFactory pdfDocumentFactory;
|
private final CustomPDFDocumentFactory pdfDocumentFactory;
|
||||||
|
|
||||||
@PostMapping(value = "/pdf/csv", consumes = "multipart/form-data")
|
@AutoJobPostMapping(value = "/pdf/csv", consumes = "multipart/form-data")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Extracts a CSV document from a PDF",
|
summary = "Extracts a CSV document from a PDF",
|
||||||
description =
|
description =
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package stirling.software.SPDF.controller.api.filters;
|
package stirling.software.SPDF.controller.api.filters;
|
||||||
|
|
||||||
|
import stirling.software.common.annotations.AutoJobPostMapping;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||||
@ -37,7 +39,7 @@ public class FilterController {
|
|||||||
|
|
||||||
private final CustomPDFDocumentFactory pdfDocumentFactory;
|
private final CustomPDFDocumentFactory pdfDocumentFactory;
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/filter-contains-text")
|
@AutoJobPostMapping(consumes = "multipart/form-data", value = "/filter-contains-text")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Checks if a PDF contains set text, returns true if does",
|
summary = "Checks if a PDF contains set text, returns true if does",
|
||||||
description = "Input:PDF Output:Boolean Type:SISO")
|
description = "Input:PDF Output:Boolean Type:SISO")
|
||||||
@ -55,7 +57,7 @@ public class FilterController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO
|
// TODO
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/filter-contains-image")
|
@AutoJobPostMapping(consumes = "multipart/form-data", value = "/filter-contains-image")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Checks if a PDF contains an image",
|
summary = "Checks if a PDF contains an image",
|
||||||
description = "Input:PDF Output:Boolean Type:SISO")
|
description = "Input:PDF Output:Boolean Type:SISO")
|
||||||
@ -71,7 +73,7 @@ public class FilterController {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/filter-page-count")
|
@AutoJobPostMapping(consumes = "multipart/form-data", value = "/filter-page-count")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Checks if a PDF is greater, less or equal to a setPageCount",
|
summary = "Checks if a PDF is greater, less or equal to a setPageCount",
|
||||||
description = "Input:PDF Output:Boolean Type:SISO")
|
description = "Input:PDF Output:Boolean Type:SISO")
|
||||||
@ -104,7 +106,7 @@ public class FilterController {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/filter-page-size")
|
@AutoJobPostMapping(consumes = "multipart/form-data", value = "/filter-page-size")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Checks if a PDF is of a certain size",
|
summary = "Checks if a PDF is of a certain size",
|
||||||
description = "Input:PDF Output:Boolean Type:SISO")
|
description = "Input:PDF Output:Boolean Type:SISO")
|
||||||
@ -147,7 +149,7 @@ public class FilterController {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/filter-file-size")
|
@AutoJobPostMapping(consumes = "multipart/form-data", value = "/filter-file-size")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Checks if a PDF is a set file size",
|
summary = "Checks if a PDF is a set file size",
|
||||||
description = "Input:PDF Output:Boolean Type:SISO")
|
description = "Input:PDF Output:Boolean Type:SISO")
|
||||||
@ -180,7 +182,7 @@ public class FilterController {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/filter-page-rotation")
|
@AutoJobPostMapping(consumes = "multipart/form-data", value = "/filter-page-rotation")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Checks if a PDF is of a certain rotation",
|
summary = "Checks if a PDF is of a certain rotation",
|
||||||
description = "Input:PDF Output:Boolean Type:SISO")
|
description = "Input:PDF Output:Boolean Type:SISO")
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package stirling.software.SPDF.controller.api.misc;
|
package stirling.software.SPDF.controller.api.misc;
|
||||||
|
|
||||||
|
import stirling.software.common.annotations.AutoJobPostMapping;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@ -34,7 +36,7 @@ public class AttachmentController {
|
|||||||
|
|
||||||
private final AttachmentServiceInterface pdfAttachmentService;
|
private final AttachmentServiceInterface pdfAttachmentService;
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/add-attachments")
|
@AutoJobPostMapping(consumes = "multipart/form-data", value = "/add-attachments")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Add attachments to PDF",
|
summary = "Add attachments to PDF",
|
||||||
description =
|
description =
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package stirling.software.SPDF.controller.api.misc;
|
package stirling.software.SPDF.controller.api.misc;
|
||||||
|
|
||||||
|
import stirling.software.common.annotations.AutoJobPostMapping;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
@ -38,7 +40,7 @@ public class AutoRenameController {
|
|||||||
|
|
||||||
private final CustomPDFDocumentFactory pdfDocumentFactory;
|
private final CustomPDFDocumentFactory pdfDocumentFactory;
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/auto-rename")
|
@AutoJobPostMapping(consumes = "multipart/form-data", value = "/auto-rename")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Extract header from PDF file",
|
summary = "Extract header from PDF file",
|
||||||
description =
|
description =
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package stirling.software.SPDF.controller.api.misc;
|
package stirling.software.SPDF.controller.api.misc;
|
||||||
|
|
||||||
|
import stirling.software.common.annotations.AutoJobPostMapping;
|
||||||
|
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
import java.awt.image.DataBufferByte;
|
import java.awt.image.DataBufferByte;
|
||||||
import java.awt.image.DataBufferInt;
|
import java.awt.image.DataBufferInt;
|
||||||
@ -102,7 +104,7 @@ public class AutoSplitPdfController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping(value = "/auto-split-pdf", consumes = "multipart/form-data")
|
@AutoJobPostMapping(value = "/auto-split-pdf", consumes = "multipart/form-data")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Auto split PDF pages into separate documents",
|
summary = "Auto split PDF pages into separate documents",
|
||||||
description =
|
description =
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package stirling.software.SPDF.controller.api.misc;
|
package stirling.software.SPDF.controller.api.misc;
|
||||||
|
|
||||||
|
import stirling.software.common.annotations.AutoJobPostMapping;
|
||||||
|
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@ -69,7 +71,7 @@ public class BlankPageController {
|
|||||||
return whitePixelPercentage >= whitePercent;
|
return whitePixelPercentage >= whitePercent;
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/remove-blanks")
|
@AutoJobPostMapping(consumes = "multipart/form-data", value = "/remove-blanks")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Remove blank pages from a PDF file",
|
summary = "Remove blank pages from a PDF file",
|
||||||
description =
|
description =
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package stirling.software.SPDF.controller.api.misc;
|
package stirling.software.SPDF.controller.api.misc;
|
||||||
|
|
||||||
|
import stirling.software.common.annotations.AutoJobPostMapping;
|
||||||
|
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
@ -658,7 +660,7 @@ public class CompressController {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/compress-pdf")
|
@AutoJobPostMapping(consumes = "multipart/form-data", value = "/compress-pdf")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Optimize PDF file",
|
summary = "Optimize PDF file",
|
||||||
description =
|
description =
|
||||||
|
@ -0,0 +1,129 @@
|
|||||||
|
package stirling.software.SPDF.controller.api.misc;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.springframework.context.ApplicationContext;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.Hidden;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
|
||||||
|
import stirling.software.SPDF.config.EndpointConfiguration;
|
||||||
|
import stirling.software.common.configuration.AppConfig;
|
||||||
|
import stirling.software.common.model.ApplicationProperties;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@Tag(name = "Config", description = "Configuration APIs")
|
||||||
|
@RequestMapping("/api/v1/config")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@Hidden
|
||||||
|
public class ConfigController {
|
||||||
|
|
||||||
|
private final ApplicationProperties applicationProperties;
|
||||||
|
private final ApplicationContext applicationContext;
|
||||||
|
private final EndpointConfiguration endpointConfiguration;
|
||||||
|
|
||||||
|
@GetMapping("/app-config")
|
||||||
|
public ResponseEntity<Map<String, Object>> getAppConfig() {
|
||||||
|
Map<String, Object> configData = new HashMap<>();
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Get AppConfig bean
|
||||||
|
AppConfig appConfig = applicationContext.getBean(AppConfig.class);
|
||||||
|
|
||||||
|
// Extract key configuration values from AppConfig
|
||||||
|
configData.put("baseUrl", appConfig.getBaseUrl());
|
||||||
|
configData.put("contextPath", appConfig.getContextPath());
|
||||||
|
configData.put("serverPort", appConfig.getServerPort());
|
||||||
|
|
||||||
|
// Extract values from ApplicationProperties
|
||||||
|
configData.put("appName", applicationProperties.getUi().getAppName());
|
||||||
|
configData.put("appNameNavbar", applicationProperties.getUi().getAppNameNavbar());
|
||||||
|
configData.put("homeDescription", applicationProperties.getUi().getHomeDescription());
|
||||||
|
configData.put("languages", applicationProperties.getUi().getLanguages());
|
||||||
|
|
||||||
|
// Security settings
|
||||||
|
configData.put("enableLogin", applicationProperties.getSecurity().getEnableLogin());
|
||||||
|
|
||||||
|
// System settings
|
||||||
|
configData.put(
|
||||||
|
"enableAlphaFunctionality",
|
||||||
|
applicationProperties.getSystem().getEnableAlphaFunctionality());
|
||||||
|
configData.put(
|
||||||
|
"enableAnalytics", applicationProperties.getSystem().getEnableAnalytics());
|
||||||
|
|
||||||
|
// Premium/Enterprise settings
|
||||||
|
configData.put("premiumEnabled", applicationProperties.getPremium().isEnabled());
|
||||||
|
|
||||||
|
// Legal settings
|
||||||
|
configData.put(
|
||||||
|
"termsAndConditions", applicationProperties.getLegal().getTermsAndConditions());
|
||||||
|
configData.put("privacyPolicy", applicationProperties.getLegal().getPrivacyPolicy());
|
||||||
|
configData.put("cookiePolicy", applicationProperties.getLegal().getCookiePolicy());
|
||||||
|
configData.put("impressum", applicationProperties.getLegal().getImpressum());
|
||||||
|
configData.put(
|
||||||
|
"accessibilityStatement",
|
||||||
|
applicationProperties.getLegal().getAccessibilityStatement());
|
||||||
|
|
||||||
|
// Try to get EEAppConfig values if available
|
||||||
|
try {
|
||||||
|
if (applicationContext.containsBean("runningProOrHigher")) {
|
||||||
|
configData.put(
|
||||||
|
"runningProOrHigher",
|
||||||
|
applicationContext.getBean("runningProOrHigher", Boolean.class));
|
||||||
|
}
|
||||||
|
if (applicationContext.containsBean("runningEE")) {
|
||||||
|
configData.put(
|
||||||
|
"runningEE", applicationContext.getBean("runningEE", Boolean.class));
|
||||||
|
}
|
||||||
|
if (applicationContext.containsBean("license")) {
|
||||||
|
configData.put("license", applicationContext.getBean("license", String.class));
|
||||||
|
}
|
||||||
|
if (applicationContext.containsBean("GoogleDriveEnabled")) {
|
||||||
|
configData.put(
|
||||||
|
"GoogleDriveEnabled",
|
||||||
|
applicationContext.getBean("GoogleDriveEnabled", Boolean.class));
|
||||||
|
}
|
||||||
|
if (applicationContext.containsBean("SSOAutoLogin")) {
|
||||||
|
configData.put(
|
||||||
|
"SSOAutoLogin",
|
||||||
|
applicationContext.getBean("SSOAutoLogin", Boolean.class));
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
// EE features not available, continue without them
|
||||||
|
}
|
||||||
|
|
||||||
|
return ResponseEntity.ok(configData);
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
// Return basic config if there are any issues
|
||||||
|
configData.put("error", "Unable to retrieve full configuration");
|
||||||
|
return ResponseEntity.ok(configData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/endpoint-enabled")
|
||||||
|
public ResponseEntity<Boolean> isEndpointEnabled(@RequestParam(name = "endpoint") String endpoint) {
|
||||||
|
boolean enabled = endpointConfiguration.isEndpointEnabled(endpoint);
|
||||||
|
return ResponseEntity.ok(enabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/endpoints-enabled")
|
||||||
|
public ResponseEntity<Map<String, Boolean>> areEndpointsEnabled(
|
||||||
|
@RequestParam(name = "endpoints") String endpoints) {
|
||||||
|
Map<String, Boolean> result = new HashMap<>();
|
||||||
|
String[] endpointArray = endpoints.split(",");
|
||||||
|
for (String endpoint : endpointArray) {
|
||||||
|
String trimmedEndpoint = endpoint.trim();
|
||||||
|
result.put(trimmedEndpoint, endpointConfiguration.isEndpointEnabled(trimmedEndpoint));
|
||||||
|
}
|
||||||
|
return ResponseEntity.ok(result);
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,7 @@
|
|||||||
package stirling.software.SPDF.controller.api.misc;
|
package stirling.software.SPDF.controller.api.misc;
|
||||||
|
|
||||||
|
import stirling.software.common.annotations.AutoJobPostMapping;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
@ -38,7 +40,7 @@ public class DecompressPdfController {
|
|||||||
|
|
||||||
private final CustomPDFDocumentFactory pdfDocumentFactory;
|
private final CustomPDFDocumentFactory pdfDocumentFactory;
|
||||||
|
|
||||||
@PostMapping(value = "/decompress-pdf", consumes = "multipart/form-data")
|
@AutoJobPostMapping(value = "/decompress-pdf", consumes = "multipart/form-data")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Decompress PDF streams",
|
summary = "Decompress PDF streams",
|
||||||
description = "Fully decompresses all PDF streams including text content")
|
description = "Fully decompresses all PDF streams including text content")
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package stirling.software.SPDF.controller.api.misc;
|
package stirling.software.SPDF.controller.api.misc;
|
||||||
|
|
||||||
|
import stirling.software.common.annotations.AutoJobPostMapping;
|
||||||
|
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@ -50,7 +52,7 @@ public class ExtractImageScansController {
|
|||||||
|
|
||||||
private final CustomPDFDocumentFactory pdfDocumentFactory;
|
private final CustomPDFDocumentFactory pdfDocumentFactory;
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/extract-image-scans")
|
@AutoJobPostMapping(consumes = "multipart/form-data", value = "/extract-image-scans")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Extract image scans from an input file",
|
summary = "Extract image scans from an input file",
|
||||||
description =
|
description =
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package stirling.software.SPDF.controller.api.misc;
|
package stirling.software.SPDF.controller.api.misc;
|
||||||
|
|
||||||
|
import stirling.software.common.annotations.AutoJobPostMapping;
|
||||||
|
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
import java.awt.image.RenderedImage;
|
import java.awt.image.RenderedImage;
|
||||||
@ -54,7 +56,7 @@ public class ExtractImagesController {
|
|||||||
|
|
||||||
private final CustomPDFDocumentFactory pdfDocumentFactory;
|
private final CustomPDFDocumentFactory pdfDocumentFactory;
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/extract-images")
|
@AutoJobPostMapping(consumes = "multipart/form-data", value = "/extract-images")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Extract images from a PDF file",
|
summary = "Extract images from a PDF file",
|
||||||
description =
|
description =
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package stirling.software.SPDF.controller.api.misc;
|
package stirling.software.SPDF.controller.api.misc;
|
||||||
|
|
||||||
|
import stirling.software.common.annotations.AutoJobPostMapping;
|
||||||
|
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
@ -38,7 +40,7 @@ public class FlattenController {
|
|||||||
|
|
||||||
private final CustomPDFDocumentFactory pdfDocumentFactory;
|
private final CustomPDFDocumentFactory pdfDocumentFactory;
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/flatten")
|
@AutoJobPostMapping(consumes = "multipart/form-data", value = "/flatten")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Flatten PDF form fields or full page",
|
summary = "Flatten PDF form fields or full page",
|
||||||
description =
|
description =
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package stirling.software.SPDF.controller.api.misc;
|
package stirling.software.SPDF.controller.api.misc;
|
||||||
|
|
||||||
|
import stirling.software.common.annotations.AutoJobPostMapping;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.text.ParseException;
|
import java.text.ParseException;
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
@ -51,7 +53,7 @@ public class MetadataController {
|
|||||||
binder.registerCustomEditor(Map.class, "allRequestParams", new StringToMapPropertyEditor());
|
binder.registerCustomEditor(Map.class, "allRequestParams", new StringToMapPropertyEditor());
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/update-metadata")
|
@AutoJobPostMapping(consumes = "multipart/form-data", value = "/update-metadata")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Update metadata of a PDF file",
|
summary = "Update metadata of a PDF file",
|
||||||
description =
|
description =
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package stirling.software.SPDF.controller.api.misc;
|
package stirling.software.SPDF.controller.api.misc;
|
||||||
|
|
||||||
|
import stirling.software.common.annotations.AutoJobPostMapping;
|
||||||
|
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
@ -76,7 +78,7 @@ public class OCRController {
|
|||||||
.toList();
|
.toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/ocr-pdf")
|
@AutoJobPostMapping(consumes = "multipart/form-data", value = "/ocr-pdf")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Process a PDF file with OCR",
|
summary = "Process a PDF file with OCR",
|
||||||
description =
|
description =
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package stirling.software.SPDF.controller.api.misc;
|
package stirling.software.SPDF.controller.api.misc;
|
||||||
|
|
||||||
|
import stirling.software.common.annotations.AutoJobPostMapping;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
@ -31,7 +33,7 @@ public class OverlayImageController {
|
|||||||
|
|
||||||
private final CustomPDFDocumentFactory pdfDocumentFactory;
|
private final CustomPDFDocumentFactory pdfDocumentFactory;
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/add-image")
|
@AutoJobPostMapping(consumes = "multipart/form-data", value = "/add-image")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Overlay image onto a PDF file",
|
summary = "Overlay image onto a PDF file",
|
||||||
description =
|
description =
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package stirling.software.SPDF.controller.api.misc;
|
package stirling.software.SPDF.controller.api.misc;
|
||||||
|
|
||||||
|
import stirling.software.common.annotations.AutoJobPostMapping;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -37,7 +39,7 @@ public class PageNumbersController {
|
|||||||
|
|
||||||
private final CustomPDFDocumentFactory pdfDocumentFactory;
|
private final CustomPDFDocumentFactory pdfDocumentFactory;
|
||||||
|
|
||||||
@PostMapping(value = "/add-page-numbers", consumes = "multipart/form-data")
|
@AutoJobPostMapping(value = "/add-page-numbers", consumes = "multipart/form-data")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Add page numbers to a PDF document",
|
summary = "Add page numbers to a PDF document",
|
||||||
description =
|
description =
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package stirling.software.SPDF.controller.api.misc;
|
package stirling.software.SPDF.controller.api.misc;
|
||||||
|
|
||||||
|
import stirling.software.common.annotations.AutoJobPostMapping;
|
||||||
|
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
import java.awt.print.PageFormat;
|
import java.awt.print.PageFormat;
|
||||||
@ -37,7 +39,7 @@ import stirling.software.SPDF.model.api.misc.PrintFileRequest;
|
|||||||
public class PrintFileController {
|
public class PrintFileController {
|
||||||
|
|
||||||
// TODO
|
// TODO
|
||||||
// @PostMapping(value = "/print-file", consumes = "multipart/form-data")
|
// @AutoJobPostMapping(value = "/print-file", consumes = "multipart/form-data")
|
||||||
// @Operation(
|
// @Operation(
|
||||||
// summary = "Prints PDF/Image file to a set printer",
|
// summary = "Prints PDF/Image file to a set printer",
|
||||||
// description =
|
// description =
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package stirling.software.SPDF.controller.api.misc;
|
package stirling.software.SPDF.controller.api.misc;
|
||||||
|
|
||||||
|
import stirling.software.common.annotations.AutoJobPostMapping;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -46,7 +48,7 @@ public class RepairController {
|
|||||||
return endpointConfiguration.isGroupEnabled("qpdf");
|
return endpointConfiguration.isGroupEnabled("qpdf");
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/repair")
|
@AutoJobPostMapping(consumes = "multipart/form-data", value = "/repair")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Repair a PDF file",
|
summary = "Repair a PDF file",
|
||||||
description =
|
description =
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package stirling.software.SPDF.controller.api.misc;
|
package stirling.software.SPDF.controller.api.misc;
|
||||||
|
|
||||||
|
import stirling.software.common.annotations.AutoJobPostMapping;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
import org.springframework.core.io.InputStreamResource;
|
import org.springframework.core.io.InputStreamResource;
|
||||||
@ -27,7 +29,7 @@ public class ReplaceAndInvertColorController {
|
|||||||
|
|
||||||
private final ReplaceAndInvertColorService replaceAndInvertColorService;
|
private final ReplaceAndInvertColorService replaceAndInvertColorService;
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/replace-invert-pdf")
|
@AutoJobPostMapping(consumes = "multipart/form-data", value = "/replace-invert-pdf")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Replace-Invert Color PDF",
|
summary = "Replace-Invert Color PDF",
|
||||||
description =
|
description =
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package stirling.software.SPDF.controller.api.misc;
|
package stirling.software.SPDF.controller.api.misc;
|
||||||
|
|
||||||
|
import stirling.software.common.annotations.AutoJobPostMapping;
|
||||||
|
|
||||||
import java.awt.Color;
|
import java.awt.Color;
|
||||||
import java.awt.Graphics2D;
|
import java.awt.Graphics2D;
|
||||||
import java.awt.RenderingHints;
|
import java.awt.RenderingHints;
|
||||||
@ -52,7 +54,7 @@ public class ScannerEffectController {
|
|||||||
private static final int MAX_IMAGE_HEIGHT = 8192;
|
private static final int MAX_IMAGE_HEIGHT = 8192;
|
||||||
private static final long MAX_IMAGE_PIXELS = 16_777_216; // 4096x4096
|
private static final long MAX_IMAGE_PIXELS = 16_777_216; // 4096x4096
|
||||||
|
|
||||||
@PostMapping(value = "/scanner-effect", consumes = "multipart/form-data")
|
@AutoJobPostMapping(value = "/scanner-effect", consumes = "multipart/form-data")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Apply scanner effect to PDF",
|
summary = "Apply scanner effect to PDF",
|
||||||
description =
|
description =
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package stirling.software.SPDF.controller.api.misc;
|
package stirling.software.SPDF.controller.api.misc;
|
||||||
|
|
||||||
|
import stirling.software.common.annotations.AutoJobPostMapping;
|
||||||
|
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
@ -32,7 +34,7 @@ public class ShowJavascript {
|
|||||||
|
|
||||||
private final CustomPDFDocumentFactory pdfDocumentFactory;
|
private final CustomPDFDocumentFactory pdfDocumentFactory;
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/show-javascript")
|
@AutoJobPostMapping(consumes = "multipart/form-data", value = "/show-javascript")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Grabs all JS from a PDF and returns a single JS file with all code",
|
summary = "Grabs all JS from a PDF and returns a single JS file with all code",
|
||||||
description = "desc. Input:PDF Output:JS Type:SISO")
|
description = "desc. Input:PDF Output:JS Type:SISO")
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package stirling.software.SPDF.controller.api.misc;
|
package stirling.software.SPDF.controller.api.misc;
|
||||||
|
|
||||||
|
import stirling.software.common.annotations.AutoJobPostMapping;
|
||||||
|
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
@ -52,7 +54,7 @@ public class StampController {
|
|||||||
private final CustomPDFDocumentFactory pdfDocumentFactory;
|
private final CustomPDFDocumentFactory pdfDocumentFactory;
|
||||||
private final TempFileManager tempFileManager;
|
private final TempFileManager tempFileManager;
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/add-stamp")
|
@AutoJobPostMapping(consumes = "multipart/form-data", value = "/add-stamp")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Add stamp to a PDF file",
|
summary = "Add stamp to a PDF file",
|
||||||
description =
|
description =
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package stirling.software.SPDF.controller.api.misc;
|
package stirling.software.SPDF.controller.api.misc;
|
||||||
|
|
||||||
|
import stirling.software.common.annotations.AutoJobPostMapping;
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
@ -37,7 +39,7 @@ public class UnlockPDFFormsController {
|
|||||||
this.pdfDocumentFactory = pdfDocumentFactory;
|
this.pdfDocumentFactory = pdfDocumentFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/unlock-pdf-forms")
|
@AutoJobPostMapping(consumes = "multipart/form-data", value = "/unlock-pdf-forms")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Remove read-only property from form fields",
|
summary = "Remove read-only property from form fields",
|
||||||
description =
|
description =
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package stirling.software.SPDF.controller.api.pipeline;
|
package stirling.software.SPDF.controller.api.pipeline;
|
||||||
|
|
||||||
|
import stirling.software.common.annotations.AutoJobPostMapping;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
@ -46,7 +48,7 @@ public class PipelineController {
|
|||||||
|
|
||||||
private final PostHogService postHogService;
|
private final PostHogService postHogService;
|
||||||
|
|
||||||
@PostMapping(value = "/handleData", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
|
@AutoJobPostMapping(value = "/handleData", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
|
||||||
public ResponseEntity<byte[]> handleData(@ModelAttribute HandleDataRequest request)
|
public ResponseEntity<byte[]> handleData(@ModelAttribute HandleDataRequest request)
|
||||||
throws JsonMappingException, JsonProcessingException {
|
throws JsonMappingException, JsonProcessingException {
|
||||||
MultipartFile[] files = request.getFileInput();
|
MultipartFile[] files = request.getFileInput();
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package stirling.software.SPDF.controller.api.security;
|
package stirling.software.SPDF.controller.api.security;
|
||||||
|
|
||||||
|
import stirling.software.common.annotations.AutoJobPostMapping;
|
||||||
|
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.beans.PropertyEditorSupport;
|
import java.beans.PropertyEditorSupport;
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
@ -138,7 +140,7 @@ public class CertSignController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping(
|
@AutoJobPostMapping(
|
||||||
consumes = {
|
consumes = {
|
||||||
MediaType.MULTIPART_FORM_DATA_VALUE,
|
MediaType.MULTIPART_FORM_DATA_VALUE,
|
||||||
MediaType.APPLICATION_FORM_URLENCODED_VALUE
|
MediaType.APPLICATION_FORM_URLENCODED_VALUE
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package stirling.software.SPDF.controller.api.security;
|
package stirling.software.SPDF.controller.api.security;
|
||||||
|
|
||||||
|
import stirling.software.common.annotations.AutoJobPostMapping;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
@ -188,7 +190,7 @@ public class GetInfoOnPDF {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/get-info-on-pdf")
|
@AutoJobPostMapping(consumes = "multipart/form-data", value = "/get-info-on-pdf")
|
||||||
@Operation(summary = "Summary here", description = "desc. Input:PDF Output:JSON Type:SISO")
|
@Operation(summary = "Summary here", description = "desc. Input:PDF Output:JSON Type:SISO")
|
||||||
public ResponseEntity<byte[]> getPdfInfo(@ModelAttribute PDFFile request) throws IOException {
|
public ResponseEntity<byte[]> getPdfInfo(@ModelAttribute PDFFile request) throws IOException {
|
||||||
MultipartFile inputFile = request.getFileInput();
|
MultipartFile inputFile = request.getFileInput();
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package stirling.software.SPDF.controller.api.security;
|
package stirling.software.SPDF.controller.api.security;
|
||||||
|
|
||||||
|
import stirling.software.common.annotations.AutoJobPostMapping;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||||
@ -32,7 +34,7 @@ public class PasswordController {
|
|||||||
|
|
||||||
private final CustomPDFDocumentFactory pdfDocumentFactory;
|
private final CustomPDFDocumentFactory pdfDocumentFactory;
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/remove-password")
|
@AutoJobPostMapping(consumes = "multipart/form-data", value = "/remove-password")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Remove password from a PDF file",
|
summary = "Remove password from a PDF file",
|
||||||
description =
|
description =
|
||||||
@ -58,7 +60,7 @@ public class PasswordController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/add-password")
|
@AutoJobPostMapping(consumes = "multipart/form-data", value = "/add-password")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Add password to a PDF file",
|
summary = "Add password to a PDF file",
|
||||||
description =
|
description =
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package stirling.software.SPDF.controller.api.security;
|
package stirling.software.SPDF.controller.api.security;
|
||||||
|
|
||||||
|
import stirling.software.common.annotations.AutoJobPostMapping;
|
||||||
|
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@ -56,7 +58,7 @@ public class RedactController {
|
|||||||
List.class, "redactions", new StringToArrayListPropertyEditor());
|
List.class, "redactions", new StringToArrayListPropertyEditor());
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping(value = "/redact", consumes = "multipart/form-data")
|
@AutoJobPostMapping(value = "/redact", consumes = "multipart/form-data")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Redacts areas and pages in a PDF document",
|
summary = "Redacts areas and pages in a PDF document",
|
||||||
description =
|
description =
|
||||||
@ -190,7 +192,7 @@ public class RedactController {
|
|||||||
return pageNumbers;
|
return pageNumbers;
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping(value = "/auto-redact", consumes = "multipart/form-data")
|
@AutoJobPostMapping(value = "/auto-redact", consumes = "multipart/form-data")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Redacts listOfText in a PDF document",
|
summary = "Redacts listOfText in a PDF document",
|
||||||
description =
|
description =
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package stirling.software.SPDF.controller.api.security;
|
package stirling.software.SPDF.controller.api.security;
|
||||||
|
|
||||||
|
import stirling.software.common.annotations.AutoJobPostMapping;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||||
@ -32,7 +34,7 @@ public class RemoveCertSignController {
|
|||||||
|
|
||||||
private final CustomPDFDocumentFactory pdfDocumentFactory;
|
private final CustomPDFDocumentFactory pdfDocumentFactory;
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/remove-cert-sign")
|
@AutoJobPostMapping(consumes = "multipart/form-data", value = "/remove-cert-sign")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Remove digital signature from PDF",
|
summary = "Remove digital signature from PDF",
|
||||||
description =
|
description =
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package stirling.software.SPDF.controller.api.security;
|
package stirling.software.SPDF.controller.api.security;
|
||||||
|
|
||||||
|
import stirling.software.common.annotations.AutoJobPostMapping;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
import org.apache.pdfbox.cos.COSDictionary;
|
import org.apache.pdfbox.cos.COSDictionary;
|
||||||
@ -46,7 +48,7 @@ public class SanitizeController {
|
|||||||
|
|
||||||
private final CustomPDFDocumentFactory pdfDocumentFactory;
|
private final CustomPDFDocumentFactory pdfDocumentFactory;
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/sanitize-pdf")
|
@AutoJobPostMapping(consumes = "multipart/form-data", value = "/sanitize-pdf")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Sanitize a PDF file",
|
summary = "Sanitize a PDF file",
|
||||||
description =
|
description =
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package stirling.software.SPDF.controller.api.security;
|
package stirling.software.SPDF.controller.api.security;
|
||||||
|
|
||||||
|
import stirling.software.common.annotations.AutoJobPostMapping;
|
||||||
|
|
||||||
import java.beans.PropertyEditorSupport;
|
import java.beans.PropertyEditorSupport;
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@ -69,7 +71,7 @@ public class ValidateSignatureController {
|
|||||||
description =
|
description =
|
||||||
"Validates the digital signatures in a PDF file against default or custom"
|
"Validates the digital signatures in a PDF file against default or custom"
|
||||||
+ " certificates. Input:PDF Output:JSON Type:SISO")
|
+ " certificates. Input:PDF Output:JSON Type:SISO")
|
||||||
@PostMapping(value = "/validate-signature", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
|
@AutoJobPostMapping(value = "/validate-signature", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
|
||||||
public ResponseEntity<List<SignatureValidationResult>> validateSignature(
|
public ResponseEntity<List<SignatureValidationResult>> validateSignature(
|
||||||
@ModelAttribute SignatureValidationRequest request) throws IOException {
|
@ModelAttribute SignatureValidationRequest request) throws IOException {
|
||||||
List<SignatureValidationResult> results = new ArrayList<>();
|
List<SignatureValidationResult> results = new ArrayList<>();
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package stirling.software.SPDF.controller.api.security;
|
package stirling.software.SPDF.controller.api.security;
|
||||||
|
|
||||||
|
import stirling.software.common.annotations.AutoJobPostMapping;
|
||||||
|
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
import java.beans.PropertyEditorSupport;
|
import java.beans.PropertyEditorSupport;
|
||||||
@ -64,7 +66,7 @@ public class WatermarkController {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/add-watermark")
|
@AutoJobPostMapping(consumes = "multipart/form-data", value = "/add-watermark")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Add watermark to a PDF file",
|
summary = "Add watermark to a PDF file",
|
||||||
description =
|
description =
|
||||||
|
@ -0,0 +1,18 @@
|
|||||||
|
package stirling.software.SPDF.controller.web;
|
||||||
|
|
||||||
|
import org.springframework.stereotype.Controller;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
|
||||||
|
@Controller
|
||||||
|
public class ReactRoutingController {
|
||||||
|
|
||||||
|
@GetMapping("/{path:^(?!api|static|robots\\.txt|favicon\\.ico)[^\\.]*$}")
|
||||||
|
public String forwardRootPaths() {
|
||||||
|
return "forward:/index.html";
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/{path:^(?!api|static)[^\\.]*}/{subpath:^(?!.*\\.).*$}")
|
||||||
|
public String forwardNestedPaths() {
|
||||||
|
return "forward:/index.html";
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,7 @@
|
|||||||
package stirling.software.SPDF.model.api.converters;
|
package stirling.software.SPDF.model.api.converters;
|
||||||
|
|
||||||
|
import stirling.software.common.annotations.AutoJobPostMapping;
|
||||||
|
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
@ -18,7 +20,7 @@ import stirling.software.common.util.PDFToFile;
|
|||||||
@RequestMapping("/api/v1/convert")
|
@RequestMapping("/api/v1/convert")
|
||||||
public class ConvertPDFToMarkdown {
|
public class ConvertPDFToMarkdown {
|
||||||
|
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/pdf/markdown")
|
@AutoJobPostMapping(consumes = "multipart/form-data", value = "/pdf/markdown")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Convert PDF to Markdown",
|
summary = "Convert PDF to Markdown",
|
||||||
description =
|
description =
|
||||||
|
@ -1844,22 +1844,6 @@ cookieBanner.preferencesModal.necessary.description=These cookies are essential
|
|||||||
cookieBanner.preferencesModal.analytics.title=Analytics
|
cookieBanner.preferencesModal.analytics.title=Analytics
|
||||||
cookieBanner.preferencesModal.analytics.description=These cookies help us understand how our tools are being used, so we can focus on building the features our community values most. Rest assured—Stirling PDF cannot and will never track the content of the documents you work with.
|
cookieBanner.preferencesModal.analytics.description=These cookies help us understand how our tools are being used, so we can focus on building the features our community values most. Rest assured—Stirling PDF cannot and will never track the content of the documents you work with.
|
||||||
|
|
||||||
#scannerEffect
|
|
||||||
scannerEffect.title=Scanner Effect
|
|
||||||
scannerEffect.header=Scanner Effect
|
|
||||||
scannerEffect.description=Create a PDF that looks like it was scanned
|
|
||||||
scannerEffect.selectPDF=Select PDF:
|
|
||||||
scannerEffect.quality=Scan Quality
|
|
||||||
scannerEffect.quality.low=Low
|
|
||||||
scannerEffect.quality.medium=Medium
|
|
||||||
scannerEffect.quality.high=High
|
|
||||||
scannerEffect.rotation=Rotation Angle
|
|
||||||
scannerEffect.rotation.none=None
|
|
||||||
scannerEffect.rotation.slight=Slight
|
|
||||||
scannerEffect.rotation.moderate=Moderate
|
|
||||||
scannerEffect.rotation.severe=Severe
|
|
||||||
scannerEffect.submit=Create Scanner Effect
|
|
||||||
|
|
||||||
#home.scannerEffect
|
#home.scannerEffect
|
||||||
home.scannerEffect.title=Scanner Effect
|
home.scannerEffect.title=Scanner Effect
|
||||||
home.scannerEffect.desc=Create a PDF that looks like it was scanned
|
home.scannerEffect.desc=Create a PDF that looks like it was scanned
|
||||||
@ -1902,3 +1886,73 @@ editTableOfContents.desc.1=This tool allows you to add or edit the table of cont
|
|||||||
editTableOfContents.desc.2=You can create a hierarchical structure by adding child bookmarks to parent bookmarks.
|
editTableOfContents.desc.2=You can create a hierarchical structure by adding child bookmarks to parent bookmarks.
|
||||||
editTableOfContents.desc.3=Each bookmark requires a title and target page number.
|
editTableOfContents.desc.3=Each bookmark requires a title and target page number.
|
||||||
editTableOfContents.submit=Apply Table of Contents
|
editTableOfContents.submit=Apply Table of Contents
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
####################
|
||||||
|
# React Frontend #
|
||||||
|
####################
|
||||||
|
# Common UI elements
|
||||||
|
removeMetadata.submit=Remove Metadata
|
||||||
|
sidebar.toggle=Toggle Sidebar
|
||||||
|
theme.toggle=Toggle Theme
|
||||||
|
view.viewer=Viewer
|
||||||
|
view.pageEditor=Page Editor
|
||||||
|
view.fileManager=File Manager
|
||||||
|
|
||||||
|
# File management
|
||||||
|
fileManager.dragDrop=Drag & Drop files here
|
||||||
|
fileManager.clickToUpload=Click to upload files
|
||||||
|
fileManager.selectedFiles=Selected Files
|
||||||
|
fileManager.clearAll=Clear All
|
||||||
|
fileManager.storage=Storage
|
||||||
|
fileManager.filesStored=files stored
|
||||||
|
fileManager.storageError=Storage error occurred
|
||||||
|
fileManager.storageLow=Storage is running low. Consider removing old files.
|
||||||
|
fileManager.uploadError=Failed to upload some files.
|
||||||
|
fileManager.supportMessage=Powered by browser database storage for unlimited capacity
|
||||||
|
|
||||||
|
# Page Editor
|
||||||
|
pageEditor.title=Page Editor
|
||||||
|
pageEditor.save=Save Changes
|
||||||
|
pageEditor.noPdfLoaded=No PDF loaded. Please upload a PDF to edit.
|
||||||
|
pageEditor.rotatedLeft=Rotated left:
|
||||||
|
pageEditor.rotatedRight=Rotated right:
|
||||||
|
pageEditor.deleted=Deleted:
|
||||||
|
pageEditor.movedLeft=Moved left:
|
||||||
|
pageEditor.movedRight=Moved right:
|
||||||
|
pageEditor.splitAt=Split at:
|
||||||
|
pageEditor.insertedPageBreak=Inserted page break at:
|
||||||
|
pageEditor.addFileNotImplemented=Add file not implemented in demo
|
||||||
|
pageEditor.closePdf=Close PDF
|
||||||
|
|
||||||
|
# Viewer
|
||||||
|
viewer.noPdfLoaded=No PDF loaded. Click to upload a PDF.
|
||||||
|
viewer.choosePdf=Choose PDF
|
||||||
|
viewer.noPagesToDisplay=No pages to display.
|
||||||
|
viewer.singlePageView=Single Page View
|
||||||
|
viewer.dualPageView=Dual Page View
|
||||||
|
viewer.hideSidebars=Hide Sidebars
|
||||||
|
viewer.showSidebars=Show Sidebars
|
||||||
|
viewer.zoomOut=Zoom out
|
||||||
|
viewer.zoomIn=Zoom in
|
||||||
|
|
||||||
|
# Tool Picker
|
||||||
|
toolPicker.searchPlaceholder=Search tools...
|
||||||
|
toolPicker.noToolsFound=No tools found
|
||||||
|
pageEditor.reset=Reset Changes
|
||||||
|
pageEditor.zoomIn=Zoom In
|
||||||
|
pageEditor.zoomOut=Zoom Out
|
||||||
|
pageEditor.fitToWidth=Fit to Width
|
||||||
|
pageEditor.actualSize=Actual Size
|
||||||
|
|
||||||
|
# Viewer
|
||||||
|
viewer.previousPage=Previous Page
|
||||||
|
viewer.nextPage=Next Page
|
||||||
|
viewer.pageNavigation=Page Navigation
|
||||||
|
viewer.currentPage=Current Page
|
||||||
|
viewer.totalPages=Total Pages
|
||||||
|
@ -0,0 +1,484 @@
|
|||||||
|
package stirling.software.proprietary.controller.api;
|
||||||
|
|
||||||
|
import static stirling.software.common.util.ProviderUtils.validateProvider;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.time.temporal.ChronoUnit;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Qualifier;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
|
import org.springframework.security.oauth2.core.user.OAuth2User;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import stirling.software.common.model.ApplicationProperties;
|
||||||
|
import stirling.software.common.model.ApplicationProperties.Security;
|
||||||
|
import stirling.software.common.model.ApplicationProperties.Security.OAUTH2;
|
||||||
|
import stirling.software.common.model.ApplicationProperties.Security.OAUTH2.Client;
|
||||||
|
import stirling.software.common.model.ApplicationProperties.Security.SAML2;
|
||||||
|
import stirling.software.common.model.FileInfo;
|
||||||
|
import stirling.software.common.model.enumeration.Role;
|
||||||
|
import stirling.software.common.model.oauth2.GitHubProvider;
|
||||||
|
import stirling.software.common.model.oauth2.GoogleProvider;
|
||||||
|
import stirling.software.common.model.oauth2.KeycloakProvider;
|
||||||
|
import stirling.software.proprietary.audit.AuditEventType;
|
||||||
|
import stirling.software.proprietary.audit.AuditLevel;
|
||||||
|
import stirling.software.proprietary.config.AuditConfigurationProperties;
|
||||||
|
import stirling.software.proprietary.model.Team;
|
||||||
|
import stirling.software.proprietary.model.dto.TeamWithUserCountDTO;
|
||||||
|
import stirling.software.proprietary.security.config.EnterpriseEndpoint;
|
||||||
|
import stirling.software.proprietary.security.database.repository.SessionRepository;
|
||||||
|
import stirling.software.proprietary.security.database.repository.UserRepository;
|
||||||
|
import stirling.software.proprietary.security.model.Authority;
|
||||||
|
import stirling.software.proprietary.security.model.SessionEntity;
|
||||||
|
import stirling.software.proprietary.security.model.User;
|
||||||
|
import stirling.software.proprietary.security.repository.TeamRepository;
|
||||||
|
import stirling.software.proprietary.security.saml2.CustomSaml2AuthenticatedPrincipal;
|
||||||
|
import stirling.software.proprietary.security.service.DatabaseService;
|
||||||
|
import stirling.software.proprietary.security.service.TeamService;
|
||||||
|
import stirling.software.proprietary.security.session.SessionPersistentRegistry;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/v1/proprietary/ui-data")
|
||||||
|
@Tag(name = "Proprietary UI Data", description = "APIs for React UI data (Proprietary features)")
|
||||||
|
@EnterpriseEndpoint
|
||||||
|
public class ProprietaryUIDataController {
|
||||||
|
|
||||||
|
private final ApplicationProperties applicationProperties;
|
||||||
|
private final AuditConfigurationProperties auditConfig;
|
||||||
|
private final SessionPersistentRegistry sessionPersistentRegistry;
|
||||||
|
private final UserRepository userRepository;
|
||||||
|
private final TeamRepository teamRepository;
|
||||||
|
private final SessionRepository sessionRepository;
|
||||||
|
private final DatabaseService databaseService;
|
||||||
|
private final boolean runningEE;
|
||||||
|
private final ObjectMapper objectMapper;
|
||||||
|
|
||||||
|
public ProprietaryUIDataController(
|
||||||
|
ApplicationProperties applicationProperties,
|
||||||
|
AuditConfigurationProperties auditConfig,
|
||||||
|
SessionPersistentRegistry sessionPersistentRegistry,
|
||||||
|
UserRepository userRepository,
|
||||||
|
TeamRepository teamRepository,
|
||||||
|
SessionRepository sessionRepository,
|
||||||
|
DatabaseService databaseService,
|
||||||
|
ObjectMapper objectMapper,
|
||||||
|
@Qualifier("runningEE") boolean runningEE) {
|
||||||
|
this.applicationProperties = applicationProperties;
|
||||||
|
this.auditConfig = auditConfig;
|
||||||
|
this.sessionPersistentRegistry = sessionPersistentRegistry;
|
||||||
|
this.userRepository = userRepository;
|
||||||
|
this.teamRepository = teamRepository;
|
||||||
|
this.sessionRepository = sessionRepository;
|
||||||
|
this.databaseService = databaseService;
|
||||||
|
this.objectMapper = objectMapper;
|
||||||
|
this.runningEE = runningEE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/audit-dashboard")
|
||||||
|
@PreAuthorize("hasRole('ADMIN')")
|
||||||
|
@Operation(summary = "Get audit dashboard data")
|
||||||
|
public ResponseEntity<AuditDashboardData> getAuditDashboardData() {
|
||||||
|
AuditDashboardData data = new AuditDashboardData();
|
||||||
|
data.setAuditEnabled(auditConfig.isEnabled());
|
||||||
|
data.setAuditLevel(auditConfig.getAuditLevel());
|
||||||
|
data.setAuditLevelInt(auditConfig.getLevel());
|
||||||
|
data.setRetentionDays(auditConfig.getRetentionDays());
|
||||||
|
data.setAuditLevels(AuditLevel.values());
|
||||||
|
data.setAuditEventTypes(AuditEventType.values());
|
||||||
|
|
||||||
|
return ResponseEntity.ok(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/login")
|
||||||
|
@Operation(summary = "Get login page data")
|
||||||
|
public ResponseEntity<LoginData> getLoginData() {
|
||||||
|
LoginData data = new LoginData();
|
||||||
|
Map<String, String> providerList = new HashMap<>();
|
||||||
|
Security securityProps = applicationProperties.getSecurity();
|
||||||
|
OAUTH2 oauth = securityProps.getOauth2();
|
||||||
|
|
||||||
|
if (oauth != null && oauth.getEnabled()) {
|
||||||
|
if (oauth.isSettingsValid()) {
|
||||||
|
String firstChar = String.valueOf(oauth.getProvider().charAt(0));
|
||||||
|
String clientName =
|
||||||
|
oauth.getProvider().replaceFirst(firstChar, firstChar.toUpperCase());
|
||||||
|
providerList.put("/oauth2/authorization/" + oauth.getProvider(), clientName);
|
||||||
|
}
|
||||||
|
|
||||||
|
Client client = oauth.getClient();
|
||||||
|
if (client != null) {
|
||||||
|
GoogleProvider google = client.getGoogle();
|
||||||
|
if (validateProvider(google)) {
|
||||||
|
providerList.put(
|
||||||
|
"/oauth2/authorization/" + google.getName(), google.getClientName());
|
||||||
|
}
|
||||||
|
|
||||||
|
GitHubProvider github = client.getGithub();
|
||||||
|
if (validateProvider(github)) {
|
||||||
|
providerList.put(
|
||||||
|
"/oauth2/authorization/" + github.getName(), github.getClientName());
|
||||||
|
}
|
||||||
|
|
||||||
|
KeycloakProvider keycloak = client.getKeycloak();
|
||||||
|
if (validateProvider(keycloak)) {
|
||||||
|
providerList.put(
|
||||||
|
"/oauth2/authorization/" + keycloak.getName(),
|
||||||
|
keycloak.getClientName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SAML2 saml2 = securityProps.getSaml2();
|
||||||
|
if (securityProps.isSaml2Active()
|
||||||
|
&& applicationProperties.getSystem().getEnableAlphaFunctionality()
|
||||||
|
&& applicationProperties.getPremium().isEnabled()) {
|
||||||
|
String samlIdp = saml2.getProvider();
|
||||||
|
String saml2AuthenticationPath = "/saml2/authenticate/" + saml2.getRegistrationId();
|
||||||
|
|
||||||
|
if (!applicationProperties.getPremium().getProFeatures().isSsoAutoLogin()) {
|
||||||
|
providerList.put(saml2AuthenticationPath, samlIdp + " (SAML 2)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove null entries
|
||||||
|
providerList
|
||||||
|
.entrySet()
|
||||||
|
.removeIf(entry -> entry.getKey() == null || entry.getValue() == null);
|
||||||
|
|
||||||
|
data.setProviderList(providerList);
|
||||||
|
data.setLoginMethod(securityProps.getLoginMethod());
|
||||||
|
data.setAltLogin(!providerList.isEmpty() && securityProps.isAltLogin());
|
||||||
|
|
||||||
|
return ResponseEntity.ok(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/admin-settings")
|
||||||
|
@PreAuthorize("hasRole('ROLE_ADMIN')")
|
||||||
|
@Operation(summary = "Get admin settings data")
|
||||||
|
public ResponseEntity<AdminSettingsData> getAdminSettingsData(Authentication authentication) {
|
||||||
|
List<User> allUsers = userRepository.findAllWithTeam();
|
||||||
|
Iterator<User> iterator = allUsers.iterator();
|
||||||
|
Map<String, String> roleDetails = Role.getAllRoleDetails();
|
||||||
|
|
||||||
|
Map<String, Boolean> userSessions = new HashMap<>();
|
||||||
|
Map<String, Date> userLastRequest = new HashMap<>();
|
||||||
|
int activeUsers = 0;
|
||||||
|
int disabledUsers = 0;
|
||||||
|
|
||||||
|
while (iterator.hasNext()) {
|
||||||
|
User user = iterator.next();
|
||||||
|
if (user != null) {
|
||||||
|
boolean shouldRemove = false;
|
||||||
|
|
||||||
|
// Check if user is an INTERNAL_API_USER
|
||||||
|
for (Authority authority : user.getAuthorities()) {
|
||||||
|
if (authority.getAuthority().equals(Role.INTERNAL_API_USER.getRoleId())) {
|
||||||
|
shouldRemove = true;
|
||||||
|
roleDetails.remove(Role.INTERNAL_API_USER.getRoleId());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if user is part of the Internal team
|
||||||
|
if (user.getTeam() != null
|
||||||
|
&& user.getTeam().getName().equals(TeamService.INTERNAL_TEAM_NAME)) {
|
||||||
|
shouldRemove = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shouldRemove) {
|
||||||
|
iterator.remove();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Session status and last request time
|
||||||
|
int maxInactiveInterval = sessionPersistentRegistry.getMaxInactiveInterval();
|
||||||
|
boolean hasActiveSession = false;
|
||||||
|
Date lastRequest = null;
|
||||||
|
Optional<SessionEntity> latestSession =
|
||||||
|
sessionPersistentRegistry.findLatestSession(user.getUsername());
|
||||||
|
|
||||||
|
if (latestSession.isPresent()) {
|
||||||
|
SessionEntity sessionEntity = latestSession.get();
|
||||||
|
Date lastAccessedTime = sessionEntity.getLastRequest();
|
||||||
|
Instant now = Instant.now();
|
||||||
|
Instant expirationTime =
|
||||||
|
lastAccessedTime
|
||||||
|
.toInstant()
|
||||||
|
.plus(maxInactiveInterval, ChronoUnit.SECONDS);
|
||||||
|
|
||||||
|
if (now.isAfter(expirationTime)) {
|
||||||
|
sessionPersistentRegistry.expireSession(sessionEntity.getSessionId());
|
||||||
|
} else {
|
||||||
|
hasActiveSession = !sessionEntity.isExpired();
|
||||||
|
}
|
||||||
|
lastRequest = sessionEntity.getLastRequest();
|
||||||
|
} else {
|
||||||
|
lastRequest = new Date(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
userSessions.put(user.getUsername(), hasActiveSession);
|
||||||
|
userLastRequest.put(user.getUsername(), lastRequest);
|
||||||
|
|
||||||
|
if (hasActiveSession) activeUsers++;
|
||||||
|
if (!user.isEnabled()) disabledUsers++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort users by active status and last request date
|
||||||
|
List<User> sortedUsers =
|
||||||
|
allUsers.stream()
|
||||||
|
.sorted(
|
||||||
|
(u1, u2) -> {
|
||||||
|
boolean u1Active = userSessions.get(u1.getUsername());
|
||||||
|
boolean u2Active = userSessions.get(u2.getUsername());
|
||||||
|
if (u1Active && !u2Active) return -1;
|
||||||
|
if (!u1Active && u2Active) return 1;
|
||||||
|
|
||||||
|
Date u1LastRequest =
|
||||||
|
userLastRequest.getOrDefault(
|
||||||
|
u1.getUsername(), new Date(0));
|
||||||
|
Date u2LastRequest =
|
||||||
|
userLastRequest.getOrDefault(
|
||||||
|
u2.getUsername(), new Date(0));
|
||||||
|
return u2LastRequest.compareTo(u1LastRequest);
|
||||||
|
})
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
List<Team> allTeams =
|
||||||
|
teamRepository.findAll().stream()
|
||||||
|
.filter(team -> !team.getName().equals(TeamService.INTERNAL_TEAM_NAME))
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
AdminSettingsData data = new AdminSettingsData();
|
||||||
|
data.setUsers(sortedUsers);
|
||||||
|
data.setCurrentUsername(authentication.getName());
|
||||||
|
data.setRoleDetails(roleDetails);
|
||||||
|
data.setUserSessions(userSessions);
|
||||||
|
data.setUserLastRequest(userLastRequest);
|
||||||
|
data.setTotalUsers(allUsers.size());
|
||||||
|
data.setActiveUsers(activeUsers);
|
||||||
|
data.setDisabledUsers(disabledUsers);
|
||||||
|
data.setTeams(allTeams);
|
||||||
|
data.setMaxPaidUsers(applicationProperties.getPremium().getMaxUsers());
|
||||||
|
|
||||||
|
return ResponseEntity.ok(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/account")
|
||||||
|
@PreAuthorize("!hasAuthority('ROLE_DEMO_USER')")
|
||||||
|
@Operation(summary = "Get account page data")
|
||||||
|
public ResponseEntity<AccountData> getAccountData(Authentication authentication) {
|
||||||
|
if (authentication == null || !authentication.isAuthenticated()) {
|
||||||
|
return ResponseEntity.status(401).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
Object principal = authentication.getPrincipal();
|
||||||
|
String username = null;
|
||||||
|
boolean isOAuth2Login = false;
|
||||||
|
boolean isSaml2Login = false;
|
||||||
|
|
||||||
|
if (principal instanceof UserDetails detailsUser) {
|
||||||
|
username = detailsUser.getUsername();
|
||||||
|
} else if (principal instanceof OAuth2User oAuth2User) {
|
||||||
|
username = oAuth2User.getName();
|
||||||
|
isOAuth2Login = true;
|
||||||
|
} else if (principal instanceof CustomSaml2AuthenticatedPrincipal saml2User) {
|
||||||
|
username = saml2User.name();
|
||||||
|
isSaml2Login = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (username == null) {
|
||||||
|
return ResponseEntity.status(401).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
Optional<User> user = userRepository.findByUsernameIgnoreCaseWithSettings(username);
|
||||||
|
if (user.isEmpty()) {
|
||||||
|
return ResponseEntity.status(404).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
String settingsJson;
|
||||||
|
try {
|
||||||
|
settingsJson = objectMapper.writeValueAsString(user.get().getSettings());
|
||||||
|
} catch (JsonProcessingException e) {
|
||||||
|
log.error("Error converting settings map", e);
|
||||||
|
return ResponseEntity.status(500).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
AccountData data = new AccountData();
|
||||||
|
data.setUsername(username);
|
||||||
|
data.setRole(user.get().getRolesAsString());
|
||||||
|
data.setSettings(settingsJson);
|
||||||
|
data.setChangeCredsFlag(user.get().isFirstLogin());
|
||||||
|
data.setOAuth2Login(isOAuth2Login);
|
||||||
|
data.setSaml2Login(isSaml2Login);
|
||||||
|
|
||||||
|
return ResponseEntity.ok(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/teams")
|
||||||
|
@PreAuthorize("hasRole('ROLE_ADMIN')")
|
||||||
|
@Operation(summary = "Get teams list data")
|
||||||
|
public ResponseEntity<TeamsData> getTeamsData() {
|
||||||
|
List<TeamWithUserCountDTO> allTeamsWithCounts = teamRepository.findAllTeamsWithUserCount();
|
||||||
|
List<TeamWithUserCountDTO> teamsWithCounts =
|
||||||
|
allTeamsWithCounts.stream()
|
||||||
|
.filter(team -> !team.getName().equals(TeamService.INTERNAL_TEAM_NAME))
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
List<Object[]> teamActivities = sessionRepository.findLatestActivityByTeam();
|
||||||
|
Map<Long, Date> teamLastRequest = new HashMap<>();
|
||||||
|
for (Object[] result : teamActivities) {
|
||||||
|
Long teamId = (Long) result[0];
|
||||||
|
Date lastActivity = (Date) result[1];
|
||||||
|
teamLastRequest.put(teamId, lastActivity);
|
||||||
|
}
|
||||||
|
|
||||||
|
TeamsData data = new TeamsData();
|
||||||
|
data.setTeamsWithCounts(teamsWithCounts);
|
||||||
|
data.setTeamLastRequest(teamLastRequest);
|
||||||
|
|
||||||
|
return ResponseEntity.ok(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/teams/{id}")
|
||||||
|
@PreAuthorize("hasRole('ROLE_ADMIN')")
|
||||||
|
@Operation(summary = "Get team details data")
|
||||||
|
public ResponseEntity<TeamDetailsData> getTeamDetailsData(@PathVariable("id") Long id) {
|
||||||
|
Team team =
|
||||||
|
teamRepository
|
||||||
|
.findById(id)
|
||||||
|
.orElseThrow(() -> new RuntimeException("Team not found"));
|
||||||
|
|
||||||
|
if (team.getName().equals(TeamService.INTERNAL_TEAM_NAME)) {
|
||||||
|
return ResponseEntity.status(403).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
List<User> teamUsers = userRepository.findAllByTeamId(id);
|
||||||
|
List<User> allUsers = userRepository.findAllWithTeam();
|
||||||
|
List<User> availableUsers =
|
||||||
|
allUsers.stream()
|
||||||
|
.filter(
|
||||||
|
user ->
|
||||||
|
(user.getTeam() == null
|
||||||
|
|| !user.getTeam().getId().equals(id))
|
||||||
|
&& (user.getTeam() == null
|
||||||
|
|| !user.getTeam()
|
||||||
|
.getName()
|
||||||
|
.equals(
|
||||||
|
TeamService
|
||||||
|
.INTERNAL_TEAM_NAME)))
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
List<Object[]> userSessions = sessionRepository.findLatestSessionByTeamId(id);
|
||||||
|
Map<String, Date> userLastRequest = new HashMap<>();
|
||||||
|
for (Object[] result : userSessions) {
|
||||||
|
String username = (String) result[0];
|
||||||
|
Date lastRequest = (Date) result[1];
|
||||||
|
userLastRequest.put(username, lastRequest);
|
||||||
|
}
|
||||||
|
|
||||||
|
TeamDetailsData data = new TeamDetailsData();
|
||||||
|
data.setTeam(team);
|
||||||
|
data.setTeamUsers(teamUsers);
|
||||||
|
data.setAvailableUsers(availableUsers);
|
||||||
|
data.setUserLastRequest(userLastRequest);
|
||||||
|
|
||||||
|
return ResponseEntity.ok(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/database")
|
||||||
|
@PreAuthorize("hasRole('ROLE_ADMIN')")
|
||||||
|
@Operation(summary = "Get database management data")
|
||||||
|
public ResponseEntity<DatabaseData> getDatabaseData() {
|
||||||
|
List<FileInfo> backupList = databaseService.getBackupList();
|
||||||
|
String dbVersion = databaseService.getH2Version();
|
||||||
|
boolean isVersionUnknown = "Unknown".equalsIgnoreCase(dbVersion);
|
||||||
|
|
||||||
|
DatabaseData data = new DatabaseData();
|
||||||
|
data.setBackupFiles(backupList);
|
||||||
|
data.setDatabaseVersion(dbVersion);
|
||||||
|
data.setVersionUnknown(isVersionUnknown);
|
||||||
|
|
||||||
|
return ResponseEntity.ok(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Data classes
|
||||||
|
@Data
|
||||||
|
public static class AuditDashboardData {
|
||||||
|
private boolean auditEnabled;
|
||||||
|
private AuditLevel auditLevel;
|
||||||
|
private int auditLevelInt;
|
||||||
|
private int retentionDays;
|
||||||
|
private AuditLevel[] auditLevels;
|
||||||
|
private AuditEventType[] auditEventTypes;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public static class LoginData {
|
||||||
|
private Map<String, String> providerList;
|
||||||
|
private String loginMethod;
|
||||||
|
private boolean altLogin;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public static class AdminSettingsData {
|
||||||
|
private List<User> users;
|
||||||
|
private String currentUsername;
|
||||||
|
private Map<String, String> roleDetails;
|
||||||
|
private Map<String, Boolean> userSessions;
|
||||||
|
private Map<String, Date> userLastRequest;
|
||||||
|
private int totalUsers;
|
||||||
|
private int activeUsers;
|
||||||
|
private int disabledUsers;
|
||||||
|
private List<Team> teams;
|
||||||
|
private int maxPaidUsers;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public static class AccountData {
|
||||||
|
private String username;
|
||||||
|
private String role;
|
||||||
|
private String settings;
|
||||||
|
private boolean changeCredsFlag;
|
||||||
|
private boolean oAuth2Login;
|
||||||
|
private boolean saml2Login;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public static class TeamsData {
|
||||||
|
private List<TeamWithUserCountDTO> teamsWithCounts;
|
||||||
|
private Map<Long, Date> teamLastRequest;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public static class TeamDetailsData {
|
||||||
|
private Team team;
|
||||||
|
private List<User> teamUsers;
|
||||||
|
private List<User> availableUsers;
|
||||||
|
private Map<String, Date> userLastRequest;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public static class DatabaseData {
|
||||||
|
private List<FileInfo> backupFiles;
|
||||||
|
private String databaseVersion;
|
||||||
|
private boolean versionUnknown;
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,7 @@
|
|||||||
package stirling.software.proprietary.security.controller.api;
|
package stirling.software.proprietary.security.controller.api;
|
||||||
|
|
||||||
|
import stirling.software.common.annotations.AutoJobPostMapping;
|
||||||
|
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||||
import org.springframework.http.HttpStatus;
|
import org.springframework.http.HttpStatus;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
@ -42,7 +44,7 @@ public class EmailController {
|
|||||||
* attachment.
|
* attachment.
|
||||||
* @return ResponseEntity with success or error message.
|
* @return ResponseEntity with success or error message.
|
||||||
*/
|
*/
|
||||||
@PostMapping(consumes = "multipart/form-data", value = "/send-email")
|
@AutoJobPostMapping(consumes = "multipart/form-data", value = "/send-email")
|
||||||
@Operation(
|
@Operation(
|
||||||
summary = "Send an email with an attachment",
|
summary = "Send an email with an attachment",
|
||||||
description =
|
description =
|
||||||
|
10
build.gradle
10
build.gradle
@ -212,9 +212,17 @@ subprojects {
|
|||||||
|
|
||||||
tasks.withType(JavaCompile).configureEach {
|
tasks.withType(JavaCompile).configureEach {
|
||||||
options.encoding = "UTF-8"
|
options.encoding = "UTF-8"
|
||||||
|
if (!project.hasProperty("noSpotless")) {
|
||||||
dependsOn "spotlessApply"
|
dependsOn "spotlessApply"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
gradle.taskGraph.whenReady { graph ->
|
||||||
|
if (project.hasProperty("noSpotless")) {
|
||||||
|
tasks.matching { it.name.startsWith("spotless") }.configureEach {
|
||||||
|
enabled = false
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
licenseReport {
|
licenseReport {
|
||||||
projects = [project]
|
projects = [project]
|
||||||
renderers = [new JsonReportRenderer()]
|
renderers = [new JsonReportRenderer()]
|
||||||
|
56
docker/README.md
Normal file
56
docker/README.md
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
# Docker Setup for Stirling-PDF
|
||||||
|
|
||||||
|
This directory contains the organized Docker configurations for the split frontend/backend architecture.
|
||||||
|
|
||||||
|
## Directory Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
docker/
|
||||||
|
├── backend/ # Backend Docker files
|
||||||
|
│ ├── Dockerfile # Standard backend
|
||||||
|
│ ├── Dockerfile.ultra-lite # Minimal backend
|
||||||
|
│ └── Dockerfile.fat # Full-featured backend
|
||||||
|
├── frontend/ # Frontend Docker files
|
||||||
|
│ ├── Dockerfile # React/Vite frontend with nginx
|
||||||
|
│ ├── nginx.conf # Nginx configuration
|
||||||
|
│ └── entrypoint.sh # Dynamic backend URL setup
|
||||||
|
└── compose/ # Docker Compose files
|
||||||
|
├── docker-compose.yml # Standard setup
|
||||||
|
├── docker-compose.ultra-lite.yml # Ultra-lite setup
|
||||||
|
└── docker-compose.fat.yml # Full-featured setup
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### Separate Containers (Recommended)
|
||||||
|
|
||||||
|
From the project root directory:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Standard version
|
||||||
|
docker-compose -f docker/compose/docker-compose.yml up --build
|
||||||
|
|
||||||
|
# Ultra-lite version
|
||||||
|
docker-compose -f docker/compose/docker-compose.ultra-lite.yml up --build
|
||||||
|
|
||||||
|
# Fat version
|
||||||
|
docker-compose -f docker/compose/docker-compose.fat.yml up --build
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Access Points
|
||||||
|
|
||||||
|
- **Frontend**: http://localhost:3000
|
||||||
|
- **Backend API (debugging)**: http://localhost:8080 (TODO: Remove in production)
|
||||||
|
- **Backend API (via frontend)**: http://localhost:3000/api/*
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
- **Backend URL**: Set `VITE_API_BASE_URL` environment variable for custom backend locations
|
||||||
|
- **Custom Ports**: Modify port mappings in docker-compose files
|
||||||
|
- **Memory Limits**: Adjust memory limits per variant (2G ultra-lite, 4G standard, 6G fat)
|
||||||
|
|
||||||
|
## Development vs Production
|
||||||
|
|
||||||
|
- **Development**: Keep backend port 8080 exposed for debugging
|
||||||
|
- **Production**: Remove backend port exposure, use only frontend proxy
|
123
docker/backend/Dockerfile
Normal file
123
docker/backend/Dockerfile
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
# Backend Dockerfile - Java Spring Boot with all dependencies and build stage
|
||||||
|
# Build the application
|
||||||
|
FROM gradle:8.14-jdk21 AS build
|
||||||
|
|
||||||
|
COPY build.gradle .
|
||||||
|
COPY settings.gradle .
|
||||||
|
COPY gradlew .
|
||||||
|
COPY gradle gradle/
|
||||||
|
COPY app/core/build.gradle core/.
|
||||||
|
COPY app/common/build.gradle common/.
|
||||||
|
COPY app/proprietary/build.gradle proprietary/.
|
||||||
|
RUN ./gradlew build -x spotlessApply -x spotlessCheck -x test -x sonarqube || return 0
|
||||||
|
|
||||||
|
# Set the working directory
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copy the entire project to the working directory
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# Build the application with DISABLE_ADDITIONAL_FEATURES=false
|
||||||
|
RUN DISABLE_ADDITIONAL_FEATURES=false \
|
||||||
|
STIRLING_PDF_DESKTOP_UI=false \
|
||||||
|
./gradlew clean build -x spotlessApply -x spotlessCheck -x test -x sonarqube
|
||||||
|
|
||||||
|
# Main stage
|
||||||
|
FROM alpine:3.22.1@sha256:4bcff63911fcb4448bd4fdacec207030997caf25e9bea4045fa6c8c44de311d1
|
||||||
|
|
||||||
|
# Copy necessary files
|
||||||
|
COPY scripts /scripts
|
||||||
|
COPY pipeline /pipeline
|
||||||
|
COPY app/core/src/main/resources/static/fonts/*.ttf /usr/share/fonts/opentype/noto/
|
||||||
|
# first /app directory is for the build stage, second is for the final image
|
||||||
|
COPY --from=build /app/app/core/build/libs/*.jar app.jar
|
||||||
|
|
||||||
|
ARG VERSION_TAG
|
||||||
|
|
||||||
|
LABEL org.opencontainers.image.title="Stirling-PDF Backend"
|
||||||
|
LABEL org.opencontainers.image.description="Backend service for Stirling-PDF - Java Spring Boot with PDF processing capabilities"
|
||||||
|
LABEL org.opencontainers.image.source="https://github.com/Stirling-Tools/Stirling-PDF"
|
||||||
|
LABEL org.opencontainers.image.licenses="MIT"
|
||||||
|
LABEL org.opencontainers.image.vendor="Stirling-Tools"
|
||||||
|
LABEL org.opencontainers.image.url="https://www.stirlingpdf.com"
|
||||||
|
LABEL org.opencontainers.image.documentation="https://docs.stirlingpdf.com"
|
||||||
|
LABEL maintainer="Stirling-Tools"
|
||||||
|
LABEL org.opencontainers.image.authors="Stirling-Tools"
|
||||||
|
LABEL org.opencontainers.image.version="${VERSION_TAG}"
|
||||||
|
LABEL org.opencontainers.image.keywords="PDF, manipulation, backend, API, Spring Boot"
|
||||||
|
|
||||||
|
# Set Environment Variables
|
||||||
|
ENV DISABLE_ADDITIONAL_FEATURES=false \
|
||||||
|
VERSION_TAG=$VERSION_TAG \
|
||||||
|
JAVA_BASE_OPTS="-XX:+UnlockExperimentalVMOptions -XX:MaxRAMPercentage=75 -XX:InitiatingHeapOccupancyPercent=20 -XX:+G1PeriodicGCInvokesConcurrent -XX:G1PeriodicGCInterval=10000 -XX:+UseStringDeduplication -XX:G1PeriodicGCSystemLoadThreshold=70" \
|
||||||
|
JAVA_CUSTOM_OPTS="" \
|
||||||
|
HOME=/home/stirlingpdfuser \
|
||||||
|
PUID=1000 \
|
||||||
|
PGID=1000 \
|
||||||
|
UMASK=022 \
|
||||||
|
PYTHONPATH=/usr/lib/libreoffice/program:/opt/venv/lib/python3.12/site-packages \
|
||||||
|
UNO_PATH=/usr/lib/libreoffice/program \
|
||||||
|
URE_BOOTSTRAP=file:///usr/lib/libreoffice/program/fundamentalrc \
|
||||||
|
PATH=$PATH:/opt/venv/bin \
|
||||||
|
STIRLING_TEMPFILES_DIRECTORY=/tmp/stirling-pdf \
|
||||||
|
TMPDIR=/tmp/stirling-pdf \
|
||||||
|
TEMP=/tmp/stirling-pdf \
|
||||||
|
TMP=/tmp/stirling-pdf
|
||||||
|
|
||||||
|
# JDK for app and all dependencies
|
||||||
|
RUN echo "@main https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /etc/apk/repositories && \
|
||||||
|
echo "@community https://dl-cdn.alpinelinux.org/alpine/edge/community" | tee -a /etc/apk/repositories && \
|
||||||
|
echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/testing" | tee -a /etc/apk/repositories && \
|
||||||
|
apk upgrade --no-cache -a && \
|
||||||
|
apk add --no-cache \
|
||||||
|
ca-certificates \
|
||||||
|
tzdata \
|
||||||
|
tini \
|
||||||
|
bash \
|
||||||
|
curl \
|
||||||
|
shadow \
|
||||||
|
su-exec \
|
||||||
|
openssl \
|
||||||
|
openssl-dev \
|
||||||
|
openjdk21-jre \
|
||||||
|
# Doc conversion
|
||||||
|
gcompat \
|
||||||
|
libc6-compat \
|
||||||
|
libreoffice \
|
||||||
|
# pdftohtml
|
||||||
|
poppler-utils \
|
||||||
|
# OCR MY PDF (unpaper for descew and other advanced features)
|
||||||
|
unpaper \
|
||||||
|
tesseract-ocr-data-eng \
|
||||||
|
tesseract-ocr-data-chi_sim \
|
||||||
|
tesseract-ocr-data-deu \
|
||||||
|
tesseract-ocr-data-fra \
|
||||||
|
tesseract-ocr-data-por \
|
||||||
|
ocrmypdf \
|
||||||
|
# CV
|
||||||
|
py3-opencv \
|
||||||
|
python3 \
|
||||||
|
py3-pip \
|
||||||
|
py3-pillow@testing \
|
||||||
|
py3-pdf2image@testing && \
|
||||||
|
python3 -m venv /opt/venv && \
|
||||||
|
/opt/venv/bin/pip install --upgrade pip setuptools && \
|
||||||
|
/opt/venv/bin/pip install --no-cache-dir --upgrade unoserver weasyprint && \
|
||||||
|
ln -s /usr/lib/libreoffice/program/uno.py /opt/venv/lib/python3.12/site-packages/ && \
|
||||||
|
ln -s /usr/lib/libreoffice/program/unohelper.py /opt/venv/lib/python3.12/site-packages/ && \
|
||||||
|
ln -s /usr/lib/libreoffice/program /opt/venv/lib/python3.12/site-packages/LibreOffice && \
|
||||||
|
mv /usr/share/tessdata /usr/share/tessdata-original && \
|
||||||
|
mkdir -p $HOME /configs /logs /customFiles /pipeline/watchedFolders /pipeline/finishedFolders /tmp/stirling-pdf && \
|
||||||
|
fc-cache -f -v && \
|
||||||
|
chmod +x /scripts/* && \
|
||||||
|
chmod +x /scripts/init.sh && \
|
||||||
|
# User permissions
|
||||||
|
addgroup -S stirlingpdfgroup && adduser -S stirlingpdfuser -G stirlingpdfgroup && \
|
||||||
|
chown -R stirlingpdfuser:stirlingpdfgroup $HOME /scripts /usr/share/fonts/opentype/noto /configs /customFiles /pipeline /tmp/stirling-pdf && \
|
||||||
|
chown stirlingpdfuser:stirlingpdfgroup /app.jar
|
||||||
|
|
||||||
|
EXPOSE 8080/tcp
|
||||||
|
|
||||||
|
# Set user and run command
|
||||||
|
ENTRYPOINT ["tini", "--", "/scripts/init.sh"]
|
||||||
|
CMD ["sh", "-c", "java -Dfile.encoding=UTF-8 -Djava.io.tmpdir=/tmp/stirling-pdf -jar /app.jar & /opt/venv/bin/unoserver --port 2003 --interface 127.0.0.1"]
|
113
docker/backend/Dockerfile.fat
Normal file
113
docker/backend/Dockerfile.fat
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
# Backend fat Dockerfile - Java Spring Boot with all dependencies and build stage
|
||||||
|
# Build the application
|
||||||
|
FROM gradle:8.14-jdk21 AS build
|
||||||
|
|
||||||
|
COPY build.gradle .
|
||||||
|
COPY settings.gradle .
|
||||||
|
COPY gradlew .
|
||||||
|
COPY gradle gradle/
|
||||||
|
COPY app/core/build.gradle core/.
|
||||||
|
COPY app/common/build.gradle common/.
|
||||||
|
COPY app/proprietary/build.gradle proprietary/.
|
||||||
|
RUN ./gradlew build -x spotlessApply -x spotlessCheck -x test -x sonarqube || return 0
|
||||||
|
|
||||||
|
# Set the working directory
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copy the entire project to the working directory
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# Build the application with DISABLE_ADDITIONAL_FEATURES=false
|
||||||
|
RUN DISABLE_ADDITIONAL_FEATURES=false \
|
||||||
|
STIRLING_PDF_DESKTOP_UI=false \
|
||||||
|
./gradlew clean build -x spotlessApply -x spotlessCheck -x test -x sonarqube
|
||||||
|
|
||||||
|
# Main stage
|
||||||
|
FROM alpine:3.22.1@sha256:4bcff63911fcb4448bd4fdacec207030997caf25e9bea4045fa6c8c44de311d1
|
||||||
|
|
||||||
|
# Copy necessary files
|
||||||
|
COPY scripts /scripts
|
||||||
|
COPY pipeline /pipeline
|
||||||
|
COPY app/core/src/main/resources/static/fonts/*.ttf /usr/share/fonts/opentype/noto/
|
||||||
|
# first /app directory is for the build stage, second is for the final image
|
||||||
|
COPY --from=build /app/app/core/build/libs/*.jar app.jar
|
||||||
|
|
||||||
|
ARG VERSION_TAG
|
||||||
|
|
||||||
|
# Set Environment Variables
|
||||||
|
ENV DISABLE_ADDITIONAL_FEATURES=true \
|
||||||
|
VERSION_TAG=$VERSION_TAG \
|
||||||
|
JAVA_BASE_OPTS="-XX:+UnlockExperimentalVMOptions -XX:MaxRAMPercentage=75 -XX:InitiatingHeapOccupancyPercent=20 -XX:+G1PeriodicGCInvokesConcurrent -XX:G1PeriodicGCInterval=10000 -XX:+UseStringDeduplication -XX:G1PeriodicGCSystemLoadThreshold=70" \
|
||||||
|
JAVA_CUSTOM_OPTS="" \
|
||||||
|
HOME=/home/stirlingpdfuser \
|
||||||
|
PUID=1000 \
|
||||||
|
PGID=1000 \
|
||||||
|
UMASK=022 \
|
||||||
|
FAT_DOCKER=true \
|
||||||
|
INSTALL_BOOK_AND_ADVANCED_HTML_OPS=false \
|
||||||
|
PYTHONPATH=/usr/lib/libreoffice/program:/opt/venv/lib/python3.12/site-packages \
|
||||||
|
UNO_PATH=/usr/lib/libreoffice/program \
|
||||||
|
URE_BOOTSTRAP=file:///usr/lib/libreoffice/program/fundamentalrc \
|
||||||
|
PATH=$PATH:/opt/venv/bin \
|
||||||
|
STIRLING_TEMPFILES_DIRECTORY=/tmp/stirling-pdf \
|
||||||
|
TMPDIR=/tmp/stirling-pdf \
|
||||||
|
TEMP=/tmp/stirling-pdf \
|
||||||
|
TMP=/tmp/stirling-pdf
|
||||||
|
|
||||||
|
# JDK for app
|
||||||
|
RUN echo "@main https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /etc/apk/repositories && \
|
||||||
|
echo "@community https://dl-cdn.alpinelinux.org/alpine/edge/community" | tee -a /etc/apk/repositories && \
|
||||||
|
echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/testing" | tee -a /etc/apk/repositories && \
|
||||||
|
apk upgrade --no-cache -a && \
|
||||||
|
apk add --no-cache \
|
||||||
|
ca-certificates \
|
||||||
|
tzdata \
|
||||||
|
tini \
|
||||||
|
bash \
|
||||||
|
curl \
|
||||||
|
shadow \
|
||||||
|
su-exec \
|
||||||
|
openssl \
|
||||||
|
openssl-dev \
|
||||||
|
openjdk21-jre \
|
||||||
|
# Doc conversion
|
||||||
|
gcompat \
|
||||||
|
libc6-compat \
|
||||||
|
libreoffice \
|
||||||
|
# pdftohtml
|
||||||
|
poppler-utils \
|
||||||
|
# OCR MY PDF (unpaper for descew and other advanced featues)
|
||||||
|
unpaper \
|
||||||
|
tesseract-ocr-data-eng \
|
||||||
|
tesseract-ocr-data-chi_sim \
|
||||||
|
tesseract-ocr-data-deu \
|
||||||
|
tesseract-ocr-data-fra \
|
||||||
|
tesseract-ocr-data-por \
|
||||||
|
ocrmypdf \
|
||||||
|
font-terminus font-dejavu font-noto font-noto-cjk font-awesome font-noto-extra font-liberation font-linux-libertine \
|
||||||
|
# CV
|
||||||
|
py3-opencv \
|
||||||
|
python3 \
|
||||||
|
py3-pip \
|
||||||
|
py3-pillow@testing \
|
||||||
|
py3-pdf2image@testing && \
|
||||||
|
python3 -m venv /opt/venv && \
|
||||||
|
/opt/venv/bin/pip install --upgrade pip setuptools && \
|
||||||
|
/opt/venv/bin/pip install --no-cache-dir --upgrade unoserver weasyprint && \
|
||||||
|
ln -s /usr/lib/libreoffice/program/uno.py /opt/venv/lib/python3.12/site-packages/ && \
|
||||||
|
ln -s /usr/lib/libreoffice/program/unohelper.py /opt/venv/lib/python3.12/site-packages/ && \
|
||||||
|
ln -s /usr/lib/libreoffice/program /opt/venv/lib/python3.12/site-packages/LibreOffice && \
|
||||||
|
mv /usr/share/tessdata /usr/share/tessdata-original && \
|
||||||
|
mkdir -p $HOME /configs /logs /customFiles /pipeline/watchedFolders /pipeline/finishedFolders /tmp/stirling-pdf && \
|
||||||
|
fc-cache -f -v && \
|
||||||
|
chmod +x /scripts/* && \
|
||||||
|
chmod +x /scripts/init.sh && \
|
||||||
|
# User permissions
|
||||||
|
addgroup -S stirlingpdfgroup && adduser -S stirlingpdfuser -G stirlingpdfgroup && \
|
||||||
|
chown -R stirlingpdfuser:stirlingpdfgroup $HOME /scripts /usr/share/fonts/opentype/noto /configs /customFiles /pipeline /tmp/stirling-pdf && \
|
||||||
|
chown stirlingpdfuser:stirlingpdfgroup /app.jar
|
||||||
|
|
||||||
|
EXPOSE 8080/tcp
|
||||||
|
# Set user and run command
|
||||||
|
ENTRYPOINT ["tini", "--", "/scripts/init.sh"]
|
||||||
|
CMD ["sh", "-c", "java -Dfile.encoding=UTF-8 -Djava.io.tmpdir=/tmp/stirling-pdf -jar /app.jar & /opt/venv/bin/unoserver --port 2003 --interface 127.0.0.1"]
|
78
docker/backend/Dockerfile.ultra-lite
Normal file
78
docker/backend/Dockerfile.ultra-lite
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
# Backend ultra-lite Dockerfile - Java Spring Boot with minimal dependencies and build stage
|
||||||
|
# Build the application
|
||||||
|
FROM gradle:8.14-jdk21 AS build
|
||||||
|
|
||||||
|
COPY build.gradle .
|
||||||
|
COPY settings.gradle .
|
||||||
|
COPY gradlew .
|
||||||
|
COPY gradle gradle/
|
||||||
|
COPY app/core/build.gradle core/.
|
||||||
|
COPY app/common/build.gradle common/.
|
||||||
|
COPY app/proprietary/build.gradle proprietary/.
|
||||||
|
RUN ./gradlew build -x spotlessApply -x spotlessCheck -x test -x sonarqube || return 0
|
||||||
|
|
||||||
|
# Set the working directory
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copy the entire project to the working directory
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# Build the application with DISABLE_ADDITIONAL_FEATURES=true
|
||||||
|
RUN DISABLE_ADDITIONAL_FEATURES=true \
|
||||||
|
STIRLING_PDF_DESKTOP_UI=false \
|
||||||
|
./gradlew clean build -x spotlessApply -x spotlessCheck -x test -x sonarqube
|
||||||
|
|
||||||
|
# Main stage
|
||||||
|
FROM alpine:3.22.1@sha256:4bcff63911fcb4448bd4fdacec207030997caf25e9bea4045fa6c8c44de311d1
|
||||||
|
|
||||||
|
ARG VERSION_TAG
|
||||||
|
|
||||||
|
# Set Environment Variables
|
||||||
|
ENV DISABLE_ADDITIONAL_FEATURES=true \
|
||||||
|
HOME=/home/stirlingpdfuser \
|
||||||
|
VERSION_TAG=$VERSION_TAG \
|
||||||
|
JAVA_BASE_OPTS="-XX:+UnlockExperimentalVMOptions -XX:MaxRAMPercentage=75 -XX:InitiatingHeapOccupancyPercent=20 -XX:+G1PeriodicGCInvokesConcurrent -XX:G1PeriodicGCInterval=10000 -XX:+UseStringDeduplication -XX:G1PeriodicGCSystemLoadThreshold=70" \
|
||||||
|
JAVA_CUSTOM_OPTS="" \
|
||||||
|
PUID=1000 \
|
||||||
|
PGID=1000 \
|
||||||
|
UMASK=022 \
|
||||||
|
STIRLING_TEMPFILES_DIRECTORY=/tmp/stirling-pdf \
|
||||||
|
TMPDIR=/tmp/stirling-pdf \
|
||||||
|
TEMP=/tmp/stirling-pdf \
|
||||||
|
TMP=/tmp/stirling-pdf
|
||||||
|
|
||||||
|
# Copy necessary files
|
||||||
|
COPY scripts/init-without-ocr.sh /scripts/init-without-ocr.sh
|
||||||
|
COPY scripts/installFonts.sh /scripts/installFonts.sh
|
||||||
|
COPY pipeline /pipeline
|
||||||
|
COPY --from=build /app/app/core/build/libs/*.jar app.jar
|
||||||
|
|
||||||
|
# Set up necessary directories and permissions
|
||||||
|
RUN echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /etc/apk/repositories && \
|
||||||
|
echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/community" | tee -a /etc/apk/repositories && \
|
||||||
|
echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/testing" | tee -a /etc/apk/repositories && \
|
||||||
|
apk upgrade --no-cache -a && \
|
||||||
|
apk add --no-cache \
|
||||||
|
ca-certificates \
|
||||||
|
tzdata \
|
||||||
|
tini \
|
||||||
|
bash \
|
||||||
|
curl \
|
||||||
|
shadow \
|
||||||
|
su-exec \
|
||||||
|
openjdk21-jre && \
|
||||||
|
# User permissions
|
||||||
|
mkdir -p /configs /logs /customFiles /usr/share/fonts/opentype/noto /tmp/stirling-pdf && \
|
||||||
|
chmod +x /scripts/*.sh && \
|
||||||
|
addgroup -S stirlingpdfgroup && adduser -S stirlingpdfuser -G stirlingpdfgroup && \
|
||||||
|
chown -R stirlingpdfuser:stirlingpdfgroup $HOME /scripts /configs /customFiles /pipeline /tmp/stirling-pdf && \
|
||||||
|
chown stirlingpdfuser:stirlingpdfgroup /app.jar
|
||||||
|
|
||||||
|
# Set environment variables
|
||||||
|
ENV ENDPOINTS_GROUPS_TO_REMOVE=CLI
|
||||||
|
|
||||||
|
EXPOSE 8080/tcp
|
||||||
|
|
||||||
|
# Run the application
|
||||||
|
ENTRYPOINT ["tini", "--", "/scripts/init-without-ocr.sh"]
|
||||||
|
CMD ["java", "-Dfile.encoding=UTF-8", "-Djava.io.tmpdir=/tmp/stirling-pdf", "-jar", "/app.jar"]
|
62
docker/compose/docker-compose.fat.yml
Normal file
62
docker/compose/docker-compose.fat.yml
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
services:
|
||||||
|
backend:
|
||||||
|
build:
|
||||||
|
context: ../..
|
||||||
|
dockerfile: docker/backend/Dockerfile.fat
|
||||||
|
container_name: stirling-pdf-backend-fat
|
||||||
|
restart: on-failure:5
|
||||||
|
deploy:
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
memory: 6G
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "curl -f http://localhost:8080/api/v1/info/status | grep -q 'UP'"]
|
||||||
|
interval: 5s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 16
|
||||||
|
ports:
|
||||||
|
- "8080:8080" # TODO: Remove in production - for debugging only
|
||||||
|
expose:
|
||||||
|
- "8080"
|
||||||
|
volumes:
|
||||||
|
- ../../stirling/latest/data:/usr/share/tessdata:rw
|
||||||
|
- ../../stirling/latest/config:/configs:rw
|
||||||
|
- ../../stirling/latest/logs:/logs:rw
|
||||||
|
environment:
|
||||||
|
DISABLE_ADDITIONAL_FEATURES: "false"
|
||||||
|
SECURITY_ENABLELOGIN: "false"
|
||||||
|
FAT_DOCKER: "true"
|
||||||
|
SYSTEM_DEFAULTLOCALE: en-US
|
||||||
|
UI_APPNAME: Stirling-PDF
|
||||||
|
UI_HOMEDESCRIPTION: Full-featured Stirling-PDF with all capabilities
|
||||||
|
UI_APPNAMENAVBAR: Stirling-PDF Fat
|
||||||
|
SYSTEM_MAXFILESIZE: "200"
|
||||||
|
METRICS_ENABLED: "true"
|
||||||
|
SYSTEM_GOOGLEVISIBILITY: "true"
|
||||||
|
SHOW_SURVEY: "true"
|
||||||
|
networks:
|
||||||
|
- stirling-network
|
||||||
|
|
||||||
|
frontend:
|
||||||
|
build:
|
||||||
|
context: ../..
|
||||||
|
dockerfile: docker/frontend/Dockerfile
|
||||||
|
container_name: stirling-pdf-frontend-fat
|
||||||
|
restart: on-failure:5
|
||||||
|
ports:
|
||||||
|
- "3000:80"
|
||||||
|
environment:
|
||||||
|
BACKEND_URL: http://backend:8080
|
||||||
|
depends_on:
|
||||||
|
- backend
|
||||||
|
networks:
|
||||||
|
- stirling-network
|
||||||
|
|
||||||
|
networks:
|
||||||
|
stirling-network:
|
||||||
|
driver: bridge
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
stirling-data:
|
||||||
|
stirling-config:
|
||||||
|
stirling-logs:
|
54
docker/compose/docker-compose.ultra-lite.yml
Normal file
54
docker/compose/docker-compose.ultra-lite.yml
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
services:
|
||||||
|
backend:
|
||||||
|
build:
|
||||||
|
context: ../..
|
||||||
|
dockerfile: docker/backend/Dockerfile.ultra-lite
|
||||||
|
container_name: stirling-pdf-backend-ultra-lite
|
||||||
|
restart: on-failure:5
|
||||||
|
deploy:
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
memory: 2G
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "curl -f http://localhost:8080/api/v1/info/status | grep -q 'UP'"]
|
||||||
|
interval: 5s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 16
|
||||||
|
ports:
|
||||||
|
- "8080:8080" # TODO: Remove in production - for debugging only
|
||||||
|
expose:
|
||||||
|
- "8080"
|
||||||
|
volumes:
|
||||||
|
- ../../stirling/latest/config:/configs:rw
|
||||||
|
- ../../stirling/latest/logs:/logs:rw
|
||||||
|
environment:
|
||||||
|
DISABLE_ADDITIONAL_FEATURES: "true"
|
||||||
|
SECURITY_ENABLELOGIN: "false"
|
||||||
|
ENDPOINTS_GROUPS_TO_REMOVE: "CLI"
|
||||||
|
LANGS: "en_GB,en_US"
|
||||||
|
SYSTEM_DEFAULTLOCALE: en-US
|
||||||
|
UI_APPNAME: Stirling-PDF
|
||||||
|
UI_HOMEDESCRIPTION: Ultra-lite version of Stirling-PDF
|
||||||
|
UI_APPNAMENAVBAR: Stirling-PDF Ultra-lite
|
||||||
|
SYSTEM_MAXFILESIZE: "50"
|
||||||
|
networks:
|
||||||
|
- stirling-network
|
||||||
|
|
||||||
|
frontend:
|
||||||
|
build:
|
||||||
|
context: ../..
|
||||||
|
dockerfile: docker/frontend/Dockerfile
|
||||||
|
container_name: stirling-pdf-frontend-ultra-lite
|
||||||
|
restart: on-failure:5
|
||||||
|
ports:
|
||||||
|
- "3000:80"
|
||||||
|
environment:
|
||||||
|
BACKEND_URL: http://backend:8080
|
||||||
|
depends_on:
|
||||||
|
- backend
|
||||||
|
networks:
|
||||||
|
- stirling-network
|
||||||
|
|
||||||
|
networks:
|
||||||
|
stirling-network:
|
||||||
|
driver: bridge
|
61
docker/compose/docker-compose.yml
Normal file
61
docker/compose/docker-compose.yml
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
services:
|
||||||
|
backend:
|
||||||
|
build:
|
||||||
|
context: ../..
|
||||||
|
dockerfile: docker/backend/Dockerfile
|
||||||
|
container_name: stirling-pdf-backend
|
||||||
|
restart: on-failure:5
|
||||||
|
deploy:
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
memory: 4G
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "curl -f http://localhost:8080/api/v1/info/status | grep -q 'UP'"]
|
||||||
|
interval: 5s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 16
|
||||||
|
ports:
|
||||||
|
- "8080:8080" # TODO: Remove in production - for debugging only
|
||||||
|
expose:
|
||||||
|
- "8080"
|
||||||
|
volumes:
|
||||||
|
- ../../stirling/latest/data:/usr/share/tessdata:rw
|
||||||
|
- ../../stirling/latest/config:/configs:rw
|
||||||
|
- ../../stirling/latest/logs:/logs:rw
|
||||||
|
environment:
|
||||||
|
DISABLE_ADDITIONAL_FEATURES: "true"
|
||||||
|
SECURITY_ENABLELOGIN: "false"
|
||||||
|
SYSTEM_DEFAULTLOCALE: en-US
|
||||||
|
UI_APPNAME: Stirling-PDF
|
||||||
|
UI_HOMEDESCRIPTION: Demo site for Stirling-PDF Latest
|
||||||
|
UI_APPNAMENAVBAR: Stirling-PDF Latest
|
||||||
|
SYSTEM_MAXFILESIZE: "100"
|
||||||
|
METRICS_ENABLED: "true"
|
||||||
|
SYSTEM_GOOGLEVISIBILITY: "true"
|
||||||
|
SHOW_SURVEY: "true"
|
||||||
|
networks:
|
||||||
|
- stirling-network
|
||||||
|
|
||||||
|
frontend:
|
||||||
|
build:
|
||||||
|
context: ../..
|
||||||
|
dockerfile: docker/frontend/Dockerfile
|
||||||
|
container_name: stirling-pdf-frontend
|
||||||
|
restart: on-failure:5
|
||||||
|
ports:
|
||||||
|
- "3000:80"
|
||||||
|
environment:
|
||||||
|
BACKEND_URL: http://backend:8080
|
||||||
|
depends_on:
|
||||||
|
- backend
|
||||||
|
networks:
|
||||||
|
- stirling-network
|
||||||
|
|
||||||
|
networks:
|
||||||
|
stirling-network:
|
||||||
|
driver: bridge
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
stirling-data:
|
||||||
|
stirling-config:
|
||||||
|
stirling-logs:
|
38
docker/frontend/Dockerfile
Normal file
38
docker/frontend/Dockerfile
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
# Frontend Dockerfile - React/Vite application
|
||||||
|
FROM node:20-alpine AS build
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copy package files
|
||||||
|
COPY frontend/package*.json ./
|
||||||
|
|
||||||
|
# Install dependencies
|
||||||
|
RUN npm ci
|
||||||
|
|
||||||
|
# Copy source code
|
||||||
|
COPY frontend .
|
||||||
|
|
||||||
|
# Build the application
|
||||||
|
RUN npm run build
|
||||||
|
|
||||||
|
# Production stage
|
||||||
|
FROM nginx:alpine
|
||||||
|
|
||||||
|
# Copy built files from build stage
|
||||||
|
COPY --from=build /app/dist /usr/share/nginx/html
|
||||||
|
|
||||||
|
# Copy nginx configuration and entrypoint
|
||||||
|
COPY docker/frontend/nginx.conf /etc/nginx/nginx.conf
|
||||||
|
COPY docker/frontend/entrypoint.sh /entrypoint.sh
|
||||||
|
|
||||||
|
# Make entrypoint executable
|
||||||
|
RUN chmod +x /entrypoint.sh
|
||||||
|
|
||||||
|
# Expose port 80 (standard HTTP port)
|
||||||
|
EXPOSE 80
|
||||||
|
|
||||||
|
# Environment variables for flexibility
|
||||||
|
ENV VITE_API_BASE_URL=http://backend:8080
|
||||||
|
|
||||||
|
# Use custom entrypoint
|
||||||
|
ENTRYPOINT ["/entrypoint.sh"]
|
10
docker/frontend/entrypoint.sh
Normal file
10
docker/frontend/entrypoint.sh
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# Set default backend URL if not provided
|
||||||
|
VITE_API_BASE_URL=${VITE_API_BASE_URL:-"http://backend:8080"}
|
||||||
|
|
||||||
|
# Replace the placeholder in nginx.conf with the actual backend URL
|
||||||
|
sed -i "s|\${VITE_API_BASE_URL}|${VITE_API_BASE_URL}|g" /etc/nginx/nginx.conf
|
||||||
|
|
||||||
|
# Start nginx
|
||||||
|
exec nginx -g "daemon off;"
|
105
docker/frontend/nginx.conf
Normal file
105
docker/frontend/nginx.conf
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
events {
|
||||||
|
worker_connections 1024;
|
||||||
|
}
|
||||||
|
|
||||||
|
http {
|
||||||
|
include /etc/nginx/mime.types;
|
||||||
|
default_type application/octet-stream;
|
||||||
|
|
||||||
|
# Gzip compression
|
||||||
|
gzip on;
|
||||||
|
gzip_vary on;
|
||||||
|
gzip_min_length 1024;
|
||||||
|
gzip_types text/plain text/css text/xml text/javascript application/javascript application/xml+rss application/json;
|
||||||
|
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name _;
|
||||||
|
root /usr/share/nginx/html;
|
||||||
|
index index.html index.htm;
|
||||||
|
|
||||||
|
# Global settings for file uploads
|
||||||
|
client_max_body_size 100m;
|
||||||
|
|
||||||
|
# Handle client-side routing - support subpaths
|
||||||
|
location / {
|
||||||
|
try_files $uri $uri/ /index.html;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Proxy API calls to backend
|
||||||
|
location /api/ {
|
||||||
|
proxy_pass ${VITE_API_BASE_URL}/api/;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
proxy_set_header X-Forwarded-Host $host;
|
||||||
|
proxy_set_header X-Forwarded-Port $server_port;
|
||||||
|
|
||||||
|
# Additional headers for proper API proxying
|
||||||
|
proxy_set_header Connection '';
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_buffering off;
|
||||||
|
proxy_cache off;
|
||||||
|
|
||||||
|
# Timeout settings for large file uploads
|
||||||
|
proxy_connect_timeout 60s;
|
||||||
|
proxy_send_timeout 60s;
|
||||||
|
proxy_read_timeout 60s;
|
||||||
|
|
||||||
|
# Request size limits for file uploads
|
||||||
|
client_max_body_size 100m;
|
||||||
|
proxy_request_buffering off;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Proxy Swagger UI to backend (including versioned paths)
|
||||||
|
location ~ ^/swagger-ui(.*)$ {
|
||||||
|
proxy_pass ${VITE_API_BASE_URL}/swagger-ui$1;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
proxy_set_header X-Forwarded-Host $host;
|
||||||
|
proxy_set_header X-Forwarded-Port $server_port;
|
||||||
|
|
||||||
|
proxy_set_header Connection '';
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_buffering off;
|
||||||
|
proxy_cache off;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Proxy API docs to backend (with query parameters and sub-paths)
|
||||||
|
location ~ ^/v3/api-docs(.*)$ {
|
||||||
|
proxy_pass ${VITE_API_BASE_URL}/v3/api-docs$1;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
proxy_set_header X-Forwarded-Host $host;
|
||||||
|
proxy_set_header X-Forwarded-Port $server_port;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Proxy v1 API docs to backend (with query parameters and sub-paths)
|
||||||
|
location ~ ^/v1/api-docs(.*)$ {
|
||||||
|
proxy_pass ${VITE_API_BASE_URL}/v1/api-docs$1;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
proxy_set_header X-Forwarded-Host $host;
|
||||||
|
proxy_set_header X-Forwarded-Port $server_port;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Cache static assets
|
||||||
|
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
|
||||||
|
expires 1y;
|
||||||
|
add_header Cache-Control "public, immutable";
|
||||||
|
}
|
||||||
|
|
||||||
|
# Security headers
|
||||||
|
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||||
|
add_header X-Content-Type-Options "nosniff" always;
|
||||||
|
add_header X-XSS-Protection "1; mode=block" always;
|
||||||
|
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
||||||
|
}
|
||||||
|
}
|
@ -1,36 +0,0 @@
|
|||||||
|
|
||||||
services:
|
|
||||||
stirling-pdf:
|
|
||||||
container_name: Stirling-PDF-Fat-Disable-Endpoints
|
|
||||||
image: docker.stirlingpdf.com/stirlingtools/stirling-pdf:latest-fat
|
|
||||||
deploy:
|
|
||||||
resources:
|
|
||||||
limits:
|
|
||||||
memory: 4G
|
|
||||||
healthcheck:
|
|
||||||
test: ["CMD-SHELL", "curl -f http://localhost:8080/api/v1/info/status | grep -q 'UP'"]
|
|
||||||
interval: 5s
|
|
||||||
timeout: 10s
|
|
||||||
retries: 16
|
|
||||||
ports:
|
|
||||||
- 8080:8080
|
|
||||||
volumes:
|
|
||||||
- ./stirling/latest/data:/usr/share/tessdata:rw
|
|
||||||
- ./stirling/latest/config:/configs:rw
|
|
||||||
- ./stirling/latest/logs:/logs:rw
|
|
||||||
- ../testing/allEndpointsRemovedSettings.yml:/configs/settings.yml:rw
|
|
||||||
environment:
|
|
||||||
DISABLE_ADDITIONAL_FEATURES: "false"
|
|
||||||
SECURITY_ENABLELOGIN: "false"
|
|
||||||
PUID: 1002
|
|
||||||
PGID: 1002
|
|
||||||
UMASK: "022"
|
|
||||||
SYSTEM_DEFAULTLOCALE: en-US
|
|
||||||
UI_APPNAME: Stirling-PDF
|
|
||||||
UI_HOMEDESCRIPTION: Demo site for Stirling-PDF Latest-fat with all Endpoints Disabled
|
|
||||||
UI_APPNAMENAVBAR: Stirling-PDF Latest-fat
|
|
||||||
SYSTEM_MAXFILESIZE: "100"
|
|
||||||
METRICS_ENABLED: "true"
|
|
||||||
SYSTEM_GOOGLEVISIBILITY: "true"
|
|
||||||
SHOW_SURVEY: "true"
|
|
||||||
restart: on-failure:5
|
|
@ -1,64 +0,0 @@
|
|||||||
services:
|
|
||||||
stirling-pdf:
|
|
||||||
container_name: Stirling-PDF-Security-Fat-Postgres
|
|
||||||
image: docker.stirlingpdf.com/stirlingtools/stirling-pdf:latest-fat-postgres
|
|
||||||
deploy:
|
|
||||||
resources:
|
|
||||||
limits:
|
|
||||||
memory: 4G
|
|
||||||
depends_on:
|
|
||||||
- db
|
|
||||||
healthcheck:
|
|
||||||
test: [ "CMD-SHELL", "curl -f http://localhost:8080/api/v1/info/status | grep -q 'UP'" ]
|
|
||||||
interval: 5s
|
|
||||||
timeout: 10s
|
|
||||||
retries: 16
|
|
||||||
ports:
|
|
||||||
- 8080:8080
|
|
||||||
volumes:
|
|
||||||
- ./stirling/latest/data:/usr/share/tessdata:rw
|
|
||||||
- ./stirling/latest/config:/configs:rw
|
|
||||||
- ./stirling/latest/logs:/logs:rw
|
|
||||||
environment:
|
|
||||||
DISABLE_ADDITIONAL_FEATURES: "false"
|
|
||||||
SECURITY_ENABLELOGIN: "false"
|
|
||||||
PUID: 1002
|
|
||||||
PGID: 1002
|
|
||||||
UMASK: "022"
|
|
||||||
SYSTEM_DEFAULTLOCALE: en-US
|
|
||||||
UI_APPNAME: Stirling-PDF
|
|
||||||
UI_HOMEDESCRIPTION: Demo site for Stirling-PDF Latest-fat with Security and PostgreSQL
|
|
||||||
UI_APPNAMENAVBAR: Stirling-PDF Latest-fat-PostgreSQL
|
|
||||||
SYSTEM_MAXFILESIZE: "100"
|
|
||||||
METRICS_ENABLED: "true"
|
|
||||||
SYSTEM_GOOGLEVISIBILITY: "true"
|
|
||||||
SYSTEM_DATASOURCE_ENABLECUSTOMDATABASE: "true"
|
|
||||||
SYSTEM_DATASOURCE_CUSTOMDATABASEURL: "jdbc:postgresql://db:5432/stirling_pdf"
|
|
||||||
SYSTEM_DATASOURCE_USERNAME: "admin"
|
|
||||||
SYSTEM_DATASOURCE_PASSWORD: "stirling"
|
|
||||||
SHOW_SURVEY: "true"
|
|
||||||
restart: on-failure:5
|
|
||||||
|
|
||||||
db:
|
|
||||||
image: 'postgres:17.2-alpine'
|
|
||||||
restart: on-failure:5
|
|
||||||
container_name: db
|
|
||||||
ports:
|
|
||||||
- "5432:5432"
|
|
||||||
environment:
|
|
||||||
POSTGRES_DB: "stirling_pdf"
|
|
||||||
POSTGRES_USER: "admin"
|
|
||||||
POSTGRES_PASSWORD: "stirling"
|
|
||||||
shm_size: "512mb"
|
|
||||||
deploy:
|
|
||||||
resources:
|
|
||||||
limits:
|
|
||||||
memory: 512m
|
|
||||||
cpus: "0.5"
|
|
||||||
healthcheck:
|
|
||||||
test: [ "CMD-SHELL", "pg_isready -U admin stirling_pdf" ]
|
|
||||||
interval: 1s
|
|
||||||
timeout: 5s
|
|
||||||
retries: 10
|
|
||||||
volumes:
|
|
||||||
- ./stirling/latest/data:/pgdata
|
|
@ -1,34 +0,0 @@
|
|||||||
services:
|
|
||||||
stirling-pdf:
|
|
||||||
container_name: Stirling-PDF-Security-Fat
|
|
||||||
image: docker.stirlingpdf.com/stirlingtools/stirling-pdf:latest-fat
|
|
||||||
deploy:
|
|
||||||
resources:
|
|
||||||
limits:
|
|
||||||
memory: 4G
|
|
||||||
healthcheck:
|
|
||||||
test: ["CMD-SHELL", "curl -f http://localhost:8080/api/v1/info/status | grep -q 'UP'"]
|
|
||||||
interval: 5s
|
|
||||||
timeout: 10s
|
|
||||||
retries: 16
|
|
||||||
ports:
|
|
||||||
- 8080:8080
|
|
||||||
volumes:
|
|
||||||
- ./stirling/latest/data:/usr/share/tessdata:rw
|
|
||||||
- ./stirling/latest/config:/configs:rw
|
|
||||||
- ./stirling/latest/logs:/logs:rw
|
|
||||||
environment:
|
|
||||||
DISABLE_ADDITIONAL_FEATURES: "false"
|
|
||||||
SECURITY_ENABLELOGIN: "false"
|
|
||||||
PUID: 1002
|
|
||||||
PGID: 1002
|
|
||||||
UMASK: "022"
|
|
||||||
SYSTEM_DEFAULTLOCALE: en-US
|
|
||||||
UI_APPNAME: Stirling-PDF
|
|
||||||
UI_HOMEDESCRIPTION: Demo site for Stirling-PDF Latest-fat with Security
|
|
||||||
UI_APPNAMENAVBAR: Stirling-PDF Latest-fat
|
|
||||||
SYSTEM_MAXFILESIZE: "100"
|
|
||||||
METRICS_ENABLED: "true"
|
|
||||||
SYSTEM_GOOGLEVISIBILITY: "true"
|
|
||||||
SHOW_SURVEY: "true"
|
|
||||||
restart: on-failure:5
|
|
@ -1,42 +0,0 @@
|
|||||||
services:
|
|
||||||
stirling-pdf:
|
|
||||||
container_name: Stirling-PDF-Security
|
|
||||||
image: docker.stirlingpdf.com/stirlingtools/stirling-pdf:latest
|
|
||||||
deploy:
|
|
||||||
resources:
|
|
||||||
limits:
|
|
||||||
memory: 4G
|
|
||||||
healthcheck:
|
|
||||||
test: ["CMD-SHELL", "curl -f http://localhost:8080/api/v1/info/status | grep -q 'UP' && curl -fL http://localhost:8080/ | grep -q 'Please sign in'"]
|
|
||||||
interval: 5s
|
|
||||||
timeout: 10s
|
|
||||||
retries: 16
|
|
||||||
ports:
|
|
||||||
- "8080:8080"
|
|
||||||
volumes:
|
|
||||||
- ./stirling/latest/data:/usr/share/tessdata:rw
|
|
||||||
- ./stirling/latest/config:/configs:rw
|
|
||||||
- ./stirling/latest/logs:/logs:rw
|
|
||||||
environment:
|
|
||||||
DISABLE_ADDITIONAL_FEATURES: "false"
|
|
||||||
SECURITY_ENABLELOGIN: "true"
|
|
||||||
SECURITY_OAUTH2_ENABLED: "true"
|
|
||||||
SECURITY_OAUTH2_AUTOCREATEUSER: "true" # This is set to true to allow auto-creation of non-existing users in Stirling-PDF
|
|
||||||
SECURITY_OAUTH2_ISSUER: "https://accounts.google.com" # Change with any other provider that supports OpenID Connect Discovery (/.well-known/openid-configuration) end-point
|
|
||||||
SECURITY_OAUTH2_CLIENTID: "<YOUR CLIENT ID>.apps.googleusercontent.com" # Client ID from your provider
|
|
||||||
SECURITY_OAUTH2_CLIENTSECRET: "<YOUR CLIENT SECRET>" # Client Secret from your provider
|
|
||||||
SECURITY_OAUTH2_SCOPES: "openid,profile,email" # Expected OAuth2 Scope
|
|
||||||
SECURITY_OAUTH2_USEASUSERNAME: "email" # Default is 'email'; custom fields can be used as the username
|
|
||||||
SECURITY_OAUTH2_PROVIDER: "google" # Set this to your OAuth provider's name, e.g., 'google' or 'keycloak'
|
|
||||||
PUID: 1002
|
|
||||||
PGID: 1002
|
|
||||||
UMASK: "022"
|
|
||||||
SYSTEM_DEFAULTLOCALE: en-US
|
|
||||||
UI_APPNAME: Stirling-PDF
|
|
||||||
UI_HOMEDESCRIPTION: Demo site for Stirling-PDF Latest with Security
|
|
||||||
UI_APPNAMENAVBAR: Stirling-PDF Latest
|
|
||||||
SYSTEM_MAXFILESIZE: "100"
|
|
||||||
METRICS_ENABLED: "true"
|
|
||||||
SYSTEM_GOOGLEVISIBILITY: "true"
|
|
||||||
SHOW_SURVEY: "true"
|
|
||||||
restart: on-failure:5
|
|
@ -1,34 +0,0 @@
|
|||||||
services:
|
|
||||||
stirling-pdf:
|
|
||||||
container_name: Stirling-PDF-Security
|
|
||||||
image: docker.stirlingpdf.com/stirlingtools/stirling-pdf:latest
|
|
||||||
deploy:
|
|
||||||
resources:
|
|
||||||
limits:
|
|
||||||
memory: 4G
|
|
||||||
healthcheck:
|
|
||||||
test: ["CMD-SHELL", "curl -f http://localhost:8080/api/v1/info/status | grep -q 'UP' && curl -fL http://localhost:8080/ | grep -q 'Please sign in'"]
|
|
||||||
interval: 5s
|
|
||||||
timeout: 10s
|
|
||||||
retries: 16
|
|
||||||
ports:
|
|
||||||
- "8080:8080"
|
|
||||||
volumes:
|
|
||||||
- ./stirling/latest/data:/usr/share/tessdata:rw
|
|
||||||
- ./stirling/latest/config:/configs:rw
|
|
||||||
- ./stirling/latest/logs:/logs:rw
|
|
||||||
environment:
|
|
||||||
DISABLE_ADDITIONAL_FEATURES: "false"
|
|
||||||
SECURITY_ENABLELOGIN: "true"
|
|
||||||
PUID: 1002
|
|
||||||
PGID: 1002
|
|
||||||
UMASK: "022"
|
|
||||||
SYSTEM_DEFAULTLOCALE: en-US
|
|
||||||
UI_APPNAME: Stirling-PDF
|
|
||||||
UI_HOMEDESCRIPTION: Demo site for Stirling-PDF Latest with Security
|
|
||||||
UI_APPNAMENAVBAR: Stirling-PDF Latest
|
|
||||||
SYSTEM_MAXFILESIZE: "100"
|
|
||||||
METRICS_ENABLED: "true"
|
|
||||||
SYSTEM_GOOGLEVISIBILITY: "true"
|
|
||||||
SHOW_SURVEY: "true"
|
|
||||||
restart: on-failure:5
|
|
@ -1,31 +0,0 @@
|
|||||||
services:
|
|
||||||
stirling-pdf:
|
|
||||||
container_name: Stirling-PDF-Ultra-Lite-Security
|
|
||||||
image: docker.stirlingpdf.com/stirlingtools/stirling-pdf:latest-ultra-lite
|
|
||||||
deploy:
|
|
||||||
resources:
|
|
||||||
limits:
|
|
||||||
memory: 1G
|
|
||||||
healthcheck:
|
|
||||||
test: ["CMD-SHELL", "curl -f http://localhost:8080/api/v1/info/status | grep -q 'UP' && curl -fL http://localhost:8080/ | grep -q 'Please sign in'"]
|
|
||||||
interval: 5s
|
|
||||||
timeout: 10s
|
|
||||||
retries: 16
|
|
||||||
ports:
|
|
||||||
- "8080:8080"
|
|
||||||
volumes:
|
|
||||||
- ./stirling/latest/data:/usr/share/tessdata:rw
|
|
||||||
- ./stirling/latest/config:/configs:rw
|
|
||||||
- ./stirling/latest/logs:/logs:rw
|
|
||||||
environment:
|
|
||||||
DISABLE_ADDITIONAL_FEATURES: "false"
|
|
||||||
SECURITY_ENABLELOGIN: "true"
|
|
||||||
SYSTEM_DEFAULTLOCALE: en-US
|
|
||||||
UI_APPNAME: Stirling-PDF-Lite
|
|
||||||
UI_HOMEDESCRIPTION: Demo site for Stirling-PDF-Lite Latest with Security
|
|
||||||
UI_APPNAMENAVBAR: Stirling-PDF-Lite Latest
|
|
||||||
SYSTEM_MAXFILESIZE: "100"
|
|
||||||
METRICS_ENABLED: "true"
|
|
||||||
SYSTEM_GOOGLEVISIBILITY: "true"
|
|
||||||
SHOW_SURVEY: "true"
|
|
||||||
restart: on-failure:5
|
|
@ -1,31 +0,0 @@
|
|||||||
services:
|
|
||||||
stirling-pdf:
|
|
||||||
container_name: Stirling-PDF
|
|
||||||
image: docker.stirlingpdf.com/stirlingtools/stirling-pdf:latest
|
|
||||||
deploy:
|
|
||||||
resources:
|
|
||||||
limits:
|
|
||||||
memory: 4G
|
|
||||||
healthcheck:
|
|
||||||
test: ["CMD-SHELL", "curl -f http://localhost:8080/api/v1/info/status | grep -q 'UP' && curl -fL http://localhost:8080/ | grep -qv 'Please sign in'"]
|
|
||||||
interval: 5s
|
|
||||||
timeout: 10s
|
|
||||||
retries: 16
|
|
||||||
ports:
|
|
||||||
- "8080:8080"
|
|
||||||
volumes:
|
|
||||||
- ./stirling/latest/data:/usr/share/tessdata:rw
|
|
||||||
- ./stirling/latest/config:/configs:rw
|
|
||||||
- ./stirling/latest/logs:/logs:rw
|
|
||||||
environment:
|
|
||||||
SECURITY_ENABLELOGIN: "false"
|
|
||||||
LANGS: "en_GB,en_US,ar_AR,de_DE,fr_FR,es_ES,zh_CN,zh_TW,ca_CA,it_IT,sv_SE,pl_PL,ro_RO,ko_KR,pt_BR,ru_RU,el_GR,hi_IN,hu_HU,tr_TR,id_ID"
|
|
||||||
SYSTEM_DEFAULTLOCALE: en-US
|
|
||||||
UI_APPNAME: Stirling-PDF
|
|
||||||
UI_HOMEDESCRIPTION: Demo site for Stirling-PDF Latest
|
|
||||||
UI_APPNAMENAVBAR: Stirling-PDF Latest
|
|
||||||
SYSTEM_MAXFILESIZE: "100"
|
|
||||||
METRICS_ENABLED: "true"
|
|
||||||
SYSTEM_GOOGLEVISIBILITY: "true"
|
|
||||||
SHOW_SURVEY: "true"
|
|
||||||
restart: on-failure:5
|
|
@ -1,34 +0,0 @@
|
|||||||
services:
|
|
||||||
stirling-pdf:
|
|
||||||
container_name: Stirling-PDF-Security-Fat-with-login
|
|
||||||
image: docker.stirlingpdf.com/stirlingtools/stirling-pdf:latest-fat
|
|
||||||
deploy:
|
|
||||||
resources:
|
|
||||||
limits:
|
|
||||||
memory: 4G
|
|
||||||
healthcheck:
|
|
||||||
test: ["CMD-SHELL", "curl -f -H 'X-API-KEY: 123456789' http://localhost:8080/api/v1/info/status | grep -q 'UP'"]
|
|
||||||
interval: 5s
|
|
||||||
timeout: 10s
|
|
||||||
retries: 16
|
|
||||||
ports:
|
|
||||||
- 8080:8080
|
|
||||||
volumes:
|
|
||||||
- ./stirling/latest/data:/usr/share/tessdata:rw
|
|
||||||
- ./stirling/latest/config:/configs:rw
|
|
||||||
- ./stirling/latest/logs:/logs:rw
|
|
||||||
environment:
|
|
||||||
DISABLE_ADDITIONAL_FEATURES: "false"
|
|
||||||
SECURITY_ENABLELOGIN: "true"
|
|
||||||
PUID: 1002
|
|
||||||
PGID: 1002
|
|
||||||
UMASK: "022"
|
|
||||||
SYSTEM_DEFAULTLOCALE: en-US
|
|
||||||
UI_APPNAME: Stirling-PDF
|
|
||||||
UI_HOMEDESCRIPTION: Demo site for Stirling-PDF Latest-fat with Security
|
|
||||||
UI_APPNAMENAVBAR: Stirling-PDF Latest-fat
|
|
||||||
SYSTEM_MAXFILESIZE: "100"
|
|
||||||
METRICS_ENABLED: "true"
|
|
||||||
SYSTEM_GOOGLEVISIBILITY: "true"
|
|
||||||
SECURITY_CUSTOMGLOBALAPIKEY: "123456789"
|
|
||||||
restart: on-failure:5
|
|
27
frontend/.gitignore
vendored
Normal file
27
frontend/.gitignore
vendored
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||||
|
|
||||||
|
# dependencies
|
||||||
|
/node_modules
|
||||||
|
/.pnp
|
||||||
|
.pnp.js
|
||||||
|
|
||||||
|
# testing
|
||||||
|
/coverage
|
||||||
|
|
||||||
|
# production
|
||||||
|
/build
|
||||||
|
/dist
|
||||||
|
|
||||||
|
# misc
|
||||||
|
.DS_Store
|
||||||
|
.env.local
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
|
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
|
||||||
|
playwright-report
|
||||||
|
test-results
|
74
frontend/README.md
Normal file
74
frontend/README.md
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
# Getting Started with Create React App
|
||||||
|
|
||||||
|
This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
|
||||||
|
|
||||||
|
## Docker Setup
|
||||||
|
|
||||||
|
For Docker deployments and configuration, see the [Docker README](../docker/README.md).
|
||||||
|
|
||||||
|
## Available Scripts
|
||||||
|
|
||||||
|
In the project directory, you can run:
|
||||||
|
|
||||||
|
### `npm start`
|
||||||
|
|
||||||
|
Runs the app in the development mode.\
|
||||||
|
Open [http://localhost:3000](http://localhost:3000) to view it in your browser.
|
||||||
|
|
||||||
|
The page will reload when you make changes.\
|
||||||
|
You may also see any lint errors in the console.
|
||||||
|
|
||||||
|
### `npm test`
|
||||||
|
|
||||||
|
Launches the test runner in the interactive watch mode.\
|
||||||
|
See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
|
||||||
|
|
||||||
|
### `npm run build`
|
||||||
|
|
||||||
|
Builds the app for production to the `build` folder.\
|
||||||
|
It correctly bundles React in production mode and optimizes the build for the best performance.
|
||||||
|
|
||||||
|
The build is minified and the filenames include the hashes.\
|
||||||
|
Your app is ready to be deployed!
|
||||||
|
|
||||||
|
See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
|
||||||
|
|
||||||
|
### `npm run eject`
|
||||||
|
|
||||||
|
**Note: this is a one-way operation. Once you `eject`, you can't go back!**
|
||||||
|
|
||||||
|
If you aren't satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
|
||||||
|
|
||||||
|
Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you're on your own.
|
||||||
|
|
||||||
|
You don't have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn't feel obligated to use this feature. However we understand that this tool wouldn't be useful if you couldn't customize it when you are ready for it.
|
||||||
|
|
||||||
|
## Learn More
|
||||||
|
|
||||||
|
You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
|
||||||
|
|
||||||
|
To learn React, check out the [React documentation](https://reactjs.org/).
|
||||||
|
|
||||||
|
### Code Splitting
|
||||||
|
|
||||||
|
This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting)
|
||||||
|
|
||||||
|
### Analyzing the Bundle Size
|
||||||
|
|
||||||
|
This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size)
|
||||||
|
|
||||||
|
### Making a Progressive Web App
|
||||||
|
|
||||||
|
This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app)
|
||||||
|
|
||||||
|
### Advanced Configuration
|
||||||
|
|
||||||
|
This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration)
|
||||||
|
|
||||||
|
### Deployment
|
||||||
|
|
||||||
|
This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment)
|
||||||
|
|
||||||
|
### `npm run build` fails to minify
|
||||||
|
|
||||||
|
This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify)
|
22
frontend/index.html
Normal file
22
frontend/index.html
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<link rel="icon" href="/favicon.ico" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<meta name="theme-color" content="#000000" />
|
||||||
|
<meta
|
||||||
|
name="description"
|
||||||
|
content="The Free Adobe Acrobat alternative (10M+ Downloads)"
|
||||||
|
/>
|
||||||
|
<link rel="apple-touch-icon" href="/logo192.png" />
|
||||||
|
<link rel="manifest" href="/manifest.json" />
|
||||||
|
|
||||||
|
<title>Stirling PDF</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||||
|
<div id="root"></div>
|
||||||
|
<script type="module" src="/src/index.jsx"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
8965
frontend/package-lock.json
generated
Normal file
8965
frontend/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
82
frontend/package.json
Normal file
82
frontend/package.json
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
{
|
||||||
|
"name": "frontend",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"license": "SEE LICENSE IN https://raw.githubusercontent.com/Stirling-Tools/Stirling-PDF/refs/heads/main/proprietary/LICENSE",
|
||||||
|
"proxy": "http://localhost:8080",
|
||||||
|
"dependencies": {
|
||||||
|
"@emotion/react": "^11.14.0",
|
||||||
|
"@emotion/styled": "^11.14.0",
|
||||||
|
"@mantine/core": "^8.0.1",
|
||||||
|
"@mantine/dropzone": "^8.0.1",
|
||||||
|
"@mantine/hooks": "^8.0.1",
|
||||||
|
"@mui/icons-material": "^7.1.0",
|
||||||
|
"@mui/material": "^7.1.0",
|
||||||
|
"@tailwindcss/postcss": "^4.1.8",
|
||||||
|
"@testing-library/dom": "^10.4.0",
|
||||||
|
"@testing-library/jest-dom": "^6.6.3",
|
||||||
|
"@testing-library/react": "^16.3.0",
|
||||||
|
"@testing-library/user-event": "^13.5.0",
|
||||||
|
"autoprefixer": "^10.4.21",
|
||||||
|
"axios": "^1.9.0",
|
||||||
|
"i18next": "^25.2.1",
|
||||||
|
"i18next-browser-languagedetector": "^8.1.0",
|
||||||
|
"i18next-http-backend": "^3.0.2",
|
||||||
|
"jszip": "^3.10.1",
|
||||||
|
"material-symbols": "^0.33.0",
|
||||||
|
"pdf-lib": "^1.17.1",
|
||||||
|
"pdfjs-dist": "^3.11.174",
|
||||||
|
"react": "^19.1.0",
|
||||||
|
"react-dom": "^19.1.0",
|
||||||
|
"react-i18next": "^15.5.2",
|
||||||
|
"react-router-dom": "^7.6.0",
|
||||||
|
"tailwindcss": "^4.1.8",
|
||||||
|
"web-vitals": "^2.1.4"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "vite build",
|
||||||
|
"preview": "vite preview",
|
||||||
|
"generate-licenses": "node scripts/generate-licenses.js",
|
||||||
|
"test": "vitest",
|
||||||
|
"test:watch": "vitest --watch",
|
||||||
|
"test:coverage": "vitest --coverage",
|
||||||
|
"test:e2e": "playwright test",
|
||||||
|
"test:e2e:ui": "playwright test --ui",
|
||||||
|
"test:e2e:install": "playwright install"
|
||||||
|
},
|
||||||
|
"eslintConfig": {
|
||||||
|
"extends": [
|
||||||
|
"react-app",
|
||||||
|
"react-app/jest"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"browserslist": {
|
||||||
|
"production": [
|
||||||
|
">0.2%",
|
||||||
|
"not dead",
|
||||||
|
"not op_mini all"
|
||||||
|
],
|
||||||
|
"development": [
|
||||||
|
"last 1 chrome version",
|
||||||
|
"last 1 firefox version",
|
||||||
|
"last 1 safari version"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@playwright/test": "^1.40.0",
|
||||||
|
"@types/react": "^19.1.4",
|
||||||
|
"@types/react-dom": "^19.1.5",
|
||||||
|
"@vitejs/plugin-react": "^4.5.0",
|
||||||
|
"@vitest/coverage-v8": "^1.0.0",
|
||||||
|
"jsdom": "^23.0.0",
|
||||||
|
"license-checker": "^25.0.1",
|
||||||
|
"postcss": "^8.5.3",
|
||||||
|
"postcss-cli": "^11.0.1",
|
||||||
|
"postcss-preset-mantine": "^1.17.0",
|
||||||
|
"postcss-simple-vars": "^7.0.1",
|
||||||
|
"typescript": "^5.8.3",
|
||||||
|
"vite": "^6.3.5",
|
||||||
|
"vitest": "^1.0.0"
|
||||||
|
}
|
||||||
|
}
|
75
frontend/playwright.config.ts
Normal file
75
frontend/playwright.config.ts
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
import { defineConfig, devices } from '@playwright/test';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see https://playwright.dev/docs/test-configuration
|
||||||
|
*/
|
||||||
|
export default defineConfig({
|
||||||
|
testDir: './src/tests',
|
||||||
|
testMatch: '**/*.spec.ts',
|
||||||
|
/* Run tests in files in parallel */
|
||||||
|
fullyParallel: true,
|
||||||
|
/* Fail the build on CI if you accidentally left test.only in the source code. */
|
||||||
|
forbidOnly: !!process.env.CI,
|
||||||
|
/* Retry on CI only */
|
||||||
|
retries: process.env.CI ? 2 : 0,
|
||||||
|
/* Opt out of parallel tests on CI. */
|
||||||
|
workers: process.env.CI ? 1 : undefined,
|
||||||
|
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
||||||
|
reporter: 'html',
|
||||||
|
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
||||||
|
use: {
|
||||||
|
/* Base URL to use in actions like `await page.goto('/')`. */
|
||||||
|
baseURL: 'http://localhost:5173',
|
||||||
|
|
||||||
|
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
|
||||||
|
trace: 'on-first-retry',
|
||||||
|
},
|
||||||
|
|
||||||
|
/* Configure projects for major browsers */
|
||||||
|
projects: [
|
||||||
|
{
|
||||||
|
name: 'chromium',
|
||||||
|
use: {
|
||||||
|
...devices['Desktop Chrome'],
|
||||||
|
viewport: { width: 1920, height: 1080 }
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: 'firefox',
|
||||||
|
use: { ...devices['Desktop Firefox'] },
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
name: 'webkit',
|
||||||
|
use: { ...devices['Desktop Safari'] },
|
||||||
|
},
|
||||||
|
|
||||||
|
/* Test against mobile viewports. */
|
||||||
|
// {
|
||||||
|
// name: 'Mobile Chrome',
|
||||||
|
// use: { ...devices['Pixel 5'] },
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// name: 'Mobile Safari',
|
||||||
|
// use: { ...devices['iPhone 12'] },
|
||||||
|
// },
|
||||||
|
|
||||||
|
/* Test against branded browsers. */
|
||||||
|
// {
|
||||||
|
// name: 'Microsoft Edge',
|
||||||
|
// use: { ...devices['Desktop Edge'], channel: 'msedge' },
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// name: 'Google Chrome',
|
||||||
|
// use: { ...devices['Desktop Chrome'], channel: 'chrome' },
|
||||||
|
// },
|
||||||
|
],
|
||||||
|
|
||||||
|
/* Run your local dev server before starting the tests */
|
||||||
|
webServer: {
|
||||||
|
command: 'npm run dev',
|
||||||
|
url: 'http://localhost:5173',
|
||||||
|
reuseExistingServer: !process.env.CI,
|
||||||
|
},
|
||||||
|
});
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user