Files
TX/artifacts/api-server/package.json
T
Replit Agent 56c8bcfb85 Add download for protocol visit photos (Task #660)
Users could upload photos into Protocol "تصوير" albums but had no way to
download them — clicking a photo only opened it inline. This adds per-photo
downloads and a whole-album ZIP download.

Backend:
- objectStorage.ts: new exported extensionForContentType() mapping content
  types to file extensions (stored paths are extension-less UUIDs).
- storage.ts: opt-in ?download=1 mode on GET /storage/objects/*path sets
  Content-Disposition: attachment (with optional ?name base + derived ext);
  default inline behavior and per-entity authz unchanged.
- protocol.ts: new GET /protocol/photo-albums/:id/download streams all album
  photos as a ZIP (guarded by requireProtocolAccess), named from the album;
  unreadable objects are skipped rather than failing the archive. Empty album
  returns 404.
- Added archiver@8 dependency (+@types/archiver dev).

Frontend (protocol.tsx):
- Per-photo download button (attachment, name = album-index) in album detail.
- "تحميل الكل" / "Download all" ZIP button in album header (hidden when empty).
- Added photoDownloadSrc/albumDownloadSrc helpers, Download icon.
- AR/EN locale strings protocol.photos.download / downloadAll.

Notes / deviations:
- archiver v8 is ESM-only with no callable default export; used
  `new ZipArchive(...)` instead of `archiver("zip")`. Recorded in memory.
- Could not run authenticated e2e (seed admin password is a secret). Verified:
  api-server typecheck + build pass, tx-os typecheck passes, server healthy,
  new routes wired (401 auth-required not 404). Pre-existing typecheck errors
  in executive-meetings.ts / push.ts are unrelated and untouched.
2026-07-07 09:34:06 +00:00

67 lines
2.2 KiB
JSON

{
"name": "@workspace/api-server",
"version": "0.0.0",
"private": true,
"type": "module",
"scripts": {
"dev": "export NODE_ENV=development && pnpm run build && pnpm run start",
"build": "node ./build.mjs",
"start": "node --enable-source-maps ./dist/index.mjs",
"typecheck": "tsc -p tsconfig.json --noEmit",
"test:wait": "node ./scripts/wait-for-server.mjs",
"test:run": "node --test 'tests/*.test.mjs' && node --import tsx --test --experimental-test-module-mocks 'tests/*.test.ts' && node --test --test-concurrency=1 'tests/serial/**/*.test.mjs'",
"test": "node ./scripts/with-server.mjs -- pnpm run test:run",
"migrate": "pnpm --filter @workspace/db run push && pnpm --filter scripts run seed"
},
"dependencies": {
"@aws-sdk/client-s3": "^3.658.0",
"@aws-sdk/s3-request-presigner": "^3.658.0",
"@swc/helpers": "^0.5.21",
"@workspace/api-zod": "workspace:*",
"@workspace/db": "workspace:*",
"arabic-persian-reshaper": "1.0.1",
"archiver": "^8.0.0",
"bcryptjs": "^3.0.3",
"bidi-js": "^1.0.3",
"connect-pg-simple": "^10.0.0",
"cookie-parser": "^1.4.7",
"cors": "^2",
"drizzle-orm": "catalog:",
"express": "^5",
"express-rate-limit": "^8.5.1",
"express-session": "^1.19.0",
"nodemailer": "^8.0.7",
"pdfkit": "^0.18.0",
"pino": "^9",
"pino-http": "^10",
"playwright-core": "^1.59.1",
"sanitize-html": "^2.17.3",
"semver": "^7.6.3",
"socket.io": "^4.8.3",
"web-push": "^3.6.7",
"zod": "catalog:"
},
"devDependencies": {
"@types/archiver": "^8.0.0",
"@types/bcryptjs": "^3.0.0",
"@types/connect-pg-simple": "^7.0.3",
"@types/cookie-parser": "^1.4.10",
"@types/cors": "^2.8.19",
"@types/express": "^5.0.6",
"@types/express-session": "^1.19.0",
"@types/node": "catalog:",
"@types/nodemailer": "^8.0.0",
"@types/pdfkit": "^0.17.6",
"@types/sanitize-html": "^2.16.1",
"@types/semver": "^7.5.8",
"@types/web-push": "^3.6.4",
"esbuild": "^0.27.3",
"esbuild-plugin-pino": "^2.3.3",
"pg": "^8.20.0",
"pino-pretty": "^13",
"socket.io-client": "^4.8.3",
"thread-stream": "3.1.0",
"tsx": "catalog:"
}
}