Phoenix-powered orchestrator for intelligent job applications
The single source of truth for database schema, HH.ru integration, and real-time job orchestration
UllGetTheJob Core is the backend orchestrator that powers the entire job application automation system. Built with Phoenix and Elixir, it provides:
- ποΈ Database Schema Management - Ecto migrations and PostgreSQL integration
- π HH.ru OAuth Flow - Complete authentication with token management
- π Job Fetching Orchestrator - Periodic job search with rate limiting
- π‘ WebSocket Broadcasting - Real-time job updates to connected clients
- π― Application Submission - HH.ru API integration for resume operations
- β‘ Rate Limiting - Token bucket algorithm for API quota management
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Phoenix Core β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€
β ββββββββββββββββ ββββββββββββββββ ββββββββββββββββ β
β β HH.ru API β β Rate Limiter β β Job Fetcher β β
β β Client β β (GenServer) β β(Orchestrator)β β
β ββββββββββββββββ ββββββββββββββββ ββββββββββββββββ β
β β β β β
β ββββββββββββββββββββ΄βββββββββββββββββββ β
β β β
β ββββββββββΌβββββββββ β
β β Ecto + Postgres β β
β βββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
βββββββββββββ΄ββββββββββββ
β β
REST API (Node BFF) WebSocket (Broadcast)
- Elixir ~> 1.15
- Erlang ~> 26
- PostgreSQL 14+
- Mix (comes with Elixir)
# Clone the repository
git clone <repo-url>
cd ullgetthejob-core
# Install dependencies
mix deps.get
# Setup database (create, migrate, seed)
mix setup
# Start Phoenix server
mix phx.serverServer runs at http://localhost:4000 π
# Start with interactive shell
iex -S mix phx.server
# Useful IEx commands:
iex> Core.HH.Client.fetch_vacancies(%{text: "Elixir"})
iex> Core.Jobs.Orchestrator.fetch_jobs_now("user_id")
iex> Core.RateLimiter.get_status("user_id")Create a .env file or set these in your environment:
# Database
DATABASE_URL="postgresql://postgres:postgres@localhost:5432/ullget_dev"
# HH.ru API
HH_ACCESS_TOKEN="your_hh_api_token"
HH_CLIENT_ID="your_oauth_client_id"
HH_CLIENT_SECRET="your_oauth_client_secret"
HH_REDIRECT_URI="http://localhost:5173/auth/callback"
# Security
ORCHESTRATOR_SECRET="shared_secret_between_core_and_api"
SECRET_KEY_BASE="generate_with_mix_phx_gen_secret"
# External Services
API_BASE_URL="http://localhost:3000" # Node BFF URL# Generate SECRET_KEY_BASE
mix phx.gen.secret
# Generate ORCHESTRATOR_SECRET
mix phx.gen.secret 32POST /api/jobs/search
Content-Type: application/json
X-Core-Secret: <orchestrator_secret>
{
"text": "JavaScript Developer",
"area": "1",
"experience": "between1And3",
"employment": "full",
"schedule": "remote"
}POST /api/applications/submit
Content-Type: application/json
X-Core-Secret: <orchestrator_secret>
{
"user_id": "uuid",
"job_external_id": "hh_vacancy_id",
"customized_cv": { /* parsed CV object */ },
"cover_letter": "Dear Hiring Manager..."
}GET /auth/hh/redirect
Response:
{
"url": "https://hh.ru/oauth/authorize?...",
"state": "random_state_token"
}GET /auth/hh/callback?code=AUTH_CODE
Response:
{
"success": true,
"tokens": {
"access_token": "...",
"refresh_token": "...",
"expires_at": "2025-10-15T12:00:00Z"
}
}POST /auth/hh/refresh
Content-Type: application/json
{
"refresh_token": "existing_refresh_token"
}# List user's resumes
GET /api/hh/resumes
# Get resume details
GET /api/hh/resumes/:resume_idGET /api/v1/system/health
Response:
{
"status": "ok",
"db": "up"
}-- HH.ru OAuth tokens (MVP: plaintext, add encryption later)
hh_tokens (
id uuid PRIMARY KEY,
user_id uuid,
access_token text NOT NULL,
refresh_token text,
expires_at timestamp NOT NULL
)
-- Jobs fetched from HH.ru
jobs (
id uuid PRIMARY KEY,
external_id varchar UNIQUE NOT NULL,
title varchar NOT NULL,
company varchar,
salary varchar,
area varchar,
url text,
description text,
hh_vacancy_id varchar,
has_test boolean DEFAULT false,
skills varchar[]
)
-- Parsed CVs (managed by API service)
parsed_cvs (
id uuid PRIMARY KEY,
user_id uuid,
first_name varchar,
last_name varchar,
email varchar,
phone varchar,
title varchar,
summary text,
experience text,
education text,
skills varchar[],
...
)# Create new migration
mix ecto.gen.migration add_new_table
# Run migrations
mix ecto.migrate
# Rollback last migration
mix ecto.rollback
# Reset database
mix ecto.resetAutomatically fetches jobs from HH.ru based on scheduled searches:
# Schedule periodic job fetching for a user
Core.Jobs.Orchestrator.schedule_job_fetch(
"user_id",
%{text: "Elixir Developer", area: "1"},
1_800_000 # 30 minutes
)
# Manually trigger fetch
Core.Jobs.Orchestrator.fetch_jobs_now("user_id")
# View active schedules
Core.Jobs.Orchestrator.get_schedules()Token bucket implementation for HH.ru API compliance:
# Check if action is allowed
Core.RateLimiter.check_rate_limit("user_id", :application)
# => {:ok, 19} | {:error, :rate_limited, next_refill_time}
# Get current status
Core.RateLimiter.get_status("user_id")
# => %{tokens: 20, capacity: 20, refill_rate: 8, last_refill: ...}
# Reset limit (admin only)
Core.RateLimiter.reset_limit("user_id")Configuration:
- Capacity: 20 tokens
- Refill rate: 8 tokens/hour
- HH.ru limit: ~200 applications/day
Access Phoenix LiveDashboard (development only):
http://localhost:4000/dev/dashboard
Features:
- Real-time metrics
- Process inspection
- Ecto query analysis
- Request logging
Core emits telemetry events for:
phoenix.router_dispatch.*- Request handlingcore.repo.query.*- Database operations- Custom job orchestrator events
# Run all tests
mix test
# Run with coverage
mix test --cover
# Run specific test file
mix test test/core/hh/client_test.exs
# Run tests matching pattern
mix test --only hh_api- OAuth tokens stored as plaintext
- No user authentication/authorization
- Rate limiting per
user_idwithout validation
- Encrypt sensitive fields (tokens, secrets)
- Implement proper authentication (Guardian, Pow)
- Add request signing for API endpoints
- Enable HTTPS/TLS
- Implement CSRF protection
- Add audit logging
- Set up monitoring/alerting
lib/
βββ core/
β βββ application.ex # OTP Application
β βββ repo.ex # Ecto Repository
β βββ hh/
β β βββ client.ex # HH.ru API client
β β βββ oauth.ex # Token management
β β βββ token.ex # Token schema
β βββ jobs/
β β βββ orchestrator.ex # Job fetching orchestrator
β βββ rate_limiter.ex # Rate limiting GenServer
β βββ broadcaster.ex # WebSocket broadcast helper
βββ core_web/
β βββ endpoint.ex # Phoenix Endpoint
β βββ router.ex # Route definitions
β βββ controllers/
β β βββ auth_controller.ex # OAuth endpoints
β β βββ hh_controller.ex # Resume operations
β β βββ api/
β β βββ job_controller.ex
β β βββ application_controller.ex
β βββ telemetry.ex # Metrics
priv/
βββ repo/
βββ migrations/ # Database migrations
Core acts as the data layer and HH.ru proxy for the Node BFF:
Node BFF β [X-Core-Secret] β Core β HH.ru API
β
PostgreSQL
Frontend connects to Node BFF, which proxies to Core:
Frontend β Node BFF β Core β HH.ru
β β
WebSocket DB
MIT License Β© 2025 Aleksandr Sakhatskiy
Because job hunting should be automated