Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

11 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

PDF Exercise Maker Web

中文文档

A lightweight single-server web app for turning worksheet images or PDFs into two downloadable files:

  • a student worksheet PDF without answers
  • a complete worksheet PDF with answers and explanations

The project is designed for small VPS deployments. It uses FastAPI, SQLite, local file storage, one worker process, and Nginx by default. It does not require Docker, Redis, or PostgreSQL.

Preview

PDF Exercise Maker screenshot

Features

  • Upload JPG, PNG, WEBP, or PDF worksheet files.
  • Use OpenCV first for photo correction, enhancement, and preprocessing.
  • Prefer XeLaTeX for math/formula PDFs.
  • Preserve complex diagrams with padded source crops to avoid inaccurate redraws.
  • Store task status in SQLite and files under local data/.
  • Run a single sequential worker, suitable for 1GB VPS instances.
  • MVP limits: 10MB per file, 2 global queued/running jobs, 1 active job per IP, and 5 jobs per IP per hour.
  • Optional IPInfo Lite lookup for visitor country and AS name, cached in SQLite.
  • Optional shared authorization links so invited users can run tasks without their own API Key.
  • Hidden trial-token admin page for fixed-IP or first-use-bound trial tokens.
  • Server-side job cleanup after 24 hours, including uploads, work directories, artifacts, and SQLite job rows.
  • Browser LocalStorage for recent job references and optional user AI configuration.
  • Optional hidden visitor stats page for page_view, job_created, and artifact_download events.
  • Full-site English / Chinese UI switching, with English as the default language.

Security Notes

Never commit these files or values to Git:

  • .env
  • Cloudflare Origin Certificates and private keys under cert/
  • uploads, generated PDFs, task data, or runtime SQLite data under data/ and var/
  • real VPS IPs, production domains, SSH passwords, API Keys, admin tokens, or trial plaintext tokens

The .gitignore is configured to ignore runtime files. The public repository should contain only code, templates, static assets, and example configuration.

User AI Provider, Base URL, Model, and API Key are configured in the browser. The API Key is sent to the server only when a job is submitted, written temporarily to that job's secrets.json, read by the worker, and deleted immediately. It is not written to SQLite.

Recent tasks are stored in the user's browser LocalStorage. Server-side 24-hour cleanup does not remove browser LocalStorage; if a task has expired, the user will see a missing-task or cleaned-file message when reopening it.

Visitor stats record IP, anonymous browser client_id, User-Agent, path, and job ID references. They do not record API Keys, Base URLs, uploaded worksheet content, generated PDFs, or answer content.

Local Development

python -m venv .venv
.venv/bin/pip install -r requirements.txt
.venv/bin/uvicorn app.main:app --host 127.0.0.1 --port 8719

Open:

http://127.0.0.1:8719

Internationalization

The site uses a lightweight local i18n layer:

  • server-rendered templates use app/i18n.py
  • locale files live in app/locales/en.json and app/locales/zh-CN.json
  • dynamic browser text uses static/i18n.js

English is the default language. Users can switch between English and Chinese from the UI. The selected language is saved to LocalStorage and a cookie. ?lang=en or ?lang=zh-CN can override the current language.

To add another language, create a new locale JSON file with the same key structure, add the language code to the supported language list in app/i18n.py and static/i18n.js, then expose it in the language switcher.

VPS Deployment

Default install directory:

/opt/pdf-exercise-web

Basic packages:

sudo apt install python3-venv python3-pip nginx sqlite3 libgl1 libglib2.0-0 \
  texlive-xetex texlive-lang-chinese fonts-noto-cjk

Common environment variables:

APP_HOST=127.0.0.1
APP_PORT=8719
PUBLIC_BASE_URL=https://your-domain.example
DATA_DIR=/opt/pdf-exercise-web/data
DATABASE_PATH=/opt/pdf-exercise-web/var/pdf_exercise.sqlite3
MAX_UPLOAD_MB=10
MAX_ACTIVE_JOBS=2
MAX_ACTIVE_JOBS_PER_IP=1
MAX_JOBS_PER_IP_PER_HOUR=5

