Files
cyberyan_linkedin_search/Makefile
T
mobiniqow a464e550c9 feat: LinkedIn profile search engine (Django + React + Elasticsearch + ELK)
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
2026-09-03 08:13:50 +00:00

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"