"""Application settings - 12-Factor App Factor III: Configuration."""
from pydantic import Field
from pydantic_settings import BaseSettings
[docs]class Settings(BaseSettings):
"""Centralized configuration - all config in environment variables."""
# ===================================================================
# Factor I: Codebase & Factor III: Config
# ===================================================================
env_mode: str = Field(default="development", validation_alias="ENV_MODE")
log_level: str = Field(default="INFO", validation_alias="LOG_LEVEL")
# ===================================================================
# LLM Configuration (for AI-powered features)
# ===================================================================
ollama_host: str = Field(
default="http://localhost:11434", validation_alias="OLLAMA_HOST"
)
llm_model: str = Field(default="llama3", validation_alias="LLM_MODEL")
# ===================================================================
# Factor IV: Backing Services
# ===================================================================
database_url: str = Field(
default="sqlite:///./job_agent.db", validation_alias="DATABASE_URL"
)
db_type: str = Field(default="sqlite", validation_alias="DB_TYPE")
# ===================================================================
# Notification Services
# ===================================================================
sender_email: str = Field(default="", validation_alias="SENDER_EMAIL")
sender_password: str = Field(default="", validation_alias="SENDER_PASSWORD")
recipient_email: str = Field(default="", validation_alias="RECIPIENT_EMAIL")
smtp_host: str = Field(default="smtp.gmail.com", validation_alias="SMTP_HOST")
smtp_port: int = Field(default=587, validation_alias="SMTP_PORT")
# Twilio SMS
twilio_sid: str = Field(default="", validation_alias="TWILIO_SID")
twilio_token: str = Field(default="", validation_alias="TWILIO_TOKEN")
twilio_phone: str = Field(default="", validation_alias="TWILIO_PHONE")
recipient_phone: str = Field(default="", validation_alias="RECIPIENT_PHONE")
# ===================================================================
# -- Auth Credentials (Factor III - all from env, never hardcoded) --
# ===================================================================
# LinkedIn OAuth 2.0
linkedin_client_id: str = Field(default="", validation_alias="LINKEDIN_CLIENT_ID")
linkedin_client_secret: str = Field(
default="", validation_alias="LINKEDIN_CLIENT_SECRET"
)
# Adzuna API (developer.adzuna.com - free tier available)
adzuna_app_id: str = Field(default="", validation_alias="ADZUNA_APP_ID")
adzuna_api_key: str = Field(default="", validation_alias="ADZUNA_API_KEY")
adzuna_country: str = Field(
default="us", validation_alias="ADZUNA_COUNTRY"
) # us, gb, au, ca, de, fr, in, nl, nz, pl, ru, sg, za
# Indeed Publisher API (apply at indeed.com/publisher)
indeed_publisher_id: str = Field(default="", validation_alias="INDEED_PUBLISHER_ID")
# ZipRecruiter Partner API
ziprecruiter_api_key: str = Field(
default="", validation_alias="ZIPRECRUITER_API_KEY"
)
# GitHub personal access token (optional - avoids 60 req/h limit)
github_token: str = Field(default="", validation_alias="GITHUB_TOKEN")
# The Muse (no auth required, but optional api_key lifts rate limit)
the_muse_api_key: str = Field(default="", validation_alias="THE_MUSE_API_KEY")
# ===================================================================
# Job Search - enabled sources
# ===================================================================
# Always-on free public API boards
remote_ok_enabled: bool = Field(default=True, validation_alias="REMOTE_OK_ENABLED")
remotive_enabled: bool = Field(default=True, validation_alias="REMOTIVE_ENABLED")
arbeitnow_enabled: bool = Field(
default=True, validation_alias="ARBEITNOW_ENABLED"
) # free, no auth
himalayas_enabled: bool = Field(
default=True, validation_alias="HIMALAYAS_ENABLED"
) # free, no auth
jobicy_enabled: bool = Field(
default=True, validation_alias="JOBICY_ENABLED"
) # free, no auth
the_muse_enabled: bool = Field(
default=True, validation_alias="THE_MUSE_ENABLED"
) # free, no auth
# RSS-feed boards
we_work_remotely_enabled: bool = Field(
default=True, validation_alias="WE_WORK_REMOTELY_ENABLED"
)
# API-key required boards
adzuna_enabled: bool = Field(default=False, validation_alias="ADZUNA_ENABLED")
indeed_enabled: bool = Field(default=False, validation_alias="INDEED_ENABLED")
ziprecruiter_enabled: bool = Field(
default=False, validation_alias="ZIPRECRUITER_ENABLED"
)
# OAuth 2.0 boards
linkedin_enabled: bool = Field(default=False, validation_alias="LINKEDIN_ENABLED")
# ATS aggregators (public APIs - company-specific postings)
lever_enabled: bool = Field(default=True, validation_alias="LEVER_ENABLED")
ashby_enabled: bool = Field(default=True, validation_alias="ASHBY_ENABLED")
greenhouse_enabled: bool = Field(
default=True, validation_alias="GREENHOUSE_ENABLED"
)
# Deprecated / stub only (kept for backward compat)
glassdoor_enabled: bool = Field(default=False, validation_alias="GLASSDOOR_ENABLED")
monster_enabled: bool = Field(default=False, validation_alias="MONSTER_ENABLED")
careerjet_enabled: bool = Field(default=False, validation_alias="CAREERJET_ENABLED")
jooble_enabled: bool = Field(default=False, validation_alias="JOOBLE_ENABLED")
simplyhired_enabled: bool = Field(
default=False, validation_alias="SIMPLYHIRED_ENABLED"
)
# ===================================================================
# Search behaviour
# ===================================================================
max_jobs_per_search: int = Field(default=20, validation_alias="MAX_JOBS_PER_SEARCH")
search_interval_hours: int = Field(
default=24, validation_alias="SEARCH_INTERVAL_HOURS"
)
auto_apply_enabled: bool = Field(
default=False, validation_alias="AUTO_APPLY_ENABLED"
)
# ATS company lists (comma-separated slugs)
lever_companies: str = Field(
default="stripe,airbnb,notion,figma,twilio,datadog,snowflake,cloudflare,elastic,gitlab",
validation_alias="LEVER_COMPANIES",
)
ashby_companies: str = Field(
default="linear,cal.com,resend,raycast,polymarket,loom,retool,dbt-labs",
validation_alias="ASHBY_COMPANIES",
)
greenhouse_companies: str = Field(
default="shopify,discord,reddit,intercom,dropbox,squarespace,duolingo,eventbrite,hubspot,zendesk",
validation_alias="GREENHOUSE_COMPANIES",
)
# ===================================================================
# Factor VII: Port Binding (HTTP server)
# ===================================================================
http_host: str = Field(default="0.0.0.0", validation_alias="HTTP_HOST")
http_port: int = Field(default=8080, validation_alias="HTTP_PORT")
# ===================================================================
# Factor VI: Process Configuration
# ===================================================================
config_file_path: str = Field(
default="input/search_criteria.json", validation_alias="CONFIG_FILE_PATH"
)
data_dir: str = Field(default="data", validation_alias="DATA_DIR")
[docs] class Config:
env_file = ".env"
env_file_encoding = "utf-8"
extra = "ignore" # Allow extra fields in .env
# Global settings instance
settings = Settings()
def get_settings() -> Settings:
"""Get settings instance - enables testing with overrides."""
return settings