For Cloudflare Full Strict HTTPS, prepare both the Origin Certificate and its private key on the VPS:

PDF_EXERCISE_DOMAIN=your-domain.example
PDF_EXERCISE_ORIGIN_CERT_FILE=/path/to/origin.pem
PDF_EXERCISE_ORIGIN_KEY_FILE=/path/to/origin.key

The key file is required. A certificate .pem alone is not enough for Nginx listen 443 ssl.

Hidden Visitor Stats

Set in .env:

VISITOR_STATS_TOKEN=generate-a-long-random-token
VISITOR_EVENT_RETENTION_DAYS=90

Open:

https://your-domain.example/internal/visitors?token=change-me

This link is not exposed in the homepage, navigation, or footer. Missing or invalid tokens return 404. The stats page uses the visitor browser timezone for “today”, 7-day trends, and event times.

IPInfo

IPINFO_TOKEN=your-ipinfo-token
IPINFO_CACHE_DAYS=30

The public homepage shows only country and AS name, not the full visitor IP. Hidden stats show country and AS name next to IPs. IPInfo failures do not block page loads or job submission.

Shared No-Key Access

SHARED_ACCESS_TOKEN=generate-a-long-random-token
SHARED_AI_PROVIDER=openai
SHARED_AI_BASE_URL=https://api.example.com/v1
SHARED_AI_API_KEY=your-private-api-key
SHARED_AI_MODEL=gpt-5.5

Authorization links use URL fragments so the token is not sent in web server logs:

https://your-domain.example/#token=generate-a-long-random-token

The browser stores the token in SessionStorage and removes it from the address bar. Shared users are exempt from the hourly 5-job limit, but they are still subject to the 1 active job per IP and 2 global active job limits.

SQLite Trial Tokens

Set in .env:

TOKEN_ADMIN_TOKEN=generate-an-independent-admin-token
TRIAL_RESERVATION_TIMEOUT_HOURS=2
TRIAL_TOKEN_DEFAULT_DAYS=7

Hidden admin page:

https://your-domain.example/internal/trial-tokens#token=generate-an-independent-admin-token

The admin token is saved only in the current tab's SessionStorage and removed from the address bar. The control page supports:

  • choosing a recent IP, entering an IPv4/IPv6 address manually, or leaving the IP empty to bind on first use
  • creating limited-use or unlimited-use trial tokens
  • batch-generating up to 100 trial links
  • setting a default 7-day expiration or no expiration
  • viewing used, reserved, max, and remaining counts
  • revoking tokens

Trial links use:

https://your-domain.example/#token=trial_xxx

SQLite stores only SHA-256 token hashes, not recoverable plaintext. Unbound tokens bind to the first IP that presents the token. Job submission first reserves quota atomically; successful PDF generation finalizes usage, while failed jobs release the reservation.

Feishu QR Code

To show a Feishu QR code above the footer, place the image at:

static/feishu-qr.png

If the image is absent, the community section is hidden.

Remote Deployment Script

deploy/remote_deploy.py is an optional helper for uploading the app over SSH and running the install process. Pass target VPS details via environment variables. The script excludes .env, cert/, data/, var/, .git/, .venv/, and Python caches.

Output Files

Each completed job can produce:

  • student_pdf: worksheet PDF without answers
  • answer_pdf: answer explanation PDF
  • worksheet_json: structured transcription data
  • transcript: Markdown transcription draft
  • token_usage: AI input/output token usage

Operations

sudo systemctl status pdf-exercise-api
sudo systemctl status pdf-exercise-worker
sudo journalctl -u pdf-exercise-api -n 100
sudo journalctl -u pdf-exercise-worker -n 100

Limits

  • The default 1GB VPS setup runs one worker and is not intended for high concurrency.
  • Image correction depends on page boundary detection; severe perspective distortion, folds, shadows, and occlusions still need manual review or retaking the photo.
  • Math PDFs prefer XeLaTeX; if TeX is unavailable, the app falls back to ReportLab and records a task event.
  • LocalStorage API Key storage has XSS risk. The project avoids third-party frontend scripts and sets a basic CSP, but public production deployments should keep tightening security policy.

About

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages