-
Notifications
You must be signed in to change notification settings - Fork 0
Feat/azure native migration #4
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Aparnap2
merged 22 commits into
agent/invoicify-ai-service
from
feat/azure-native-migration
Mar 7, 2026
Merged
Changes from all commits
Commits
Show all changes
22 commits
Select commit
Hold shift + click to select a range
5318246
docs: Add comprehensive Azure deployment guides
Aparnap2 c995331
docs: Add Azure deployment to README quick start
Aparnap2 94b7a4d
feat: Add complete Azure deployment infrastructure
Aparnap2 0fbc332
feat(azure-migration): Replace Cloudflare primitives with Azure equiv…
Aparnap2 e1778ec
feat: Wire Azure Queue consumer into FastAPI + update worker deps
Aparnap2 e747171
docs: Update README and DEPLOY with current architecture
Aparnap2 d1ef0ad
docs: Update PRD and add ARCHITECTURE.md
Aparnap2 6d3d059
docs: Remove outdated documentation
Aparnap2 5386a7e
feat: implement AP workflow state machine with LangGraph
Aparnap2 1c9655a
fix: correct test imports and syntax errors
Aparnap2 c8a0736
docs: Add comprehensive implementation summary
Aparnap2 45ba30d
feat: Add production E2E test with real Azure + Docker
Aparnap2 8129e46
chore: PHASE 1+2 - Remove voice-agent + migrate to direct Postgres
Aparnap2 2c11c7f
chore: PHASE 4 - remove legacy edge-api + azure-functions + edge_call…
Aparnap2 4220811
chore: remove orphaned salesforce test file (SF replaced by HubSpot)
Aparnap2 217905a
chore: delete orphaned salesforce_mcp.py (replaced by hubspot_mcp.py)
Aparnap2 362f9bd
fix: MCP server tool registration (Server → FastMCP) + HubSpot integr…
Aparnap2 d2090c8
security: Remove all .secrets/ from git tracking (qb_tokens.json)
Aparnap2 8af5e33
docs: Update all documentation with HubSpot integration + better Merm…
Aparnap2 1fc45af
security: Remove qb_tokens.json from git (accidentally added)
Aparnap2 21e612c
fix: Address all code review issues
Aparnap2 25ecc98
security: Remove qb_tokens.json from git (accidentally added again)
Aparnap2 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,97 @@ | ||
| # .env.azure.example | ||
| # Copy to .env and fill in values for local development against Azure services. | ||
| # In production, these are injected as Azure Container Apps secrets (not .env files). | ||
| # | ||
| # NEVER commit real values. This file is for documentation only. | ||
|
|
||
| # ── LLM (OpenRouter free tier) ──────────────────────────────────────────────── | ||
| # Get key at: https://openrouter.ai/keys | ||
| OPENAI_API_KEY=sk-or-v1-... | ||
| OPENAI_BASE_URL=https://openrouter.ai/api/v1 | ||
| LLM_MODEL=z-ai/glm-4.5-air:free | ||
|
|
||
| # Groq (for fast JSON extraction step) | ||
| # Get key at: https://console.groq.com | ||
| GROQ_API_KEY=gsk_... | ||
|
|
||
| # ── Extractor mode ──────────────────────────────────────────────────────────── | ||
| # Options: fixture | azure_di | ollama | sarvam | ||
| # Use 'fixture' for local dev without Azure keys | ||
| EXTRACTOR_MODE=fixture | ||
|
|
||
| # ── Azure Document Intelligence (F0 free tier: 500 pages/month) ─────────────── | ||
| # Create at: https://portal.azure.com → Document Intelligence | ||
| AZURE_DOCUMENT_INTELLIGENCE_ENDPOINT=https://your-instance.cognitiveservices.azure.com/ | ||
| AZURE_DOCUMENT_INTELLIGENCE_KEY=your-key-here | ||
|
|
||
| # ── Azure Storage (5 GB free LRS) ───────────────────────────────────────────── | ||
| # Create at: https://portal.azure.com → Storage Accounts | ||
| AZURE_STORAGE_CONNECTION_STRING=DefaultEndpointsProtocol=https;AccountName=...;AccountKey=...;EndpointSuffix=core.windows.net | ||
| AZURE_STORAGE_CONTAINER=invoices | ||
|
|
||
| # ── Azure Storage Queues (free, no limits) ──────────────────────────────────── | ||
| # Same storage account — no extra resource needed | ||
| AZURE_QUEUE_NAME=invoice-processing | ||
| AZURE_DLQ_NAME=invoice-dlq | ||
|
|
||
| # ── Azure AI Search (F (free) tier: 50 MB, 3 indexes) ──────────────────────── | ||
| # Create at: https://portal.azure.com → AI Search | ||
| AZURE_SEARCH_ENDPOINT=https://your-search.search.windows.net | ||
| AZURE_SEARCH_KEY=your-admin-key | ||
| AZURE_SEARCH_INDEX=invoices | ||
|
|
||
| # ── PostgreSQL (local dev) ──────────────────────────────────────────────────── | ||
| # In production: Azure Flexible Server B1MS | ||
| DATABASE_URL=postgresql://invoicify:password@localhost:5432/invoicify | ||
| CHECKPOINTER_URL=postgresql://invoicify:password@localhost:5432/invoicify | ||
|
|
||
| # ── Internal service URLs ───────────────────────────────────────────────────── | ||
| # worker calls agent-core for AI processing | ||
| AGENT_CORE_URL=http://localhost:8001 | ||
| # agent-core calls worker for status callbacks | ||
| EDGE_API_BASE_URL=http://localhost:8787 | ||
|
|
||
| # ── Observability (Langfuse cloud free: 50k events/month) ──────────────────── | ||
| # Get keys at: https://cloud.langfuse.com | ||
| LANGFUSE_PUBLIC_KEY=pk-lf-... | ||
| LANGFUSE_SECRET_KEY=sk-lf-... | ||
| LANGFUSE_HOST=https://cloud.langfuse.com | ||
|
|
||
| # ── Integrations ───────────────────────────────────────────────────────────── | ||
| SLACK_BOT_TOKEN=xoxb-... | ||
| SLACK_SIGNING_SECRET=... | ||
|
|
||
| # ═══════════════════════════════════════════════════════════════ | ||
| # ERP Integrations (QuickBooks + HubSpot CRM) | ||
| # ═══════════════════════════════════════════════════════════════ | ||
|
|
||
| # QuickBooks Online (Sandbox) | ||
| # Get tokens: https://developer.intuit.com/app/developer/playground | ||
| QB_CLIENT_ID=your_client_id | ||
| QB_CLIENT_SECRET=your_client_secret | ||
| QB_REALM_ID=4620816365162546440 # sandbox company ID | ||
| QB_REFRESH_TOKEN=your_refresh_token # from one-time OAuth flow | ||
| QB_SANDBOX=true | ||
|
|
||
| # ═══════════════════════════════════════════════════════════════ | ||
| # HubSpot CRM (Free - replaces Salesforce) | ||
| # ═══════════════════════════════════════════════════════════════ | ||
|
|
||
| # HubSpot Private App API token (replaces Salesforce JWT) | ||
| # Get token: app.hubspot.com → Settings → Integrations → Private Apps | ||
| # 1. Create private app named "Invoicify" | ||
| # 2. Select scopes: crm.objects.deals.*, crm.objects.companies.* | ||
| # 3. Copy token (starts with pat-na1-...) | ||
| # 4. Token never expires unless manually revoked | ||
| HUBSPOT_API_KEY=pat-na1-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx | ||
|
|
||
| # ── Environment ─────────────────────────────────────────────────────────────── | ||
| ENVIRONMENT=development | ||
| LOG_LEVEL=INFO | ||
| STRATEGY_MODE=OPTIMIZE | ||
|
|
||
| # ── Redis (Azure Cache for Redis) ───────────────────────────────────────────── | ||
| # Used for OAuth token store in stateless containerized environments | ||
| # Create at: https://portal.azure.com → Cache for Redis (C0 free tier available) | ||
| # Connection string format: rediss://:access-key@hostname:port | ||
| REDIS_URL=rediss://:your-redis-key@your-redis-host:6380 | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,170 @@ | ||
| # .github/workflows/azure-deploy.yml | ||
| # Deploys the Invoicify monorepo to Azure free tier. | ||
| # | ||
| # Services deployed: | ||
| # invoicify-worker → Azure Container Apps (Node 20, replaces Cloudflare Worker) | ||
| # invoicify-api → Azure Container Apps (Python 3.11 agent-core) | ||
| # apps/web → Azure Static Web Apps (Next.js) | ||
| # | ||
| # Triggers: push to feat/azure-native-migration or main | ||
| # Required GitHub Secrets: | ||
| # AZURE_CLIENT_ID (federated identity, no password needed) | ||
| # AZURE_TENANT_ID | ||
| # AZURE_SUBSCRIPTION_ID | ||
| # AZURE_STATIC_WEB_APPS_TOKEN | ||
| # REGISTRY_NAME (e.g. invoicifyregistry) | ||
| # RESOURCE_GROUP (e.g. invoicify-rg) | ||
|
|
||
| name: Deploy to Azure | ||
|
|
||
| on: | ||
| push: | ||
| branches: | ||
| - main | ||
| - feat/azure-native-migration | ||
| pull_request: | ||
| types: [opened, synchronize, reopened, closed] | ||
| branches: | ||
| - main | ||
|
|
||
| permissions: | ||
| id-token: write # OIDC federated auth — no password secrets needed | ||
| contents: read | ||
|
|
||
| jobs: | ||
| # ───────────────────────────────────────────────────────────────────────── | ||
| # Job 1: Build and deploy Hono worker (replaces Cloudflare Worker) | ||
| # ───────────────────────────────────────────────────────────────────────── | ||
| deploy-worker: | ||
| name: Deploy invoicify-worker → Container Apps | ||
| runs-on: ubuntu-latest | ||
| if: github.event_name == 'push' | ||
|
|
||
| steps: | ||
| - uses: actions/checkout@v4 | ||
|
|
||
| - name: Azure login (OIDC) | ||
| uses: azure/login@v2 | ||
| with: | ||
| client-id: ${{ secrets.AZURE_CLIENT_ID }} | ||
| tenant-id: ${{ secrets.AZURE_TENANT_ID }} | ||
| subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} | ||
|
|
||
| - name: Build worker image | ||
| working-directory: invoicify-worker | ||
| run: | | ||
| az acr login --name ${{ secrets.REGISTRY_NAME }} | ||
| docker build \ | ||
| -t ${{ secrets.REGISTRY_NAME }}.azurecr.io/invoicify-worker:${{ github.sha }} \ | ||
| -t ${{ secrets.REGISTRY_NAME }}.azurecr.io/invoicify-worker:latest \ | ||
| . | ||
| docker push ${{ secrets.REGISTRY_NAME }}.azurecr.io/invoicify-worker:${{ github.sha }} | ||
| docker push ${{ secrets.REGISTRY_NAME }}.azurecr.io/invoicify-worker:latest | ||
|
|
||
| - name: Deploy worker to Container Apps | ||
| run: | | ||
| az containerapp update \ | ||
| --name invoicify-worker \ | ||
| --resource-group ${{ secrets.RESOURCE_GROUP }} \ | ||
| --image ${{ secrets.REGISTRY_NAME }}.azurecr.io/invoicify-worker:${{ github.sha }} \ | ||
| --set-env-vars \ | ||
| ENVIRONMENT=production \ | ||
| PORT=8787 \ | ||
| DATABASE_URL=secretref:database-url \ | ||
| AZURE_STORAGE_CONNECTION_STRING=secretref:storage-connection-string \ | ||
| OPENAI_API_KEY=secretref:openai-api-key \ | ||
| GROQ_API_KEY=secretref:groq-api-key \ | ||
| AGENT_CORE_URL=secretref:agent-core-url | ||
|
|
||
| # ───────────────────────────────────────────────────────────────────────── | ||
| # Job 2: Build and deploy Python agent-core | ||
| # ───────────────────────────────────────────────────────────────────────── | ||
| deploy-agent-core: | ||
| name: Deploy agent-core → Container Apps | ||
| runs-on: ubuntu-latest | ||
| if: github.event_name == 'push' | ||
|
|
||
| steps: | ||
| - uses: actions/checkout@v4 | ||
|
|
||
| - name: Azure login (OIDC) | ||
| uses: azure/login@v2 | ||
| with: | ||
| client-id: ${{ secrets.AZURE_CLIENT_ID }} | ||
| tenant-id: ${{ secrets.AZURE_TENANT_ID }} | ||
| subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }} | ||
|
|
||
| - name: Build agent-core image | ||
| working-directory: apps/agent-core | ||
| run: | | ||
| az acr login --name ${{ secrets.REGISTRY_NAME }} | ||
| docker build \ | ||
| -t ${{ secrets.REGISTRY_NAME }}.azurecr.io/agent-core:${{ github.sha }} \ | ||
| -t ${{ secrets.REGISTRY_NAME }}.azurecr.io/agent-core:latest \ | ||
| . | ||
| docker push ${{ secrets.REGISTRY_NAME }}.azurecr.io/agent-core:${{ github.sha }} | ||
| docker push ${{ secrets.REGISTRY_NAME }}.azurecr.io/agent-core:latest | ||
|
|
||
| - name: Deploy agent-core to Container Apps | ||
| run: | | ||
| az containerapp update \ | ||
| --name invoicify-api \ | ||
| --resource-group ${{ secrets.RESOURCE_GROUP }} \ | ||
| --image ${{ secrets.REGISTRY_NAME }}.azurecr.io/agent-core:${{ github.sha }} \ | ||
| --set-env-vars \ | ||
| ENVIRONMENT=production \ | ||
| EXTRACTOR_MODE=azure_di \ | ||
| DATABASE_URL=secretref:database-url \ | ||
| CHECKPOINTER_URL=secretref:database-url \ | ||
| AZURE_STORAGE_CONNECTION_STRING=secretref:storage-connection-string \ | ||
| AZURE_DOCUMENT_INTELLIGENCE_ENDPOINT=secretref:adi-endpoint \ | ||
| AZURE_DOCUMENT_INTELLIGENCE_KEY=secretref:adi-key \ | ||
| AZURE_SEARCH_ENDPOINT=secretref:search-endpoint \ | ||
| AZURE_SEARCH_KEY=secretref:search-key \ | ||
| OPENAI_API_KEY=secretref:openai-api-key \ | ||
| GROQ_API_KEY=secretref:groq-api-key \ | ||
| LANGFUSE_PUBLIC_KEY=secretref:langfuse-public-key \ | ||
| LANGFUSE_SECRET_KEY=secretref:langfuse-secret-key | ||
|
|
||
| # ───────────────────────────────────────────────────────────────────────── | ||
| # Job 3: Deploy Next.js frontend to Static Web Apps | ||
| # ───────────────────────────────────────────────────────────────────────── | ||
| deploy-web: | ||
| name: Deploy apps/web → Static Web Apps | ||
| runs-on: ubuntu-latest | ||
| if: | | ||
| github.event_name == 'push' || | ||
| (github.event_name == 'pull_request' && github.event.action != 'closed') | ||
|
|
||
| steps: | ||
| - uses: actions/checkout@v4 | ||
| with: | ||
| submodules: true | ||
|
|
||
| - name: Deploy to Azure Static Web Apps | ||
| uses: Azure/static-web-apps-deploy@v1 | ||
| with: | ||
| azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_TOKEN }} | ||
| repo_token: ${{ secrets.GITHUB_TOKEN }} | ||
| action: upload | ||
| app_location: /apps/web | ||
| api_location: '' | ||
| output_location: .next | ||
| env: | ||
| NEXT_PUBLIC_API_URL: ${{ vars.API_URL }} | ||
| NEXT_PUBLIC_WORKER_URL: ${{ vars.WORKER_URL }} | ||
|
|
||
| # ───────────────────────────────────────────────────────────────────────── | ||
| # Job 4: Close preview environment on PR close | ||
| # ───────────────────────────────────────────────────────────────────────── | ||
| close-preview: | ||
| name: Close Static Web Apps preview | ||
| runs-on: ubuntu-latest | ||
| if: github.event_name == 'pull_request' && github.event.action == 'closed' | ||
|
|
||
| steps: | ||
| - uses: actions/checkout@v4 | ||
| - uses: Azure/static-web-apps-deploy@v1 | ||
| with: | ||
| azure_static_web_apps_api_token: ${{ secrets.AZURE_STATIC_WEB_APPS_TOKEN }} | ||
| action: close |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The environment variable
EXTRACTOR_MODEis defined here and again on line 91. This redundancy can be confusing as the last one will take precedence. To improve clarity and avoid potential configuration errors, please remove one of the definitions to have a single source of truth for this setting.