Staging to Production #51
@@ -35,7 +35,7 @@ This applies to both the staging and production repos as appropriate.
|
||||
- Entity Framework Core + SQL Server (multi-schema)
|
||||
- Refit for typed HTTP clients between services
|
||||
- Serilog (JSON structured logging, Console + File + Email sinks)
|
||||
- MailKit for SMTP
|
||||
- MailKit for SMTP (used exclusively in `email-api`)
|
||||
- Docker Compose for local and production deployment
|
||||
- Watchtower for automatic container updates in production
|
||||
|
||||
@@ -63,6 +63,8 @@ This applies to both the staging and production repos as appropriate.
|
||||
Apis/
|
||||
api/ Public-facing proxy API (port 8080). Handles CORS, rate limiting, captcha, email.
|
||||
api-models/ DTOs and settings for api only.
|
||||
email-api/ Internal SMTP email relay (no public port). All email sending goes here.
|
||||
email-api-models/ Refit client + SendEmailRequest + EmailApiSettings (shared by api and cv-search-job).
|
||||
cv-matcher-api/ Internal CV match engine (port 8082). Runs CvMatcher + CvSearch + MyAi DB migrations.
|
||||
cv-matcher-api-models/ DTOs shared between api and cv-matcher-api (incl. JobSearchSettings).
|
||||
rag-api/ Internal RAG/vector-search service (port 8081).
|
||||
@@ -148,26 +150,36 @@ EF tools version warning ("older than runtime") is expected and harmless. The `H
|
||||
|
||||
```
|
||||
web → api → cv-matcher-api → rag-api
|
||||
↑
|
||||
cv-search-job
|
||||
↓ ↓
|
||||
| email-api
|
||||
↓ ↑
|
||||
cv-search-job
|
||||
```
|
||||
|
||||
`api` and `cv-search-job` both call `email-api` for all outbound email (SMTP).
|
||||
`api` never talks directly to `rag-api` — always via `cv-matcher-api`.
|
||||
|
||||
## Internal API key auth
|
||||
|
||||
All internal service-to-service calls require the `X-Internal-Api-Key` header.
|
||||
The key is shared via the `CvMatcherApi__InternalApiKey` and `RagApi__InternalApiKey` env vars.
|
||||
`startup-helpers` provides `UseInternalApiKeyProtection()` middleware that enforces it on `cv-matcher-api` and `rag-api`.
|
||||
All internal service-to-service calls require the `X-Internal-Api-Key` header.
|
||||
|
||||
| Caller | Target | Env var for key |
|
||||
|--------|--------|-----------------|
|
||||
| `api`, `cv-search-job` | `email-api` | `EmailApi__InternalApiKey` |
|
||||
| `api`, `cv-search-job` | `cv-matcher-api` | `CvMatcherApi__InternalApiKey` |
|
||||
| `cv-matcher-api` | `rag-api` | `RagApi__InternalApiKey` |
|
||||
|
||||
`startup-helpers` provides `UseInternalApiKeyProtection()` middleware (reads `InternalApi:ApiKey`); enforced on `cv-matcher-api`, `rag-api`, and `email-api`.
|
||||
|
||||
## Shared file storage
|
||||
|
||||
CV PDFs are written by `api` to `Apis/api/Files/` and read by `cv-cleanup-job` and `cv-search-job`.
|
||||
All three containers mount the same bind volume:
|
||||
CV PDFs are written by `api` to `Apis/api/Files/` and read by `cv-cleanup-job`, `cv-search-job`, and `email-api` (for email attachments).
|
||||
All four containers mount the same bind volume:
|
||||
```yaml
|
||||
- ../Apis/api/Files:/app/Files
|
||||
- ${FILES_PATH:-/opt/myai/files}:/app/Files
|
||||
```
|
||||
The path inside containers is controlled by `FileStorage__Path` (default: `Files`).
|
||||
The path inside containers is controlled by `FileStorage__Path` (default: `Files`).
|
||||
`email-api` receives only the relative filename (e.g. `abc123.pdf`) and resolves it against `FileStorage__Path`.
|
||||
|
||||
## Job task pattern
|
||||
|
||||
|
||||
@@ -100,11 +100,47 @@ services:
|
||||
labels:
|
||||
- "com.centurylinklabs.watchtower.enable=true"
|
||||
|
||||
email-api:
|
||||
image: registry.easysoft.ro/apps/myai-email-api:${IMAGE_TAG:-staging}
|
||||
container_name: myai-email-api
|
||||
environment:
|
||||
- ASPNETCORE_ENVIRONMENT=${ASPNETCORE_ENVIRONMENT:-Staging}
|
||||
- ASPNETCORE_URLS=${ASPNETCORE_URLS:-http://+:8080}
|
||||
- APP_ENVIRONMENT_NAME=${APP_ENVIRONMENT_NAME:-myai.staging}
|
||||
|
||||
- InternalApi__ApiKey=${EmailApi__InternalApiKey:-}
|
||||
- InternalApi__RequireApiKey=true
|
||||
|
||||
- Smtp__Host=${Smtp__Host:-}
|
||||
- Smtp__Port=${Smtp__Port:-587}
|
||||
- Smtp__Username=${Smtp__Username:-}
|
||||
- Smtp__Password=${Smtp__Password:-}
|
||||
- Smtp__UseStartTls=${Smtp__UseStartTls:-false}
|
||||
|
||||
- FileStorage__Path=${FileStorage__Path:-Files}
|
||||
|
||||
- Serilog__WriteTo__2__Args__fromEmail=${Serilog__WriteTo__2__Args__fromEmail:-}
|
||||
- Serilog__WriteTo__2__Args__toEmail=${Serilog__WriteTo__2__Args__toEmail:-}
|
||||
- Serilog__WriteTo__2__Args__mailServer=${Serilog__WriteTo__2__Args__mailServer:-}
|
||||
- Serilog__WriteTo__2__Args__networkCredential__userName=${Serilog__WriteTo__2__Args__networkCredential__userName:-}
|
||||
- Serilog__WriteTo__2__Args__networkCredential__password=${Serilog__WriteTo__2__Args__networkCredential__password:-}
|
||||
- Serilog__WriteTo__2__Args__port=${Serilog__WriteTo__2__Args__port:-587}
|
||||
- Serilog__WriteTo__2__Args__enableSsl=${Serilog__WriteTo__2__Args__enableSsl:-true}
|
||||
volumes:
|
||||
- ${LOGS_PATH:-/opt/myai/logs}/email-api:/app/logs
|
||||
- ${FILES_PATH:-/opt/myai/files}:/app/Files
|
||||
networks:
|
||||
- myai-network
|
||||
restart: unless-stopped
|
||||
labels:
|
||||
- "com.centurylinklabs.watchtower.enable=true"
|
||||
|
||||
api:
|
||||
image: registry.easysoft.ro/apps/myai-api:${IMAGE_TAG:-staging}
|
||||
container_name: myai-api
|
||||
depends_on:
|
||||
- cv-matcher-api
|
||||
- email-api
|
||||
environment:
|
||||
- ASPNETCORE_ENVIRONMENT=${ASPNETCORE_ENVIRONMENT:-Staging}
|
||||
- ASPNETCORE_URLS=${ASPNETCORE_URLS:-http://+:8080}
|
||||
@@ -126,11 +162,8 @@ services:
|
||||
- Subscribe__ToEmail=${Subscribe__ToEmail:-}
|
||||
- Subscribe__SubjectPrefix=${Subscribe__SubjectPrefix:-}
|
||||
|
||||
- Smtp__Host=${Smtp__Host:-}
|
||||
- Smtp__Port=${Smtp__Port:-587}
|
||||
- Smtp__Username=${Smtp__Username:-}
|
||||
- Smtp__Password=${Smtp__Password:-}
|
||||
- Smtp__UseStartTls=${Smtp__UseStartTls:-false}
|
||||
- EmailApi__BaseUrl=${EmailApi__BaseUrl:-http://email-api:8080}
|
||||
- EmailApi__InternalApiKey=${EmailApi__InternalApiKey:-}
|
||||
|
||||
- Captcha__Provider=${Captcha__Provider:-Recaptcha}
|
||||
- Captcha__SecretKey=${Captcha__SecretKey:-}
|
||||
@@ -210,6 +243,7 @@ services:
|
||||
container_name: myai-cv-search-job
|
||||
depends_on:
|
||||
- cv-matcher-api
|
||||
- email-api
|
||||
environment:
|
||||
- ASPNETCORE_ENVIRONMENT=${ASPNETCORE_ENVIRONMENT:-Staging}
|
||||
- APP_ENVIRONMENT_NAME=${APP_ENVIRONMENT_NAME:-myai.staging}
|
||||
@@ -224,11 +258,8 @@ services:
|
||||
- CvMatcherApi__BaseUrl=${CvMatcherApi__BaseUrl:-http://cv-matcher-api:8080}
|
||||
- CvMatcherApi__InternalApiKey=${CvMatcherApi__InternalApiKey:-}
|
||||
|
||||
- Smtp__Host=${Smtp__Host:-}
|
||||
- Smtp__Port=${Smtp__Port:-587}
|
||||
- Smtp__Username=${Smtp__Username:-}
|
||||
- Smtp__Password=${Smtp__Password:-}
|
||||
- Smtp__UseStartTls=${Smtp__UseStartTls:-false}
|
||||
- EmailApi__BaseUrl=${EmailApi__BaseUrl:-http://email-api:8080}
|
||||
- EmailApi__InternalApiKey=${EmailApi__InternalApiKey:-}
|
||||
|
||||
- Contact__ToEmail=${Contact__ToEmail:-}
|
||||
|
||||
|
||||
@@ -368,7 +368,7 @@ Global
|
||||
{92CA82EB-E558-44E7-9185-6FF8B8299C2A} = {D4E5F6A7-B8C9-4012-3456-789ABCDEF012}
|
||||
{02DE69CD-19E6-43C0-8916-DB98E5B5CA89} = {A9B8C7D6-E5F4-4321-ABCD-FEDCBA987654}
|
||||
{069365DB-1916-4C38-A90D-5E909BD9EDD0} = {A9B8C7D6-E5F4-4321-ABCD-FEDCBA987654}
|
||||
{BE44B4EB-9AB9-4D81-A9BF-5CF2832BEEE5} = {0FE6558F-2157-47F2-A835-558416CE0E2B}
|
||||
{BE44B4EB-9AB9-4D81-A9BF-5CF2832BEEE5} = {A9B8C7D6-E5F4-4321-ABCD-FEDCBA987654}
|
||||
{434119EA-2FFC-4433-9B8E-1E6D94006413} = {0FE6558F-2157-47F2-A835-558416CE0E2B}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
|
||||
Reference in New Issue
Block a user