Medical clinic website for Ultramed, Azerbaijan.
- Frontend: Next.js (Apps/Web)
- Backend: NestJS (Apps/API)
- Database: MySQL with Prisma (Packages/Database)
- Styling: Tailwind CSS & Shadcn UI
- Languages: Azerbaijani, English, Russian (i18n)
- DevOps: Docker & Docker Compose
apps/web: Next.js frontendapps/api: NestJS backendpackages/database: Shared Prisma client and schemapackages/ui: Shared UI components (Radix + Tailwind)
- Install dependencies:
npm install - Configure environment: Copy
.env.exampleto.env(once created) - Run development:
npm run dev
This project includes a safe media lifecycle flow for uploaded files.
When admins upload images, two things are created:
- a physical file under
/uploads - a
Mediarow in the database
Over time, some media records become unreferenced (orphan media).
Without cleanup, storage grows and management becomes harder.
Implemented in:
apps/api/src/modules/admin/admin.service.tsapps/api/src/modules/admin/admin.controller.tsapps/api/src/modules/admin/admin.constants.ts
Key points:
GET /admin/medianow returns usage counts (services,doctors,blogPosts,galleryItems,total) andisOrphan.DELETE /admin/media/:idis protected:- if media is still referenced, it returns
409 MEDIA_IN_USE.
- if media is still referenced, it returns
POST /admin/media/cleanup-orphanssupports safe batch cleanup.- Cleanup is dry-run by default (if
dryRunis not provided, no delete happens). - Cleanup supports:
limitolderThanHoursdryRun
Default limits:
DEFAULT_MEDIA_CLEANUP_GRACE_HOURS = 24DEFAULT_MEDIA_CLEANUP_BATCH_LIMIT = 100
- List all media:
GET /admin/media?limit=100
- List orphan-only media:
GET /admin/media?orphansOnly=true&olderThanHours=24&limit=100
- Dry-run cleanup:
POST /admin/media/cleanup-orphans?dryRun=true&olderThanHours=24&limit=100
- Execute cleanup:
POST /admin/media/cleanup-orphans?dryRun=false&olderThanHours=24&limit=100
UI route:
/{locale}/admin/media
Examples:/az/admin/media/en/admin/media/ru/admin/media
Implemented in:
apps/web/src/app/[locale]/admin/media/page.tsxapps/web/src/components/admin/Sidebar.tsxapps/web/src/lib/admin-api.ts
How to use:
- Open Admin > Media from the sidebar.
- Set filters:
LimitOlder than hoursOrphans only(optional)
- Click Dry-run first and review the candidate list.
- If the output is correct, click Cleanup başlat to execute deletion.
- Use Refresh to reload current media state.
Safety notes:
- Single-item delete is disabled for in-use media in UI and also enforced by backend.
- Cleanup can skip records that become referenced during execution (
skipped_in_use). - Cleanup output includes file deletion states:
deleteddeleted_file_missingdeleted_file_error