a464e550c9
Full-stack implementation of the Cyberyan full-stack trial task. Backend (Django 5 / DRF) - Normalised PostgreSQL model (Profile, Company, School, Skill, Experience, Education) as the source of truth, with GIN + trigram indexes. - Self-healing CSV reader: the supplied export carries an injected watermark column, missing columns, rows split by broken quoting and a duplicated header. A signature-scored Needleman-Wunsch alignment reconstructs 284 clean records out of 336 physical rows; only 10 rows parse correctly without it. - Pluggable search layer (Strategy): Elasticsearch by default, PostgreSQL full-text search as a fallback selected by a single environment variable. - Keyword search with per-field boosts, fuzziness and phrase boosting, six faceted filters (OR within a filter, AND across filters), an experience range, four sort orders, highlighting and facet aggregations. - JWT auth with rotation + blacklist, three roles, PII masking, scoped rate limits, a single error envelope, OpenAPI schema and health probes. Frontend (React 18 / TypeScript / Vite) - Debounced search with type-ahead, faceted filter panel, result cards, profile drawer, URL-synced search state, automatic token refresh. - Highlight snippets rendered without dangerouslySetInnerHTML. Infrastructure - Hardened multi-stage images (non-root, read-only rootfs, healthchecks). - docker-compose for the full stack plus dev and production overlays. - ELK: structured JSON logs shipped asynchronously to Logstash, routed per event type, with a composable index template and a Kibana setup script. Quality - 61 backend tests (85% coverage), ruff/black/bandit/mypy, ESLint + tsc, GitHub Actions running lint, tests and both image builds. - Persian documentation: README plus architecture, search, API, data, security and operations guides. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GnFo6fNaHCTAkJbfcdkqtG
107 lines
3.6 KiB
Makefile
107 lines
3.6 KiB
Makefile
# =============================================================================
|
|
# Task runner. `make help` lists everything.
|
|
# =============================================================================
|
|
.DEFAULT_GOAL := help
|
|
COMPOSE := docker compose
|
|
COMPOSE_DEV := docker compose -f docker-compose.yml -f docker-compose.dev.yml
|
|
COMPOSE_PROD := docker compose -f docker-compose.yml -f docker-compose.prod.yml
|
|
|
|
.PHONY: help
|
|
help: ## Show this help
|
|
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) \
|
|
| awk 'BEGIN {FS = ":.*?## "}; {printf " \033[36m%-18s\033[0m %s\n", $$1, $$2}'
|
|
|
|
# --- Lifecycle ---------------------------------------------------------------
|
|
.PHONY: env
|
|
env: ## Create .env from the template (never overwrites an existing one)
|
|
@test -f .env || (cp .env.example .env && echo "Created .env - review the secrets before deploying.")
|
|
|
|
.PHONY: up
|
|
up: env ## Build and start the whole stack
|
|
$(COMPOSE) up --build -d
|
|
@echo "SPA http://localhost:$${FRONTEND_PORT:-8080}"
|
|
@echo "API docs http://localhost:$${FRONTEND_PORT:-8080}/api/docs/"
|
|
@echo "Kibana http://localhost:$${KIBANA_PORT:-5601}"
|
|
|
|
.PHONY: dev
|
|
dev: env ## Start the stack with hot reload (Vite on :5173, runserver on :8000)
|
|
$(COMPOSE_DEV) up --build
|
|
|
|
.PHONY: prod
|
|
prod: env ## Start with the production overlay
|
|
$(COMPOSE_PROD) up --build -d
|
|
|
|
.PHONY: down
|
|
down: ## Stop the stack (keeps volumes)
|
|
$(COMPOSE) down
|
|
|
|
.PHONY: clean
|
|
clean: ## Stop the stack and delete all volumes
|
|
$(COMPOSE) down -v --remove-orphans
|
|
|
|
.PHONY: logs
|
|
logs: ## Follow the logs of every service
|
|
$(COMPOSE) logs -f --tail=100
|
|
|
|
.PHONY: ps
|
|
ps: ## Show container status
|
|
$(COMPOSE) ps
|
|
|
|
# --- Data --------------------------------------------------------------------
|
|
.PHONY: seed
|
|
seed: ## Import the dataset and rebuild the search index
|
|
$(COMPOSE) exec backend python manage.py import_profiles --truncate --reindex
|
|
|
|
.PHONY: reindex
|
|
reindex: ## Rebuild the Elasticsearch index from PostgreSQL
|
|
$(COMPOSE) exec backend python manage.py search_index --rebuild
|
|
|
|
.PHONY: index-status
|
|
index-status: ## Show index/alias status and document count
|
|
$(COMPOSE) exec backend python manage.py search_index --status
|
|
|
|
.PHONY: users
|
|
users: ## Create the demo admin / analyst / viewer accounts
|
|
$(COMPOSE) exec backend python manage.py seed_users
|
|
|
|
.PHONY: superuser
|
|
superuser: ## Create a Django superuser interactively
|
|
$(COMPOSE) exec backend python manage.py createsuperuser
|
|
|
|
.PHONY: shell
|
|
shell: ## Open a Django shell in the API container
|
|
$(COMPOSE) exec backend python manage.py shell
|
|
|
|
.PHONY: kibana-setup
|
|
kibana-setup: ## Register the app-logs data view in Kibana
|
|
./scripts/kibana_setup.sh
|
|
|
|
# --- Quality -----------------------------------------------------------------
|
|
.PHONY: test
|
|
test: ## Run the backend test suite inside the stack
|
|
$(COMPOSE) exec -e DJANGO_SETTINGS_MODULE=config.settings.test backend pytest
|
|
|
|
.PHONY: test-local
|
|
test-local: ## Run the backend test suite against a local PostgreSQL
|
|
cd backend && pytest
|
|
|
|
.PHONY: lint
|
|
lint: ## Lint and type-check both halves of the project
|
|
cd backend && ruff check . && black --check . && mypy apps config
|
|
cd frontend && npm run lint && npm run typecheck
|
|
|
|
.PHONY: format
|
|
format: ## Auto-format the codebase
|
|
cd backend && ruff check --fix . && black .
|
|
cd frontend && npm run format
|
|
|
|
.PHONY: audit
|
|
audit: ## Dependency and static security scan
|
|
cd backend && pip-audit -r requirements/base.txt && bandit -q -r apps config
|
|
cd frontend && npm audit --omit=dev
|
|
|
|
.PHONY: schema
|
|
schema: ## Write the OpenAPI schema to docs/openapi.yaml
|
|
$(COMPOSE) exec backend python manage.py spectacular --api-version v1 > docs/openapi.yaml
|
|
@echo "Wrote docs/openapi.yaml"
|