Files
hack/tools/scripts/llm-key-hunter/results/_cache/content/capstone-stemify_stemify-backend.json

1 line
1.3 MiB
Plaintext
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{"repo": "capstone-stemify/stemify-backend", "files": {"src/Services/STEMify-Backend/STEMify-Backend.AppHost/infra/ai-service.tmpl.yaml": {"aeba9aa66118a8bea1f397d76877a378f0c4ccd0": {"t": 1785745212.9179819, "n": 6967, "h": "19c4744389d1ec98", "c": "api-version: 2024-02-02-preview\nlocation: {{ .Env.AZURE_LOCATION }}\nidentity:\n type: UserAssigned\n userAssignedIdentities:\n ? \"{{ .Env.AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID }}\"\n : {}\nproperties:\n environmentId: {{ .Env.AZURE_CONTAINER_APPS_ENVIRONMENT_ID }}\n configuration:\n activeRevisionsMode: single\n ingress:\n external: true\n targetPort: {{ targetPortOrDefault 8000 }}\n transport: auto\n allowInsecure: true\n registries:\n - server: {{ .Env.AZURE_CONTAINER_REGISTRY_ENDPOINT }}\n identity: {{ .Env.AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID }}\n template:\n containers:\n - image: {{ .Image }}\n name: ai-service\n env:\n - name: PYTHONUNBUFFERED\n value: \"1\"\n - name: PYTHONDONTWRITEBYTECODE\n value: \"1\"\n{{- if .Env.CLASSROOM_GRPC_USE_TLS }}\n - name: CLASSROOM_GRPC_USE_TLS\n value: \"{{ .Env.CLASSROOM_GRPC_USE_TLS }}\"\n{{- end }}\n{{- if .Env.RESOURCE_GRPC_ENDPOINT }}\n - name: RESOURCE_GRPC_ENDPOINT\n value: \"{{ .Env.RESOURCE_GRPC_ENDPOINT }}\"\n{{- end }}\n{{- if .Env.RESOURCE_GRPC_USE_TLS }}\n - name: RESOURCE_GRPC_USE_TLS\n value: \"{{ .Env.RESOURCE_GRPC_USE_TLS }}\"\n{{- end }}\n{{- if .Env.RESOURCE_GRPC_CERT_PATH }}\n - name: RESOURCE_GRPC_CERT_PATH\n value: \"{{ .Env.RESOURCE_GRPC_CERT_PATH }}\"\n{{- end }}\n{{- if .Env.RESOURCE_GRPC_OVERRIDE_AUTHORITY }}\n - name: RESOURCE_GRPC_OVERRIDE_AUTHORITY\n value: \"{{ .Env.RESOURCE_GRPC_OVERRIDE_AUTHORITY }}\"\n{{- end }}\n{{- if .Env.CLASSROOM_GRPC_ENDPOINT }}\n - name: CLASSROOM_GRPC_ENDPOINT\n value: \"{{ .Env.CLASSROOM_GRPC_ENDPOINT }}\"\n{{- end }}\n # LLM Configuration\n{{- if .Env.OPENAI_API_KEY }}\n - name: OPENAI_API_KEY\n value: \"{{ .Env.OPENAI_API_KEY }}\"\n{{- end }}\n{{- if .Env.OPENAI_BASE_URL }}\n - name: OPENAI_BASE_URL\n value: \"{{ .Env.OPENAI_BASE_URL }}\"\n{{- end }}\n{{- if .Env.LLM_MODEL }}\n - name: LLM_MODEL\n value: \"{{ .Env.LLM_MODEL }}\"\n{{- end }}\n{{- if .Env.LLM_TEMPERATURE }}\n - name: LLM_TEMPERATURE\n value: \"{{ .Env.LLM_TEMPERATURE }}\"\n{{- end }}\n{{- if .Env.LLM_MAX_TOKENS }}\n - name: LLM_MAX_TOKENS\n value: \"{{ .Env.LLM_MAX_TOKENS }}\"\n{{- end }}\n{{- if .Env.REDIS_HOST }}\n - name: REDIS_HOST\n value: \"{{ .Env.REDIS_HOST }}\"\n{{- end }}\n{{- if .Env.REDIS_PORT }}\n - name: REDIS_PORT\n value: \"{{ .Env.REDIS_PORT }}\"\n{{- end }}\n{{- if .Env.REDIS_PASSWORD }}\n - name: REDIS_PASSWORD\n value: \"{{ .Env.REDIS_PASSWORD }}\"\n{{- end }}\n{{- if .Env.REDIS_SSL }}\n - name: REDIS_SSL\n value: \"{{ .Env.REDIS_SSL }}\"\n{{- end }}\n # DeepSeek Configuration (optional, alternative to OpenAI)\n{{- if .Env.DEEPSEEK_API_KEY }}\n - name: DEEPSEEK_API_KEY\n value: \"{{ .Env.DEEPSEEK_API_KEY }}\"\n{{- end }}\n{{- if .Env.DEEPSEEK_BASE_URL }}\n - name: DEEPSEEK_BASE_URL\n value: \"{{ .Env.DEEPSEEK_BASE_URL }}\"\n{{- end }}\n{{- if .Env.DEEPSEEK_MODEL }}\n - name: DEEPSEEK_MODEL\n value: \"{{ .Env.DEEPSEEK_MODEL }}\"\n{{- end }}\n # Content Generation Prompts\n{{- if .Env.CONTENT_GENERATION_SYSTEM_PROMPT }}\n - name: CONTENT_GENERATION_SYSTEM_PROMPT\n value: \"{{ .Env.CONTENT_GENERATION_SYSTEM_PROMPT }}\"\n{{- end }}\n{{- if .Env.CONTENT_GENERATION_SECTION_PROMPT_TEMPLATE }}\n - name: CONTENT_GENERATION_SECTION_PROMPT_TEMPLATE\n value: \"{{ .Env.CONTENT_GENERATION_SECTION_PROMPT_TEMPLATE }}\"\n{{- end }}\n # Vector Store (Qdrant)\n{{- if .Env.QDRANT_URL }}\n - name: QDRANT_URL\n value: \"{{ .Env.QDRANT_URL }}\"\n{{- end }}\n{{- if .Env.QDRANT_GRPC_URL }}\n - name: QDRANT_GRPC_URL\n value: \"{{ .Env.QDRANT_GRPC_URL }}\"\n{{- end }}\n{{- if .Env.QDRANT_COLLECTION }}\n - name: QDRANT_COLLECTION\n value: \"{{ .Env.QDRANT_COLLECTION }}\"\n{{- end }}\n{{- if .Env.QDRANT_CLOUD_ENDPOINT }}\n - name: QDRANT_CLOUD_ENDPOINT\n value: \"{{ .Env.QDRANT_CLOUD_ENDPOINT }}\"\n{{- end }}\n{{- if .Env.QDRANT_CLOUD_API_KEY }}\n - name: QDRANT_CLOUD_API_KEY\n value: \"{{ .Env.QDRANT_CLOUD_API_KEY }}\"\n{{- end }}\n # Graph Database (Neo4j)\n{{- if .Env.NEO4J_URI }}\n - name: NEO4J_URI\n value: \"{{ .Env.NEO4J_URI }}\"\n{{- end }}\n{{- if .Env.NEO4J_USER }}\n - name: NEO4J_USER\n value: \"{{ .Env.NEO4J_USER }}\"\n{{- end }}\n{{- if .Env.NEO4J_PASSWORD }}\n - name: NEO4J_PASSWORD\n value: \"{{ .Env.NEO4J_PASSWORD }}\"\n{{- end }}\n{{- if .Env.NEO4J_CLOUD_URI }}\n - name: NEO4J_CLOUD_URI\n value: \"{{ .Env.NEO4J_CLOUD_URI }}\"\n{{- end }}\n{{- if .Env.NEO4J_CLOUD_USERNAME }}\n - name: NEO4J_CLOUD_USERNAME\n value: \"{{ .Env.NEO4J_CLOUD_USERNAME }}\"\n{{- end }}\n{{- if .Env.NEO4J_CLOUD_PASSWORD }}\n - name: NEO4J_CLOUD_PASSWORD\n value: \"{{ .Env.NEO4J_CLOUD_PASSWORD }}\"\n{{- end }}\n{{- if .Env.NEO4J_CLOUD_DATABASE }}\n - name: NEO4J_CLOUD_DATABASE\n value: \"{{ .Env.NEO4J_CLOUD_DATABASE }}\"\n{{- end }}\n{{- if .Env.AURA_INSTANCE_ID }}\n - name: AURA_INSTANCE_ID\n value: \"{{ .Env.AURA_INSTANCE_ID }}\"\n{{- end }}\n{{- if .Env.AURA_INSTANCE_NAME }}\n - name: AURA_INSTANCE_NAME\n value: \"{{ .Env.AURA_INSTANCE_NAME }}\"\n{{- end }}\n # Embeddings\n{{- if .Env.EMBEDDING_MODEL }}\n - name: EMBEDDING_MODEL\n value: \"{{ .Env.EMBEDDING_MODEL }}\"\n{{- end }}\n{{- if .Env.EMBEDDING_DEVICE }}\n - name: EMBEDDING_DEVICE\n value: \"{{ .Env.EMBEDDING_DEVICE }}\"\n{{- end }}\n{{- if .Env.EMBEDDING_BATCH_SIZE }}\n - name: EMBEDDING_BATCH_SIZE\n value: \"{{ .Env.EMBEDDING_BATCH_SIZE }}\"\n{{- end }}\n # Environment\n{{- if .Env.ENVIRONMENT }}\n - name: ENVIRONMENT\n value: \"{{ .Env.ENVIRONMENT }}\"\n{{- end }}\n{{- if .Env.LOG_LEVEL }}\n - name: LOG_LEVEL\n value: \"{{ .Env.LOG_LEVEL }}\"\n{{- end }}\n # RabbitMQ / Event Bus\n{{- if .Env.RABBITMQ_URL }}\n - name: RABBITMQ_URL\n value: \"{{ .Env.RABBITMQ_URL }}\"\n{{- end }}\n probes:\n - type: Liveness\n httpGet:\n path: /health\n port: {{ targetPortOrDefault 8000 }}\n initialDelaySeconds: 30\n periodSeconds: 10\n timeoutSeconds: 5\n failureThreshold: 3\n - type: Readiness\n httpGet:\n path: /health\n port: {{ targetPortOrDefault 8000 }}\n initialDelaySeconds: 10\n periodSeconds: 5\n timeoutSeconds: 3\n failureThreshold: 3\n scale:\n minReplicas: 1\n maxReplicas: 3\ntags:\n azd-service-name: ai-service\n aspire-resource-name: ai-service\n\n"}, "3e4e97c9da0759d5d4f79c2f8608d0996203d42d": {"t": 1785748759.9884007, "n": 6967, "h": "19c4744389d1ec98", "c": "api-version: 2024-02-02-preview\nlocation: {{ .Env.AZURE_LOCATION }}\nidentity:\n type: UserAssigned\n userAssignedIdentities:\n ? \"{{ .Env.AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID }}\"\n : {}\nproperties:\n environmentId: {{ .Env.AZURE_CONTAINER_APPS_ENVIRONMENT_ID }}\n configuration:\n activeRevisionsMode: single\n ingress:\n external: true\n targetPort: {{ targetPortOrDefault 8000 }}\n transport: auto\n allowInsecure: true\n registries:\n - server: {{ .Env.AZURE_CONTAINER_REGISTRY_ENDPOINT }}\n identity: {{ .Env.AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID }}\n template:\n containers:\n - image: {{ .Image }}\n name: ai-service\n env:\n - name: PYTHONUNBUFFERED\n value: \"1\"\n - name: PYTHONDONTWRITEBYTECODE\n value: \"1\"\n{{- if .Env.CLASSROOM_GRPC_USE_TLS }}\n - name: CLASSROOM_GRPC_USE_TLS\n value: \"{{ .Env.CLASSROOM_GRPC_USE_TLS }}\"\n{{- end }}\n{{- if .Env.RESOURCE_GRPC_ENDPOINT }}\n - name: RESOURCE_GRPC_ENDPOINT\n value: \"{{ .Env.RESOURCE_GRPC_ENDPOINT }}\"\n{{- end }}\n{{- if .Env.RESOURCE_GRPC_USE_TLS }}\n - name: RESOURCE_GRPC_USE_TLS\n value: \"{{ .Env.RESOURCE_GRPC_USE_TLS }}\"\n{{- end }}\n{{- if .Env.RESOURCE_GRPC_CERT_PATH }}\n - name: RESOURCE_GRPC_CERT_PATH\n value: \"{{ .Env.RESOURCE_GRPC_CERT_PATH }}\"\n{{- end }}\n{{- if .Env.RESOURCE_GRPC_OVERRIDE_AUTHORITY }}\n - name: RESOURCE_GRPC_OVERRIDE_AUTHORITY\n value: \"{{ .Env.RESOURCE_GRPC_OVERRIDE_AUTHORITY }}\"\n{{- end }}\n{{- if .Env.CLASSROOM_GRPC_ENDPOINT }}\n - name: CLASSROOM_GRPC_ENDPOINT\n value: \"{{ .Env.CLASSROOM_GRPC_ENDPOINT }}\"\n{{- end }}\n # LLM Configuration\n{{- if .Env.OPENAI_API_KEY }}\n - name: OPENAI_API_KEY\n value: \"{{ .Env.OPENAI_API_KEY }}\"\n{{- end }}\n{{- if .Env.OPENAI_BASE_URL }}\n - name: OPENAI_BASE_URL\n value: \"{{ .Env.OPENAI_BASE_URL }}\"\n{{- end }}\n{{- if .Env.LLM_MODEL }}\n - name: LLM_MODEL\n value: \"{{ .Env.LLM_MODEL }}\"\n{{- end }}\n{{- if .Env.LLM_TEMPERATURE }}\n - name: LLM_TEMPERATURE\n value: \"{{ .Env.LLM_TEMPERATURE }}\"\n{{- end }}\n{{- if .Env.LLM_MAX_TOKENS }}\n - name: LLM_MAX_TOKENS\n value: \"{{ .Env.LLM_MAX_TOKENS }}\"\n{{- end }}\n{{- if .Env.REDIS_HOST }}\n - name: REDIS_HOST\n value: \"{{ .Env.REDIS_HOST }}\"\n{{- end }}\n{{- if .Env.REDIS_PORT }}\n - name: REDIS_PORT\n value: \"{{ .Env.REDIS_PORT }}\"\n{{- end }}\n{{- if .Env.REDIS_PASSWORD }}\n - name: REDIS_PASSWORD\n value: \"{{ .Env.REDIS_PASSWORD }}\"\n{{- end }}\n{{- if .Env.REDIS_SSL }}\n - name: REDIS_SSL\n value: \"{{ .Env.REDIS_SSL }}\"\n{{- end }}\n # DeepSeek Configuration (optional, alternative to OpenAI)\n{{- if .Env.DEEPSEEK_API_KEY }}\n - name: DEEPSEEK_API_KEY\n value: \"{{ .Env.DEEPSEEK_API_KEY }}\"\n{{- end }}\n{{- if .Env.DEEPSEEK_BASE_URL }}\n - name: DEEPSEEK_BASE_URL\n value: \"{{ .Env.DEEPSEEK_BASE_URL }}\"\n{{- end }}\n{{- if .Env.DEEPSEEK_MODEL }}\n - name: DEEPSEEK_MODEL\n value: \"{{ .Env.DEEPSEEK_MODEL }}\"\n{{- end }}\n # Content Generation Prompts\n{{- if .Env.CONTENT_GENERATION_SYSTEM_PROMPT }}\n - name: CONTENT_GENERATION_SYSTEM_PROMPT\n value: \"{{ .Env.CONTENT_GENERATION_SYSTEM_PROMPT }}\"\n{{- end }}\n{{- if .Env.CONTENT_GENERATION_SECTION_PROMPT_TEMPLATE }}\n - name: CONTENT_GENERATION_SECTION_PROMPT_TEMPLATE\n value: \"{{ .Env.CONTENT_GENERATION_SECTION_PROMPT_TEMPLATE }}\"\n{{- end }}\n # Vector Store (Qdrant)\n{{- if .Env.QDRANT_URL }}\n - name: QDRANT_URL\n value: \"{{ .Env.QDRANT_URL }}\"\n{{- end }}\n{{- if .Env.QDRANT_GRPC_URL }}\n - name: QDRANT_GRPC_URL\n value: \"{{ .Env.QDRANT_GRPC_URL }}\"\n{{- end }}\n{{- if .Env.QDRANT_COLLECTION }}\n - name: QDRANT_COLLECTION\n value: \"{{ .Env.QDRANT_COLLECTION }}\"\n{{- end }}\n{{- if .Env.QDRANT_CLOUD_ENDPOINT }}\n - name: QDRANT_CLOUD_ENDPOINT\n value: \"{{ .Env.QDRANT_CLOUD_ENDPOINT }}\"\n{{- end }}\n{{- if .Env.QDRANT_CLOUD_API_KEY }}\n - name: QDRANT_CLOUD_API_KEY\n value: \"{{ .Env.QDRANT_CLOUD_API_KEY }}\"\n{{- end }}\n # Graph Database (Neo4j)\n{{- if .Env.NEO4J_URI }}\n - name: NEO4J_URI\n value: \"{{ .Env.NEO4J_URI }}\"\n{{- end }}\n{{- if .Env.NEO4J_USER }}\n - name: NEO4J_USER\n value: \"{{ .Env.NEO4J_USER }}\"\n{{- end }}\n{{- if .Env.NEO4J_PASSWORD }}\n - name: NEO4J_PASSWORD\n value: \"{{ .Env.NEO4J_PASSWORD }}\"\n{{- end }}\n{{- if .Env.NEO4J_CLOUD_URI }}\n - name: NEO4J_CLOUD_URI\n value: \"{{ .Env.NEO4J_CLOUD_URI }}\"\n{{- end }}\n{{- if .Env.NEO4J_CLOUD_USERNAME }}\n - name: NEO4J_CLOUD_USERNAME\n value: \"{{ .Env.NEO4J_CLOUD_USERNAME }}\"\n{{- end }}\n{{- if .Env.NEO4J_CLOUD_PASSWORD }}\n - name: NEO4J_CLOUD_PASSWORD\n value: \"{{ .Env.NEO4J_CLOUD_PASSWORD }}\"\n{{- end }}\n{{- if .Env.NEO4J_CLOUD_DATABASE }}\n - name: NEO4J_CLOUD_DATABASE\n value: \"{{ .Env.NEO4J_CLOUD_DATABASE }}\"\n{{- end }}\n{{- if .Env.AURA_INSTANCE_ID }}\n - name: AURA_INSTANCE_ID\n value: \"{{ .Env.AURA_INSTANCE_ID }}\"\n{{- end }}\n{{- if .Env.AURA_INSTANCE_NAME }}\n - name: AURA_INSTANCE_NAME\n value: \"{{ .Env.AURA_INSTANCE_NAME }}\"\n{{- end }}\n # Embeddings\n{{- if .Env.EMBEDDING_MODEL }}\n - name: EMBEDDING_MODEL\n value: \"{{ .Env.EMBEDDING_MODEL }}\"\n{{- end }}\n{{- if .Env.EMBEDDING_DEVICE }}\n - name: EMBEDDING_DEVICE\n value: \"{{ .Env.EMBEDDING_DEVICE }}\"\n{{- end }}\n{{- if .Env.EMBEDDING_BATCH_SIZE }}\n - name: EMBEDDING_BATCH_SIZE\n value: \"{{ .Env.EMBEDDING_BATCH_SIZE }}\"\n{{- end }}\n # Environment\n{{- if .Env.ENVIRONMENT }}\n - name: ENVIRONMENT\n value: \"{{ .Env.ENVIRONMENT }}\"\n{{- end }}\n{{- if .Env.LOG_LEVEL }}\n - name: LOG_LEVEL\n value: \"{{ .Env.LOG_LEVEL }}\"\n{{- end }}\n # RabbitMQ / Event Bus\n{{- if .Env.RABBITMQ_URL }}\n - name: RABBITMQ_URL\n value: \"{{ .Env.RABBITMQ_URL }}\"\n{{- end }}\n probes:\n - type: Liveness\n httpGet:\n path: /health\n port: {{ targetPortOrDefault 8000 }}\n initialDelaySeconds: 30\n periodSeconds: 10\n timeoutSeconds: 5\n failureThreshold: 3\n - type: Readiness\n httpGet:\n path: /health\n port: {{ targetPortOrDefault 8000 }}\n initialDelaySeconds: 10\n periodSeconds: 5\n timeoutSeconds: 3\n failureThreshold: 3\n scale:\n minReplicas: 1\n maxReplicas: 3\ntags:\n azd-service-name: ai-service\n aspire-resource-name: ai-service\n\n"}, "50041b6a718328bacf3c008288b0f14b9cbb1069": {"t": 1785748761.2375672, "n": 6522, "h": "159b9a915f6daaea", "c": "api-version: 2024-02-02-preview\nlocation: {{ .Env.AZURE_LOCATION }}\nidentity:\n type: UserAssigned\n userAssignedIdentities:\n ? \"{{ .Env.AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID }}\"\n : {}\nproperties:\n environmentId: {{ .Env.AZURE_CONTAINER_APPS_ENVIRONMENT_ID }}\n configuration:\n activeRevisionsMode: single\n ingress:\n external: true\n targetPort: {{ targetPortOrDefault 8000 }}\n transport: auto\n allowInsecure: true\n registries:\n - server: {{ .Env.AZURE_CONTAINER_REGISTRY_ENDPOINT }}\n identity: {{ .Env.AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID }}\n template:\n containers:\n - image: {{ .Image }}\n name: ai-service\n env:\n - name: PYTHONUNBUFFERED\n value: \"1\"\n - name: PYTHONDONTWRITEBYTECODE\n value: \"1\"\n{{- if .Env.CLASSROOM_GRPC_USE_TLS }}\n - name: CLASSROOM_GRPC_USE_TLS\n value: \"{{ .Env.CLASSROOM_GRPC_USE_TLS }}\"\n{{- end }}\n{{- if .Env.RESOURCE_GRPC_ENDPOINT }}\n - name: RESOURCE_GRPC_ENDPOINT\n value: \"{{ .Env.RESOURCE_GRPC_ENDPOINT }}\"\n{{- end }}\n{{- if .Env.RESOURCE_GRPC_USE_TLS }}\n - name: RESOURCE_GRPC_USE_TLS\n value: \"{{ .Env.RESOURCE_GRPC_USE_TLS }}\"\n{{- end }}\n{{- if .Env.RESOURCE_GRPC_CERT_PATH }}\n - name: RESOURCE_GRPC_CERT_PATH\n value: \"{{ .Env.RESOURCE_GRPC_CERT_PATH }}\"\n{{- end }}\n{{- if .Env.RESOURCE_GRPC_OVERRIDE_AUTHORITY }}\n - name: RESOURCE_GRPC_OVERRIDE_AUTHORITY\n value: \"{{ .Env.RESOURCE_GRPC_OVERRIDE_AUTHORITY }}\"\n{{- end }}\n{{- if .Env.CLASSROOM_GRPC_ENDPOINT }}\n - name: CLASSROOM_GRPC_ENDPOINT\n value: \"{{ .Env.CLASSROOM_GRPC_ENDPOINT }}\"\n{{- end }}\n # LLM Configuration\n{{- if .Env.OPENAI_API_KEY }}\n - name: OPENAI_API_KEY\n value: \"{{ .Env.OPENAI_API_KEY }}\"\n{{- end }}\n{{- if .Env.OPENAI_BASE_URL }}\n - name: OPENAI_BASE_URL\n value: \"{{ .Env.OPENAI_BASE_URL }}\"\n{{- end }}\n{{- if .Env.LLM_MODEL }}\n - name: LLM_MODEL\n value: \"{{ .Env.LLM_MODEL }}\"\n{{- end }}\n{{- if .Env.LLM_TEMPERATURE }}\n - name: LLM_TEMPERATURE\n value: \"{{ .Env.LLM_TEMPERATURE }}\"\n{{- end }}\n{{- if .Env.LLM_MAX_TOKENS }}\n - name: LLM_MAX_TOKENS\n value: \"{{ .Env.LLM_MAX_TOKENS }}\"\n{{- end }}\n # DeepSeek Configuration (optional, alternative to OpenAI)\n{{- if .Env.DEEPSEEK_API_KEY }}\n - name: DEEPSEEK_API_KEY\n value: \"{{ .Env.DEEPSEEK_API_KEY }}\"\n{{- end }}\n{{- if .Env.DEEPSEEK_BASE_URL }}\n - name: DEEPSEEK_BASE_URL\n value: \"{{ .Env.DEEPSEEK_BASE_URL }}\"\n{{- end }}\n{{- if .Env.DEEPSEEK_MODEL }}\n - name: DEEPSEEK_MODEL\n value: \"{{ .Env.DEEPSEEK_MODEL }}\"\n{{- end }}\n # Content Generation Prompts\n{{- if .Env.CONTENT_GENERATION_SYSTEM_PROMPT }}\n - name: CONTENT_GENERATION_SYSTEM_PROMPT\n value: \"{{ .Env.CONTENT_GENERATION_SYSTEM_PROMPT }}\"\n{{- end }}\n{{- if .Env.CONTENT_GENERATION_SECTION_PROMPT_TEMPLATE }}\n - name: CONTENT_GENERATION_SECTION_PROMPT_TEMPLATE\n value: \"{{ .Env.CONTENT_GENERATION_SECTION_PROMPT_TEMPLATE }}\"\n{{- end }}\n # Vector Store (Qdrant)\n{{- if .Env.QDRANT_URL }}\n - name: QDRANT_URL\n value: \"{{ .Env.QDRANT_URL }}\"\n{{- end }}\n{{- if .Env.QDRANT_GRPC_URL }}\n - name: QDRANT_GRPC_URL\n value: \"{{ .Env.QDRANT_GRPC_URL }}\"\n{{- end }}\n{{- if .Env.QDRANT_COLLECTION }}\n - name: QDRANT_COLLECTION\n value: \"{{ .Env.QDRANT_COLLECTION }}\"\n{{- end }}\n{{- if .Env.QDRANT_CLOUD_ENDPOINT }}\n - name: QDRANT_CLOUD_ENDPOINT\n value: \"{{ .Env.QDRANT_CLOUD_ENDPOINT }}\"\n{{- end }}\n{{- if .Env.QDRANT_CLOUD_API_KEY }}\n - name: QDRANT_CLOUD_API_KEY\n value: \"{{ .Env.QDRANT_CLOUD_API_KEY }}\"\n{{- end }}\n # Graph Database (Neo4j)\n{{- if .Env.NEO4J_URI }}\n - name: NEO4J_URI\n value: \"{{ .Env.NEO4J_URI }}\"\n{{- end }}\n{{- if .Env.NEO4J_USER }}\n - name: NEO4J_USER\n value: \"{{ .Env.NEO4J_USER }}\"\n{{- end }}\n{{- if .Env.NEO4J_PASSWORD }}\n - name: NEO4J_PASSWORD\n value: \"{{ .Env.NEO4J_PASSWORD }}\"\n{{- end }}\n{{- if .Env.NEO4J_CLOUD_URI }}\n - name: NEO4J_CLOUD_URI\n value: \"{{ .Env.NEO4J_CLOUD_URI }}\"\n{{- end }}\n{{- if .Env.NEO4J_CLOUD_USERNAME }}\n - name: NEO4J_CLOUD_USERNAME\n value: \"{{ .Env.NEO4J_CLOUD_USERNAME }}\"\n{{- end }}\n{{- if .Env.NEO4J_CLOUD_PASSWORD }}\n - name: NEO4J_CLOUD_PASSWORD\n value: \"{{ .Env.NEO4J_CLOUD_PASSWORD }}\"\n{{- end }}\n{{- if .Env.NEO4J_CLOUD_DATABASE }}\n - name: NEO4J_CLOUD_DATABASE\n value: \"{{ .Env.NEO4J_CLOUD_DATABASE }}\"\n{{- end }}\n{{- if .Env.AURA_INSTANCE_ID }}\n - name: AURA_INSTANCE_ID\n value: \"{{ .Env.AURA_INSTANCE_ID }}\"\n{{- end }}\n{{- if .Env.AURA_INSTANCE_NAME }}\n - name: AURA_INSTANCE_NAME\n value: \"{{ .Env.AURA_INSTANCE_NAME }}\"\n{{- end }}\n # Embeddings\n{{- if .Env.EMBEDDING_MODEL }}\n - name: EMBEDDING_MODEL\n value: \"{{ .Env.EMBEDDING_MODEL }}\"\n{{- end }}\n{{- if .Env.EMBEDDING_DEVICE }}\n - name: EMBEDDING_DEVICE\n value: \"{{ .Env.EMBEDDING_DEVICE }}\"\n{{- end }}\n{{- if .Env.EMBEDDING_BATCH_SIZE }}\n - name: EMBEDDING_BATCH_SIZE\n value: \"{{ .Env.EMBEDDING_BATCH_SIZE }}\"\n{{- end }}\n # Environment\n{{- if .Env.ENVIRONMENT }}\n - name: ENVIRONMENT\n value: \"{{ .Env.ENVIRONMENT }}\"\n{{- end }}\n{{- if .Env.LOG_LEVEL }}\n - name: LOG_LEVEL\n value: \"{{ .Env.LOG_LEVEL }}\"\n{{- end }}\n # RabbitMQ / Event Bus\n{{- if .Env.RABBITMQ_URL }}\n - name: RABBITMQ_URL\n value: \"{{ .Env.RABBITMQ_URL }}\"\n{{- end }}\n probes:\n - type: Liveness\n httpGet:\n path: /health\n port: {{ targetPortOrDefault 8000 }}\n initialDelaySeconds: 30\n periodSeconds: 10\n timeoutSeconds: 5\n failureThreshold: 3\n - type: Readiness\n httpGet:\n path: /health\n port: {{ targetPortOrDefault 8000 }}\n initialDelaySeconds: 10\n periodSeconds: 5\n timeoutSeconds: 3\n failureThreshold: 3\n scale:\n minReplicas: 1\n maxReplicas: 3\ntags:\n azd-service-name: ai-service\n aspire-resource-name: ai-service\n\n"}, "fe6ee047255d35791338eae743a405c6678b8483": {"t": 1785748762.3156617, "n": 6374, "h": "3fda95bf8340d901", "c": "api-version: 2024-02-02-preview\nlocation: {{ .Env.AZURE_LOCATION }}\nidentity:\n type: UserAssigned\n userAssignedIdentities:\n ? \"{{ .Env.AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID }}\"\n : {}\nproperties:\n environmentId: {{ .Env.AZURE_CONTAINER_APPS_ENVIRONMENT_ID }}\n configuration:\n activeRevisionsMode: single\n ingress:\n external: true\n targetPort: {{ targetPortOrDefault 8000 }}\n transport: auto\n allowInsecure: true\n registries:\n - server: {{ .Env.AZURE_CONTAINER_REGISTRY_ENDPOINT }}\n identity: {{ .Env.AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID }}\n template:\n containers:\n - image: {{ .Image }}\n name: ai-service\n env:\n - name: PYTHONUNBUFFERED\n value: \"1\"\n - name: PYTHONDONTWRITEBYTECODE\n value: \"1\"\n{{- if .Env.CLASSROOM_GRPC_USE_TLS }}\n - name: CLASSROOM_GRPC_USE_TLS\n value: \"{{ .Env.CLASSROOM_GRPC_USE_TLS }}\"\n{{- end }}\n{{- if .Env.RESOURCE_GRPC_ENDPOINT }}\n - name: RESOURCE_GRPC_ENDPOINT\n value: \"{{ .Env.RESOURCE_GRPC_ENDPOINT }}\"\n{{- end }}\n{{- if .Env.RESOURCE_GRPC_USE_TLS }}\n - name: RESOURCE_GRPC_USE_TLS\n value: \"{{ .Env.RESOURCE_GRPC_USE_TLS }}\"\n{{- end }}\n{{- if .Env.RESOURCE_GRPC_CERT_PATH }}\n - name: RESOURCE_GRPC_CERT_PATH\n value: \"{{ .Env.RESOURCE_GRPC_CERT_PATH }}\"\n{{- end }}\n{{- if .Env.RESOURCE_GRPC_OVERRIDE_AUTHORITY }}\n - name: RESOURCE_GRPC_OVERRIDE_AUTHORITY\n value: \"{{ .Env.RESOURCE_GRPC_OVERRIDE_AUTHORITY }}\"\n{{- end }}\n{{- if .Env.CLASSROOM_GRPC_ENDPOINT }}\n - name: CLASSROOM_GRPC_ENDPOINT\n value: \"{{ .Env.CLASSROOM_GRPC_ENDPOINT }}\"\n{{- end }}\n # LLM Configuration\n{{- if .Env.OPENAI_API_KEY }}\n - name: OPENAI_API_KEY\n value: \"{{ .Env.OPENAI_API_KEY }}\"\n{{- end }}\n{{- if .Env.OPENAI_BASE_URL }}\n - name: OPENAI_BASE_URL\n value: \"{{ .Env.OPENAI_BASE_URL }}\"\n{{- end }}\n{{- if .Env.LLM_MODEL }}\n - name: LLM_MODEL\n value: \"{{ .Env.LLM_MODEL }}\"\n{{- end }}\n{{- if .Env.LLM_TEMPERATURE }}\n - name: LLM_TEMPERATURE\n value: \"{{ .Env.LLM_TEMPERATURE }}\"\n{{- end }}\n{{- if .Env.LLM_MAX_TOKENS }}\n - name: LLM_MAX_TOKENS\n value: \"{{ .Env.LLM_MAX_TOKENS }}\"\n{{- end }}\n # DeepSeek Configuration (optional, alternative to OpenAI)\n{{- if .Env.DEEPSEEK_API_KEY }}\n - name: DEEPSEEK_API_KEY\n value: \"{{ .Env.DEEPSEEK_API_KEY }}\"\n{{- end }}\n{{- if .Env.DEEPSEEK_BASE_URL }}\n - name: DEEPSEEK_BASE_URL\n value: \"{{ .Env.DEEPSEEK_BASE_URL }}\"\n{{- end }}\n{{- if .Env.DEEPSEEK_MODEL }}\n - name: DEEPSEEK_MODEL\n value: \"{{ .Env.DEEPSEEK_MODEL }}\"\n{{- end }}\n # Content Generation Prompts\n{{- if .Env.CONTENT_GENERATION_SYSTEM_PROMPT }}\n - name: CONTENT_GENERATION_SYSTEM_PROMPT\n value: \"{{ .Env.CONTENT_GENERATION_SYSTEM_PROMPT }}\"\n{{- end }}\n{{- if .Env.CONTENT_GENERATION_SECTION_PROMPT_TEMPLATE }}\n - name: CONTENT_GENERATION_SECTION_PROMPT_TEMPLATE\n value: \"{{ .Env.CONTENT_GENERATION_SECTION_PROMPT_TEMPLATE }}\"\n{{- end }}\n # Vector Store (Qdrant)\n{{- if .Env.QDRANT_URL }}\n - name: QDRANT_URL\n value: \"{{ .Env.QDRANT_URL }}\"\n{{- end }}\n{{- if .Env.QDRANT_GRPC_URL }}\n - name: QDRANT_GRPC_URL\n value: \"{{ .Env.QDRANT_GRPC_URL }}\"\n{{- end }}\n{{- if .Env.QDRANT_COLLECTION }}\n - name: QDRANT_COLLECTION\n value: \"{{ .Env.QDRANT_COLLECTION }}\"\n{{- end }}\n{{- if .Env.QDRANT_CLOUD_ENDPOINT }}\n - name: QDRANT_CLOUD_ENDPOINT\n value: \"{{ .Env.QDRANT_CLOUD_ENDPOINT }}\"\n{{- end }}\n{{- if .Env.QDRANT_CLOUD_API_KEY }}\n - name: QDRANT_CLOUD_API_KEY\n value: \"{{ .Env.QDRANT_CLOUD_API_KEY }}\"\n{{- end }}\n # Graph Database (Neo4j)\n{{- if .Env.NEO4J_URI }}\n - name: NEO4J_URI\n value: \"{{ .Env.NEO4J_URI }}\"\n{{- end }}\n{{- if .Env.NEO4J_USER }}\n - name: NEO4J_USER\n value: \"{{ .Env.NEO4J_USER }}\"\n{{- end }}\n{{- if .Env.NEO4J_PASSWORD }}\n - name: NEO4J_PASSWORD\n value: \"{{ .Env.NEO4J_PASSWORD }}\"\n{{- end }}\n{{- if .Env.NEO4J_CLOUD_URI }}\n - name: NEO4J_CLOUD_URI\n value: \"{{ .Env.NEO4J_CLOUD_URI }}\"\n{{- end }}\n{{- if .Env.NEO4J_CLOUD_USERNAME }}\n - name: NEO4J_CLOUD_USERNAME\n value: \"{{ .Env.NEO4J_CLOUD_USERNAME }}\"\n{{- end }}\n{{- if .Env.NEO4J_CLOUD_PASSWORD }}\n - name: NEO4J_CLOUD_PASSWORD\n value: \"{{ .Env.NEO4J_CLOUD_PASSWORD }}\"\n{{- end }}\n{{- if .Env.NEO4J_CLOUD_DATABASE }}\n - name: NEO4J_CLOUD_DATABASE\n value: \"{{ .Env.NEO4J_CLOUD_DATABASE }}\"\n{{- end }}\n{{- if .Env.AURA_INSTANCE_ID }}\n - name: AURA_INSTANCE_ID\n value: \"{{ .Env.AURA_INSTANCE_ID }}\"\n{{- end }}\n{{- if .Env.AURA_INSTANCE_NAME }}\n - name: AURA_INSTANCE_NAME\n value: \"{{ .Env.AURA_INSTANCE_NAME }}\"\n{{- end }}\n # Embeddings\n{{- if .Env.EMBEDDING_MODEL }}\n - name: EMBEDDING_MODEL\n value: \"{{ .Env.EMBEDDING_MODEL }}\"\n{{- end }}\n{{- if .Env.EMBEDDING_DEVICE }}\n - name: EMBEDDING_DEVICE\n value: \"{{ .Env.EMBEDDING_DEVICE }}\"\n{{- end }}\n{{- if .Env.EMBEDDING_BATCH_SIZE }}\n - name: EMBEDDING_BATCH_SIZE\n value: \"{{ .Env.EMBEDDING_BATCH_SIZE }}\"\n{{- end }}\n # Environment\n{{- if .Env.ENVIRONMENT }}\n - name: ENVIRONMENT\n value: \"{{ .Env.ENVIRONMENT }}\"\n{{- end }}\n{{- if .Env.LOG_LEVEL }}\n - name: LOG_LEVEL\n value: \"{{ .Env.LOG_LEVEL }}\"\n{{- end }}\n probes:\n - type: Liveness\n httpGet:\n path: /health\n port: {{ targetPortOrDefault 8000 }}\n initialDelaySeconds: 30\n periodSeconds: 10\n timeoutSeconds: 5\n failureThreshold: 3\n - type: Readiness\n httpGet:\n path: /health\n port: {{ targetPortOrDefault 8000 }}\n initialDelaySeconds: 10\n periodSeconds: 5\n timeoutSeconds: 3\n failureThreshold: 3\n scale:\n minReplicas: 1\n maxReplicas: 3\ntags:\n azd-service-name: ai-service\n aspire-resource-name: ai-service\n\n"}, "52e42439e5886e2af68f015f2ccea1e64151fa7a": {"t": 1785748763.4032764, "n": 4921, "h": "080b92d4d3f374b1", "c": "api-version: 2024-02-02-preview\nlocation: {{ .Env.AZURE_LOCATION }}\nidentity:\n type: UserAssigned\n userAssignedIdentities:\n ? \"{{ .Env.AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID }}\"\n : {}\nproperties:\n environmentId: {{ .Env.AZURE_CONTAINER_APPS_ENVIRONMENT_ID }}\n configuration:\n activeRevisionsMode: single\n ingress:\n external: true\n targetPort: {{ targetPortOrDefault 8000 }}\n transport: auto\n allowInsecure: true\n registries:\n - server: {{ .Env.AZURE_CONTAINER_REGISTRY_ENDPOINT }}\n identity: {{ .Env.AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID }}\n template:\n containers:\n - image: {{ .Image }}\n name: ai-service\n env:\n - name: PYTHONUNBUFFERED\n value: \"1\"\n - name: PYTHONDONTWRITEBYTECODE\n value: \"1\"\n{{- if .Env.CLASSROOM_GRPC_USE_TLS }}\n - name: CLASSROOM_GRPC_USE_TLS\n value: \"{{ .Env.CLASSROOM_GRPC_USE_TLS }}\"\n{{- end }}\n{{- if .Env.RESOURCE_GRPC_ENDPOINT }}\n - name: RESOURCE_GRPC_ENDPOINT\n value: \"{{ .Env.RESOURCE_GRPC_ENDPOINT }}\"\n{{- end }}\n{{- if .Env.RESOURCE_GRPC_USE_TLS }}\n - name: RESOURCE_GRPC_USE_TLS\n value: \"{{ .Env.RESOURCE_GRPC_USE_TLS }}\"\n{{- end }}\n{{- if .Env.RESOURCE_GRPC_CERT_PATH }}\n - name: RESOURCE_GRPC_CERT_PATH\n value: \"{{ .Env.RESOURCE_GRPC_CERT_PATH }}\"\n{{- end }}\n{{- if .Env.RESOURCE_GRPC_OVERRIDE_AUTHORITY }}\n - name: RESOURCE_GRPC_OVERRIDE_AUTHORITY\n value: \"{{ .Env.RESOURCE_GRPC_OVERRIDE_AUTHORITY }}\"\n{{- end }}\n{{- if .Env.CLASSROOM_GRPC_ENDPOINT }}\n - name: CLASSROOM_GRPC_ENDPOINT\n value: \"{{ .Env.CLASSROOM_GRPC_ENDPOINT }}\"\n{{- end }}\n # LLM Configuration\n{{- if .Env.OPENAI_API_KEY }}\n - name: OPENAI_API_KEY\n value: \"{{ .Env.OPENAI_API_KEY }}\"\n{{- end }}\n{{- if .Env.OPENAI_BASE_URL }}\n - name: OPENAI_BASE_URL\n value: \"{{ .Env.OPENAI_BASE_URL }}\"\n{{- end }}\n{{- if .Env.LLM_MODEL }}\n - name: LLM_MODEL\n value: \"{{ .Env.LLM_MODEL }}\"\n{{- end }}\n{{- if .Env.LLM_TEMPERATURE }}\n - name: LLM_TEMPERATURE\n value: \"{{ .Env.LLM_TEMPERATURE }}\"\n{{- end }}\n{{- if .Env.LLM_MAX_TOKENS }}\n - name: LLM_MAX_TOKENS\n value: \"{{ .Env.LLM_MAX_TOKENS }}\"\n{{- end }}\n # DeepSeek Configuration (optional, alternative to OpenAI)\n{{- if .Env.DEEPSEEK_API_KEY }}\n - name: DEEPSEEK_API_KEY\n value: \"{{ .Env.DEEPSEEK_API_KEY }}\"\n{{- end }}\n{{- if .Env.DEEPSEEK_BASE_URL }}\n - name: DEEPSEEK_BASE_URL\n value: \"{{ .Env.DEEPSEEK_BASE_URL }}\"\n{{- end }}\n{{- if .Env.DEEPSEEK_MODEL }}\n - name: DEEPSEEK_MODEL\n value: \"{{ .Env.DEEPSEEK_MODEL }}\"\n{{- end }}\n # Content Generation Prompts\n{{- if .Env.CONTENT_GENERATION_SYSTEM_PROMPT }}\n - name: CONTENT_GENERATION_SYSTEM_PROMPT\n value: \"{{ .Env.CONTENT_GENERATION_SYSTEM_PROMPT }}\"\n{{- end }}\n{{- if .Env.CONTENT_GENERATION_SECTION_PROMPT_TEMPLATE }}\n - name: CONTENT_GENERATION_SECTION_PROMPT_TEMPLATE\n value: \"{{ .Env.CONTENT_GENERATION_SECTION_PROMPT_TEMPLATE }}\"\n{{- end }}\n # Vector Store (Qdrant)\n{{- if .Env.QDRANT_URL }}\n - name: QDRANT_URL\n value: \"{{ .Env.QDRANT_URL }}\"\n{{- end }}\n{{- if .Env.QDRANT_GRPC_URL }}\n - name: QDRANT_GRPC_URL\n value: \"{{ .Env.QDRANT_GRPC_URL }}\"\n{{- end }}\n{{- if .Env.QDRANT_COLLECTION }}\n - name: QDRANT_COLLECTION\n value: \"{{ .Env.QDRANT_COLLECTION }}\"\n{{- end }}\n # Embeddings\n{{- if .Env.EMBEDDING_MODEL }}\n - name: EMBEDDING_MODEL\n value: \"{{ .Env.EMBEDDING_MODEL }}\"\n{{- end }}\n{{- if .Env.EMBEDDING_DEVICE }}\n - name: EMBEDDING_DEVICE\n value: \"{{ .Env.EMBEDDING_DEVICE }}\"\n{{- end }}\n{{- if .Env.EMBEDDING_BATCH_SIZE }}\n - name: EMBEDDING_BATCH_SIZE\n value: \"{{ .Env.EMBEDDING_BATCH_SIZE }}\"\n{{- end }}\n # Environment\n{{- if .Env.ENVIRONMENT }}\n - name: ENVIRONMENT\n value: \"{{ .Env.ENVIRONMENT }}\"\n{{- end }}\n{{- if .Env.LOG_LEVEL }}\n - name: LOG_LEVEL\n value: \"{{ .Env.LOG_LEVEL }}\"\n{{- end }}\n probes:\n - type: Liveness\n httpGet:\n path: /health\n port: {{ targetPortOrDefault 8000 }}\n initialDelaySeconds: 30\n periodSeconds: 10\n timeoutSeconds: 5\n failureThreshold: 3\n - type: Readiness\n httpGet:\n path: /health\n port: {{ targetPortOrDefault 8000 }}\n initialDelaySeconds: 10\n periodSeconds: 5\n timeoutSeconds: 3\n failureThreshold: 3\n scale:\n minReplicas: 1\n maxReplicas: 3\ntags:\n azd-service-name: ai-service\n aspire-resource-name: ai-service\n\n"}, "7f9362f56afd63c9c3d0036338bb4760548634f6": {"t": 1785748764.5416794, "n": 4776, "h": "92fbb3294da7eb8c", "c": "api-version: 2024-02-02-preview\nlocation: {{ .Env.AZURE_LOCATION }}\nidentity:\n type: UserAssigned\n userAssignedIdentities:\n ? \"{{ .Env.AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID }}\"\n : {}\nproperties:\n environmentId: {{ .Env.AZURE_CONTAINER_APPS_ENVIRONMENT_ID }}\n configuration:\n activeRevisionsMode: single\n ingress:\n external: true\n targetPort: {{ targetPortOrDefault 8000 }}\n transport: auto\n allowInsecure: true\n registries:\n - server: {{ .Env.AZURE_CONTAINER_REGISTRY_ENDPOINT }}\n identity: {{ .Env.AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID }}\n template:\n containers:\n - image: {{ .Image }}\n name: ai-service\n env:\n - name: PYTHONUNBUFFERED\n value: \"1\"\n - name: PYTHONDONTWRITEBYTECODE\n value: \"1\"\n{{- if .Env.RESOURCE_GRPC_ENDPOINT }}\n - name: RESOURCE_GRPC_ENDPOINT\n value: \"{{ .Env.RESOURCE_GRPC_ENDPOINT }}\"\n{{- end }}\n{{- if .Env.RESOURCE_GRPC_USE_TLS }}\n - name: RESOURCE_GRPC_USE_TLS\n value: \"{{ .Env.RESOURCE_GRPC_USE_TLS }}\"\n{{- end }}\n{{- if .Env.RESOURCE_GRPC_CERT_PATH }}\n - name: RESOURCE_GRPC_CERT_PATH\n value: \"{{ .Env.RESOURCE_GRPC_CERT_PATH }}\"\n{{- end }}\n{{- if .Env.RESOURCE_GRPC_OVERRIDE_AUTHORITY }}\n - name: RESOURCE_GRPC_OVERRIDE_AUTHORITY\n value: \"{{ .Env.RESOURCE_GRPC_OVERRIDE_AUTHORITY }}\"\n{{- end }}\n{{- if .Env.CLASSROOM_GRPC_ENDPOINT }}\n - name: CLASSROOM_GRPC_ENDPOINT\n value: \"{{ .Env.CLASSROOM_GRPC_ENDPOINT }}\"\n{{- end }}\n # LLM Configuration\n{{- if .Env.OPENAI_API_KEY }}\n - name: OPENAI_API_KEY\n value: \"{{ .Env.OPENAI_API_KEY }}\"\n{{- end }}\n{{- if .Env.OPENAI_BASE_URL }}\n - name: OPENAI_BASE_URL\n value: \"{{ .Env.OPENAI_BASE_URL }}\"\n{{- end }}\n{{- if .Env.LLM_MODEL }}\n - name: LLM_MODEL\n value: \"{{ .Env.LLM_MODEL }}\"\n{{- end }}\n{{- if .Env.LLM_TEMPERATURE }}\n - name: LLM_TEMPERATURE\n value: \"{{ .Env.LLM_TEMPERATURE }}\"\n{{- end }}\n{{- if .Env.LLM_MAX_TOKENS }}\n - name: LLM_MAX_TOKENS\n value: \"{{ .Env.LLM_MAX_TOKENS }}\"\n{{- end }}\n # DeepSeek Configuration (optional, alternative to OpenAI)\n{{- if .Env.DEEPSEEK_API_KEY }}\n - name: DEEPSEEK_API_KEY\n value: \"{{ .Env.DEEPSEEK_API_KEY }}\"\n{{- end }}\n{{- if .Env.DEEPSEEK_BASE_URL }}\n - name: DEEPSEEK_BASE_URL\n value: \"{{ .Env.DEEPSEEK_BASE_URL }}\"\n{{- end }}\n{{- if .Env.DEEPSEEK_MODEL }}\n - name: DEEPSEEK_MODEL\n value: \"{{ .Env.DEEPSEEK_MODEL }}\"\n{{- end }}\n # Content Generation Prompts\n{{- if .Env.CONTENT_GENERATION_SYSTEM_PROMPT }}\n - name: CONTENT_GENERATION_SYSTEM_PROMPT\n value: \"{{ .Env.CONTENT_GENERATION_SYSTEM_PROMPT }}\"\n{{- end }}\n{{- if .Env.CONTENT_GENERATION_SECTION_PROMPT_TEMPLATE }}\n - name: CONTENT_GENERATION_SECTION_PROMPT_TEMPLATE\n value: \"{{ .Env.CONTENT_GENERATION_SECTION_PROMPT_TEMPLATE }}\"\n{{- end }}\n # Vector Store (Qdrant)\n{{- if .Env.QDRANT_URL }}\n - name: QDRANT_URL\n value: \"{{ .Env.QDRANT_URL }}\"\n{{- end }}\n{{- if .Env.QDRANT_GRPC_URL }}\n - name: QDRANT_GRPC_URL\n value: \"{{ .Env.QDRANT_GRPC_URL }}\"\n{{- end }}\n{{- if .Env.QDRANT_COLLECTION }}\n - name: QDRANT_COLLECTION\n value: \"{{ .Env.QDRANT_COLLECTION }}\"\n{{- end }}\n # Embeddings\n{{- if .Env.EMBEDDING_MODEL }}\n - name: EMBEDDING_MODEL\n value: \"{{ .Env.EMBEDDING_MODEL }}\"\n{{- end }}\n{{- if .Env.EMBEDDING_DEVICE }}\n - name: EMBEDDING_DEVICE\n value: \"{{ .Env.EMBEDDING_DEVICE }}\"\n{{- end }}\n{{- if .Env.EMBEDDING_BATCH_SIZE }}\n - name: EMBEDDING_BATCH_SIZE\n value: \"{{ .Env.EMBEDDING_BATCH_SIZE }}\"\n{{- end }}\n # Environment\n{{- if .Env.ENVIRONMENT }}\n - name: ENVIRONMENT\n value: \"{{ .Env.ENVIRONMENT }}\"\n{{- end }}\n{{- if .Env.LOG_LEVEL }}\n - name: LOG_LEVEL\n value: \"{{ .Env.LOG_LEVEL }}\"\n{{- end }}\n probes:\n - type: Liveness\n httpGet:\n path: /health\n port: {{ targetPortOrDefault 8000 }}\n initialDelaySeconds: 30\n periodSeconds: 10\n timeoutSeconds: 5\n failureThreshold: 3\n - type: Readiness\n httpGet:\n path: /health\n port: {{ targetPortOrDefault 8000 }}\n initialDelaySeconds: 10\n periodSeconds: 5\n timeoutSeconds: 3\n failureThreshold: 3\n scale:\n minReplicas: 1\n maxReplicas: 3\ntags:\n azd-service-name: ai-service\n aspire-resource-name: ai-service\n\n"}, "e886d94a93ab019aa3484cb7b0e77b2dc6c067ee": {"t": 1785748765.6137574, "n": 4776, "h": "0ea40304782c8066", "c": "api-version: 2024-02-02-preview\nlocation: {{ .Env.AZURE_LOCATION }}\nidentity:\n type: UserAssigned\n userAssignedIdentities:\n ? \"{{ .Env.AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID }}\"\n : {}\nproperties:\n environmentId: {{ .Env.AZURE_CONTAINER_APPS_ENVIRONMENT_ID }}\n configuration:\n activeRevisionsMode: single\n ingress:\n external: true\n targetPort: {{ targetPortOrDefault 8000 }}\n transport: auto\n allowInsecure: true\n registries:\n - server: {{ .Env.AZURE_CONTAINER_REGISTRY_ENDPOINT }}\n identity: {{ .Env.AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID }}\n template:\n containers:\n - image: {{ .Image }}\n name: ai-service\n env:\n - name: PYTHONUNBUFFERED\n value: \"1\"\n - name: PYTHONDONTWRITEBYTECODE\n value: \"1\"\n{{- if .Env.RESOURCE_GRPC_ENDPOINT }}\n - name: RESOURCE_GRPC_ENDPOINT\n value: \"{{ .Env.RESOURCE_GRPC_ENDPOINT }}\"\n{{- end }}\n{{- if .Env.RESOURCE_GRPC_USE_TLS }}\n - name: RESOURCE_GRPC_USE_TLS\n value: \"{{ .Env.RESOURCE_GRPC_USE_TLS }}\"\n{{- end }}\n{{- if .Env.RESOURCE_GRPC_CERT_PATH }}\n - name: RESOURCE_GRPC_CERT_PATH\n value: \"{{ .Env.RESOURCE_GRPC_CERT_PATH }}\"\n{{- end }}\n{{- if .Env.RESOURCE_GRPC_OVERRIDE_AUTHORITY }}\n - name: RESOURCE_GRPC_OVERRIDE_AUTHORITY\n value: \"{{ .Env.RESOURCE_GRPC_OVERRIDE_AUTHORITY }}\"\n{{- end }}\n{{- if .Env.CLASSROOM_GRPC_ENDPOINT }}\n - name: CLASSROOM_GRPC_ENDPOINT\n value: \"{{ .Env.CLASSROOM_GRPC_ENDPOINT }}\"\n{{- end }}\n # LLM Configuration\n{{- if .Env.OPENAI_API_KEY }}\n - name: OPENAI_API_KEY\n value: \"{{ .Env.OPENAI_API_KEY }}\"\n{{- end }}\n{{- if .Env.OPENAI_BASE_URL }}\n - name: OPENAI_BASE_URL\n value: \"{{ .Env.OPENAI_BASE_URL }}\"\n{{- end }}\n{{- if .Env.LLM_MODEL }}\n - name: LLM_MODEL\n value: \"{{ .Env.LLM_MODEL }}\"\n{{- end }}\n{{- if .Env.LLM_TEMPERATURE }}\n - name: LLM_TEMPERATURE\n value: \"{{ .Env.LLM_TEMPERATURE }}\"\n{{- end }}\n{{- if .Env.LLM_MAX_TOKENS }}\n - name: LLM_MAX_TOKENS\n value: \"{{ .Env.LLM_MAX_TOKENS }}\"\n{{- end }}\n # DeepSeek Configuration (optional, alternative to OpenAI)\n{{- if .Env.DEEPSEEK_API_KEY }}\n - name: DEEPSEEK_API_KEY\n value: \"{{ .Env.DEEPSEEK_API_KEY }}\"\n{{- end }}\n{{- if .Env.DEEPSEEK_BASE_URL }}\n - name: DEEPSEEK_BASE_URL\n value: \"{{ .Env.DEEPSEEK_BASE_URL }}\"\n{{- end }}\n{{- if .Env.DEEPSEEK_MODEL }}\n - name: DEEPSEEK_MODEL\n value: \"{{ .Env.DEEPSEEK_MODEL }}\"\n{{- end }}\n # Content Generation Prompts\n{{- if .Env.CONTENT_GENERATION_SYSTEM_PROMPT }}\n - name: CONTENT_GENERATION_SYSTEM_PROMPT\n value: \"{{ .Env.CONTENT_GENERATION_SYSTEM_PROMPT }}\"\n{{- end }}\n{{- if .Env.CONTENT_GENERATION_SECTION_PROMPT_TEMPLATE }}\n - name: CONTENT_GENERATION_SECTION_PROMPT_TEMPLATE\n value: \"{{ .Env.CONTENT_GENERATION_SECTION_PROMPT_TEMPLATE }}\"\n{{- end }}\n # Vector Store (Qdrant)\n{{- if .Env.QDRANT_URL }}\n - name: QDRANT_URL\n value: \"{{ .Env.QDRANT_URL }}\"\n{{- end }}\n{{- if .Env.QDRANT_GRPC_URL }}\n - name: QDRANT_GRPC_URL\n value: \"{{ .Env.QDRANT_GRPC_URL }}\"\n{{- end }}\n{{- if .Env.QDRANT_COLLECTION }}\n - name: QDRANT_COLLECTION\n value: \"{{ .Env.QDRANT_COLLECTION }}\"\n{{- end }}\n # Embeddings\n{{- if .Env.EMBEDDING_MODEL }}\n - name: EMBEDDING_MODEL\n value: \"{{ .Env.EMBEDDING_MODEL }}\"\n{{- end }}\n{{- if .Env.EMBEDDING_DEVICE }}\n - name: EMBEDDING_DEVICE\n value: \"{{ .Env.EMBEDDING_DEVICE }}\"\n{{- end }}\n{{- if .Env.EMBEDDING_BATCH_SIZE }}\n - name: EMBEDDING_BATCH_SIZE\n value: \"{{ .Env.EMBEDDING_BATCH_SIZE }}\"\n{{- end }}\n # Environment\n{{- if .Env.ENVIRONMENT }}\n - name: ENVIRONMENT\n value: \"{{ .Env.ENVIRONMENT }}\"\n{{- end }}\n{{- if .Env.LOG_LEVEL }}\n - name: LOG_LEVEL\n value: \"{{ .Env.LOG_LEVEL }}\"\n{{- end }}\n probes:\n - type: Liveness\n httpGet:\n path: /health\n port: {{ targetPortOrDefault 8080 }}\n initialDelaySeconds: 30\n periodSeconds: 10\n timeoutSeconds: 5\n failureThreshold: 3\n - type: Readiness\n httpGet:\n path: /health\n port: {{ targetPortOrDefault 8080 }}\n initialDelaySeconds: 10\n periodSeconds: 5\n timeoutSeconds: 3\n failureThreshold: 3\n scale:\n minReplicas: 1\n maxReplicas: 3\ntags:\n azd-service-name: ai-service\n aspire-resource-name: ai-service\n\n"}, "4271e02ec2962a94c29b8bed160784d1ad882000": {"t": 1785748766.6867604, "n": 4776, "h": "0844c92634f91bb4", "c": "api-version: 2024-02-02-preview\nlocation: {{ .Env.AZURE_LOCATION }}\nidentity:\n type: UserAssigned\n userAssignedIdentities:\n ? \"{{ .Env.AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID }}\"\n : {}\nproperties:\n environmentId: {{ .Env.AZURE_CONTAINER_APPS_ENVIRONMENT_ID }}\n configuration:\n activeRevisionsMode: single\n ingress:\n external: true\n targetPort: {{ targetPortOrDefault 8080 }}\n transport: auto\n allowInsecure: true\n registries:\n - server: {{ .Env.AZURE_CONTAINER_REGISTRY_ENDPOINT }}\n identity: {{ .Env.AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID }}\n template:\n containers:\n - image: {{ .Image }}\n name: ai-service\n env:\n - name: PYTHONUNBUFFERED\n value: \"1\"\n - name: PYTHONDONTWRITEBYTECODE\n value: \"1\"\n{{- if .Env.RESOURCE_GRPC_ENDPOINT }}\n - name: RESOURCE_GRPC_ENDPOINT\n value: \"{{ .Env.RESOURCE_GRPC_ENDPOINT }}\"\n{{- end }}\n{{- if .Env.RESOURCE_GRPC_USE_TLS }}\n - name: RESOURCE_GRPC_USE_TLS\n value: \"{{ .Env.RESOURCE_GRPC_USE_TLS }}\"\n{{- end }}\n{{- if .Env.RESOURCE_GRPC_CERT_PATH }}\n - name: RESOURCE_GRPC_CERT_PATH\n value: \"{{ .Env.RESOURCE_GRPC_CERT_PATH }}\"\n{{- end }}\n{{- if .Env.RESOURCE_GRPC_OVERRIDE_AUTHORITY }}\n - name: RESOURCE_GRPC_OVERRIDE_AUTHORITY\n value: \"{{ .Env.RESOURCE_GRPC_OVERRIDE_AUTHORITY }}\"\n{{- end }}\n{{- if .Env.CLASSROOM_GRPC_ENDPOINT }}\n - name: CLASSROOM_GRPC_ENDPOINT\n value: \"{{ .Env.CLASSROOM_GRPC_ENDPOINT }}\"\n{{- end }}\n # LLM Configuration\n{{- if .Env.OPENAI_API_KEY }}\n - name: OPENAI_API_KEY\n value: \"{{ .Env.OPENAI_API_KEY }}\"\n{{- end }}\n{{- if .Env.OPENAI_BASE_URL }}\n - name: OPENAI_BASE_URL\n value: \"{{ .Env.OPENAI_BASE_URL }}\"\n{{- end }}\n{{- if .Env.LLM_MODEL }}\n - name: LLM_MODEL\n value: \"{{ .Env.LLM_MODEL }}\"\n{{- end }}\n{{- if .Env.LLM_TEMPERATURE }}\n - name: LLM_TEMPERATURE\n value: \"{{ .Env.LLM_TEMPERATURE }}\"\n{{- end }}\n{{- if .Env.LLM_MAX_TOKENS }}\n - name: LLM_MAX_TOKENS\n value: \"{{ .Env.LLM_MAX_TOKENS }}\"\n{{- end }}\n # DeepSeek Configuration (optional, alternative to OpenAI)\n{{- if .Env.DEEPSEEK_API_KEY }}\n - name: DEEPSEEK_API_KEY\n value: \"{{ .Env.DEEPSEEK_API_KEY }}\"\n{{- end }}\n{{- if .Env.DEEPSEEK_BASE_URL }}\n - name: DEEPSEEK_BASE_URL\n value: \"{{ .Env.DEEPSEEK_BASE_URL }}\"\n{{- end }}\n{{- if .Env.DEEPSEEK_MODEL }}\n - name: DEEPSEEK_MODEL\n value: \"{{ .Env.DEEPSEEK_MODEL }}\"\n{{- end }}\n # Content Generation Prompts\n{{- if .Env.CONTENT_GENERATION_SYSTEM_PROMPT }}\n - name: CONTENT_GENERATION_SYSTEM_PROMPT\n value: \"{{ .Env.CONTENT_GENERATION_SYSTEM_PROMPT }}\"\n{{- end }}\n{{- if .Env.CONTENT_GENERATION_SECTION_PROMPT_TEMPLATE }}\n - name: CONTENT_GENERATION_SECTION_PROMPT_TEMPLATE\n value: \"{{ .Env.CONTENT_GENERATION_SECTION_PROMPT_TEMPLATE }}\"\n{{- end }}\n # Vector Store (Qdrant)\n{{- if .Env.QDRANT_URL }}\n - name: QDRANT_URL\n value: \"{{ .Env.QDRANT_URL }}\"\n{{- end }}\n{{- if .Env.QDRANT_GRPC_URL }}\n - name: QDRANT_GRPC_URL\n value: \"{{ .Env.QDRANT_GRPC_URL }}\"\n{{- end }}\n{{- if .Env.QDRANT_COLLECTION }}\n - name: QDRANT_COLLECTION\n value: \"{{ .Env.QDRANT_COLLECTION }}\"\n{{- end }}\n # Embeddings\n{{- if .Env.EMBEDDING_MODEL }}\n - name: EMBEDDING_MODEL\n value: \"{{ .Env.EMBEDDING_MODEL }}\"\n{{- end }}\n{{- if .Env.EMBEDDING_DEVICE }}\n - name: EMBEDDING_DEVICE\n value: \"{{ .Env.EMBEDDING_DEVICE }}\"\n{{- end }}\n{{- if .Env.EMBEDDING_BATCH_SIZE }}\n - name: EMBEDDING_BATCH_SIZE\n value: \"{{ .Env.EMBEDDING_BATCH_SIZE }}\"\n{{- end }}\n # Environment\n{{- if .Env.ENVIRONMENT }}\n - name: ENVIRONMENT\n value: \"{{ .Env.ENVIRONMENT }}\"\n{{- end }}\n{{- if .Env.LOG_LEVEL }}\n - name: LOG_LEVEL\n value: \"{{ .Env.LOG_LEVEL }}\"\n{{- end }}\n probes:\n - type: Liveness\n httpGet:\n path: /health\n port: {{ targetPortOrDefault 8080 }}\n initialDelaySeconds: 30\n periodSeconds: 10\n timeoutSeconds: 5\n failureThreshold: 3\n - type: Readiness\n httpGet:\n path: /health\n port: {{ targetPortOrDefault 8080 }}\n initialDelaySeconds: 10\n periodSeconds: 5\n timeoutSeconds: 3\n failureThreshold: 3\n scale:\n minReplicas: 1\n maxReplicas: 3\ntags:\n azd-service-name: ai-service\n aspire-resource-name: ai-service\n\n"}, "f01100345a4dff1ac5bec54918eee8847df15aae": {"t": 1785748767.8036904, "n": 4628, "h": "ef327f5807515f00", "c": "api-version: 2024-02-02-preview\nlocation: {{ .Env.AZURE_LOCATION }}\nidentity:\n type: UserAssigned\n userAssignedIdentities:\n ? \"{{ .Env.AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID }}\"\n : {}\nproperties:\n environmentId: {{ .Env.AZURE_CONTAINER_APPS_ENVIRONMENT_ID }}\n configuration:\n activeRevisionsMode: single\n ingress:\n external: true\n targetPort: {{ targetPortOrDefault 8080 }}\n transport: auto\n allowInsecure: true\n registries:\n - server: {{ .Env.AZURE_CONTAINER_REGISTRY_ENDPOINT }}\n identity: {{ .Env.AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID }}\n template:\n containers:\n - image: {{ .Image }}\n name: ai-service\n env:\n - name: PYTHONUNBUFFERED\n value: \"1\"\n - name: PYTHONDONTWRITEBYTECODE\n value: \"1\"\n{{- if .Env.RESOURCE_GRPC_ENDPOINT }}\n - name: RESOURCE_GRPC_ENDPOINT\n value: \"{{ .Env.RESOURCE_GRPC_ENDPOINT }}\"\n{{- end }}\n{{- if .Env.RESOURCE_GRPC_USE_TLS }}\n - name: RESOURCE_GRPC_USE_TLS\n value: \"{{ .Env.RESOURCE_GRPC_USE_TLS }}\"\n{{- end }}\n{{- if .Env.RESOURCE_GRPC_CERT_PATH }}\n - name: RESOURCE_GRPC_CERT_PATH\n value: \"{{ .Env.RESOURCE_GRPC_CERT_PATH }}\"\n{{- end }}\n{{- if .Env.RESOURCE_GRPC_OVERRIDE_AUTHORITY }}\n - name: RESOURCE_GRPC_OVERRIDE_AUTHORITY\n value: \"{{ .Env.RESOURCE_GRPC_OVERRIDE_AUTHORITY }}\"\n{{- end }}\n # LLM Configuration\n{{- if .Env.OPENAI_API_KEY }}\n - name: OPENAI_API_KEY\n value: \"{{ .Env.OPENAI_API_KEY }}\"\n{{- end }}\n{{- if .Env.OPENAI_BASE_URL }}\n - name: OPENAI_BASE_URL\n value: \"{{ .Env.OPENAI_BASE_URL }}\"\n{{- end }}\n{{- if .Env.LLM_MODEL }}\n - name: LLM_MODEL\n value: \"{{ .Env.LLM_MODEL }}\"\n{{- end }}\n{{- if .Env.LLM_TEMPERATURE }}\n - name: LLM_TEMPERATURE\n value: \"{{ .Env.LLM_TEMPERATURE }}\"\n{{- end }}\n{{- if .Env.LLM_MAX_TOKENS }}\n - name: LLM_MAX_TOKENS\n value: \"{{ .Env.LLM_MAX_TOKENS }}\"\n{{- end }}\n # DeepSeek Configuration (optional, alternative to OpenAI)\n{{- if .Env.DEEPSEEK_API_KEY }}\n - name: DEEPSEEK_API_KEY\n value: \"{{ .Env.DEEPSEEK_API_KEY }}\"\n{{- end }}\n{{- if .Env.DEEPSEEK_BASE_URL }}\n - name: DEEPSEEK_BASE_URL\n value: \"{{ .Env.DEEPSEEK_BASE_URL }}\"\n{{- end }}\n{{- if .Env.DEEPSEEK_MODEL }}\n - name: DEEPSEEK_MODEL\n value: \"{{ .Env.DEEPSEEK_MODEL }}\"\n{{- end }}\n # Content Generation Prompts\n{{- if .Env.CONTENT_GENERATION_SYSTEM_PROMPT }}\n - name: CONTENT_GENERATION_SYSTEM_PROMPT\n value: \"{{ .Env.CONTENT_GENERATION_SYSTEM_PROMPT }}\"\n{{- end }}\n{{- if .Env.CONTENT_GENERATION_SECTION_PROMPT_TEMPLATE }}\n - name: CONTENT_GENERATION_SECTION_PROMPT_TEMPLATE\n value: \"{{ .Env.CONTENT_GENERATION_SECTION_PROMPT_TEMPLATE }}\"\n{{- end }}\n # Vector Store (Qdrant)\n{{- if .Env.QDRANT_URL }}\n - name: QDRANT_URL\n value: \"{{ .Env.QDRANT_URL }}\"\n{{- end }}\n{{- if .Env.QDRANT_GRPC_URL }}\n - name: QDRANT_GRPC_URL\n value: \"{{ .Env.QDRANT_GRPC_URL }}\"\n{{- end }}\n{{- if .Env.QDRANT_COLLECTION }}\n - name: QDRANT_COLLECTION\n value: \"{{ .Env.QDRANT_COLLECTION }}\"\n{{- end }}\n # Embeddings\n{{- if .Env.EMBEDDING_MODEL }}\n - name: EMBEDDING_MODEL\n value: \"{{ .Env.EMBEDDING_MODEL }}\"\n{{- end }}\n{{- if .Env.EMBEDDING_DEVICE }}\n - name: EMBEDDING_DEVICE\n value: \"{{ .Env.EMBEDDING_DEVICE }}\"\n{{- end }}\n{{- if .Env.EMBEDDING_BATCH_SIZE }}\n - name: EMBEDDING_BATCH_SIZE\n value: \"{{ .Env.EMBEDDING_BATCH_SIZE }}\"\n{{- end }}\n # Environment\n{{- if .Env.ENVIRONMENT }}\n - name: ENVIRONMENT\n value: \"{{ .Env.ENVIRONMENT }}\"\n{{- end }}\n{{- if .Env.LOG_LEVEL }}\n - name: LOG_LEVEL\n value: \"{{ .Env.LOG_LEVEL }}\"\n{{- end }}\n probes:\n - type: Liveness\n httpGet:\n path: /health\n port: {{ targetPortOrDefault 8080 }}\n initialDelaySeconds: 30\n periodSeconds: 10\n timeoutSeconds: 5\n failureThreshold: 3\n - type: Readiness\n httpGet:\n path: /health\n port: {{ targetPortOrDefault 8080 }}\n initialDelaySeconds: 10\n periodSeconds: 5\n timeoutSeconds: 3\n failureThreshold: 3\n scale:\n minReplicas: 1\n maxReplicas: 3\ntags:\n azd-service-name: ai-service\n aspire-resource-name: ai-service\n\n"}, "3bf8da6803c1687b5b7bd1b98771b766772f5e97": {"t": 1785748768.9139695, "n": 4716, "h": "b34d160e02547dc9", "c": "api-version: 2024-02-02-preview\nlocation: {{ .Env.AZURE_LOCATION }}\nidentity:\n type: UserAssigned\n userAssignedIdentities:\n ? \"{{ .Env.AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID }}\"\n : {}\nproperties:\n environmentId: {{ .Env.AZURE_CONTAINER_APPS_ENVIRONMENT_ID }}\n configuration:\n activeRevisionsMode: single\n ingress:\n external: true\n targetPort: {{ targetPortOrDefault 8080 }}\n transport: auto\n allowInsecure: true\n registries:\n - server: {{ .Env.AZURE_CONTAINER_REGISTRY_ENDPOINT }}\n identity: {{ .Env.AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID }}\n template:\n containers:\n - image: {{ .Image }}\n name: ai-service\n env:\n - name: PYTHONUNBUFFERED\n value: \"1\"\n - name: PYTHONDONTWRITEBYTECODE\n value: \"1\"\n - name: RESOURCE_GRPC_ENDPOINT\n value: {{ if .Env.RESOURCE_GRPC_ENDPOINT }}{{ .Env.RESOURCE_GRPC_ENDPOINT }}{{ else }}{{ end }}\n - name: RESOURCE_GRPC_USE_TLS\n value: {{ if .Env.RESOURCE_GRPC_USE_TLS }}{{ .Env.RESOURCE_GRPC_USE_TLS }}{{ else }}{{ end }}\n - name: RESOURCE_GRPC_CERT_PATH\n value: {{ if .Env.RESOURCE_GRPC_CERT_PATH }}{{ .Env.RESOURCE_GRPC_CERT_PATH }}{{ else }}{{ end }}\n - name: RESOURCE_GRPC_OVERRIDE_AUTHORITY\n value: {{ if .Env.RESOURCE_GRPC_OVERRIDE_AUTHORITY }}{{ .Env.RESOURCE_GRPC_OVERRIDE_AUTHORITY }}{{ else }}{{ end }}\n # LLM Configuration\n - name: OPENAI_API_KEY\n value: {{ if .Env.OPENAI_API_KEY }}{{ .Env.OPENAI_API_KEY }}{{ else }}{{ end }}\n - name: OPENAI_BASE_URL\n value: {{ if .Env.OPENAI_BASE_URL }}{{ .Env.OPENAI_BASE_URL }}{{ else }}{{ end }}\n - name: LLM_MODEL\n value: {{ if .Env.LLM_MODEL }}{{ .Env.LLM_MODEL }}{{ else }}{{ end }}\n - name: LLM_TEMPERATURE\n value: {{ if .Env.LLM_TEMPERATURE }}{{ .Env.LLM_TEMPERATURE }}{{ else }}{{ end }}\n - name: LLM_MAX_TOKENS\n value: {{ if .Env.LLM_MAX_TOKENS }}{{ .Env.LLM_MAX_TOKENS }}{{ else }}{{ end }}\n # DeepSeek Configuration (optional, alternative to OpenAI)\n - name: DEEPSEEK_API_KEY\n value: {{ if .Env.DEEPSEEK_API_KEY }}{{ .Env.DEEPSEEK_API_KEY }}{{ else }}{{ end }}\n - name: DEEPSEEK_BASE_URL\n value: {{ if .Env.DEEPSEEK_BASE_URL }}{{ .Env.DEEPSEEK_BASE_URL }}{{ else }}{{ end }}\n - name: DEEPSEEK_MODEL\n value: {{ if .Env.DEEPSEEK_MODEL }}{{ .Env.DEEPSEEK_MODEL }}{{ else }}{{ end }}\n # Content Generation Prompts\n - name: CONTENT_GENERATION_SYSTEM_PROMPT\n value: {{ if .Env.CONTENT_GENERATION_SYSTEM_PROMPT }}{{ .Env.CONTENT_GENERATION_SYSTEM_PROMPT }}{{ else }}{{ end }}\n - name: CONTENT_GENERATION_SECTION_PROMPT_TEMPLATE\n value: {{ if .Env.CONTENT_GENERATION_SECTION_PROMPT_TEMPLATE }}{{ .Env.CONTENT_GENERATION_SECTION_PROMPT_TEMPLATE }}{{ else }}{{ end }}\n # Vector Store (Qdrant)\n - name: QDRANT_URL\n value: {{ if .Env.QDRANT_URL }}{{ .Env.QDRANT_URL }}{{ else }}{{ end }}\n - name: QDRANT_GRPC_URL\n value: {{ if .Env.QDRANT_GRPC_URL }}{{ .Env.QDRANT_GRPC_URL }}{{ else }}{{ end }}\n - name: QDRANT_COLLECTION\n value: {{ if .Env.QDRANT_COLLECTION }}{{ .Env.QDRANT_COLLECTION }}{{ else }}{{ end }}\n # Embeddings\n - name: EMBEDDING_MODEL\n value: {{ if .Env.EMBEDDING_MODEL }}{{ .Env.EMBEDDING_MODEL }}{{ else }}{{ end }}\n - name: EMBEDDING_DEVICE\n value: {{ if .Env.EMBEDDING_DEVICE }}{{ .Env.EMBEDDING_DEVICE }}{{ else }}{{ end }}\n - name: EMBEDDING_BATCH_SIZE\n value: {{ if .Env.EMBEDDING_BATCH_SIZE }}{{ .Env.EMBEDDING_BATCH_SIZE }}{{ else }}{{ end }}\n # Environment\n - name: ENVIRONMENT\n value: {{ if .Env.ENVIRONMENT }}{{ .Env.ENVIRONMENT }}{{ else }}{{ end }}\n - name: LOG_LEVEL\n value: {{ if .Env.LOG_LEVEL }}{{ .Env.LOG_LEVEL }}{{ else }}{{ end }}\n probes:\n - type: Liveness\n httpGet:\n path: /health\n port: {{ targetPortOrDefault 8080 }}\n initialDelaySeconds: 30\n periodSeconds: 10\n timeoutSeconds: 5\n failureThreshold: 3\n - type: Readiness\n httpGet:\n path: /health\n port: {{ targetPortOrDefault 8080 }}\n initialDelaySeconds: 10\n periodSeconds: 5\n timeoutSeconds: 3\n failureThreshold: 3\n scale:\n minReplicas: 1\n maxReplicas: 3\ntags:\n azd-service-name: ai-service\n aspire-resource-name: ai-service\n\n"}, "4193ce53c198ab14ec158755c410fc7a0a9060fe": {"t": 1785748769.9841285, "n": 5068, "h": "a868ef449c632a7e", "c": "api-version: 2024-02-02-preview\nlocation: {{ .Env.AZURE_LOCATION }}\nidentity:\n type: UserAssigned\n userAssignedIdentities:\n ? \"{{ .Env.AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID }}\"\n : {}\nproperties:\n environmentId: {{ .Env.AZURE_CONTAINER_APPS_ENVIRONMENT_ID }}\n configuration:\n activeRevisionsMode: single\n ingress:\n external: true\n targetPort: {{ targetPortOrDefault 8080 }}\n transport: auto\n allowInsecure: true\n registries:\n - server: {{ .Env.AZURE_CONTAINER_REGISTRY_ENDPOINT }}\n identity: {{ .Env.AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID }}\n template:\n containers:\n - image: {{ .Image }}\n name: ai-service\n env:\n - name: PYTHONUNBUFFERED\n value: \"1\"\n - name: PYTHONDONTWRITEBYTECODE\n value: \"1\"\n {{- if .Env.RESOURCE_GRPC_ENDPOINT }}\n - name: RESOURCE_GRPC_ENDPOINT\n value: \"{{ .Env.RESOURCE_GRPC_ENDPOINT }}\"\n {{- end }}\n {{- if .Env.RESOURCE_GRPC_USE_TLS }}\n - name: RESOURCE_GRPC_USE_TLS\n value: \"{{ .Env.RESOURCE_GRPC_USE_TLS }}\"\n {{- end }}\n {{- if .Env.RESOURCE_GRPC_CERT_PATH }}\n - name: RESOURCE_GRPC_CERT_PATH\n value: \"{{ .Env.RESOURCE_GRPC_CERT_PATH }}\"\n {{- end }}\n {{- if .Env.RESOURCE_GRPC_OVERRIDE_AUTHORITY }}\n - name: RESOURCE_GRPC_OVERRIDE_AUTHORITY\n value: \"{{ .Env.RESOURCE_GRPC_OVERRIDE_AUTHORITY }}\"\n {{- end }}\n # LLM Configuration\n {{- if .Env.OPENAI_API_KEY }}\n - name: OPENAI_API_KEY\n value: \"{{ .Env.OPENAI_API_KEY }}\"\n {{- end }}\n {{- if .Env.OPENAI_BASE_URL }}\n - name: OPENAI_BASE_URL\n value: \"{{ .Env.OPENAI_BASE_URL }}\"\n {{- end }}\n {{- if .Env.LLM_MODEL }}\n - name: LLM_MODEL\n value: \"{{ .Env.LLM_MODEL }}\"\n {{- end }}\n {{- if .Env.LLM_TEMPERATURE }}\n - name: LLM_TEMPERATURE\n value: \"{{ .Env.LLM_TEMPERATURE }}\"\n {{- end }}\n {{- if .Env.LLM_MAX_TOKENS }}\n - name: LLM_MAX_TOKENS\n value: \"{{ .Env.LLM_MAX_TOKENS }}\"\n {{- end }}\n # DeepSeek Configuration (optional, alternative to OpenAI)\n {{- if .Env.DEEPSEEK_API_KEY }}\n - name: DEEPSEEK_API_KEY\n value: \"{{ .Env.DEEPSEEK_API_KEY }}\"\n {{- end }}\n {{- if .Env.DEEPSEEK_BASE_URL }}\n - name: DEEPSEEK_BASE_URL\n value: \"{{ .Env.DEEPSEEK_BASE_URL }}\"\n {{- end }}\n {{- if .Env.DEEPSEEK_MODEL }}\n - name: DEEPSEEK_MODEL\n value: \"{{ .Env.DEEPSEEK_MODEL }}\"\n {{- end }}\n # Content Generation Prompts\n {{- if .Env.CONTENT_GENERATION_SYSTEM_PROMPT }}\n - name: CONTENT_GENERATION_SYSTEM_PROMPT\n value: \"{{ .Env.CONTENT_GENERATION_SYSTEM_PROMPT }}\"\n {{- end }}\n {{- if .Env.CONTENT_GENERATION_SECTION_PROMPT_TEMPLATE }}\n - name: CONTENT_GENERATION_SECTION_PROMPT_TEMPLATE\n value: \"{{ .Env.CONTENT_GENERATION_SECTION_PROMPT_TEMPLATE }}\"\n {{- end }}\n # Vector Store (Qdrant)\n {{- if .Env.QDRANT_URL }}\n - name: QDRANT_URL\n value: \"{{ .Env.QDRANT_URL }}\"\n {{- end }}\n {{- if .Env.QDRANT_GRPC_URL }}\n - name: QDRANT_GRPC_URL\n value: \"{{ .Env.QDRANT_GRPC_URL }}\"\n {{- end }}\n {{- if .Env.QDRANT_COLLECTION }}\n - name: QDRANT_COLLECTION\n value: \"{{ .Env.QDRANT_COLLECTION }}\"\n {{- end }}\n # Embeddings\n {{- if .Env.EMBEDDING_MODEL }}\n - name: EMBEDDING_MODEL\n value: \"{{ .Env.EMBEDDING_MODEL }}\"\n {{- end }}\n {{- if .Env.EMBEDDING_DEVICE }}\n - name: EMBEDDING_DEVICE\n value: \"{{ .Env.EMBEDDING_DEVICE }}\"\n {{- end }}\n {{- if .Env.EMBEDDING_BATCH_SIZE }}\n - name: EMBEDDING_BATCH_SIZE\n value: \"{{ .Env.EMBEDDING_BATCH_SIZE }}\"\n {{- end }}\n # Environment\n {{- if .Env.ENVIRONMENT }}\n - name: ENVIRONMENT\n value: \"{{ .Env.ENVIRONMENT }}\"\n {{- end }}\n {{- if .Env.LOG_LEVEL }}\n - name: LOG_LEVEL\n value: \"{{ .Env.LOG_LEVEL }}\"\n {{- end }}\n probes:\n - type: Liveness\n httpGet:\n path: /health\n port: {{ targetPortOrDefault 8080 }}\n initialDelaySeconds: 30\n periodSeconds: 10\n timeoutSeconds: 5\n failureThreshold: 3\n - type: Readiness\n httpGet:\n path: /health\n port: {{ targetPortOrDefault 8080 }}\n initialDelaySeconds: 10\n periodSeconds: 5\n timeoutSeconds: 3\n failureThreshold: 3\n scale:\n minReplicas: 1\n maxReplicas: 3\ntags:\n azd-service-name: ai-service\n aspire-resource-name: ai-service\n\n"}, "1cc1ab92bc76cc07bb6ba66735c6d7eb393d24f1": {"t": 1785748771.4762928, "n": 5024, "h": "dfe5644420b95fb4", "c": "api-version: 2024-02-02-preview\nlocation: {{ .Env.AZURE_LOCATION }}\nidentity:\n type: UserAssigned\n userAssignedIdentities:\n ? \"{{ .Env.AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID }}\"\n : {}\nproperties:\n environmentId: {{ .Env.AZURE_CONTAINER_APPS_ENVIRONMENT_ID }}\n configuration:\n activeRevisionsMode: single\n ingress:\n external: true\n targetPort: {{ targetPortOrDefault 8080 }}\n transport: auto\n allowInsecure: true\n registries:\n - server: {{ .Env.AZURE_CONTAINER_REGISTRY_ENDPOINT }}\n identity: {{ .Env.AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID }}\n template:\n containers:\n - image: {{ .Image }}\n name: ai-service\n env:\n - name: PYTHONUNBUFFERED\n value: \"1\"\n - name: PYTHONDONTWRITEBYTECODE\n value: \"1\"\n {{ if .Env.RESOURCE_GRPC_ENDPOINT }}\n - name: RESOURCE_GRPC_ENDPOINT\n value: \"{{ .Env.RESOURCE_GRPC_ENDPOINT }}\"\n {{ end }}\n {{ if .Env.RESOURCE_GRPC_USE_TLS }}\n - name: RESOURCE_GRPC_USE_TLS\n value: \"{{ .Env.RESOURCE_GRPC_USE_TLS }}\"\n {{ end }}\n {{ if .Env.RESOURCE_GRPC_CERT_PATH }}\n - name: RESOURCE_GRPC_CERT_PATH\n value: \"{{ .Env.RESOURCE_GRPC_CERT_PATH }}\"\n {{ end }}\n {{ if .Env.RESOURCE_GRPC_OVERRIDE_AUTHORITY }}\n - name: RESOURCE_GRPC_OVERRIDE_AUTHORITY\n value: \"{{ .Env.RESOURCE_GRPC_OVERRIDE_AUTHORITY }}\"\n {{ end }}\n # LLM Configuration\n {{ if .Env.OPENAI_API_KEY }}\n - name: OPENAI_API_KEY\n value: \"{{ .Env.OPENAI_API_KEY }}\"\n {{ end }}\n {{ if .Env.OPENAI_BASE_URL }}\n - name: OPENAI_BASE_URL\n value: \"{{ .Env.OPENAI_BASE_URL }}\"\n {{ end }}\n {{ if .Env.LLM_MODEL }}\n - name: LLM_MODEL\n value: \"{{ .Env.LLM_MODEL }}\"\n {{ end }}\n {{ if .Env.LLM_TEMPERATURE }}\n - name: LLM_TEMPERATURE\n value: \"{{ .Env.LLM_TEMPERATURE }}\"\n {{ end }}\n {{ if .Env.LLM_MAX_TOKENS }}\n - name: LLM_MAX_TOKENS\n value: \"{{ .Env.LLM_MAX_TOKENS }}\"\n {{ end }}\n # DeepSeek Configuration (optional, alternative to OpenAI)\n {{ if .Env.DEEPSEEK_API_KEY }}\n - name: DEEPSEEK_API_KEY\n value: \"{{ .Env.DEEPSEEK_API_KEY }}\"\n {{ end }}\n {{ if .Env.DEEPSEEK_BASE_URL }}\n - name: DEEPSEEK_BASE_URL\n value: \"{{ .Env.DEEPSEEK_BASE_URL }}\"\n {{ end }}\n {{ if .Env.DEEPSEEK_MODEL }}\n - name: DEEPSEEK_MODEL\n value: \"{{ .Env.DEEPSEEK_MODEL }}\"\n {{ end }}\n # Content Generation Prompts\n {{ if .Env.CONTENT_GENERATION_SYSTEM_PROMPT }}\n - name: CONTENT_GENERATION_SYSTEM_PROMPT\n value: \"{{ .Env.CONTENT_GENERATION_SYSTEM_PROMPT }}\"\n {{ end }}\n {{ if .Env.CONTENT_GENERATION_SECTION_PROMPT_TEMPLATE }}\n - name: CONTENT_GENERATION_SECTION_PROMPT_TEMPLATE\n value: \"{{ .Env.CONTENT_GENERATION_SECTION_PROMPT_TEMPLATE }}\"\n {{ end }}\n # Vector Store (Qdrant)\n {{ if .Env.QDRANT_URL }}\n - name: QDRANT_URL\n value: \"{{ .Env.QDRANT_URL }}\"\n {{ end }}\n {{ if .Env.QDRANT_GRPC_URL }}\n - name: QDRANT_GRPC_URL\n value: \"{{ .Env.QDRANT_GRPC_URL }}\"\n {{ end }}\n {{ if .Env.QDRANT_COLLECTION }}\n - name: QDRANT_COLLECTION\n value: \"{{ .Env.QDRANT_COLLECTION }}\"\n {{ end }}\n # Embeddings\n {{ if .Env.EMBEDDING_MODEL }}\n - name: EMBEDDING_MODEL\n value: \"{{ .Env.EMBEDDING_MODEL }}\"\n {{ end }}\n {{ if .Env.EMBEDDING_DEVICE }}\n - name: EMBEDDING_DEVICE\n value: \"{{ .Env.EMBEDDING_DEVICE }}\"\n {{ end }}\n {{ if .Env.EMBEDDING_BATCH_SIZE }}\n - name: EMBEDDING_BATCH_SIZE\n value: \"{{ .Env.EMBEDDING_BATCH_SIZE }}\"\n {{ end }}\n # Environment\n {{ if .Env.ENVIRONMENT }}\n - name: ENVIRONMENT\n value: \"{{ .Env.ENVIRONMENT }}\"\n {{ end }}\n {{ if .Env.LOG_LEVEL }}\n - name: LOG_LEVEL\n value: \"{{ .Env.LOG_LEVEL }}\"\n {{ end }}\n probes:\n - type: Liveness\n httpGet:\n path: /health\n port: {{ targetPortOrDefault 8080 }}\n initialDelaySeconds: 30\n periodSeconds: 10\n timeoutSeconds: 5\n failureThreshold: 3\n - type: Readiness\n httpGet:\n path: /health\n port: {{ targetPortOrDefault 8080 }}\n initialDelaySeconds: 10\n periodSeconds: 5\n timeoutSeconds: 3\n failureThreshold: 3\n scale:\n minReplicas: 1\n maxReplicas: 3\ntags:\n azd-service-name: ai-service\n aspire-resource-name: ai-service\n\n"}, "fc50c143630b107773b8e1b5a2f02463251afdcf": {"t": 1785748772.5852344, "n": 5039, "h": "472fad97ef661781", "c": "api-version: 2024-02-02-preview\nlocation: {{ .Env.AZURE_LOCATION }}\nidentity:\n type: UserAssigned\n userAssignedIdentities:\n ? \"{{ .Env.AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID }}\"\n : {}\nproperties:\n environmentId: {{ .Env.AZURE_CONTAINER_APPS_ENVIRONMENT_ID }}\n configuration:\n activeRevisionsMode: single\n ingress:\n external: true\n targetPort: {{ targetPortOrDefault 8080 }}\n transport: auto\n allowInsecure: true\n registries:\n - server: {{ .Env.AZURE_CONTAINER_REGISTRY_ENDPOINT }}\n identity: {{ .Env.AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID }}\n template:\n containers:\n - image: {{ .Image }}\n name: ai-service\n env:\n - name: PYTHONUNBUFFERED\n value: \"1\"\n - name: PYTHONDONTWRITEBYTECODE\n value: \"1\"\n - name: RESOURCE_GRPC_ENDPOINT\n value: \"{{ if .Env.RESOURCE_GRPC_ENDPOINT }}{{ .Env.RESOURCE_GRPC_ENDPOINT }}{{ else }}resource-api:5082{{ end }}\"\n - name: RESOURCE_GRPC_USE_TLS\n value: \"{{ if .Env.RESOURCE_GRPC_USE_TLS }}{{ .Env.RESOURCE_GRPC_USE_TLS }}{{ else }}false{{ end }}\"\n - name: RESOURCE_GRPC_CERT_PATH\n value: {{ if .Env.RESOURCE_GRPC_CERT_PATH }}\"{{ .Env.RESOURCE_GRPC_CERT_PATH }}\"{{ else }}\"\"{{ end }}\n - name: RESOURCE_GRPC_OVERRIDE_AUTHORITY\n value: {{ if .Env.RESOURCE_GRPC_OVERRIDE_AUTHORITY }}\"{{ .Env.RESOURCE_GRPC_OVERRIDE_AUTHORITY }}\"{{ else }}\"localhost\"{{ end }}\n # LLM Configuration\n - name: OPENAI_API_KEY\n value: {{ if .Env.OPENAI_API_KEY }}\"{{ .Env.OPENAI_API_KEY }}\"{{ else }}\"\"{{ end }}\n - name: OPENAI_BASE_URL\n value: \"{{ if .Env.OPENAI_BASE_URL }}{{ .Env.OPENAI_BASE_URL }}{{ else }}https://api.openai.com/v1{{ end }}\"\n - name: LLM_MODEL\n value: \"{{ if .Env.LLM_MODEL }}{{ .Env.LLM_MODEL }}{{ else }}gpt-4o-mini{{ end }}\"\n - name: LLM_TEMPERATURE\n value: \"{{ if .Env.LLM_TEMPERATURE }}{{ .Env.LLM_TEMPERATURE }}{{ else }}0.7{{ end }}\"\n - name: LLM_MAX_TOKENS\n value: \"{{ if .Env.LLM_MAX_TOKENS }}{{ .Env.LLM_MAX_TOKENS }}{{ else }}2000{{ end }}\"\n # DeepSeek Configuration (optional, alternative to OpenAI)\n - name: DEEPSEEK_API_KEY\n value: {{ if .Env.DEEPSEEK_API_KEY }}\"{{ .Env.DEEPSEEK_API_KEY }}\"{{ else }}\"\"{{ end }}\n - name: DEEPSEEK_BASE_URL\n value: \"{{ if .Env.DEEPSEEK_BASE_URL }}{{ .Env.DEEPSEEK_BASE_URL }}{{ else }}https://api.deepseek.com/v1{{ end }}\"\n - name: DEEPSEEK_MODEL\n value: \"{{ if .Env.DEEPSEEK_MODEL }}{{ .Env.DEEPSEEK_MODEL }}{{ else }}deepseek-chat{{ end }}\"\n # Content Generation Prompts\n - name: CONTENT_GENERATION_SYSTEM_PROMPT\n value: \"{{ if .Env.CONTENT_GENERATION_SYSTEM_PROMPT }}{{ .Env.CONTENT_GENERATION_SYSTEM_PROMPT }}{{ else }}You are a helpful educational designer.{{ end }}\"\n - name: CONTENT_GENERATION_SECTION_PROMPT_TEMPLATE\n value: {{ if .Env.CONTENT_GENERATION_SECTION_PROMPT_TEMPLATE }}\"{{ .Env.CONTENT_GENERATION_SECTION_PROMPT_TEMPLATE }}\"{{ else }}\"\"{{ end }}\n # Vector Store (Qdrant)\n - name: QDRANT_URL\n value: \"{{ if .Env.QDRANT_URL }}{{ .Env.QDRANT_URL }}{{ else }}http://localhost:6333{{ end }}\"\n - name: QDRANT_GRPC_URL\n value: \"{{ if .Env.QDRANT_GRPC_URL }}{{ .Env.QDRANT_GRPC_URL }}{{ else }}http://localhost:6334{{ end }}\"\n - name: QDRANT_COLLECTION\n value: \"{{ if .Env.QDRANT_COLLECTION }}{{ .Env.QDRANT_COLLECTION }}{{ else }}classroom_insights{{ end }}\"\n # Embeddings\n - name: EMBEDDING_MODEL\n value: \"{{ if .Env.EMBEDDING_MODEL }}{{ .Env.EMBEDDING_MODEL }}{{ else }}paraphrase-multilingual-MiniLM-L12-v2{{ end }}\"\n - name: EMBEDDING_DEVICE\n value: \"{{ if .Env.EMBEDDING_DEVICE }}{{ .Env.EMBEDDING_DEVICE }}{{ else }}cpu{{ end }}\"\n - name: EMBEDDING_BATCH_SIZE\n value: \"{{ if .Env.EMBEDDING_BATCH_SIZE }}{{ .Env.EMBEDDING_BATCH_SIZE }}{{ else }}32{{ end }}\"\n # Environment\n - name: ENVIRONMENT\n value: \"{{ if .Env.ENVIRONMENT }}{{ .Env.ENVIRONMENT }}{{ else }}Production{{ end }}\"\n - name: LOG_LEVEL\n value: \"{{ if .Env.LOG_LEVEL }}{{ .Env.LOG_LEVEL }}{{ else }}INFO{{ end }}\"\n probes:\n - type: Liveness\n httpGet:\n path: /health\n port: {{ targetPortOrDefault 8080 }}\n initialDelaySeconds: 30\n periodSeconds: 10\n timeoutSeconds: 5\n failureThreshold: 3\n - type: Readiness\n httpGet:\n path: /health\n port: {{ targetPortOrDefault 8080 }}\n initialDelaySeconds: 10\n periodSeconds: 5\n timeoutSeconds: 3\n failureThreshold: 3\n scale:\n minReplicas: 1\n maxReplicas: 3\ntags:\n azd-service-name: ai-service\n aspire-resource-name: ai-service\n\n"}, "4a17beb05aebb9028236bce6a532451d57368aea": {"t": 1785748773.6945581, "n": 5045, "h": "e1c953c10d60dd20", "c": "api-version: 2024-02-02-preview\nlocation: {{ .Env.AZURE_LOCATION }}\nidentity:\n type: UserAssigned\n userAssignedIdentities:\n ? \"{{ .Env.AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID }}\"\n : {}\nproperties:\n environmentId: {{ .Env.AZURE_CONTAINER_APPS_ENVIRONMENT_ID }}\n configuration:\n activeRevisionsMode: single\n ingress:\n external: true\n targetPort: {{ targetPortOrDefault 8080 }}\n transport: auto\n allowInsecure: true\n registries:\n - server: {{ .Env.AZURE_CONTAINER_REGISTRY_ENDPOINT }}\n identity: {{ .Env.AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID }}\n template:\n containers:\n - image: {{ .Image }}\n name: ai-service\n env:\n - name: PYTHONUNBUFFERED\n value: \"1\"\n - name: PYTHONDONTWRITEBYTECODE\n value: \"1\"\n - name: RESOURCE_GRPC_ENDPOINT\n value: \"{{ if .Env.RESOURCE_GRPC_ENDPOINT }}{{ .Env.RESOURCE_GRPC_ENDPOINT }}{{ else }}resource-api:5082{{ end }}\"\n - name: RESOURCE_GRPC_USE_TLS\n value: \"{{ if .Env.RESOURCE_GRPC_USE_TLS }}{{ .Env.RESOURCE_GRPC_USE_TLS }}{{ else }}false{{ end }}\"\n - name: RESOURCE_GRPC_CERT_PATH\n value: \"{{ if .Env.RESOURCE_GRPC_CERT_PATH }}{{ .Env.RESOURCE_GRPC_CERT_PATH }}{{ else }}\\\"\\\"{{ end }}\"\n - name: RESOURCE_GRPC_OVERRIDE_AUTHORITY\n value: \"{{ if .Env.RESOURCE_GRPC_OVERRIDE_AUTHORITY }}{{ .Env.RESOURCE_GRPC_OVERRIDE_AUTHORITY }}{{ else }}localhost{{ end }}\"\n # LLM Configuration\n - name: OPENAI_API_KEY\n value: \"{{ if .Env.OPENAI_API_KEY }}{{ .Env.OPENAI_API_KEY }}{{ else }}\\\"\\\"{{ end }}\"\n - name: OPENAI_BASE_URL\n value: \"{{ if .Env.OPENAI_BASE_URL }}{{ .Env.OPENAI_BASE_URL }}{{ else }}https://api.openai.com/v1{{ end }}\"\n - name: LLM_MODEL\n value: \"{{ if .Env.LLM_MODEL }}{{ .Env.LLM_MODEL }}{{ else }}gpt-4o-mini{{ end }}\"\n - name: LLM_TEMPERATURE\n value: \"{{ if .Env.LLM_TEMPERATURE }}{{ .Env.LLM_TEMPERATURE }}{{ else }}0.7{{ end }}\"\n - name: LLM_MAX_TOKENS\n value: \"{{ if .Env.LLM_MAX_TOKENS }}{{ .Env.LLM_MAX_TOKENS }}{{ else }}2000{{ end }}\"\n # DeepSeek Configuration (optional, alternative to OpenAI)\n - name: DEEPSEEK_API_KEY\n value: \"{{ if .Env.DEEPSEEK_API_KEY }}{{ .Env.DEEPSEEK_API_KEY }}{{ else }}\\\"\\\"{{ end }}\"\n - name: DEEPSEEK_BASE_URL\n value: \"{{ if .Env.DEEPSEEK_BASE_URL }}{{ .Env.DEEPSEEK_BASE_URL }}{{ else }}https://api.deepseek.com/v1{{ end }}\"\n - name: DEEPSEEK_MODEL\n value: \"{{ if .Env.DEEPSEEK_MODEL }}{{ .Env.DEEPSEEK_MODEL }}{{ else }}deepseek-chat{{ end }}\"\n # Content Generation Prompts\n - name: CONTENT_GENERATION_SYSTEM_PROMPT\n value: \"{{ if .Env.CONTENT_GENERATION_SYSTEM_PROMPT }}{{ .Env.CONTENT_GENERATION_SYSTEM_PROMPT }}{{ else }}You are a helpful educational designer.{{ end }}\"\n - name: CONTENT_GENERATION_SECTION_PROMPT_TEMPLATE\n value: \"{{ if .Env.CONTENT_GENERATION_SECTION_PROMPT_TEMPLATE }}{{ .Env.CONTENT_GENERATION_SECTION_PROMPT_TEMPLATE }}{{ else }}\\\"\\\"{{ end }}\"\n # Vector Store (Qdrant)\n - name: QDRANT_URL\n value: \"{{ if .Env.QDRANT_URL }}{{ .Env.QDRANT_URL }}{{ else }}http://localhost:6333{{ end }}\"\n - name: QDRANT_GRPC_URL\n value: \"{{ if .Env.QDRANT_GRPC_URL }}{{ .Env.QDRANT_GRPC_URL }}{{ else }}http://localhost:6334{{ end }}\"\n - name: QDRANT_COLLECTION\n value: \"{{ if .Env.QDRANT_COLLECTION }}{{ .Env.QDRANT_COLLECTION }}{{ else }}classroom_insights{{ end }}\"\n # Embeddings\n - name: EMBEDDING_MODEL\n value: \"{{ if .Env.EMBEDDING_MODEL }}{{ .Env.EMBEDDING_MODEL }}{{ else }}paraphrase-multilingual-MiniLM-L12-v2{{ end }}\"\n - name: EMBEDDING_DEVICE\n value: \"{{ if .Env.EMBEDDING_DEVICE }}{{ .Env.EMBEDDING_DEVICE }}{{ else }}cpu{{ end }}\"\n - name: EMBEDDING_BATCH_SIZE\n value: \"{{ if .Env.EMBEDDING_BATCH_SIZE }}{{ .Env.EMBEDDING_BATCH_SIZE }}{{ else }}32{{ end }}\"\n # Environment\n - name: ENVIRONMENT\n value: \"{{ if .Env.ENVIRONMENT }}{{ .Env.ENVIRONMENT }}{{ else }}Production{{ end }}\"\n - name: LOG_LEVEL\n value: \"{{ if .Env.LOG_LEVEL }}{{ .Env.LOG_LEVEL }}{{ else }}INFO{{ end }}\"\n probes:\n - type: Liveness\n httpGet:\n path: /health\n port: {{ targetPortOrDefault 8080 }}\n initialDelaySeconds: 30\n periodSeconds: 10\n timeoutSeconds: 5\n failureThreshold: 3\n - type: Readiness\n httpGet:\n path: /health\n port: {{ targetPortOrDefault 8080 }}\n initialDelaySeconds: 10\n periodSeconds: 5\n timeoutSeconds: 3\n failureThreshold: 3\n scale:\n minReplicas: 1\n maxReplicas: 3\ntags:\n azd-service-name: ai-service\n aspire-resource-name: ai-service\n\n"}, "7326bfad95bb444a3d4c8c19a99e4eeef9ea6cc0": {"t": 1785748774.799012, "n": 4986, "h": "4129ee98029c5c3e", "c": "api-version: 2024-02-02-preview\nlocation: {{ .Env.AZURE_LOCATION }}\nidentity:\n type: UserAssigned\n userAssignedIdentities:\n ? \"{{ .Env.AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID }}\"\n : {}\nproperties:\n environmentId: {{ .Env.AZURE_CONTAINER_APPS_ENVIRONMENT_ID }}\n configuration:\n activeRevisionsMode: single\n ingress:\n external: true\n targetPort: {{ targetPortOrDefault 8080 }}\n transport: auto\n allowInsecure: true\n registries:\n - server: {{ .Env.AZURE_CONTAINER_REGISTRY_ENDPOINT }}\n identity: {{ .Env.AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID }}\n template:\n containers:\n - image: {{ .Image }}\n name: ai-service\n env:\n - name: PYTHONUNBUFFERED\n value: \"1\"\n - name: PYTHONDONTWRITEBYTECODE\n value: \"1\"\n - name: RESOURCE_GRPC_ENDPOINT\n value: {{ if .Env.RESOURCE_GRPC_ENDPOINT }}{{ .Env.RESOURCE_GRPC_ENDPOINT }}{{ else }}resource-api:5082{{ end }}\n - name: RESOURCE_GRPC_USE_TLS\n value: {{ if .Env.RESOURCE_GRPC_USE_TLS }}{{ .Env.RESOURCE_GRPC_USE_TLS }}{{ else }}false{{ end }}\n - name: RESOURCE_GRPC_CERT_PATH\n value: {{ if .Env.RESOURCE_GRPC_CERT_PATH }}{{ .Env.RESOURCE_GRPC_CERT_PATH }}{{ else }}{{ end }}\n - name: RESOURCE_GRPC_OVERRIDE_AUTHORITY\n value: {{ if .Env.RESOURCE_GRPC_OVERRIDE_AUTHORITY }}{{ .Env.RESOURCE_GRPC_OVERRIDE_AUTHORITY }}{{ else }}localhost{{ end }}\n # LLM Configuration\n - name: OPENAI_API_KEY\n value: {{ if .Env.OPENAI_API_KEY }}{{ .Env.OPENAI_API_KEY }}{{ else }}{{ end }}\n - name: OPENAI_BASE_URL\n value: {{ if .Env.OPENAI_BASE_URL }}{{ .Env.OPENAI_BASE_URL }}{{ else }}https://api.openai.com/v1{{ end }}\n - name: LLM_MODEL\n value: {{ if .Env.LLM_MODEL }}{{ .Env.LLM_MODEL }}{{ else }}gpt-4o-mini{{ end }}\n - name: LLM_TEMPERATURE\n value: {{ if .Env.LLM_TEMPERATURE }}{{ .Env.LLM_TEMPERATURE }}{{ else }}0.7{{ end }}\n - name: LLM_MAX_TOKENS\n value: {{ if .Env.LLM_MAX_TOKENS }}{{ .Env.LLM_MAX_TOKENS }}{{ else }}2000{{ end }}\n # DeepSeek Configuration (optional, alternative to OpenAI)\n - name: DEEPSEEK_API_KEY\n value: {{ if .Env.DEEPSEEK_API_KEY }}{{ .Env.DEEPSEEK_API_KEY }}{{ else }}{{ end }}\n - name: DEEPSEEK_BASE_URL\n value: {{ if .Env.DEEPSEEK_BASE_URL }}{{ .Env.DEEPSEEK_BASE_URL }}{{ else }}https://api.deepseek.com/v1{{ end }}\n - name: DEEPSEEK_MODEL\n value: {{ if .Env.DEEPSEEK_MODEL }}{{ .Env.DEEPSEEK_MODEL }}{{ else }}deepseek-chat{{ end }}\n # Content Generation Prompts\n - name: CONTENT_GENERATION_SYSTEM_PROMPT\n value: {{ if .Env.CONTENT_GENERATION_SYSTEM_PROMPT }}{{ .Env.CONTENT_GENERATION_SYSTEM_PROMPT }}{{ else }}You are a helpful educational designer.{{ end }}\n - name: CONTENT_GENERATION_SECTION_PROMPT_TEMPLATE\n value: {{ if .Env.CONTENT_GENERATION_SECTION_PROMPT_TEMPLATE }}{{ .Env.CONTENT_GENERATION_SECTION_PROMPT_TEMPLATE }}{{ else }} {{ end }}\n # Vector Store (Qdrant)\n - name: QDRANT_URL\n value: {{ if .Env.QDRANT_URL }}{{ .Env.QDRANT_URL }}{{ else }}http://localhost:6333{{ end }}\n - name: QDRANT_GRPC_URL\n value: {{ if .Env.QDRANT_GRPC_URL }}{{ .Env.QDRANT_GRPC_URL }}{{ else }}http://localhost:6334{{ end }}\n - name: QDRANT_COLLECTION\n value: {{ if .Env.QDRANT_COLLECTION }}{{ .Env.QDRANT_COLLECTION }}{{ else }}classroom_insights{{ end }}\n # Embeddings\n - name: EMBEDDING_MODEL\n value: {{ if .Env.EMBEDDING_MODEL }}{{ .Env.EMBEDDING_MODEL }}{{ else }}paraphrase-multilingual-MiniLM-L12-v2{{ end }}\n - name: EMBEDDING_DEVICE\n value: {{ if .Env.EMBEDDING_DEVICE }}{{ .Env.EMBEDDING_DEVICE }}{{ else }}cpu{{ end }}\n - name: EMBEDDING_BATCH_SIZE\n value: {{ if .Env.EMBEDDING_BATCH_SIZE }}{{ .Env.EMBEDDING_BATCH_SIZE }}{{ else }}32{{ end }}\n # Environment\n - name: ENVIRONMENT\n value: {{ if .Env.ENVIRONMENT }}{{ .Env.ENVIRONMENT }}{{ else }}Production{{ end }}\n - name: LOG_LEVEL\n value: {{ if .Env.LOG_LEVEL }}{{ .Env.LOG_LEVEL }}{{ else }}INFO{{ end }}\n probes:\n - type: Liveness\n httpGet:\n path: /health\n port: {{ targetPortOrDefault 8080 }}\n initialDelaySeconds: 30\n periodSeconds: 10\n timeoutSeconds: 5\n failureThreshold: 3\n - type: Readiness\n httpGet:\n path: /health\n port: {{ targetPortOrDefault 8080 }}\n initialDelaySeconds: 10\n periodSeconds: 5\n timeoutSeconds: 3\n failureThreshold: 3\n scale:\n minReplicas: 1\n maxReplicas: 3\ntags:\n azd-service-name: ai-service\n aspire-resource-name: ai-service\n\n"}, "2c8ab872883a60001732328f55a9f18f9abf3037": {"t": 1785748775.8772426, "n": 4985, "h": "374dc6a45860484c", "c": "api-version: 2024-02-02-preview\nlocation: {{ .Env.AZURE_LOCATION }}\nidentity:\n type: UserAssigned\n userAssignedIdentities:\n ? \"{{ .Env.AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID }}\"\n : {}\nproperties:\n environmentId: {{ .Env.AZURE_CONTAINER_APPS_ENVIRONMENT_ID }}\n configuration:\n activeRevisionsMode: single\n ingress:\n external: true\n targetPort: {{ targetPortOrDefault 8080 }}\n transport: auto\n allowInsecure: true\n registries:\n - server: {{ .Env.AZURE_CONTAINER_REGISTRY_ENDPOINT }}\n identity: {{ .Env.AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID }}\n template:\n containers:\n - image: {{ .Image }}\n name: ai-service\n env:\n - name: PYTHONUNBUFFERED\n value: \"1\"\n - name: PYTHONDONTWRITEBYTECODE\n value: \"1\"\n - name: RESOURCE_GRPC_ENDPOINT\n value: {{ if .Env.RESOURCE_GRPC_ENDPOINT }}{{ .Env.RESOURCE_GRPC_ENDPOINT }}{{ else }}resource-api:5082{{ end }}\n - name: RESOURCE_GRPC_USE_TLS\n value: {{ if .Env.RESOURCE_GRPC_USE_TLS }}{{ .Env.RESOURCE_GRPC_USE_TLS }}{{ else }}false{{ end }}\n - name: RESOURCE_GRPC_CERT_PATH\n value: {{ if .Env.RESOURCE_GRPC_CERT_PATH }}{{ .Env.RESOURCE_GRPC_CERT_PATH }}{{ else }}{{ end }}\n - name: RESOURCE_GRPC_OVERRIDE_AUTHORITY\n value: {{ if .Env.RESOURCE_GRPC_OVERRIDE_AUTHORITY }}{{ .Env.RESOURCE_GRPC_OVERRIDE_AUTHORITY }}{{ else }}localhost{{ end }}\n # LLM Configuration\n - name: OPENAI_API_KEY\n value: {{ if .Env.OPENAI_API_KEY }}{{ .Env.OPENAI_API_KEY }}{{ else }}{{ end }}\n - name: OPENAI_BASE_URL\n value: {{ if .Env.OPENAI_BASE_URL }}{{ .Env.OPENAI_BASE_URL }}{{ else }}https://api.openai.com/v1{{ end }}\n - name: LLM_MODEL\n value: {{ if .Env.LLM_MODEL }}{{ .Env.LLM_MODEL }}{{ else }}gpt-4o-mini{{ end }}\n - name: LLM_TEMPERATURE\n value: {{ if .Env.LLM_TEMPERATURE }}{{ .Env.LLM_TEMPERATURE }}{{ else }}0.7{{ end }}\n - name: LLM_MAX_TOKENS\n value: {{ if .Env.LLM_MAX_TOKENS }}{{ .Env.LLM_MAX_TOKENS }}{{ else }}2000{{ end }}\n # DeepSeek Configuration (optional, alternative to OpenAI)\n - name: DEEPSEEK_API_KEY\n value: {{ if .Env.DEEPSEEK_API_KEY }}{{ .Env.DEEPSEEK_API_KEY }}{{ else }}{{ end }}\n - name: DEEPSEEK_BASE_URL\n value: {{ if .Env.DEEPSEEK_BASE_URL }}{{ .Env.DEEPSEEK_BASE_URL }}{{ else }}https://api.deepseek.com/v1{{ end }}\n - name: DEEPSEEK_MODEL\n value: {{ if .Env.DEEPSEEK_MODEL }}{{ .Env.DEEPSEEK_MODEL }}{{ else }}deepseek-chat{{ end }}\n # Content Generation Prompts\n - name: CONTENT_GENERATION_SYSTEM_PROMPT\n value: {{ if .Env.CONTENT_GENERATION_SYSTEM_PROMPT }}{{ .Env.CONTENT_GENERATION_SYSTEM_PROMPT }}{{ else }}You are a helpful educational designer.{{ end }}\n - name: CONTENT_GENERATION_SECTION_PROMPT_TEMPLATE\n value: {{ if .Env.CONTENT_GENERATION_SECTION_PROMPT_TEMPLATE }}{{ .Env.CONTENT_GENERATION_SECTION_PROMPT_TEMPLATE }}{{ else }}{{ end }}\n # Vector Store (Qdrant)\n - name: QDRANT_URL\n value: {{ if .Env.QDRANT_URL }}{{ .Env.QDRANT_URL }}{{ else }}http://localhost:6333{{ end }}\n - name: QDRANT_GRPC_URL\n value: {{ if .Env.QDRANT_GRPC_URL }}{{ .Env.QDRANT_GRPC_URL }}{{ else }}http://localhost:6334{{ end }}\n - name: QDRANT_COLLECTION\n value: {{ if .Env.QDRANT_COLLECTION }}{{ .Env.QDRANT_COLLECTION }}{{ else }}classroom_insights{{ end }}\n # Embeddings\n - name: EMBEDDING_MODEL\n value: {{ if .Env.EMBEDDING_MODEL }}{{ .Env.EMBEDDING_MODEL }}{{ else }}paraphrase-multilingual-MiniLM-L12-v2{{ end }}\n - name: EMBEDDING_DEVICE\n value: {{ if .Env.EMBEDDING_DEVICE }}{{ .Env.EMBEDDING_DEVICE }}{{ else }}cpu{{ end }}\n - name: EMBEDDING_BATCH_SIZE\n value: {{ if .Env.EMBEDDING_BATCH_SIZE }}{{ .Env.EMBEDDING_BATCH_SIZE }}{{ else }}32{{ end }}\n # Environment\n - name: ENVIRONMENT\n value: {{ if .Env.ENVIRONMENT }}{{ .Env.ENVIRONMENT }}{{ else }}Production{{ end }}\n - name: LOG_LEVEL\n value: {{ if .Env.LOG_LEVEL }}{{ .Env.LOG_LEVEL }}{{ else }}INFO{{ end }}\n probes:\n - type: Liveness\n httpGet:\n path: /health\n port: {{ targetPortOrDefault 8080 }}\n initialDelaySeconds: 30\n periodSeconds: 10\n timeoutSeconds: 5\n failureThreshold: 3\n - type: Readiness\n httpGet:\n path: /health\n port: {{ targetPortOrDefault 8080 }}\n initialDelaySeconds: 10\n periodSeconds: 5\n timeoutSeconds: 3\n failureThreshold: 3\n scale:\n minReplicas: 1\n maxReplicas: 3\ntags:\n azd-service-name: ai-service\n aspire-resource-name: ai-service\n\n"}, "a91d0cb34d83397f851ae47031a9c55ea276cfe0": {"t": 1785748776.9693797, "n": 4955, "h": "4e35b0656bc44840", "c": "api-version: 2024-02-02-preview\nlocation: {{ .Env.AZURE_LOCATION }}\nidentity:\n type: UserAssigned\n userAssignedIdentities:\n ? \"{{ .Env.AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID }}\"\n : {}\nproperties:\n environmentId: {{ .Env.AZURE_CONTAINER_APPS_ENVIRONMENT_ID }}\n configuration:\n activeRevisionsMode: single\n ingress:\n external: true\n targetPort: {{ targetPortOrDefault 8080 }}\n transport: auto\n allowInsecure: true\n registries:\n - server: {{ .Env.AZURE_CONTAINER_REGISTRY_ENDPOINT }}\n identity: {{ .Env.AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID }}\n template:\n containers:\n - image: {{ .Image }}\n name: ai-service\n env:\n - name: PYTHONUNBUFFERED\n value: \"1\"\n - name: PYTHONDONTWRITEBYTECODE\n value: \"1\"\n - name: RESOURCE_GRPC_ENDPOINT\n value: {{ if .Env.RESOURCE_GRPC_ENDPOINT }}{{ .Env.RESOURCE_GRPC_ENDPOINT }}{{ else }}resource-api:5082{{ end }}\n - name: RESOURCE_GRPC_USE_TLS\n value: {{ if .Env.RESOURCE_GRPC_USE_TLS }}{{ .Env.RESOURCE_GRPC_USE_TLS }}{{ else }}false{{ end }}\n - name: RESOURCE_GRPC_CERT_PATH\n value: {{ if .Env.RESOURCE_GRPC_CERT_PATH }}{{ .Env.RESOURCE_GRPC_CERT_PATH }}{{ else }}{{ end }}\n - name: RESOURCE_GRPC_OVERRIDE_AUTHORITY\n value: {{ if .Env.RESOURCE_GRPC_OVERRIDE_AUTHORITY }}{{ .Env.RESOURCE_GRPC_OVERRIDE_AUTHORITY }}{{ else }}localhost{{ end }}\n # LLM Configuration\n - name: OPENAI_API_KEY\n value: {{ if .Env.OPENAI_API_KEY }}{{ .Env.OPENAI_API_KEY }}{{ else }}{{ end }}\n - name: OPENAI_BASE_URL\n value: {{ if .Env.OPENAI_BASE_URL }}{{ .Env.OPENAI_BASE_URL }}{{ else }}https://api.openai.com/v1{{ end }}\n - name: LLM_MODEL\n value: {{ if .Env.LLM_MODEL }}{{ .Env.LLM_MODEL }}{{ else }}gpt-4o-mini{{ end }}\n - name: LLM_TEMPERATURE\n value: {{ if .Env.LLM_TEMPERATURE }}{{ .Env.LLM_TEMPERATURE }}{{ else }}0.7{{ end }}\n - name: LLM_MAX_TOKENS\n value: {{ if .Env.LLM_MAX_TOKENS }}{{ .Env.LLM_MAX_TOKENS }}{{ else }}2000{{ end }}\n # DeepSeek Configuration (optional, alternative to OpenAI)\n - name: DEEPSEEK_API_KEY\n value: {{ if .Env.DEEPSEEK_API_KEY }}{{ .Env.DEEPSEEK_API_KEY }}{{ else }}{{ end }}\n - name: DEEPSEEK_BASE_URL\n value: {{ if .Env.DEEPSEEK_BASE_URL }}{{ .Env.DEEPSEEK_BASE_URL }}{{ else }}https://api.deepseek.com/v1{{ end }}\n - name: DEEPSEEK_MODEL\n value: {{ if .Env.DEEPSEEK_MODEL }}{{ .Env.DEEPSEEK_MODEL }}{{ else }}deepseek-chat{{ end }}\n # Content Generation Prompts\n - name: CONTENT_GENERATION_SYSTEM_PROMPT\n value: {{ if .Env.CONTENT_GENERATION_SYSTEM_PROMPT }}{{ .Env.CONTENT_GENERATION_SYSTEM_PROMPT }}{{ else }}You are a helpful educational designer.{{ end }}\n {{- with .Env.CONTENT_GENERATION_SECTION_PROMPT_TEMPLATE }}\n - name: CONTENT_GENERATION_SECTION_PROMPT_TEMPLATE\n value: {{ . }}\n {{- end }}\n # Vector Store (Qdrant)\n - name: QDRANT_URL\n value: {{ if .Env.QDRANT_URL }}{{ .Env.QDRANT_URL }}{{ else }}http://localhost:6333{{ end }}\n - name: QDRANT_GRPC_URL\n value: {{ if .Env.QDRANT_GRPC_URL }}{{ .Env.QDRANT_GRPC_URL }}{{ else }}http://localhost:6334{{ end }}\n - name: QDRANT_COLLECTION\n value: {{ if .Env.QDRANT_COLLECTION }}{{ .Env.QDRANT_COLLECTION }}{{ else }}classroom_insights{{ end }}\n # Embeddings\n - name: EMBEDDING_MODEL\n value: {{ if .Env.EMBEDDING_MODEL }}{{ .Env.EMBEDDING_MODEL }}{{ else }}paraphrase-multilingual-MiniLM-L12-v2{{ end }}\n - name: EMBEDDING_DEVICE\n value: {{ if .Env.EMBEDDING_DEVICE }}{{ .Env.EMBEDDING_DEVICE }}{{ else }}cpu{{ end }}\n - name: EMBEDDING_BATCH_SIZE\n value: {{ if .Env.EMBEDDING_BATCH_SIZE }}{{ .Env.EMBEDDING_BATCH_SIZE }}{{ else }}32{{ end }}\n # Environment\n - name: ENVIRONMENT\n value: {{ if .Env.ENVIRONMENT }}{{ .Env.ENVIRONMENT }}{{ else }}Production{{ end }}\n - name: LOG_LEVEL\n value: {{ if .Env.LOG_LEVEL }}{{ .Env.LOG_LEVEL }}{{ else }}INFO{{ end }}\n probes:\n - type: Liveness\n httpGet:\n path: /health\n port: {{ targetPortOrDefault 8080 }}\n initialDelaySeconds: 30\n periodSeconds: 10\n timeoutSeconds: 5\n failureThreshold: 3\n - type: Readiness\n httpGet:\n path: /health\n port: {{ targetPortOrDefault 8080 }}\n initialDelaySeconds: 10\n periodSeconds: 5\n timeoutSeconds: 3\n failureThreshold: 3\n scale:\n minReplicas: 1\n maxReplicas: 3\ntags:\n azd-service-name: ai-service\n aspire-resource-name: ai-service\n\n"}, "2e14154c366388bce108a62413fa41b8d2483104": {"t": 1785748778.0678473, "n": 5009, "h": "0a9eff5c5f54e66d", "c": "api-version: 2024-02-02-preview\nlocation: {{ .Env.AZURE_LOCATION }}\nidentity:\n type: UserAssigned\n userAssignedIdentities:\n ? \"{{ .Env.AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID }}\"\n : {}\nproperties:\n environmentId: {{ .Env.AZURE_CONTAINER_APPS_ENVIRONMENT_ID }}\n configuration:\n activeRevisionsMode: single\n ingress:\n external: true\n targetPort: {{ targetPortOrDefault 8080 }}\n transport: auto\n allowInsecure: true\n registries:\n - server: {{ .Env.AZURE_CONTAINER_REGISTRY_ENDPOINT }}\n identity: {{ .Env.AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID }}\n template:\n containers:\n - image: {{ .Image }}\n name: ai-service\n env:\n - name: PYTHONUNBUFFERED\n value: \"1\"\n - name: PYTHONDONTWRITEBYTECODE\n value: \"1\"\n - name: RESOURCE_GRPC_ENDPOINT\n value: {{ if .Env.RESOURCE_GRPC_ENDPOINT }}{{ .Env.RESOURCE_GRPC_ENDPOINT }}{{ else }}resource-api.{{ .Env.AZURE_CONTAINER_APPS_ENVIRONMENT_DEFAULT_DOMAIN }}{{ end }}\n - name: RESOURCE_GRPC_USE_TLS\n value: {{ if .Env.RESOURCE_GRPC_USE_TLS }}{{ .Env.RESOURCE_GRPC_USE_TLS }}{{ else }}false{{ end }}\n - name: RESOURCE_GRPC_CERT_PATH\n value: {{ if .Env.RESOURCE_GRPC_CERT_PATH }}{{ .Env.RESOURCE_GRPC_CERT_PATH }}{{ else }}{{ end }}\n - name: RESOURCE_GRPC_OVERRIDE_AUTHORITY\n value: {{ if .Env.RESOURCE_GRPC_OVERRIDE_AUTHORITY }}{{ .Env.RESOURCE_GRPC_OVERRIDE_AUTHORITY }}{{ else }}localhost{{ end }}\n # LLM Configuration\n - name: OPENAI_API_KEY\n value: {{ if .Env.OPENAI_API_KEY }}{{ .Env.OPENAI_API_KEY }}{{ else }}{{ end }}\n - name: OPENAI_BASE_URL\n value: {{ if .Env.OPENAI_BASE_URL }}{{ .Env.OPENAI_BASE_URL }}{{ else }}https://api.openai.com/v1{{ end }}\n - name: LLM_MODEL\n value: {{ if .Env.LLM_MODEL }}{{ .Env.LLM_MODEL }}{{ else }}gpt-4o-mini{{ end }}\n - name: LLM_TEMPERATURE\n value: {{ if .Env.LLM_TEMPERATURE }}{{ .Env.LLM_TEMPERATURE }}{{ else }}0.7{{ end }}\n - name: LLM_MAX_TOKENS\n value: {{ if .Env.LLM_MAX_TOKENS }}{{ .Env.LLM_MAX_TOKENS }}{{ else }}2000{{ end }}\n # DeepSeek Configuration (optional, alternative to OpenAI)\n - name: DEEPSEEK_API_KEY\n value: {{ if .Env.DEEPSEEK_API_KEY }}{{ .Env.DEEPSEEK_API_KEY }}{{ else }}{{ end }}\n - name: DEEPSEEK_BASE_URL\n value: {{ if .Env.DEEPSEEK_BASE_URL }}{{ .Env.DEEPSEEK_BASE_URL }}{{ else }}https://api.deepseek.com/v1{{ end }}\n - name: DEEPSEEK_MODEL\n value: {{ if .Env.DEEPSEEK_MODEL }}{{ .Env.DEEPSEEK_MODEL }}{{ else }}deepseek-chat{{ end }}\n # Content Generation Prompts\n - name: CONTENT_GENERATION_SYSTEM_PROMPT\n value: {{ if .Env.CONTENT_GENERATION_SYSTEM_PROMPT }}{{ .Env.CONTENT_GENERATION_SYSTEM_PROMPT }}{{ else }}You are a helpful educational designer.{{ end }}\n {{- with .Env.CONTENT_GENERATION_SECTION_PROMPT_TEMPLATE }}\n - name: CONTENT_GENERATION_SECTION_PROMPT_TEMPLATE\n value: {{ . }}\n {{- end }}\n # Vector Store (Qdrant)\n - name: QDRANT_URL\n value: {{ if .Env.QDRANT_URL }}{{ .Env.QDRANT_URL }}{{ else }}http://localhost:6333{{ end }}\n - name: QDRANT_GRPC_URL\n value: {{ if .Env.QDRANT_GRPC_URL }}{{ .Env.QDRANT_GRPC_URL }}{{ else }}http://localhost:6334{{ end }}\n - name: QDRANT_COLLECTION\n value: {{ if .Env.QDRANT_COLLECTION }}{{ .Env.QDRANT_COLLECTION }}{{ else }}classroom_insights{{ end }}\n # Embeddings\n - name: EMBEDDING_MODEL\n value: {{ if .Env.EMBEDDING_MODEL }}{{ .Env.EMBEDDING_MODEL }}{{ else }}paraphrase-multilingual-MiniLM-L12-v2{{ end }}\n - name: EMBEDDING_DEVICE\n value: {{ if .Env.EMBEDDING_DEVICE }}{{ .Env.EMBEDDING_DEVICE }}{{ else }}cpu{{ end }}\n - name: EMBEDDING_BATCH_SIZE\n value: {{ if .Env.EMBEDDING_BATCH_SIZE }}{{ .Env.EMBEDDING_BATCH_SIZE }}{{ else }}32{{ end }}\n # Environment\n - name: ENVIRONMENT\n value: {{ if .Env.ENVIRONMENT }}{{ .Env.ENVIRONMENT }}{{ else }}Production{{ end }}\n - name: LOG_LEVEL\n value: {{ if .Env.LOG_LEVEL }}{{ .Env.LOG_LEVEL }}{{ else }}INFO{{ end }}\n probes:\n - type: Liveness\n httpGet:\n path: /health\n port: {{ targetPortOrDefault 8080 }}\n initialDelaySeconds: 30\n periodSeconds: 10\n timeoutSeconds: 5\n failureThreshold: 3\n - type: Readiness\n httpGet:\n path: /health\n port: {{ targetPortOrDefault 8080 }}\n initialDelaySeconds: 10\n periodSeconds: 5\n timeoutSeconds: 3\n failureThreshold: 3\n scale:\n minReplicas: 1\n maxReplicas: 3\ntags:\n azd-service-name: ai-service\n aspire-resource-name: ai-service\n\n"}, "6dd3c571bbe72d429755a8b060a46bd9d3c4824f": {"t": 1785748779.3218176, "n": 5078, "h": "4357798abbe9d057", "c": "api-version: 2024-02-02-preview\nlocation: {{ .Env.AZURE_LOCATION }}\nidentity:\n type: UserAssigned\n userAssignedIdentities:\n ? \"{{ .Env.AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID }}\"\n : {}\nproperties:\n environmentId: {{ .Env.AZURE_CONTAINER_APPS_ENVIRONMENT_ID }}\n configuration:\n activeRevisionsMode: single\n ingress:\n external: true\n targetPort: {{ targetPortOrDefault 8080 }}\n transport: auto\n allowInsecure: true\n registries:\n - server: {{ .Env.AZURE_CONTAINER_REGISTRY_ENDPOINT }}\n identity: {{ .Env.AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID }}\n template:\n containers:\n - image: {{ .Image }}\n name: ai-service\n env:\n - name: PYTHONUNBUFFERED\n value: \"1\"\n - name: PYTHONDONTWRITEBYTECODE\n value: \"1\"\n - name: RESOURCE_GRPC_ENDPOINT\n value: {{ if .Env.RESOURCE_GRPC_ENDPOINT }}{{ .Env.RESOURCE_GRPC_ENDPOINT }}{{ else }}resource-api.{{ .Env.AZURE_CONTAINER_APPS_ENVIRONMENT_DEFAULT_DOMAIN }}{{ end }}\n - name: RESOURCE_GRPC_USE_TLS\n value: {{ if .Env.RESOURCE_GRPC_USE_TLS }}{{ .Env.RESOURCE_GRPC_USE_TLS }}{{ else }}false{{ end }}\n - name: RESOURCE_GRPC_CERT_PATH\n value: {{ if .Env.RESOURCE_GRPC_CERT_PATH }}{{ .Env.RESOURCE_GRPC_CERT_PATH }}{{ else }}{{ end }}\n - name: RESOURCE_GRPC_OVERRIDE_AUTHORITY\n value: {{ if .Env.RESOURCE_GRPC_OVERRIDE_AUTHORITY }}{{ .Env.RESOURCE_GRPC_OVERRIDE_AUTHORITY }}{{ else }}http://resource-api.{{ .Env.AZURE_CONTAINER_APPS_ENVIRONMENT_DEFAULT_DOMAIN }}{{ end }}\n # LLM Configuration\n - name: OPENAI_API_KEY\n value: {{ if .Env.OPENAI_API_KEY }}{{ .Env.OPENAI_API_KEY }}{{ else }}{{ end }}\n - name: OPENAI_BASE_URL\n value: {{ if .Env.OPENAI_BASE_URL }}{{ .Env.OPENAI_BASE_URL }}{{ else }}https://api.openai.com/v1{{ end }}\n - name: LLM_MODEL\n value: {{ if .Env.LLM_MODEL }}{{ .Env.LLM_MODEL }}{{ else }}gpt-4o-mini{{ end }}\n - name: LLM_TEMPERATURE\n value: {{ if .Env.LLM_TEMPERATURE }}{{ .Env.LLM_TEMPERATURE }}{{ else }}0.7{{ end }}\n - name: LLM_MAX_TOKENS\n value: {{ if .Env.LLM_MAX_TOKENS }}{{ .Env.LLM_MAX_TOKENS }}{{ else }}2000{{ end }}\n # DeepSeek Configuration (optional, alternative to OpenAI)\n - name: DEEPSEEK_API_KEY\n value: {{ if .Env.DEEPSEEK_API_KEY }}{{ .Env.DEEPSEEK_API_KEY }}{{ else }}{{ end }}\n - name: DEEPSEEK_BASE_URL\n value: {{ if .Env.DEEPSEEK_BASE_URL }}{{ .Env.DEEPSEEK_BASE_URL }}{{ else }}https://api.deepseek.com/v1{{ end }}\n - name: DEEPSEEK_MODEL\n value: {{ if .Env.DEEPSEEK_MODEL }}{{ .Env.DEEPSEEK_MODEL }}{{ else }}deepseek-chat{{ end }}\n # Content Generation Prompts\n - name: CONTENT_GENERATION_SYSTEM_PROMPT\n value: {{ if .Env.CONTENT_GENERATION_SYSTEM_PROMPT }}{{ .Env.CONTENT_GENERATION_SYSTEM_PROMPT }}{{ else }}You are a helpful educational designer.{{ end }}\n {{- with .Env.CONTENT_GENERATION_SECTION_PROMPT_TEMPLATE }}\n - name: CONTENT_GENERATION_SECTION_PROMPT_TEMPLATE\n value: {{ . }}\n {{- end }}\n # Vector Store (Qdrant)\n - name: QDRANT_URL\n value: {{ if .Env.QDRANT_URL }}{{ .Env.QDRANT_URL }}{{ else }}http://localhost:6333{{ end }}\n - name: QDRANT_GRPC_URL\n value: {{ if .Env.QDRANT_GRPC_URL }}{{ .Env.QDRANT_GRPC_URL }}{{ else }}http://localhost:6334{{ end }}\n - name: QDRANT_COLLECTION\n value: {{ if .Env.QDRANT_COLLECTION }}{{ .Env.QDRANT_COLLECTION }}{{ else }}classroom_insights{{ end }}\n # Embeddings\n - name: EMBEDDING_MODEL\n value: {{ if .Env.EMBEDDING_MODEL }}{{ .Env.EMBEDDING_MODEL }}{{ else }}paraphrase-multilingual-MiniLM-L12-v2{{ end }}\n - name: EMBEDDING_DEVICE\n value: {{ if .Env.EMBEDDING_DEVICE }}{{ .Env.EMBEDDING_DEVICE }}{{ else }}cpu{{ end }}\n - name: EMBEDDING_BATCH_SIZE\n value: {{ if .Env.EMBEDDING_BATCH_SIZE }}{{ .Env.EMBEDDING_BATCH_SIZE }}{{ else }}32{{ end }}\n # Environment\n - name: ENVIRONMENT\n value: {{ if .Env.ENVIRONMENT }}{{ .Env.ENVIRONMENT }}{{ else }}Production{{ end }}\n - name: LOG_LEVEL\n value: {{ if .Env.LOG_LEVEL }}{{ .Env.LOG_LEVEL }}{{ else }}INFO{{ end }}\n probes:\n - type: Liveness\n httpGet:\n path: /health\n port: {{ targetPortOrDefault 8080 }}\n initialDelaySeconds: 30\n periodSeconds: 10\n timeoutSeconds: 5\n failureThreshold: 3\n - type: Readiness\n httpGet:\n path: /health\n port: {{ targetPortOrDefault 8080 }}\n initialDelaySeconds: 10\n periodSeconds: 5\n timeoutSeconds: 3\n failureThreshold: 3\n scale:\n minReplicas: 1\n maxReplicas: 3\ntags:\n azd-service-name: ai-service\n aspire-resource-name: ai-service\n\n"}, "a9d41352578b21f525525541d06604ee864545b6": {"t": 1785748784.8277345, "n": 5173, "h": "3e1897dce2028941", "c": "api-version: 2024-02-02-preview\nlocation: {{ .Env.AZURE_LOCATION }}\nidentity:\n type: UserAssigned\n userAssignedIdentities:\n ? \"{{ .Env.AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID }}\"\n : {}\nproperties:\n environmentId: {{ .Env.AZURE_CONTAINER_APPS_ENVIRONMENT_ID }}\n configuration:\n activeRevisionsMode: single\n ingress:\n external: true\n targetPort: {{ targetPortOrDefault 8080 }}\n transport: auto\n allowInsecure: true\n registries:\n - server: {{ .Env.AZURE_CONTAINER_REGISTRY_ENDPOINT }}\n identity: {{ .Env.AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID }}\n template:\n containers:\n - image: {{ .Image }}\n name: ai-service\n env:\n - name: PYTHONUNBUFFERED\n value: \"1\"\n - name: PYTHONDONTWRITEBYTECODE\n value: \"1\"\n - name: RESOURCE_GRPC_ENDPOINT\n value: {{ if .Env.RESOURCE_GRPC_ENDPOINT }}{{ .Env.RESOURCE_GRPC_ENDPOINT }}{{ else }}resource-api.{{ .Env.AZURE_CONTAINER_APPS_ENVIRONMENT_DEFAULT_DOMAIN }}{{ end }}\n - name: RESOURCE_GRPC_USE_TLS\n value: {{ if .Env.RESOURCE_GRPC_USE_TLS }}{{ .Env.RESOURCE_GRPC_USE_TLS }}{{ else }}false{{ end }}\n {{- if .Env.RESOURCE_GRPC_CERT_PATH }}\n - name: RESOURCE_GRPC_CERT_PATH\n value: {{ .Env.RESOURCE_GRPC_CERT_PATH }}\n {{- end }}\n {{- with .Env.RESOURCE_GRPC_OVERRIDE_AUTHORITY }}\n - name: RESOURCE_GRPC_OVERRIDE_AUTHORITY\n value: {{ if .Env.RESOURCE_GRPC_OVERRIDE_AUTHORITY }}{{ .Env.RESOURCE_GRPC_OVERRIDE_AUTHORITY }}{{ else }}http://resource-api.{{ .Env.AZURE_CONTAINER_APPS_ENVIRONMENT_DEFAULT_DOMAIN }}{{ end }}\n {{- end }}\n # LLM Configuration\n - name: OPENAI_API_KEY\n value: {{ if .Env.OPENAI_API_KEY }}{{ .Env.OPENAI_API_KEY }}{{ else }}{{ end }}\n - name: OPENAI_BASE_URL\n value: {{ if .Env.OPENAI_BASE_URL }}{{ .Env.OPENAI_BASE_URL }}{{ else }}https://api.openai.com/v1{{ end }}\n - name: LLM_MODEL\n value: {{ if .Env.LLM_MODEL }}{{ .Env.LLM_MODEL }}{{ else }}gpt-4o-mini{{ end }}\n - name: LLM_TEMPERATURE\n value: {{ if .Env.LLM_TEMPERATURE }}{{ .Env.LLM_TEMPERATURE }}{{ else }}0.7{{ end }}\n - name: LLM_MAX_TOKENS\n value: {{ if .Env.LLM_MAX_TOKENS }}{{ .Env.LLM_MAX_TOKENS }}{{ else }}2000{{ end }}\n # DeepSeek Configuration (optional, alternative to OpenAI)\n - name: DEEPSEEK_API_KEY\n value: {{ if .Env.DEEPSEEK_API_KEY }}{{ .Env.DEEPSEEK_API_KEY }}{{ else }}{{ end }}\n - name: DEEPSEEK_BASE_URL\n value: {{ if .Env.DEEPSEEK_BASE_URL }}{{ .Env.DEEPSEEK_BASE_URL }}{{ else }}https://api.deepseek.com/v1{{ end }}\n - name: DEEPSEEK_MODEL\n value: {{ if .Env.DEEPSEEK_MODEL }}{{ .Env.DEEPSEEK_MODEL }}{{ else }}deepseek-chat{{ end }}\n # Content Generation Prompts\n - name: CONTENT_GENERATION_SYSTEM_PROMPT\n value: {{ if .Env.CONTENT_GENERATION_SYSTEM_PROMPT }}{{ .Env.CONTENT_GENERATION_SYSTEM_PROMPT }}{{ else }}You are a helpful educational designer.{{ end }}\n {{- with .Env.CONTENT_GENERATION_SECTION_PROMPT_TEMPLATE }}\n - name: CONTENT_GENERATION_SECTION_PROMPT_TEMPLATE\n value: {{ . }}\n {{- end }}\n # Vector Store (Qdrant)\n - name: QDRANT_URL\n value: {{ if .Env.QDRANT_URL }}{{ .Env.QDRANT_URL }}{{ else }}http://localhost:6333{{ end }}\n - name: QDRANT_GRPC_URL\n value: {{ if .Env.QDRANT_GRPC_URL }}{{ .Env.QDRANT_GRPC_URL }}{{ else }}http://localhost:6334{{ end }}\n - name: QDRANT_COLLECTION\n value: {{ if .Env.QDRANT_COLLECTION }}{{ .Env.QDRANT_COLLECTION }}{{ else }}classroom_insights{{ end }}\n # Embeddings\n - name: EMBEDDING_MODEL\n value: {{ if .Env.EMBEDDING_MODEL }}{{ .Env.EMBEDDING_MODEL }}{{ else }}paraphrase-multilingual-MiniLM-L12-v2{{ end }}\n - name: EMBEDDING_DEVICE\n value: {{ if .Env.EMBEDDING_DEVICE }}{{ .Env.EMBEDDING_DEVICE }}{{ else }}cpu{{ end }}\n - name: EMBEDDING_BATCH_SIZE\n value: {{ if .Env.EMBEDDING_BATCH_SIZE }}{{ .Env.EMBEDDING_BATCH_SIZE }}{{ else }}32{{ end }}\n # Environment\n - name: ENVIRONMENT\n value: {{ if .Env.ENVIRONMENT }}{{ .Env.ENVIRONMENT }}{{ else }}Production{{ end }}\n - name: LOG_LEVEL\n value: {{ if .Env.LOG_LEVEL }}{{ .Env.LOG_LEVEL }}{{ else }}INFO{{ end }}\n probes:\n - type: Liveness\n httpGet:\n path: /health\n port: {{ targetPortOrDefault 8080 }}\n initialDelaySeconds: 30\n periodSeconds: 10\n timeoutSeconds: 5\n failureThreshold: 3\n - type: Readiness\n httpGet:\n path: /health\n port: {{ targetPortOrDefault 8080 }}\n initialDelaySeconds: 10\n periodSeconds: 5\n timeoutSeconds: 3\n failureThreshold: 3\n scale:\n minReplicas: 1\n maxReplicas: 3\ntags:\n azd-service-name: ai-service\n aspire-resource-name: ai-service\n\n"}, "2900c25b1d3fd2dfa0d65a92c3124295c11c0d5a": {"t": 1785748785.9289165, "n": 5065, "h": "8010a2647fcac44d", "c": "api-version: 2024-02-02-preview\nlocation: {{ .Env.AZURE_LOCATION }}\nidentity:\n type: UserAssigned\n userAssignedIdentities:\n ? \"{{ .Env.AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID }}\"\n : {}\nproperties:\n environmentId: {{ .Env.AZURE_CONTAINER_APPS_ENVIRONMENT_ID }}\n configuration:\n activeRevisionsMode: single\n ingress:\n external: true\n targetPort: {{ targetPortOrDefault 8080 }}\n transport: auto\n allowInsecure: true\n registries:\n - server: {{ .Env.AZURE_CONTAINER_REGISTRY_ENDPOINT }}\n identity: {{ .Env.AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID }}\n template:\n containers:\n - image: {{ .Image }}\n name: ai-service\n env:\n - name: PYTHONUNBUFFERED\n value: \"1\"\n - name: PYTHONDONTWRITEBYTECODE\n value: \"1\"\n - name: RESOURCE_GRPC_ENDPOINT\n value: {{ if .Env.RESOURCE_GRPC_ENDPOINT }}{{ .Env.RESOURCE_GRPC_ENDPOINT }}{{ else }}resource-api:5003{{ end }}\n - name: RESOURCE_GRPC_USE_TLS\n value: {{ if .Env.RESOURCE_GRPC_USE_TLS }}{{ .Env.RESOURCE_GRPC_USE_TLS }}{{ else }}false{{ end }}\n {{- if .Env.RESOURCE_GRPC_CERT_PATH }}\n - name: RESOURCE_GRPC_CERT_PATH\n value: {{ .Env.RESOURCE_GRPC_CERT_PATH }}\n {{- end }}\n {{- with .Env.RESOURCE_GRPC_OVERRIDE_AUTHORITY }}\n - name: RESOURCE_GRPC_OVERRIDE_AUTHORITY\n value: {{ if .Env.RESOURCE_GRPC_OVERRIDE_AUTHORITY }}{{ .Env.RESOURCE_GRPC_OVERRIDE_AUTHORITY }}{{ else }}http://resource-api:5082{{ end }}\n {{- end }}\n # LLM Configuration\n - name: OPENAI_API_KEY\n value: {{ if .Env.OPENAI_API_KEY }}{{ .Env.OPENAI_API_KEY }}{{ else }}{{ end }}\n - name: OPENAI_BASE_URL\n value: {{ if .Env.OPENAI_BASE_URL }}{{ .Env.OPENAI_BASE_URL }}{{ else }}https://api.openai.com/v1{{ end }}\n - name: LLM_MODEL\n value: {{ if .Env.LLM_MODEL }}{{ .Env.LLM_MODEL }}{{ else }}gpt-4o-mini{{ end }}\n - name: LLM_TEMPERATURE\n value: {{ if .Env.LLM_TEMPERATURE }}{{ .Env.LLM_TEMPERATURE }}{{ else }}0.7{{ end }}\n - name: LLM_MAX_TOKENS\n value: {{ if .Env.LLM_MAX_TOKENS }}{{ .Env.LLM_MAX_TOKENS }}{{ else }}2000{{ end }}\n # DeepSeek Configuration (optional, alternative to OpenAI)\n - name: DEEPSEEK_API_KEY\n value: {{ if .Env.DEEPSEEK_API_KEY }}{{ .Env.DEEPSEEK_API_KEY }}{{ else }}{{ end }}\n - name: DEEPSEEK_BASE_URL\n value: {{ if .Env.DEEPSEEK_BASE_URL }}{{ .Env.DEEPSEEK_BASE_URL }}{{ else }}https://api.deepseek.com/v1{{ end }}\n - name: DEEPSEEK_MODEL\n value: {{ if .Env.DEEPSEEK_MODEL }}{{ .Env.DEEPSEEK_MODEL }}{{ else }}deepseek-chat{{ end }}\n # Content Generation Prompts\n - name: CONTENT_GENERATION_SYSTEM_PROMPT\n value: {{ if .Env.CONTENT_GENERATION_SYSTEM_PROMPT }}{{ .Env.CONTENT_GENERATION_SYSTEM_PROMPT }}{{ else }}You are a helpful educational designer.{{ end }}\n {{- with .Env.CONTENT_GENERATION_SECTION_PROMPT_TEMPLATE }}\n - name: CONTENT_GENERATION_SECTION_PROMPT_TEMPLATE\n value: {{ . }}\n {{- end }}\n # Vector Store (Qdrant)\n - name: QDRANT_URL\n value: {{ if .Env.QDRANT_URL }}{{ .Env.QDRANT_URL }}{{ else }}http://localhost:6333{{ end }}\n - name: QDRANT_GRPC_URL\n value: {{ if .Env.QDRANT_GRPC_URL }}{{ .Env.QDRANT_GRPC_URL }}{{ else }}http://localhost:6334{{ end }}\n - name: QDRANT_COLLECTION\n value: {{ if .Env.QDRANT_COLLECTION }}{{ .Env.QDRANT_COLLECTION }}{{ else }}classroom_insights{{ end }}\n # Embeddings\n - name: EMBEDDING_MODEL\n value: {{ if .Env.EMBEDDING_MODEL }}{{ .Env.EMBEDDING_MODEL }}{{ else }}paraphrase-multilingual-MiniLM-L12-v2{{ end }}\n - name: EMBEDDING_DEVICE\n value: {{ if .Env.EMBEDDING_DEVICE }}{{ .Env.EMBEDDING_DEVICE }}{{ else }}cpu{{ end }}\n - name: EMBEDDING_BATCH_SIZE\n value: {{ if .Env.EMBEDDING_BATCH_SIZE }}{{ .Env.EMBEDDING_BATCH_SIZE }}{{ else }}32{{ end }}\n # Environment\n - name: ENVIRONMENT\n value: {{ if .Env.ENVIRONMENT }}{{ .Env.ENVIRONMENT }}{{ else }}Production{{ end }}\n - name: LOG_LEVEL\n value: {{ if .Env.LOG_LEVEL }}{{ .Env.LOG_LEVEL }}{{ else }}INFO{{ end }}\n probes:\n - type: Liveness\n httpGet:\n path: /health\n port: {{ targetPortOrDefault 8080 }}\n initialDelaySeconds: 30\n periodSeconds: 10\n timeoutSeconds: 5\n failureThreshold: 3\n - type: Readiness\n httpGet:\n path: /health\n port: {{ targetPortOrDefault 8080 }}\n initialDelaySeconds: 10\n periodSeconds: 5\n timeoutSeconds: 3\n failureThreshold: 3\n scale:\n minReplicas: 1\n maxReplicas: 3\ntags:\n azd-service-name: ai-service\n aspire-resource-name: ai-service\n\n"}, "8abafa41215fa083f6d9c972f997675200699018": {"t": 1785748788.0626974, "n": 5069, "h": "eaa2d0652b251769", "c": "api-version: 2024-02-02-preview\nlocation: {{ .Env.AZURE_LOCATION }}\nidentity:\n type: UserAssigned\n userAssignedIdentities:\n ? \"{{ .Env.AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID }}\"\n : {}\nproperties:\n environmentId: {{ .Env.AZURE_CONTAINER_APPS_ENVIRONMENT_ID }}\n configuration:\n activeRevisionsMode: single\n ingress:\n external: true\n targetPort: {{ targetPortOrDefault 8080 }}\n transport: auto\n allowInsecure: true\n registries:\n - server: {{ .Env.AZURE_CONTAINER_REGISTRY_ENDPOINT }}\n identity: {{ .Env.AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID }}\n template:\n containers:\n - image: {{ .Image }}\n name: ai-service\n env:\n - name: PYTHONUNBUFFERED\n value: \"1\"\n - name: PYTHONDONTWRITEBYTECODE\n value: \"1\"\n - name: RESOURCE_GRPC_ENDPOINT\n value: {{ if .Env.RESOURCE_GRPC_ENDPOINT }}{{ .Env.RESOURCE_GRPC_ENDPOINT }}{{ else }}resource-api:5003{{ end }}\n - name: RESOURCE_GRPC_USE_TLS\n value: {{ if .Env.RESOURCE_GRPC_USE_TLS }}{{ .Env.RESOURCE_GRPC_USE_TLS }}{{ else }}false{{ end }}\n {{- if .Env.RESOURCE_GRPC_CERT_PATH }}\n - name: RESOURCE_GRPC_CERT_PATH\n value: {{ .Env.RESOURCE_GRPC_CERT_PATH }}\n {{- end }}\n {{- with .Env.RESOURCE_GRPC_OVERRIDE_AUTHORITY }}\n - name: RESOURCE_GRPC_OVERRIDE_AUTHORITY\n value: {{ if .Env.RESOURCE_GRPC_OVERRIDE_AUTHORITY }}{{ .Env.RESOURCE_GRPC_OVERRIDE_AUTHORITY }}{{ else }}http://resource-api:5082{{ end }}\n {{- end }}\n # LLM Configuration\n # - name: OPENAI_API_KEY\n # value: {{ if .Env.OPENAI_API_KEY }}{{ .Env.OPENAI_API_KEY }}{{ else }}{{ end }}\n - name: OPENAI_BASE_URL\n value: {{ if .Env.OPENAI_BASE_URL }}{{ .Env.OPENAI_BASE_URL }}{{ else }}https://api.openai.com/v1{{ end }}\n - name: LLM_MODEL\n value: {{ if .Env.LLM_MODEL }}{{ .Env.LLM_MODEL }}{{ else }}gpt-4o-mini{{ end }}\n - name: LLM_TEMPERATURE\n value: {{ if .Env.LLM_TEMPERATURE }}{{ .Env.LLM_TEMPERATURE }}{{ else }}0.7{{ end }}\n - name: LLM_MAX_TOKENS\n value: {{ if .Env.LLM_MAX_TOKENS }}{{ .Env.LLM_MAX_TOKENS }}{{ else }}2000{{ end }}\n # DeepSeek Configuration (optional, alternative to OpenAI)\n - name: DEEPSEEK_API_KEY\n value: {{ if .Env.DEEPSEEK_API_KEY }}{{ .Env.DEEPSEEK_API_KEY }}{{ else }}{{ end }}\n - name: DEEPSEEK_BASE_URL\n value: {{ if .Env.DEEPSEEK_BASE_URL }}{{ .Env.DEEPSEEK_BASE_URL }}{{ else }}https://api.deepseek.com/v1{{ end }}\n - name: DEEPSEEK_MODEL\n value: {{ if .Env.DEEPSEEK_MODEL }}{{ .Env.DEEPSEEK_MODEL }}{{ else }}deepseek-chat{{ end }}\n # Content Generation Prompts\n - name: CONTENT_GENERATION_SYSTEM_PROMPT\n value: {{ if .Env.CONTENT_GENERATION_SYSTEM_PROMPT }}{{ .Env.CONTENT_GENERATION_SYSTEM_PROMPT }}{{ else }}You are a helpful educational designer.{{ end }}\n {{- with .Env.CONTENT_GENERATION_SECTION_PROMPT_TEMPLATE }}\n - name: CONTENT_GENERATION_SECTION_PROMPT_TEMPLATE\n value: {{ . }}\n {{- end }}\n # Vector Store (Qdrant)\n - name: QDRANT_URL\n value: {{ if .Env.QDRANT_URL }}{{ .Env.QDRANT_URL }}{{ else }}http://localhost:6333{{ end }}\n - name: QDRANT_GRPC_URL\n value: {{ if .Env.QDRANT_GRPC_URL }}{{ .Env.QDRANT_GRPC_URL }}{{ else }}http://localhost:6334{{ end }}\n - name: QDRANT_COLLECTION\n value: {{ if .Env.QDRANT_COLLECTION }}{{ .Env.QDRANT_COLLECTION }}{{ else }}classroom_insights{{ end }}\n # Embeddings\n - name: EMBEDDING_MODEL\n value: {{ if .Env.EMBEDDING_MODEL }}{{ .Env.EMBEDDING_MODEL }}{{ else }}paraphrase-multilingual-MiniLM-L12-v2{{ end }}\n - name: EMBEDDING_DEVICE\n value: {{ if .Env.EMBEDDING_DEVICE }}{{ .Env.EMBEDDING_DEVICE }}{{ else }}cpu{{ end }}\n - name: EMBEDDING_BATCH_SIZE\n value: {{ if .Env.EMBEDDING_BATCH_SIZE }}{{ .Env.EMBEDDING_BATCH_SIZE }}{{ else }}32{{ end }}\n # Environment\n - name: ENVIRONMENT\n value: {{ if .Env.ENVIRONMENT }}{{ .Env.ENVIRONMENT }}{{ else }}Production{{ end }}\n - name: LOG_LEVEL\n value: {{ if .Env.LOG_LEVEL }}{{ .Env.LOG_LEVEL }}{{ else }}INFO{{ end }}\n probes:\n - type: Liveness\n httpGet:\n path: /health\n port: {{ targetPortOrDefault 8080 }}\n initialDelaySeconds: 30\n periodSeconds: 10\n timeoutSeconds: 5\n failureThreshold: 3\n - type: Readiness\n httpGet:\n path: /health\n port: {{ targetPortOrDefault 8080 }}\n initialDelaySeconds: 10\n periodSeconds: 5\n timeoutSeconds: 3\n failureThreshold: 3\n scale:\n minReplicas: 1\n maxReplicas: 3\ntags:\n azd-service-name: ai-service\n aspire-resource-name: ai-service\n\n"}, "2dddbacb02452721807aa2b4df249ed211ca3f0b": {"t": 1785748789.650952, "n": 5059, "h": "8676fe0d8b305736", "c": "api-version: 2024-02-02-preview\nlocation: {{ .Env.AZURE_LOCATION }}\nidentity:\n type: UserAssigned\n userAssignedIdentities:\n ? \"{{ .Env.AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID }}\"\n : {}\nproperties:\n environmentId: {{ .Env.AZURE_CONTAINER_APPS_ENVIRONMENT_ID }}\n configuration:\n activeRevisionsMode: single\n ingress:\n external: true\n targetPort: {{ targetPortOrDefault 80 }}\n transport: auto\n allowInsecure: true\n registries:\n - server: {{ .Env.AZURE_CONTAINER_REGISTRY_ENDPOINT }}\n identity: {{ .Env.AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID }}\n template:\n containers:\n - image: {{ .Image }}\n name: ai-service\n env:\n - name: PYTHONUNBUFFERED\n value: \"1\"\n - name: PYTHONDONTWRITEBYTECODE\n value: \"1\"\n - name: RESOURCE_GRPC_ENDPOINT\n value: {{ if .Env.RESOURCE_GRPC_ENDPOINT }}{{ .Env.RESOURCE_GRPC_ENDPOINT }}{{ else }}resource-api:5003{{ end }}\n - name: RESOURCE_GRPC_USE_TLS\n value: {{ if .Env.RESOURCE_GRPC_USE_TLS }}{{ .Env.RESOURCE_GRPC_USE_TLS }}{{ else }}false{{ end }}\n {{- if .Env.RESOURCE_GRPC_CERT_PATH }}\n - name: RESOURCE_GRPC_CERT_PATH\n value: {{ .Env.RESOURCE_GRPC_CERT_PATH }}\n {{- end }}\n {{- with .Env.RESOURCE_GRPC_OVERRIDE_AUTHORITY }}\n - name: RESOURCE_GRPC_OVERRIDE_AUTHORITY\n value: {{ if .Env.RESOURCE_GRPC_OVERRIDE_AUTHORITY }}{{ .Env.RESOURCE_GRPC_OVERRIDE_AUTHORITY }}{{ else }}http://resource-api:5082{{ end }}\n {{- end }}\n # LLM Configuration\n - name: OPENAI_API_KEY\n value: {{ if .Env.OPENAI_API_KEY }}{{ .Env.OPENAI_API_KEY }}{{ else }}{{ end }}\n - name: OPENAI_BASE_URL\n value: {{ if .Env.OPENAI_BASE_URL }}{{ .Env.OPENAI_BASE_URL }}{{ else }}https://api.openai.com/v1{{ end }}\n - name: LLM_MODEL\n value: {{ if .Env.LLM_MODEL }}{{ .Env.LLM_MODEL }}{{ else }}gpt-4o-mini{{ end }}\n - name: LLM_TEMPERATURE\n value: {{ if .Env.LLM_TEMPERATURE }}{{ .Env.LLM_TEMPERATURE }}{{ else }}0.7{{ end }}\n - name: LLM_MAX_TOKENS\n value: {{ if .Env.LLM_MAX_TOKENS }}{{ .Env.LLM_MAX_TOKENS }}{{ else }}2000{{ end }}\n # DeepSeek Configuration (optional, alternative to OpenAI)\n - name: DEEPSEEK_API_KEY\n value: {{ if .Env.DEEPSEEK_API_KEY }}{{ .Env.DEEPSEEK_API_KEY }}{{ else }}{{ end }}\n - name: DEEPSEEK_BASE_URL\n value: {{ if .Env.DEEPSEEK_BASE_URL }}{{ .Env.DEEPSEEK_BASE_URL }}{{ else }}https://api.deepseek.com/v1{{ end }}\n - name: DEEPSEEK_MODEL\n value: {{ if .Env.DEEPSEEK_MODEL }}{{ .Env.DEEPSEEK_MODEL }}{{ else }}deepseek-chat{{ end }}\n # Content Generation Prompts\n - name: CONTENT_GENERATION_SYSTEM_PROMPT\n value: {{ if .Env.CONTENT_GENERATION_SYSTEM_PROMPT }}{{ .Env.CONTENT_GENERATION_SYSTEM_PROMPT }}{{ else }}You are a helpful educational designer.{{ end }}\n {{- with .Env.CONTENT_GENERATION_SECTION_PROMPT_TEMPLATE }}\n - name: CONTENT_GENERATION_SECTION_PROMPT_TEMPLATE\n value: {{ . }}\n {{- end }}\n # Vector Store (Qdrant)\n - name: QDRANT_URL\n value: {{ if .Env.QDRANT_URL }}{{ .Env.QDRANT_URL }}{{ else }}http://localhost:6333{{ end }}\n - name: QDRANT_GRPC_URL\n value: {{ if .Env.QDRANT_GRPC_URL }}{{ .Env.QDRANT_GRPC_URL }}{{ else }}http://localhost:6334{{ end }}\n - name: QDRANT_COLLECTION\n value: {{ if .Env.QDRANT_COLLECTION }}{{ .Env.QDRANT_COLLECTION }}{{ else }}classroom_insights{{ end }}\n # Embeddings\n - name: EMBEDDING_MODEL\n value: {{ if .Env.EMBEDDING_MODEL }}{{ .Env.EMBEDDING_MODEL }}{{ else }}paraphrase-multilingual-MiniLM-L12-v2{{ end }}\n - name: EMBEDDING_DEVICE\n value: {{ if .Env.EMBEDDING_DEVICE }}{{ .Env.EMBEDDING_DEVICE }}{{ else }}cpu{{ end }}\n - name: EMBEDDING_BATCH_SIZE\n value: {{ if .Env.EMBEDDING_BATCH_SIZE }}{{ .Env.EMBEDDING_BATCH_SIZE }}{{ else }}32{{ end }}\n # Environment\n - name: ENVIRONMENT\n value: {{ if .Env.ENVIRONMENT }}{{ .Env.ENVIRONMENT }}{{ else }}Production{{ end }}\n - name: LOG_LEVEL\n value: {{ if .Env.LOG_LEVEL }}{{ .Env.LOG_LEVEL }}{{ else }}INFO{{ end }}\n probes:\n - type: Liveness\n httpGet:\n path: /health\n port: {{ targetPortOrDefault 80 }}\n initialDelaySeconds: 30\n periodSeconds: 10\n timeoutSeconds: 5\n failureThreshold: 3\n - type: Readiness\n httpGet:\n path: /health\n port: {{ targetPortOrDefault 80 }}\n initialDelaySeconds: 10\n periodSeconds: 5\n timeoutSeconds: 3\n failureThreshold: 3\n scale:\n minReplicas: 1\n maxReplicas: 3\ntags:\n azd-service-name: ai-service\n aspire-resource-name: ai-service\n\n"}, "6dd8b2db071209a30c41da695edbbc9f3fe8d247": {"t": 1785748791.734244, "n": 5063, "h": "05b869a692f230a3", "c": "api-version: 2024-02-02-preview\nlocation: {{ .Env.AZURE_LOCATION }}\nidentity:\n type: UserAssigned\n userAssignedIdentities:\n ? \"{{ .Env.AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID }}\"\n : {}\nproperties:\n environmentId: {{ .Env.AZURE_CONTAINER_APPS_ENVIRONMENT_ID }}\n configuration:\n activeRevisionsMode: single\n ingress:\n external: true\n targetPort: {{ targetPortOrDefault 80 }}\n transport: auto\n allowInsecure: true\n registries:\n - server: {{ .Env.AZURE_CONTAINER_REGISTRY_ENDPOINT }}\n identity: {{ .Env.AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID }}\n template:\n containers:\n - image: {{ .Image }}\n name: ai-service\n env:\n - name: PYTHONUNBUFFERED\n value: \"1\"\n - name: PYTHONDONTWRITEBYTECODE\n value: \"1\"\n - name: RESOURCE_GRPC_ENDPOINT\n value: {{ if .Env.RESOURCE_GRPC_ENDPOINT }}{{ .Env.RESOURCE_GRPC_ENDPOINT }}{{ else }}resource-api:5003{{ end }}\n - name: RESOURCE_GRPC_USE_TLS\n value: {{ if .Env.RESOURCE_GRPC_USE_TLS }}{{ .Env.RESOURCE_GRPC_USE_TLS }}{{ else }}false{{ end }}\n {{- if .Env.RESOURCE_GRPC_CERT_PATH }}\n - name: RESOURCE_GRPC_CERT_PATH\n value: {{ .Env.RESOURCE_GRPC_CERT_PATH }}\n {{- end }}\n {{- with .Env.RESOURCE_GRPC_OVERRIDE_AUTHORITY }}\n - name: RESOURCE_GRPC_OVERRIDE_AUTHORITY\n value: {{ if .Env.RESOURCE_GRPC_OVERRIDE_AUTHORITY }}{{ .Env.RESOURCE_GRPC_OVERRIDE_AUTHORITY }}{{ else }}http://resource-api:5082{{ end }}\n {{- end }}\n # LLM Configuration\n # - name: OPENAI_API_KEY\n # value: {{ if .Env.OPENAI_API_KEY }}{{ .Env.OPENAI_API_KEY }}{{ else }}{{ end }}\n - name: OPENAI_BASE_URL\n value: {{ if .Env.OPENAI_BASE_URL }}{{ .Env.OPENAI_BASE_URL }}{{ else }}https://api.openai.com/v1{{ end }}\n - name: LLM_MODEL\n value: {{ if .Env.LLM_MODEL }}{{ .Env.LLM_MODEL }}{{ else }}gpt-4o-mini{{ end }}\n - name: LLM_TEMPERATURE\n value: {{ if .Env.LLM_TEMPERATURE }}{{ .Env.LLM_TEMPERATURE }}{{ else }}0.7{{ end }}\n - name: LLM_MAX_TOKENS\n value: {{ if .Env.LLM_MAX_TOKENS }}{{ .Env.LLM_MAX_TOKENS }}{{ else }}2000{{ end }}\n # DeepSeek Configuration (optional, alternative to OpenAI)\n - name: DEEPSEEK_API_KEY\n value: {{ if .Env.DEEPSEEK_API_KEY }}{{ .Env.DEEPSEEK_API_KEY }}{{ else }}{{ end }}\n - name: DEEPSEEK_BASE_URL\n value: {{ if .Env.DEEPSEEK_BASE_URL }}{{ .Env.DEEPSEEK_BASE_URL }}{{ else }}https://api.deepseek.com/v1{{ end }}\n - name: DEEPSEEK_MODEL\n value: {{ if .Env.DEEPSEEK_MODEL }}{{ .Env.DEEPSEEK_MODEL }}{{ else }}deepseek-chat{{ end }}\n # Content Generation Prompts\n - name: CONTENT_GENERATION_SYSTEM_PROMPT\n value: {{ if .Env.CONTENT_GENERATION_SYSTEM_PROMPT }}{{ .Env.CONTENT_GENERATION_SYSTEM_PROMPT }}{{ else }}You are a helpful educational designer.{{ end }}\n {{- with .Env.CONTENT_GENERATION_SECTION_PROMPT_TEMPLATE }}\n - name: CONTENT_GENERATION_SECTION_PROMPT_TEMPLATE\n value: {{ . }}\n {{- end }}\n # Vector Store (Qdrant)\n - name: QDRANT_URL\n value: {{ if .Env.QDRANT_URL }}{{ .Env.QDRANT_URL }}{{ else }}http://localhost:6333{{ end }}\n - name: QDRANT_GRPC_URL\n value: {{ if .Env.QDRANT_GRPC_URL }}{{ .Env.QDRANT_GRPC_URL }}{{ else }}http://localhost:6334{{ end }}\n - name: QDRANT_COLLECTION\n value: {{ if .Env.QDRANT_COLLECTION }}{{ .Env.QDRANT_COLLECTION }}{{ else }}classroom_insights{{ end }}\n # Embeddings\n - name: EMBEDDING_MODEL\n value: {{ if .Env.EMBEDDING_MODEL }}{{ .Env.EMBEDDING_MODEL }}{{ else }}paraphrase-multilingual-MiniLM-L12-v2{{ end }}\n - name: EMBEDDING_DEVICE\n value: {{ if .Env.EMBEDDING_DEVICE }}{{ .Env.EMBEDDING_DEVICE }}{{ else }}cpu{{ end }}\n - name: EMBEDDING_BATCH_SIZE\n value: {{ if .Env.EMBEDDING_BATCH_SIZE }}{{ .Env.EMBEDDING_BATCH_SIZE }}{{ else }}32{{ end }}\n # Environment\n - name: ENVIRONMENT\n value: {{ if .Env.ENVIRONMENT }}{{ .Env.ENVIRONMENT }}{{ else }}Production{{ end }}\n - name: LOG_LEVEL\n value: {{ if .Env.LOG_LEVEL }}{{ .Env.LOG_LEVEL }}{{ else }}INFO{{ end }}\n probes:\n - type: Liveness\n httpGet:\n path: /health\n port: {{ targetPortOrDefault 80 }}\n initialDelaySeconds: 30\n periodSeconds: 10\n timeoutSeconds: 5\n failureThreshold: 3\n - type: Readiness\n httpGet:\n path: /health\n port: {{ targetPortOrDefault 80 }}\n initialDelaySeconds: 10\n periodSeconds: 5\n timeoutSeconds: 3\n failureThreshold: 3\n scale:\n minReplicas: 1\n maxReplicas: 3\ntags:\n azd-service-name: ai-service\n aspire-resource-name: ai-service\n\n"}, "6643cf5abff687373e9c81d074609ede9d939ad2": {"t": 1785748792.840536, "n": 4938, "h": "26cff465487409af", "c": "api-version: 2024-02-02-preview\nlocation: {{ .Env.AZURE_LOCATION }}\nidentity:\n type: UserAssigned\n userAssignedIdentities:\n ? \"{{ .Env.AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID }}\"\n : {}\nproperties:\n environmentId: {{ .Env.AZURE_CONTAINER_APPS_ENVIRONMENT_ID }}\n configuration:\n activeRevisionsMode: single\n ingress:\n external: true\n targetPort: {{ targetPortOrDefault 80 }}\n transport: auto\n allowInsecure: true\n registries:\n - server: {{ .Env.AZURE_CONTAINER_REGISTRY_ENDPOINT }}\n identity: {{ .Env.AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID }}\n template:\n containers:\n - image: {{ .Image }}\n name: ai-service\n env:\n - name: PYTHONUNBUFFERED\n value: \"1\"\n - name: PYTHONDONTWRITEBYTECODE\n value: \"1\"\n - name: RESOURCE_GRPC_ENDPOINT\n value: {{ if .Env.RESOURCE_GRPC_ENDPOINT }}{{ .Env.RESOURCE_GRPC_ENDPOINT }}{{ else }}resource-api:5003{{ end }}\n - name: RESOURCE_GRPC_USE_TLS\n value: {{ if .Env.RESOURCE_GRPC_USE_TLS }}{{ .Env.RESOURCE_GRPC_USE_TLS }}{{ else }}false{{ end }}\n {{- if .Env.RESOURCE_GRPC_CERT_PATH }}\n - name: RESOURCE_GRPC_CERT_PATH\n value: {{ .Env.RESOURCE_GRPC_CERT_PATH }}\n {{- end }}\n {{- with .Env.RESOURCE_GRPC_OVERRIDE_AUTHORITY }}\n - name: RESOURCE_GRPC_OVERRIDE_AUTHORITY\n value: {{ . }}\n {{- end }}\n # LLM Configuration\n # - name: OPENAI_API_KEY\n # value: {{ if .Env.OPENAI_API_KEY }}{{ .Env.OPENAI_API_KEY }}{{ else }}{{ end }}\n - name: OPENAI_BASE_URL\n value: {{ if .Env.OPENAI_BASE_URL }}{{ .Env.OPENAI_BASE_URL }}{{ else }}https://api.openai.com/v1{{ end }}\n - name: LLM_MODEL\n value: {{ if .Env.LLM_MODEL }}{{ .Env.LLM_MODEL }}{{ else }}gpt-4o-mini{{ end }}\n - name: LLM_TEMPERATURE\n value: {{ if .Env.LLM_TEMPERATURE }}{{ .Env.LLM_TEMPERATURE }}{{ else }}0.7{{ end }}\n - name: LLM_MAX_TOKENS\n value: {{ if .Env.LLM_MAX_TOKENS }}{{ .Env.LLM_MAX_TOKENS }}{{ else }}2000{{ end }}\n # DeepSeek Configuration (optional, alternative to OpenAI)\n - name: DEEPSEEK_API_KEY\n value: {{ if .Env.DEEPSEEK_API_KEY }}{{ .Env.DEEPSEEK_API_KEY }}{{ else }}{{ end }}\n - name: DEEPSEEK_BASE_URL\n value: {{ if .Env.DEEPSEEK_BASE_URL }}{{ .Env.DEEPSEEK_BASE_URL }}{{ else }}https://api.deepseek.com/v1{{ end }}\n - name: DEEPSEEK_MODEL\n value: {{ if .Env.DEEPSEEK_MODEL }}{{ .Env.DEEPSEEK_MODEL }}{{ else }}deepseek-chat{{ end }}\n # Content Generation Prompts\n - name: CONTENT_GENERATION_SYSTEM_PROMPT\n value: {{ if .Env.CONTENT_GENERATION_SYSTEM_PROMPT }}{{ .Env.CONTENT_GENERATION_SYSTEM_PROMPT }}{{ else }}You are a helpful educational designer.{{ end }}\n {{- with .Env.CONTENT_GENERATION_SECTION_PROMPT_TEMPLATE }}\n - name: CONTENT_GENERATION_SECTION_PROMPT_TEMPLATE\n value: {{ . }}\n {{- end }}\n # Vector Store (Qdrant)\n - name: QDRANT_URL\n value: {{ if .Env.QDRANT_URL }}{{ .Env.QDRANT_URL }}{{ else }}http://localhost:6333{{ end }}\n - name: QDRANT_GRPC_URL\n value: {{ if .Env.QDRANT_GRPC_URL }}{{ .Env.QDRANT_GRPC_URL }}{{ else }}http://localhost:6334{{ end }}\n - name: QDRANT_COLLECTION\n value: {{ if .Env.QDRANT_COLLECTION }}{{ .Env.QDRANT_COLLECTION }}{{ else }}classroom_insights{{ end }}\n # Embeddings\n - name: EMBEDDING_MODEL\n value: {{ if .Env.EMBEDDING_MODEL }}{{ .Env.EMBEDDING_MODEL }}{{ else }}paraphrase-multilingual-MiniLM-L12-v2{{ end }}\n - name: EMBEDDING_DEVICE\n value: {{ if .Env.EMBEDDING_DEVICE }}{{ .Env.EMBEDDING_DEVICE }}{{ else }}cpu{{ end }}\n - name: EMBEDDING_BATCH_SIZE\n value: {{ if .Env.EMBEDDING_BATCH_SIZE }}{{ .Env.EMBEDDING_BATCH_SIZE }}{{ else }}32{{ end }}\n # Environment\n - name: ENVIRONMENT\n value: {{ if .Env.ENVIRONMENT }}{{ .Env.ENVIRONMENT }}{{ else }}Production{{ end }}\n - name: LOG_LEVEL\n value: {{ if .Env.LOG_LEVEL }}{{ .Env.LOG_LEVEL }}{{ else }}INFO{{ end }}\n probes:\n - type: Liveness\n httpGet:\n path: /health\n port: {{ targetPortOrDefault 80 }}\n initialDelaySeconds: 30\n periodSeconds: 10\n timeoutSeconds: 5\n failureThreshold: 3\n - type: Readiness\n httpGet:\n path: /health\n port: {{ targetPortOrDefault 80 }}\n initialDelaySeconds: 10\n periodSeconds: 5\n timeoutSeconds: 3\n failureThreshold: 3\n scale:\n minReplicas: 1\n maxReplicas: 3\ntags:\n azd-service-name: ai-service\n aspire-resource-name: ai-service\n\n"}, "f639d869227fc253893b8d9fbe7b20ea47b75ddb": {"t": 1785748793.9024765, "n": 4988, "h": "3dfa64714b2d6b23", "c": "api-version: 2024-02-02-preview\nlocation: {{ .Env.AZURE_LOCATION }}\nidentity:\n type: UserAssigned\n userAssignedIdentities:\n ? \"{{ .Env.AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID }}\"\n : {}\nproperties:\n environmentId: {{ .Env.AZURE_CONTAINER_APPS_ENVIRONMENT_ID }}\n configuration:\n activeRevisionsMode: single\n ingress:\n external: true\n targetPort: {{ targetPortOrDefault 80 }}\n transport: auto\n allowInsecure: true\n registries:\n - server: {{ .Env.AZURE_CONTAINER_REGISTRY_ENDPOINT }}\n identity: {{ .Env.AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID }}\n template:\n containers:\n - image: {{ .Image }}\n name: ai-service\n env:\n - name: PYTHONUNBUFFERED\n value: \"1\"\n - name: PYTHONDONTWRITEBYTECODE\n value: \"1\"\n - name: RESOURCE_GRPC_ENDPOINT\n value: {{ if .Env.RESOURCE_GRPC_ENDPOINT }}{{ .Env.RESOURCE_GRPC_ENDPOINT }}{{ else }}resource-api:5003{{ end }}\n - name: RESOURCE_GRPC_USE_TLS\n value: {{ if .Env.RESOURCE_GRPC_USE_TLS }}{{ .Env.RESOURCE_GRPC_USE_TLS }}{{ else }}false{{ end }}\n {{- if .Env.RESOURCE_GRPC_CERT_PATH }}\n - name: RESOURCE_GRPC_CERT_PATH\n value: {{ .Env.RESOURCE_GRPC_CERT_PATH }}\n {{- end }}\n - name: RESOURCE_GRPC_OVERRIDE_AUTHORITY\n value: {{ if .Env.RESOURCE_GRPC_OVERRIDE_AUTHORITY }}{{ .Env.RESOURCE_GRPC_OVERRIDE_AUTHORITY }}{{ else }}{{ end }}\n # LLM Configuration\n # - name: OPENAI_API_KEY\n # value: {{ if .Env.OPENAI_API_KEY }}{{ .Env.OPENAI_API_KEY }}{{ else }}{{ end }}\n - name: OPENAI_BASE_URL\n value: {{ if .Env.OPENAI_BASE_URL }}{{ .Env.OPENAI_BASE_URL }}{{ else }}https://api.openai.com/v1{{ end }}\n - name: LLM_MODEL\n value: {{ if .Env.LLM_MODEL }}{{ .Env.LLM_MODEL }}{{ else }}gpt-4o-mini{{ end }}\n - name: LLM_TEMPERATURE\n value: {{ if .Env.LLM_TEMPERATURE }}{{ .Env.LLM_TEMPERATURE }}{{ else }}0.7{{ end }}\n - name: LLM_MAX_TOKENS\n value: {{ if .Env.LLM_MAX_TOKENS }}{{ .Env.LLM_MAX_TOKENS }}{{ else }}2000{{ end }}\n # DeepSeek Configuration (optional, alternative to OpenAI)\n - name: DEEPSEEK_API_KEY\n value: {{ if .Env.DEEPSEEK_API_KEY }}{{ .Env.DEEPSEEK_API_KEY }}{{ else }}{{ end }}\n - name: DEEPSEEK_BASE_URL\n value: {{ if .Env.DEEPSEEK_BASE_URL }}{{ .Env.DEEPSEEK_BASE_URL }}{{ else }}https://api.deepseek.com/v1{{ end }}\n - name: DEEPSEEK_MODEL\n value: {{ if .Env.DEEPSEEK_MODEL }}{{ .Env.DEEPSEEK_MODEL }}{{ else }}deepseek-chat{{ end }}\n # Content Generation Prompts\n - name: CONTENT_GENERATION_SYSTEM_PROMPT\n value: {{ if .Env.CONTENT_GENERATION_SYSTEM_PROMPT }}{{ .Env.CONTENT_GENERATION_SYSTEM_PROMPT }}{{ else }}You are a helpful educational designer.{{ end }}\n - name: CONTENT_GENERATION_SECTION_PROMPT_TEMPLATE\n value: {{ if .Env.CONTENT_GENERATION_SECTION_PROMPT_TEMPLATE }}{{ .Env.CONTENT_GENERATION_SECTION_PROMPT_TEMPLATE }}{{ else }}{{ end }}\n # Vector Store (Qdrant)\n - name: QDRANT_URL\n value: {{ if .Env.QDRANT_URL }}{{ .Env.QDRANT_URL }}{{ else }}http://localhost:6333{{ end }}\n - name: QDRANT_GRPC_URL\n value: {{ if .Env.QDRANT_GRPC_URL }}{{ .Env.QDRANT_GRPC_URL }}{{ else }}http://localhost:6334{{ end }}\n - name: QDRANT_COLLECTION\n value: {{ if .Env.QDRANT_COLLECTION }}{{ .Env.QDRANT_COLLECTION }}{{ else }}classroom_insights{{ end }}\n # Embeddings\n - name: EMBEDDING_MODEL\n value: {{ if .Env.EMBEDDING_MODEL }}{{ .Env.EMBEDDING_MODEL }}{{ else }}paraphrase-multilingual-MiniLM-L12-v2{{ end }}\n - name: EMBEDDING_DEVICE\n value: {{ if .Env.EMBEDDING_DEVICE }}{{ .Env.EMBEDDING_DEVICE }}{{ else }}cpu{{ end }}\n - name: EMBEDDING_BATCH_SIZE\n value: {{ if .Env.EMBEDDING_BATCH_SIZE }}{{ .Env.EMBEDDING_BATCH_SIZE }}{{ else }}32{{ end }}\n # Environment\n - name: ENVIRONMENT\n value: {{ if .Env.ENVIRONMENT }}{{ .Env.ENVIRONMENT }}{{ else }}Production{{ end }}\n - name: LOG_LEVEL\n value: {{ if .Env.LOG_LEVEL }}{{ .Env.LOG_LEVEL }}{{ else }}INFO{{ end }}\n probes:\n - type: Liveness\n httpGet:\n path: /health\n port: {{ targetPortOrDefault 80 }}\n initialDelaySeconds: 30\n periodSeconds: 10\n timeoutSeconds: 5\n failureThreshold: 3\n - type: Readiness\n httpGet:\n path: /health\n port: {{ targetPortOrDefault 80 }}\n initialDelaySeconds: 10\n periodSeconds: 5\n timeoutSeconds: 3\n failureThreshold: 3\n scale:\n minReplicas: 1\n maxReplicas: 3\ntags:\n azd-service-name: ai-service\n aspire-resource-name: ai-service\n\n"}, "80c436a42fe0ea565c79db90f835a83a49f951fe": {"t": 1785748795.045449, "n": 4976, "h": "042a611a86ba5dee", "c": "api-version: 2024-02-02-preview\nlocation: {{ .Env.AZURE_LOCATION }}\nidentity:\n type: UserAssigned\n userAssignedIdentities:\n ? \"{{ .Env.AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID }}\"\n : {}\nproperties:\n environmentId: {{ .Env.AZURE_CONTAINER_APPS_ENVIRONMENT_ID }}\n configuration:\n activeRevisionsMode: single\n ingress:\n external: true\n targetPort: {{ targetPortOrDefault 80 }}\n transport: auto\n allowInsecure: true\n registries:\n - server: {{ .Env.AZURE_CONTAINER_REGISTRY_ENDPOINT }}\n identity: {{ .Env.AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID }}\n template:\n containers:\n - image: {{ .Image }}\n name: ai-service\n env:\n - name: PYTHONUNBUFFERED\n value: \"1\"\n - name: PYTHONDONTWRITEBYTECODE\n value: \"1\"\n - name: RESOURCE_GRPC_ENDPOINT\n value: {{ if .Env.RESOURCE_GRPC_ENDPOINT }}{{ .Env.RESOURCE_GRPC_ENDPOINT }}{{ else }}resource-api:5003{{ end }}\n - name: RESOURCE_GRPC_USE_TLS\n value: {{ if .Env.RESOURCE_GRPC_USE_TLS }}{{ .Env.RESOURCE_GRPC_USE_TLS }}{{ else }}false{{ end }}\n #- name: RESOURCE_GRPC_CERT_PATH\n # value: {{ if .Env.RESOURCE_GRPC_CERT_PATH }}{{ .Env.RESOURCE_GRPC_CERT_PATH }}{{ else }}{{ end }}\n - name: RESOURCE_GRPC_OVERRIDE_AUTHORITY\n value: {{ if .Env.RESOURCE_GRPC_OVERRIDE_AUTHORITY }}{{ .Env.RESOURCE_GRPC_OVERRIDE_AUTHORITY }}{{ else }}{{ end }}\n # LLM Configuration\n # - name: OPENAI_API_KEY\n # value: {{ if .Env.OPENAI_API_KEY }}{{ .Env.OPENAI_API_KEY }}{{ else }}{{ end }}\n - name: OPENAI_BASE_URL\n value: {{ if .Env.OPENAI_BASE_URL }}{{ .Env.OPENAI_BASE_URL }}{{ else }}https://api.openai.com/v1{{ end }}\n - name: LLM_MODEL\n value: {{ if .Env.LLM_MODEL }}{{ .Env.LLM_MODEL }}{{ else }}gpt-4o-mini{{ end }}\n - name: LLM_TEMPERATURE\n value: {{ if .Env.LLM_TEMPERATURE }}{{ .Env.LLM_TEMPERATURE }}{{ else }}0.7{{ end }}\n - name: LLM_MAX_TOKENS\n value: {{ if .Env.LLM_MAX_TOKENS }}{{ .Env.LLM_MAX_TOKENS }}{{ else }}2000{{ end }}\n # DeepSeek Configuration (optional, alternative to OpenAI)\n - name: DEEPSEEK_API_KEY\n value: {{ if .Env.DEEPSEEK_API_KEY }}{{ .Env.DEEPSEEK_API_KEY }}{{ else }}{{ end }}\n - name: DEEPSEEK_BASE_URL\n value: {{ if .Env.DEEPSEEK_BASE_URL }}{{ .Env.DEEPSEEK_BASE_URL }}{{ else }}https://api.deepseek.com/v1{{ end }}\n - name: DEEPSEEK_MODEL\n value: {{ if .Env.DEEPSEEK_MODEL }}{{ .Env.DEEPSEEK_MODEL }}{{ else }}deepseek-chat{{ end }}\n # Content Generation Prompts\n - name: CONTENT_GENERATION_SYSTEM_PROMPT\n value: {{ if .Env.CONTENT_GENERATION_SYSTEM_PROMPT }}{{ .Env.CONTENT_GENERATION_SYSTEM_PROMPT }}{{ else }}You are a helpful educational designer.{{ end }}\n - name: CONTENT_GENERATION_SECTION_PROMPT_TEMPLATE\n value: {{ if .Env.CONTENT_GENERATION_SECTION_PROMPT_TEMPLATE }}{{ .Env.CONTENT_GENERATION_SECTION_PROMPT_TEMPLATE }}{{ else }}{{ end }}\n # Vector Store (Qdrant)\n - name: QDRANT_URL\n value: {{ if .Env.QDRANT_URL }}{{ .Env.QDRANT_URL }}{{ else }}http://localhost:6333{{ end }}\n - name: QDRANT_GRPC_URL\n value: {{ if .Env.QDRANT_GRPC_URL }}{{ .Env.QDRANT_GRPC_URL }}{{ else }}http://localhost:6334{{ end }}\n - name: QDRANT_COLLECTION\n value: {{ if .Env.QDRANT_COLLECTION }}{{ .Env.QDRANT_COLLECTION }}{{ else }}classroom_insights{{ end }}\n # Embeddings\n - name: EMBEDDING_MODEL\n value: {{ if .Env.EMBEDDING_MODEL }}{{ .Env.EMBEDDING_MODEL }}{{ else }}paraphrase-multilingual-MiniLM-L12-v2{{ end }}\n - name: EMBEDDING_DEVICE\n value: {{ if .Env.EMBEDDING_DEVICE }}{{ .Env.EMBEDDING_DEVICE }}{{ else }}cpu{{ end }}\n - name: EMBEDDING_BATCH_SIZE\n value: {{ if .Env.EMBEDDING_BATCH_SIZE }}{{ .Env.EMBEDDING_BATCH_SIZE }}{{ else }}32{{ end }}\n # Environment\n - name: ENVIRONMENT\n value: {{ if .Env.ENVIRONMENT }}{{ .Env.ENVIRONMENT }}{{ else }}Production{{ end }}\n - name: LOG_LEVEL\n value: {{ if .Env.LOG_LEVEL }}{{ .Env.LOG_LEVEL }}{{ else }}INFO{{ end }}\n probes:\n - type: Liveness\n httpGet:\n path: /health\n port: {{ targetPortOrDefault 80 }}\n initialDelaySeconds: 30\n periodSeconds: 10\n timeoutSeconds: 5\n failureThreshold: 3\n - type: Readiness\n httpGet:\n path: /health\n port: {{ targetPortOrDefault 80 }}\n initialDelaySeconds: 10\n periodSeconds: 5\n timeoutSeconds: 3\n failureThreshold: 3\n scale:\n minReplicas: 1\n maxReplicas: 3\ntags:\n azd-service-name: ai-service\n aspire-resource-name: ai-service\n\n"}, "bb6647c9eeae5d19be8863d34bfc4be33e106db2": {"t": 1785748796.1288898, "n": 4970, "h": "ec26f8c55c6629d6", "c": "api-version: 2024-02-02-preview\nlocation: {{ .Env.AZURE_LOCATION }}\nidentity:\n type: UserAssigned\n userAssignedIdentities:\n ? \"{{ .Env.AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID }}\"\n : {}\nproperties:\n environmentId: {{ .Env.AZURE_CONTAINER_APPS_ENVIRONMENT_ID }}\n configuration:\n activeRevisionsMode: single\n ingress:\n external: true\n targetPort: {{ targetPortOrDefault 80 }}\n transport: auto\n allowInsecure: true\n registries:\n - server: {{ .Env.AZURE_CONTAINER_REGISTRY_ENDPOINT }}\n identity: {{ .Env.AZURE_CONTAINER_REGISTRY_MANAGED_IDENTITY_ID }}\n template:\n containers:\n - image: {{ .Image }}\n name: ai-service\n env:\n - name: PYTHONUNBUFFERED\n value: \"1\"\n - name: PYTHONDONTWRITEBYTECODE\n value: \"1\"\n - name: RESOURCE_GRPC_ENDPOINT\n value: {{ if .Env.RESOURCE_GRPC_ENDPOINT }}{{ .Env.RESOURCE_GRPC_ENDPOINT }}{{ else }}resource-api:5003{{ end }}\n - name: RESOURCE_GRPC_USE_TLS\n value: {{ if .Env.RESOURCE_GRPC_USE_TLS }}{{ .Env.RESOURCE_GRPC_USE_TLS }}{{ else }}false{{ end }}\n - name: RESOURCE_GRPC_CERT_PATH\n value: {{ if .Env.RESOURCE_GRPC_CERT_PATH }}{{ .Env.RESOURCE_GRPC_CERT_PATH }}{{ else }}{{ end }}\n - name: RESOURCE_GRPC_OVERRIDE_AUTHORITY\n value: {{ if .Env.RESOURCE_GRPC_OVERRIDE_AUTHORITY }}{{ .Env.RESOURCE_GRPC_OVERRIDE_AUTHORITY }}{{ else }}{{ end }}\n # LLM Configuration\n - name: OPENAI_API_KEY\n value: {{ if .Env.OPENAI_API_KEY }}{{ .Env.OPENAI_API_KEY }}{{ else }}{{ end }}\n - name: OPENAI_BASE_URL\n value: {{ if .Env.OPENAI_BASE_URL }}{{ .Env.OPENAI_BASE_URL }}{{ else }}https://api.openai.com/v1{{ end }}\n - name: LLM_MODEL\n value: {{ if .Env.LLM_MODEL }}{{ .Env.LLM_MODEL }}{{ else }}gpt-4o-mini{{ end }}\n - name: LLM_TEMPERATURE\n value: {{ if .Env.LLM_TEMPERATURE }}{{ .Env.LLM_TEMPERATURE }}{{ else }}0.7{{ end }}\n - name: LLM_MAX_TOKENS\n value: {{ if .Env.LLM_MAX_TOKENS }}{{ .Env.LLM_MAX_TOKENS }}{{ else }}2000{{ end }}\n # DeepSeek Configuration (optional, alternative to OpenAI)\n - name: DEEPSEEK_API_KEY\n value: {{ if .Env.DEEPSEEK_API_KEY }}{{ .Env.DEEPSEEK_API_KEY }}{{ else }}{{ end }}\n - name: DEEPSEEK_BASE_URL\n value: {{ if .Env.DEEPSEEK_BASE_URL }}{{ .Env.DEEPSEEK_BASE_URL }}{{ else }}https://api.deepseek.com/v1{{ end }}\n - name: DEEPSEEK_MODEL\n value: {{ if .Env.DEEPSEEK_MODEL }}{{ .Env.DEEPSEEK_MODEL }}{{ else }}deepseek-chat{{ end }}\n # Content Generation Prompts\n - name: CONTENT_GENERATION_SYSTEM_PROMPT\n value: {{ if .Env.CONTENT_GENERATION_SYSTEM_PROMPT }}{{ .Env.CONTENT_GENERATION_SYSTEM_PROMPT }}{{ else }}You are a helpful educational designer.{{ end }}\n - name: CONTENT_GENERATION_SECTION_PROMPT_TEMPLATE\n value: {{ if .Env.CONTENT_GENERATION_SECTION_PROMPT_TEMPLATE }}{{ .Env.CONTENT_GENERATION_SECTION_PROMPT_TEMPLATE }}{{ else }}{{ end }}\n # Vector Store (Qdrant)\n - name: QDRANT_URL\n value: {{ if .Env.QDRANT_URL }}{{ .Env.QDRANT_URL }}{{ else }}http://localhost:6333{{ end }}\n - name: QDRANT_GRPC_URL\n value: {{ if .Env.QDRANT_GRPC_URL }}{{ .Env.QDRANT_GRPC_URL }}{{ else }}http://localhost:6334{{ end }}\n - name: QDRANT_COLLECTION\n value: {{ if .Env.QDRANT_COLLECTION }}{{ .Env.QDRANT_COLLECTION }}{{ else }}classroom_insights{{ end }}\n # Embeddings\n - name: EMBEDDING_MODEL\n value: {{ if .Env.EMBEDDING_MODEL }}{{ .Env.EMBEDDING_MODEL }}{{ else }}paraphrase-multilingual-MiniLM-L12-v2{{ end }}\n - name: EMBEDDING_DEVICE\n value: {{ if .Env.EMBEDDING_DEVICE }}{{ .Env.EMBEDDING_DEVICE }}{{ else }}cpu{{ end }}\n - name: EMBEDDING_BATCH_SIZE\n value: {{ if .Env.EMBEDDING_BATCH_SIZE }}{{ .Env.EMBEDDING_BATCH_SIZE }}{{ else }}32{{ end }}\n # Environment\n - name: ENVIRONMENT\n value: {{ if .Env.ENVIRONMENT }}{{ .Env.ENVIRONMENT }}{{ else }}Production{{ end }}\n - name: LOG_LEVEL\n value: {{ if .Env.LOG_LEVEL }}{{ .Env.LOG_LEVEL }}{{ else }}INFO{{ end }}\n probes:\n - type: Liveness\n httpGet:\n path: /health\n port: {{ targetPortOrDefault 80 }}\n initialDelaySeconds: 30\n periodSeconds: 10\n timeoutSeconds: 5\n failureThreshold: 3\n - type: Readiness\n httpGet:\n path: /health\n port: {{ targetPortOrDefault 80 }}\n initialDelaySeconds: 10\n periodSeconds: 5\n timeoutSeconds: 3\n failureThreshold: 3\n scale:\n minReplicas: 1\n maxReplicas: 3\ntags:\n azd-service-name: ai-service\n aspire-resource-name: ai-service\n\n"}}, "src/Services/STEMify-Backend/STEMify-Backend.AppHost/Extensions/ServiceExtensions.cs": {"aeba9aa66118a8bea1f397d76877a378f0c4ccd0": {"t": 1785745825.1534772, "n": 23023, "h": "3ba247aa7247e058", "c": "using Microsoft.Extensions.Hosting;\nusing Aspire.Hosting;\nusing Aspire.Hosting.ApplicationModel;\n\nnamespace STEMifyBackend.AppHost.Extensions\n{\n public static class ServiceExtensions\n {\n public static IDistributedApplicationBuilder AddApplicationServices(this IDistributedApplicationBuilder builder)\n {\n // Get or create snapshot-based data directory for Postgres persistence\n var postgresDataPath = GetOrCreatePostgresSnapshotPath(builder.Environment.IsDevelopment());\n\n var postgres = builder.AddPostgres(\"stemify-postgres\");\n if (builder.Environment.IsDevelopment())\n postgres.WithBindMount(postgresDataPath, \"/var/lib/postgresql/data\", isReadOnly: false);\n\n var identityDb = postgres.AddDatabase(\"stemifyidentity\");\n\n var classroomDb = postgres.AddDatabase(\"stemifyclassroom\");\n\n var resourceDb = postgres.AddDatabase(\"stemifyresource\");\n\n var notificationDb = postgres.AddDatabase(\"stemifynotification\");\n\n var productDb = postgres.AddDatabase(\"stemifyproduct\");\n\n var orderDb = postgres.AddDatabase(\"stemifyorder\");\n var paymentDb = postgres.AddDatabase(\"stemifypayment\");\n\n var cartDb = postgres.AddDatabase(\"stemifycart\");\n\n var hangfireDb = postgres.AddDatabase(\"stemifyhangfire\");\n\n var aiMemoryDb = postgres.AddDatabase(\"stemifyaimemory\");\n\n // // Add pgAdmin for database administration (Production)\n // var pgAdmin = builder\n // .AddContainer(\"pgadmin\", \"dpage/pgadmin4\")\n // .WithHttpEndpoint(targetPort: 5050, name: \"http\")\n // .WithExternalHttpEndpoints()\n // .WithEnvironment(\"PGADMIN_DEFAULT_EMAIL\", \"admin@stemify.com\")\n // .WithEnvironment(\"PGADMIN_DEFAULT_PASSWORD\", Environment.GetEnvironmentVariable(\"PGADMIN_PASSWORD\") ?? \"admin\")\n // .WithEnvironment(\"PGADMIN_CONFIG_SERVER_MODE\", \"False\")\n // .WithEnvironment(\"PGADMIN_CONFIG_MASTER_PASSWORD_REQUIRED\", \"False\")\n // .WithEnvironment(\"PGADMIN_LISTEN_PORT\", \"5050\")\n // .WaitFor(postgres);\n\n // Add RabbitMQ for messaging (required for Payment Service Saga pattern)\n IResourceBuilder<IResourceWithConnectionString> rabbitmqReference;\n if (builder.Environment.IsProduction())\n {\n var cloudAmqpUrl = builder.Configuration[\"RabbitMQ:Url\"]\n ?? Environment.GetEnvironmentVariable(\"RABBITMQ_URL\")\n ?? throw new InvalidOperationException(\"RabbitMQ URL not configured.\");\n\n rabbitmqReference = builder.AddConnectionString(\"messaging\", cloudAmqpUrl);\n }\n else\n {\n var rabbitmq = builder.AddRabbitMQ(\"messaging\")\n .WithManagementPlugin();\n rabbitmqReference = rabbitmq;\n }\n\n\n var manualFlag = Environment.GetEnvironmentVariable(\"ENABLE_OBSERVABILITY_STACK\");\n var externalObservabilityEnabled = builder.Environment.IsDevelopment() ||\n string.Equals(manualFlag, \"true\", StringComparison.OrdinalIgnoreCase);\n EndpointReference? grafanaEndpoint = null;\n externalObservabilityEnabled = false;\n if (externalObservabilityEnabled)\n {\n var prometheus = builder.AddContainer(\"prometheus\", \"prom/prometheus\", \"v3.5.0\")\n .WithBindMount(\"../../../../config/prometheus\", \"/etc/prometheus\", isReadOnly: true)\n .WithArgs(\n \"--web.enable-otlp-receiver\",\n \"--config.file=/etc/prometheus/prometheus.yml\",\n \"--storage.tsdb.retention.time=15d\",\n \"--storage.tsdb.retention.size=10GB\"\n )\n .WithHttpEndpoint(targetPort: 9090, name: \"http\");\n\n var alertmanager = builder.AddContainer(\"alertmanager\", \"prom/alertmanager\", \"v0.27.0\")\n .WithBindMount(\"../../../../config/alertmanager\", \"/etc/alertmanager\", isReadOnly: true)\n .WithArgs(\"--config.file=/etc/alertmanager/alertmanager.yml\", \"--storage.path=/alertmanager\")\n .WithHttpEndpoint(targetPort: 9093, name: \"http\");\n\n // Loki - Log aggregation system\n var loki = builder.AddContainer(\"loki\", \"grafana/loki\", \"2.9.0\")\n .WithBindMount(\"../../../../config/loki\", \"/etc/loki\", isReadOnly: true)\n .WithArgs(\"-config.file=/etc/loki/loki-config.yaml\")\n .WithHttpEndpoint(targetPort: 3100, name: \"http\")\n .WithEnvironment(\"LOKI_CONFIG_FILE\", \"/etc/loki/loki-config.yaml\");\n\n // Promtail - Log shipper for Loki\n var promtail = builder.AddContainer(\"promtail\", \"grafana/promtail\", \"2.9.0\")\n .WithBindMount(\"../../../../config/promtail\", \"/etc/promtail\", isReadOnly: true)\n .WithBindMount(\"/var/run/docker.sock\", \"/var/run/docker.sock\", isReadOnly: true)\n .WithArgs(\"-config.file=/etc/promtail/promtail-config.yaml\")\n .WithHttpEndpoint(targetPort: 9080, name: \"http\")\n .WithEnvironment(\"PROMTAIL_CONFIG_FILE\", \"/etc/promtail/promtail-config.yaml\")\n .WaitFor(loki);\n\n // Tempo - Distributed tracing backend\n var tempo = builder.AddContainer(\"tempo\", \"grafana/tempo\", \"2.3.0\")\n .WithBindMount(\"../../../../config/tempo\", \"/etc/tempo\", isReadOnly: true)\n .WithArgs(\"-config.file=/etc/tempo/tempo-config.yaml\")\n .WithHttpEndpoint(targetPort: 3200, name: \"http\")\n .WithHttpEndpoint(targetPort: 4317, name: \"otlp-grpc\")\n .WithHttpEndpoint(targetPort: 4318, name: \"otlp-http\")\n .WithHttpEndpoint(targetPort: 14268, name: \"jaeger-http\")\n .WithHttpEndpoint(targetPort: 14250, name: \"jaeger-grpc\")\n .WithHttpEndpoint(targetPort: 9411, name: \"zipkin\")\n .WithEnvironment(\"TEMPO_CONFIG_FILE\", \"/etc/tempo/tempo-config.yaml\");\n\n var grafana = builder.AddContainer(\"grafana\", \"grafana/grafana\")\n .WithBindMount(\"../../../../config/grafana/config\", \"/etc/grafana\", isReadOnly: true)\n .WithBindMount(\"../../../../config/grafana/dashboards\", \"/var/lib/grafana/dashboards\", isReadOnly: true)\n .WithEnvironment(\"PROMETHEUS_ENDPOINT\", prometheus.GetEndpoint(\"http\"))\n .WithEnvironment(\"LOKI_ENDPOINT\", loki.GetEndpoint(\"http\"))\n .WithEnvironment(\"TEMPO_ENDPOINT\", tempo.GetEndpoint(\"http\"))\n .WithHttpEndpoint(targetPort: 3000, name: \"http\")\n .WaitFor(prometheus)\n .WaitFor(loki)\n .WaitFor(tempo);\n\n grafanaEndpoint = grafana.GetEndpoint(\"http\");\n\n var configFile = \"../../../../config/otelcollector/config.dev.yaml\";\n\n builder.AddOpenTelemetryCollector(\"otelcollector\", configFile)\n .WithEnvironment(\"PROMETHEUS_ENDPOINT\", $\"{prometheus.GetEndpoint(\"http\")}/api/v1/otlp\")\n .WithEnvironment(\"ALERTMANAGER_ENDPOINT\", alertmanager.GetEndpoint(\"http\"))\n .WithEnvironment(\"LOKI_ENDPOINT\", loki.GetEndpoint(\"http\"))\n .WithEnvironment(\"TEMPO_ENDPOINT_GRPC\", tempo.GetEndpoint(\"otlp-grpc\"))\n .WaitFor(loki)\n .WaitFor(tempo)\n .WaitFor(tempo);\n }\n\n var pgAdmin = builder.Environment.IsProduction()\n ? builder\n .AddContainer(\"pgadmin\", \"dpage/pgadmin4:8.10\")\n .WithHttpEndpoint(targetPort: 5050, name: \"pgadmin-http\")\n .WithExternalHttpEndpoints()\n .WithEnvironment(\"PGADMIN_DEFAULT_EMAIL\", \"admin@stemify.com\")\n .WithEnvironment(\n \"PGADMIN_DEFAULT_PASSWORD\",\n Environment.GetEnvironmentVariable(\"PGADMIN_PASSWORD\") ?? \"admin\"\n )\n .WithEnvironment(\"PGADMIN_CONFIG_SERVER_MODE\", \"False\")\n .WithEnvironment(\"PGADMIN_CONFIG_MASTER_PASSWORD_REQUIRED\", \"False\")\n .WithEnvironment(\"PGADMIN_LISTEN_PORT\", \"5050\")\n .WithEnvironment(\"PGADMIN_CONFIG_CHECK_EMAIL_DELIVERABILITY\", \"False\")\n .WithEnvironment(\"GUNICORN_TIMEOUT\", \"120\")\n .WaitFor(postgres)\n : postgres.WithPgAdmin(x =>\n {\n x.WithHttpEndpoint(targetPort: 5050, name: \"pgadmin-dev\");\n x.WithExternalHttpEndpoints();\n x.WithEnvironment(\"PGADMIN_CONFIG_CHECK_EMAIL_DELIVERABILITY\", \"False\");\n x.WithEnvironment(\"GUNICORN_TIMEOUT\", \"120\");\n });\n\n var identityService = builder\n .AddProject<Projects.Identity_Web>(\"identity\")\n .WithExternalHttpEndpoints()\n .WithReference(identityDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(identityDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(identityService);\n\n var identityApiService = builder\n .AddProject<Projects.Identity_API>(\"identity-api\")\n .WithExternalHttpEndpoints()\n .WithReference(identityDb)\n .WithReference(identityService)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(identityDb)\n .WaitFor(identityService)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(identityApiService);\n\n // var sagaOrchestrator = builder\n // .AddProject<Projects.Saga_Orchestrator>(\"saga-orchestrator\")\n // .WithReference(identityService)\n // .WithReference(rabbitmqReference)\n // .WaitFor(identityService)\n // .WaitFor(rabbitmqReference)\n // ;\n // ConfigureObservability(sagaOrchestrator);\n\n var classroomService = builder\n .AddProject<Projects.Classroom_API>(\"classroom-api\")\n .WithExternalHttpEndpoints()\n .WithReference(classroomDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(classroomDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(classroomService);\n\n var resourceApiService = builder\n .AddProject<Projects.Resource_API>(\"resource-api\")\n .WithExternalHttpEndpoints()\n .WithReference(resourceDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(resourceDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(resourceApiService);\n\n var notificationApiService = builder\n .AddProject<Projects.Notification_API>(\"notification-api\")\n .WithExternalHttpEndpoints()\n .WithReference(notificationDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(notificationDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(notificationApiService);\n\n var productApiService = builder\n .AddProject<Projects.Product_API>(\"product-api\")\n .WithExternalHttpEndpoints()\n .WithReference(productDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(productDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(productApiService);\n\n var orderApiService = builder\n .AddProject<Projects.Order_API>(\"order-api\")\n .WithExternalHttpEndpoints()\n .WithReference(orderDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(orderDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(orderApiService);\n\n // var paymentApiService = builder\n // .AddProject<Projects.Payment_API>(\"payment-api\")\n // .WithExternalHttpEndpoints()\n // .WithReference(paymentDb)\n // .WithReference(rabbitmq)\n // .WaitFor(postgres)\n // .WaitFor(paymentDb)\n // .WaitFor(rabbitmq)\n // ;\n // ConfigureObservability(paymentApiService);\n\n // var cartApiService = builder\n // .AddProject<Projects.Cart_API>(\"cart-api\")\n // .WithExternalHttpEndpoints()\n // .WithReference(cartDb)\n // .WithReference(rabbitmq)\n // .WaitFor(postgres)\n // .WaitFor(cartDb)\n // .WaitFor(rabbitmq)\n // ;\n // ConfigureObservability(cartApiService);\n \n IResourceBuilder<IResourceWithConnectionString>? emulatorMongoDbReference = null;\n \n var mongo = builder.AddMongoDB(\"emulator-mongodb\")\n .WithLifetime(ContainerLifetime.Persistent);\n \n var emulatorMongoDb = mongo.AddDatabase(\"stemify-emulator\");\n emulatorMongoDbReference = emulatorMongoDb;\n \n\n var emulatorApiService = builder\n .AddProject<Projects.Emulator_API>(\"emulator-api\")\n .WithExternalHttpEndpoints();\n \n if (emulatorMongoDbReference is not null)\n {\n emulatorApiService\n .WithReference(emulatorMongoDbReference)\n .WaitFor(emulatorMongoDbReference);\n }\n \n ConfigureObservability(emulatorApiService);\n\n var hangfireApiService = builder\n .AddProject<Projects.Hangfire_API>(\"hangfire-api\")\n .WithExternalHttpEndpoints()\n .WithReference(hangfireDb)\n .WithReference(identityDb)\n .WithReference(orderDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(hangfireDb)\n .WaitFor(identityDb)\n .WaitFor(orderDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(hangfireApiService);\n\n \n var resourceGrpcPort = builder.Configuration[\"ResourceApi:GrpcPort\"] ?? \"5082\";\n var resourceGrpcEndpoint = $\"localhost:{resourceGrpcPort}\";\n \n var aiService = builder\n .AddUvicornApp(\"ai-service\", \"../../AIService\", \"app.main:app\")\n .WithExternalHttpEndpoints()\n .WithHttpHealthCheck(\"/health\")\n .WithReference(aiMemoryDb)\n .WithReference(rabbitmqReference)\n .WithEnvironment(\"AI_MEMORY_DB_CONNECTION\", aiMemoryDb.Resource.ConnectionStringExpression)\n .WithEnvironment(\"RABBITMQ_URL\", rabbitmqReference.Resource.ConnectionStringExpression)\n .WithEnvironment(\"RESOURCE_GRPC_ENDPOINT\", resourceGrpcEndpoint)\n .WithEnvironment(\"RESOURCE_GRPC_USE_TLS\", \"false\")\n // Redis (optional) - use configuration or environment variables if provided\n .WithEnvironment(\"REDIS_HOST\", builder.Configuration[\"AIService:Redis:Host\"]\n ?? Environment.GetEnvironmentVariable(\"REDIS_HOST\") ?? string.Empty)\n .WithEnvironment(\"REDIS_PORT\", builder.Configuration[\"AIService:Redis:Port\"]\n ?? Environment.GetEnvironmentVariable(\"REDIS_PORT\") ?? \"6379\")\n .WithEnvironment(\"REDIS_PASSWORD\", builder.Configuration[\"AIService:Redis:Password\"]\n ?? Environment.GetEnvironmentVariable(\"REDIS_PASSWORD\") ?? string.Empty)\n .WithEnvironment(\"REDIS_SSL\", builder.Configuration[\"AIService:Redis:SSL\"]\n ?? Environment.GetEnvironmentVariable(\"REDIS_SSL\") ?? \"false\")\n .WithEnvironment(\"PYTHONUNBUFFERED\", \"1\")\n .WithEnvironment(\"PYTHONDONTWRITEBYTECODE\", \"1\")\n .WithEnvironment(\"PORT\", \"8000\")\n .WithEnvironment(\"OPENAI_API_KEY\", builder.Configuration[\"AIService:LLM:OpenAI:ApiKey\"]\n ?? builder.Configuration[\"OpenAI:ApiKey\"]\n ?? Environment.GetEnvironmentVariable(\"OPENAI_API_KEY\")\n ?? \"\")\n .WithEnvironment(\"DEEPSEEK_API_KEY\", builder.Configuration[\"AIService:LLM:DeepSeek:ApiKey\"]\n ?? builder.Configuration[\"DeepSeek:ApiKey\"]\n ?? Environment.GetEnvironmentVariable(\"DEEPSEEK_API_KEY\")\n ?? \"\")\n .WithReference(resourceApiService)\n .WaitFor(resourceApiService)\n .WaitFor(rabbitmqReference);\n ConfigureObservability(aiService);\n\n var apiGateway = builder\n .AddProject<Projects.ApiGateway>(\"apigateway\")\n .WithExternalHttpEndpoints()\n .WithReference(identityService)\n // .WithReference(identityApiService)\n .WithReference(classroomService)\n .WithReference(resourceApiService)\n .WithReference(notificationApiService)\n .WithReference(emulatorApiService)\n .WaitFor(identityService)\n .WaitFor(resourceApiService)\n .WaitFor(classroomService)\n .WaitFor(notificationApiService)\n .WaitFor(productApiService)\n .WaitFor(orderApiService)\n .WaitFor(emulatorApiService)\n .WaitFor(aiService)\n ;\n ConfigureObservability(apiGateway);\n\n void ConfigureObservability<TResource>(IResourceBuilder<TResource> resourceBuilder)\n where TResource : IResourceWithEnvironment\n {\n var enabledValue = externalObservabilityEnabled ? \"true\" : \"false\";\n resourceBuilder.WithEnvironment(\"Observability__ExternalStackEnabled\", enabledValue);\n\n if (grafanaEndpoint is not null)\n {\n resourceBuilder.WithEnvironment(\"GRAFANA_URL\", grafanaEndpoint);\n }\n }\n\n return builder;\n }\n\n \n private static string GetOrCreatePostgresSnapshotPath(bool isDevelopment)\n {\n // if (!isDevelopment)\n // {\n // var productionPath = Path.Combine(\n // Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),\n // \"STEMify\",\n // \"postgres-data\");\n // Directory.CreateDirectory(productionPath);\n // return productionPath;\n // }\n\n var baseSnapshotsPath = Path.Combine(\n Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),\n \"STEMify\",\n \"postgres-snapshots\");\n\n \n Directory.CreateDirectory(baseSnapshotsPath);\n\n var mostRecentSnapshot = FindMostRecentSnapshot(baseSnapshotsPath);\n\n var timestamp = DateTime.Now.ToString(\"yyyyMMdd-HHmmss\");\n var newSnapshotPath = Path.Combine(baseSnapshotsPath, $\"postgres-data-{timestamp}\");\n \n if (mostRecentSnapshot != null && Directory.Exists(mostRecentSnapshot))\n {\n CopyDirectory(mostRecentSnapshot, newSnapshotPath);\n }\n else\n {\n \n Directory.CreateDirectory(newSnapshotPath);\n }\n\n return newSnapshotPath;\n }\n\n private static void CopyDirectory(string sourceDir, string destinationDir)\n {\n var dir = new DirectoryInfo(sourceDir);\n DirectoryInfo[] dirs = dir.GetDirectories();\n\n Directory.CreateDirectory(destinationDir);\n\n foreach (FileInfo file in dir.GetFiles())\n {\n string targetFilePath = Path.Combine(destinationDir, file.Name);\n file.CopyTo(targetFilePath, overwrite: true);\n }\n\n foreach (DirectoryInfo subDir in dirs)\n {\n string newDestinationDir = Path.Combine(destinationDir, subDir.Name);\n CopyDirectory(subDir.FullName, newDestinationDir);\n }\n }\n\n private static string? FindMostRecentSnapshot(string snapshotsBasePath)\n {\n if (!Directory.Exists(snapshotsBasePath))\n return null;\n\n var snapshotDirectories = Directory.GetDirectories(snapshotsBasePath, \"postgres-data-*\")\n .Where(dir =>\n {\n var dirName = Path.GetFileName(dir);\n if (dirName.Length < 24 || !dirName.StartsWith(\"postgres-data-\"))\n return false;\n \n var timestampPart = dirName.Substring(\"postgres-data-\".Length);\n return timestampPart.Length == 15 && // YYYYMMDD-HHmmss = 15 chars\n timestampPart[8] == '-' &&\n DateTime.TryParseExact(timestampPart, \"yyyyMMdd-HHmmss\", null, \n System.Globalization.DateTimeStyles.None, out _);\n })\n .OrderByDescending(dir =>\n {\n var dirName = Path.GetFileName(dir);\n var timestampPart = dirName.Substring(\"postgres-data-\".Length);\n if (DateTime.TryParseExact(timestampPart, \"yyyyMMdd-HHmmss\", null,\n System.Globalization.DateTimeStyles.None, out var timestamp))\n {\n return timestamp;\n }\n return DateTime.MinValue;\n })\n .ToList();\n\n return snapshotDirectories.FirstOrDefault();\n }\n }\n}\n"}, "3e4e97c9da0759d5d4f79c2f8608d0996203d42d": {"t": 1785748758.7684908, "n": 23023, "h": "3ba247aa7247e058", "c": "using Microsoft.Extensions.Hosting;\nusing Aspire.Hosting;\nusing Aspire.Hosting.ApplicationModel;\n\nnamespace STEMifyBackend.AppHost.Extensions\n{\n public static class ServiceExtensions\n {\n public static IDistributedApplicationBuilder AddApplicationServices(this IDistributedApplicationBuilder builder)\n {\n // Get or create snapshot-based data directory for Postgres persistence\n var postgresDataPath = GetOrCreatePostgresSnapshotPath(builder.Environment.IsDevelopment());\n\n var postgres = builder.AddPostgres(\"stemify-postgres\");\n if (builder.Environment.IsDevelopment())\n postgres.WithBindMount(postgresDataPath, \"/var/lib/postgresql/data\", isReadOnly: false);\n\n var identityDb = postgres.AddDatabase(\"stemifyidentity\");\n\n var classroomDb = postgres.AddDatabase(\"stemifyclassroom\");\n\n var resourceDb = postgres.AddDatabase(\"stemifyresource\");\n\n var notificationDb = postgres.AddDatabase(\"stemifynotification\");\n\n var productDb = postgres.AddDatabase(\"stemifyproduct\");\n\n var orderDb = postgres.AddDatabase(\"stemifyorder\");\n var paymentDb = postgres.AddDatabase(\"stemifypayment\");\n\n var cartDb = postgres.AddDatabase(\"stemifycart\");\n\n var hangfireDb = postgres.AddDatabase(\"stemifyhangfire\");\n\n var aiMemoryDb = postgres.AddDatabase(\"stemifyaimemory\");\n\n // // Add pgAdmin for database administration (Production)\n // var pgAdmin = builder\n // .AddContainer(\"pgadmin\", \"dpage/pgadmin4\")\n // .WithHttpEndpoint(targetPort: 5050, name: \"http\")\n // .WithExternalHttpEndpoints()\n // .WithEnvironment(\"PGADMIN_DEFAULT_EMAIL\", \"admin@stemify.com\")\n // .WithEnvironment(\"PGADMIN_DEFAULT_PASSWORD\", Environment.GetEnvironmentVariable(\"PGADMIN_PASSWORD\") ?? \"admin\")\n // .WithEnvironment(\"PGADMIN_CONFIG_SERVER_MODE\", \"False\")\n // .WithEnvironment(\"PGADMIN_CONFIG_MASTER_PASSWORD_REQUIRED\", \"False\")\n // .WithEnvironment(\"PGADMIN_LISTEN_PORT\", \"5050\")\n // .WaitFor(postgres);\n\n // Add RabbitMQ for messaging (required for Payment Service Saga pattern)\n IResourceBuilder<IResourceWithConnectionString> rabbitmqReference;\n if (builder.Environment.IsProduction())\n {\n var cloudAmqpUrl = builder.Configuration[\"RabbitMQ:Url\"]\n ?? Environment.GetEnvironmentVariable(\"RABBITMQ_URL\")\n ?? throw new InvalidOperationException(\"RabbitMQ URL not configured.\");\n\n rabbitmqReference = builder.AddConnectionString(\"messaging\", cloudAmqpUrl);\n }\n else\n {\n var rabbitmq = builder.AddRabbitMQ(\"messaging\")\n .WithManagementPlugin();\n rabbitmqReference = rabbitmq;\n }\n\n\n var manualFlag = Environment.GetEnvironmentVariable(\"ENABLE_OBSERVABILITY_STACK\");\n var externalObservabilityEnabled = builder.Environment.IsDevelopment() ||\n string.Equals(manualFlag, \"true\", StringComparison.OrdinalIgnoreCase);\n EndpointReference? grafanaEndpoint = null;\n externalObservabilityEnabled = false;\n if (externalObservabilityEnabled)\n {\n var prometheus = builder.AddContainer(\"prometheus\", \"prom/prometheus\", \"v3.5.0\")\n .WithBindMount(\"../../../../config/prometheus\", \"/etc/prometheus\", isReadOnly: true)\n .WithArgs(\n \"--web.enable-otlp-receiver\",\n \"--config.file=/etc/prometheus/prometheus.yml\",\n \"--storage.tsdb.retention.time=15d\",\n \"--storage.tsdb.retention.size=10GB\"\n )\n .WithHttpEndpoint(targetPort: 9090, name: \"http\");\n\n var alertmanager = builder.AddContainer(\"alertmanager\", \"prom/alertmanager\", \"v0.27.0\")\n .WithBindMount(\"../../../../config/alertmanager\", \"/etc/alertmanager\", isReadOnly: true)\n .WithArgs(\"--config.file=/etc/alertmanager/alertmanager.yml\", \"--storage.path=/alertmanager\")\n .WithHttpEndpoint(targetPort: 9093, name: \"http\");\n\n // Loki - Log aggregation system\n var loki = builder.AddContainer(\"loki\", \"grafana/loki\", \"2.9.0\")\n .WithBindMount(\"../../../../config/loki\", \"/etc/loki\", isReadOnly: true)\n .WithArgs(\"-config.file=/etc/loki/loki-config.yaml\")\n .WithHttpEndpoint(targetPort: 3100, name: \"http\")\n .WithEnvironment(\"LOKI_CONFIG_FILE\", \"/etc/loki/loki-config.yaml\");\n\n // Promtail - Log shipper for Loki\n var promtail = builder.AddContainer(\"promtail\", \"grafana/promtail\", \"2.9.0\")\n .WithBindMount(\"../../../../config/promtail\", \"/etc/promtail\", isReadOnly: true)\n .WithBindMount(\"/var/run/docker.sock\", \"/var/run/docker.sock\", isReadOnly: true)\n .WithArgs(\"-config.file=/etc/promtail/promtail-config.yaml\")\n .WithHttpEndpoint(targetPort: 9080, name: \"http\")\n .WithEnvironment(\"PROMTAIL_CONFIG_FILE\", \"/etc/promtail/promtail-config.yaml\")\n .WaitFor(loki);\n\n // Tempo - Distributed tracing backend\n var tempo = builder.AddContainer(\"tempo\", \"grafana/tempo\", \"2.3.0\")\n .WithBindMount(\"../../../../config/tempo\", \"/etc/tempo\", isReadOnly: true)\n .WithArgs(\"-config.file=/etc/tempo/tempo-config.yaml\")\n .WithHttpEndpoint(targetPort: 3200, name: \"http\")\n .WithHttpEndpoint(targetPort: 4317, name: \"otlp-grpc\")\n .WithHttpEndpoint(targetPort: 4318, name: \"otlp-http\")\n .WithHttpEndpoint(targetPort: 14268, name: \"jaeger-http\")\n .WithHttpEndpoint(targetPort: 14250, name: \"jaeger-grpc\")\n .WithHttpEndpoint(targetPort: 9411, name: \"zipkin\")\n .WithEnvironment(\"TEMPO_CONFIG_FILE\", \"/etc/tempo/tempo-config.yaml\");\n\n var grafana = builder.AddContainer(\"grafana\", \"grafana/grafana\")\n .WithBindMount(\"../../../../config/grafana/config\", \"/etc/grafana\", isReadOnly: true)\n .WithBindMount(\"../../../../config/grafana/dashboards\", \"/var/lib/grafana/dashboards\", isReadOnly: true)\n .WithEnvironment(\"PROMETHEUS_ENDPOINT\", prometheus.GetEndpoint(\"http\"))\n .WithEnvironment(\"LOKI_ENDPOINT\", loki.GetEndpoint(\"http\"))\n .WithEnvironment(\"TEMPO_ENDPOINT\", tempo.GetEndpoint(\"http\"))\n .WithHttpEndpoint(targetPort: 3000, name: \"http\")\n .WaitFor(prometheus)\n .WaitFor(loki)\n .WaitFor(tempo);\n\n grafanaEndpoint = grafana.GetEndpoint(\"http\");\n\n var configFile = \"../../../../config/otelcollector/config.dev.yaml\";\n\n builder.AddOpenTelemetryCollector(\"otelcollector\", configFile)\n .WithEnvironment(\"PROMETHEUS_ENDPOINT\", $\"{prometheus.GetEndpoint(\"http\")}/api/v1/otlp\")\n .WithEnvironment(\"ALERTMANAGER_ENDPOINT\", alertmanager.GetEndpoint(\"http\"))\n .WithEnvironment(\"LOKI_ENDPOINT\", loki.GetEndpoint(\"http\"))\n .WithEnvironment(\"TEMPO_ENDPOINT_GRPC\", tempo.GetEndpoint(\"otlp-grpc\"))\n .WaitFor(loki)\n .WaitFor(tempo)\n .WaitFor(tempo);\n }\n\n var pgAdmin = builder.Environment.IsProduction()\n ? builder\n .AddContainer(\"pgadmin\", \"dpage/pgadmin4:8.10\")\n .WithHttpEndpoint(targetPort: 5050, name: \"pgadmin-http\")\n .WithExternalHttpEndpoints()\n .WithEnvironment(\"PGADMIN_DEFAULT_EMAIL\", \"admin@stemify.com\")\n .WithEnvironment(\n \"PGADMIN_DEFAULT_PASSWORD\",\n Environment.GetEnvironmentVariable(\"PGADMIN_PASSWORD\") ?? \"admin\"\n )\n .WithEnvironment(\"PGADMIN_CONFIG_SERVER_MODE\", \"False\")\n .WithEnvironment(\"PGADMIN_CONFIG_MASTER_PASSWORD_REQUIRED\", \"False\")\n .WithEnvironment(\"PGADMIN_LISTEN_PORT\", \"5050\")\n .WithEnvironment(\"PGADMIN_CONFIG_CHECK_EMAIL_DELIVERABILITY\", \"False\")\n .WithEnvironment(\"GUNICORN_TIMEOUT\", \"120\")\n .WaitFor(postgres)\n : postgres.WithPgAdmin(x =>\n {\n x.WithHttpEndpoint(targetPort: 5050, name: \"pgadmin-dev\");\n x.WithExternalHttpEndpoints();\n x.WithEnvironment(\"PGADMIN_CONFIG_CHECK_EMAIL_DELIVERABILITY\", \"False\");\n x.WithEnvironment(\"GUNICORN_TIMEOUT\", \"120\");\n });\n\n var identityService = builder\n .AddProject<Projects.Identity_Web>(\"identity\")\n .WithExternalHttpEndpoints()\n .WithReference(identityDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(identityDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(identityService);\n\n var identityApiService = builder\n .AddProject<Projects.Identity_API>(\"identity-api\")\n .WithExternalHttpEndpoints()\n .WithReference(identityDb)\n .WithReference(identityService)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(identityDb)\n .WaitFor(identityService)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(identityApiService);\n\n // var sagaOrchestrator = builder\n // .AddProject<Projects.Saga_Orchestrator>(\"saga-orchestrator\")\n // .WithReference(identityService)\n // .WithReference(rabbitmqReference)\n // .WaitFor(identityService)\n // .WaitFor(rabbitmqReference)\n // ;\n // ConfigureObservability(sagaOrchestrator);\n\n var classroomService = builder\n .AddProject<Projects.Classroom_API>(\"classroom-api\")\n .WithExternalHttpEndpoints()\n .WithReference(classroomDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(classroomDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(classroomService);\n\n var resourceApiService = builder\n .AddProject<Projects.Resource_API>(\"resource-api\")\n .WithExternalHttpEndpoints()\n .WithReference(resourceDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(resourceDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(resourceApiService);\n\n var notificationApiService = builder\n .AddProject<Projects.Notification_API>(\"notification-api\")\n .WithExternalHttpEndpoints()\n .WithReference(notificationDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(notificationDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(notificationApiService);\n\n var productApiService = builder\n .AddProject<Projects.Product_API>(\"product-api\")\n .WithExternalHttpEndpoints()\n .WithReference(productDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(productDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(productApiService);\n\n var orderApiService = builder\n .AddProject<Projects.Order_API>(\"order-api\")\n .WithExternalHttpEndpoints()\n .WithReference(orderDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(orderDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(orderApiService);\n\n // var paymentApiService = builder\n // .AddProject<Projects.Payment_API>(\"payment-api\")\n // .WithExternalHttpEndpoints()\n // .WithReference(paymentDb)\n // .WithReference(rabbitmq)\n // .WaitFor(postgres)\n // .WaitFor(paymentDb)\n // .WaitFor(rabbitmq)\n // ;\n // ConfigureObservability(paymentApiService);\n\n // var cartApiService = builder\n // .AddProject<Projects.Cart_API>(\"cart-api\")\n // .WithExternalHttpEndpoints()\n // .WithReference(cartDb)\n // .WithReference(rabbitmq)\n // .WaitFor(postgres)\n // .WaitFor(cartDb)\n // .WaitFor(rabbitmq)\n // ;\n // ConfigureObservability(cartApiService);\n \n IResourceBuilder<IResourceWithConnectionString>? emulatorMongoDbReference = null;\n \n var mongo = builder.AddMongoDB(\"emulator-mongodb\")\n .WithLifetime(ContainerLifetime.Persistent);\n \n var emulatorMongoDb = mongo.AddDatabase(\"stemify-emulator\");\n emulatorMongoDbReference = emulatorMongoDb;\n \n\n var emulatorApiService = builder\n .AddProject<Projects.Emulator_API>(\"emulator-api\")\n .WithExternalHttpEndpoints();\n \n if (emulatorMongoDbReference is not null)\n {\n emulatorApiService\n .WithReference(emulatorMongoDbReference)\n .WaitFor(emulatorMongoDbReference);\n }\n \n ConfigureObservability(emulatorApiService);\n\n var hangfireApiService = builder\n .AddProject<Projects.Hangfire_API>(\"hangfire-api\")\n .WithExternalHttpEndpoints()\n .WithReference(hangfireDb)\n .WithReference(identityDb)\n .WithReference(orderDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(hangfireDb)\n .WaitFor(identityDb)\n .WaitFor(orderDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(hangfireApiService);\n\n \n var resourceGrpcPort = builder.Configuration[\"ResourceApi:GrpcPort\"] ?? \"5082\";\n var resourceGrpcEndpoint = $\"localhost:{resourceGrpcPort}\";\n \n var aiService = builder\n .AddUvicornApp(\"ai-service\", \"../../AIService\", \"app.main:app\")\n .WithExternalHttpEndpoints()\n .WithHttpHealthCheck(\"/health\")\n .WithReference(aiMemoryDb)\n .WithReference(rabbitmqReference)\n .WithEnvironment(\"AI_MEMORY_DB_CONNECTION\", aiMemoryDb.Resource.ConnectionStringExpression)\n .WithEnvironment(\"RABBITMQ_URL\", rabbitmqReference.Resource.ConnectionStringExpression)\n .WithEnvironment(\"RESOURCE_GRPC_ENDPOINT\", resourceGrpcEndpoint)\n .WithEnvironment(\"RESOURCE_GRPC_USE_TLS\", \"false\")\n // Redis (optional) - use configuration or environment variables if provided\n .WithEnvironment(\"REDIS_HOST\", builder.Configuration[\"AIService:Redis:Host\"]\n ?? Environment.GetEnvironmentVariable(\"REDIS_HOST\") ?? string.Empty)\n .WithEnvironment(\"REDIS_PORT\", builder.Configuration[\"AIService:Redis:Port\"]\n ?? Environment.GetEnvironmentVariable(\"REDIS_PORT\") ?? \"6379\")\n .WithEnvironment(\"REDIS_PASSWORD\", builder.Configuration[\"AIService:Redis:Password\"]\n ?? Environment.GetEnvironmentVariable(\"REDIS_PASSWORD\") ?? string.Empty)\n .WithEnvironment(\"REDIS_SSL\", builder.Configuration[\"AIService:Redis:SSL\"]\n ?? Environment.GetEnvironmentVariable(\"REDIS_SSL\") ?? \"false\")\n .WithEnvironment(\"PYTHONUNBUFFERED\", \"1\")\n .WithEnvironment(\"PYTHONDONTWRITEBYTECODE\", \"1\")\n .WithEnvironment(\"PORT\", \"8000\")\n .WithEnvironment(\"OPENAI_API_KEY\", builder.Configuration[\"AIService:LLM:OpenAI:ApiKey\"]\n ?? builder.Configuration[\"OpenAI:ApiKey\"]\n ?? Environment.GetEnvironmentVariable(\"OPENAI_API_KEY\")\n ?? \"\")\n .WithEnvironment(\"DEEPSEEK_API_KEY\", builder.Configuration[\"AIService:LLM:DeepSeek:ApiKey\"]\n ?? builder.Configuration[\"DeepSeek:ApiKey\"]\n ?? Environment.GetEnvironmentVariable(\"DEEPSEEK_API_KEY\")\n ?? \"\")\n .WithReference(resourceApiService)\n .WaitFor(resourceApiService)\n .WaitFor(rabbitmqReference);\n ConfigureObservability(aiService);\n\n var apiGateway = builder\n .AddProject<Projects.ApiGateway>(\"apigateway\")\n .WithExternalHttpEndpoints()\n .WithReference(identityService)\n // .WithReference(identityApiService)\n .WithReference(classroomService)\n .WithReference(resourceApiService)\n .WithReference(notificationApiService)\n .WithReference(emulatorApiService)\n .WaitFor(identityService)\n .WaitFor(resourceApiService)\n .WaitFor(classroomService)\n .WaitFor(notificationApiService)\n .WaitFor(productApiService)\n .WaitFor(orderApiService)\n .WaitFor(emulatorApiService)\n .WaitFor(aiService)\n ;\n ConfigureObservability(apiGateway);\n\n void ConfigureObservability<TResource>(IResourceBuilder<TResource> resourceBuilder)\n where TResource : IResourceWithEnvironment\n {\n var enabledValue = externalObservabilityEnabled ? \"true\" : \"false\";\n resourceBuilder.WithEnvironment(\"Observability__ExternalStackEnabled\", enabledValue);\n\n if (grafanaEndpoint is not null)\n {\n resourceBuilder.WithEnvironment(\"GRAFANA_URL\", grafanaEndpoint);\n }\n }\n\n return builder;\n }\n\n \n private static string GetOrCreatePostgresSnapshotPath(bool isDevelopment)\n {\n // if (!isDevelopment)\n // {\n // var productionPath = Path.Combine(\n // Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),\n // \"STEMify\",\n // \"postgres-data\");\n // Directory.CreateDirectory(productionPath);\n // return productionPath;\n // }\n\n var baseSnapshotsPath = Path.Combine(\n Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),\n \"STEMify\",\n \"postgres-snapshots\");\n\n \n Directory.CreateDirectory(baseSnapshotsPath);\n\n var mostRecentSnapshot = FindMostRecentSnapshot(baseSnapshotsPath);\n\n var timestamp = DateTime.Now.ToString(\"yyyyMMdd-HHmmss\");\n var newSnapshotPath = Path.Combine(baseSnapshotsPath, $\"postgres-data-{timestamp}\");\n \n if (mostRecentSnapshot != null && Directory.Exists(mostRecentSnapshot))\n {\n CopyDirectory(mostRecentSnapshot, newSnapshotPath);\n }\n else\n {\n \n Directory.CreateDirectory(newSnapshotPath);\n }\n\n return newSnapshotPath;\n }\n\n private static void CopyDirectory(string sourceDir, string destinationDir)\n {\n var dir = new DirectoryInfo(sourceDir);\n DirectoryInfo[] dirs = dir.GetDirectories();\n\n Directory.CreateDirectory(destinationDir);\n\n foreach (FileInfo file in dir.GetFiles())\n {\n string targetFilePath = Path.Combine(destinationDir, file.Name);\n file.CopyTo(targetFilePath, overwrite: true);\n }\n\n foreach (DirectoryInfo subDir in dirs)\n {\n string newDestinationDir = Path.Combine(destinationDir, subDir.Name);\n CopyDirectory(subDir.FullName, newDestinationDir);\n }\n }\n\n private static string? FindMostRecentSnapshot(string snapshotsBasePath)\n {\n if (!Directory.Exists(snapshotsBasePath))\n return null;\n\n var snapshotDirectories = Directory.GetDirectories(snapshotsBasePath, \"postgres-data-*\")\n .Where(dir =>\n {\n var dirName = Path.GetFileName(dir);\n if (dirName.Length < 24 || !dirName.StartsWith(\"postgres-data-\"))\n return false;\n \n var timestampPart = dirName.Substring(\"postgres-data-\".Length);\n return timestampPart.Length == 15 && // YYYYMMDD-HHmmss = 15 chars\n timestampPart[8] == '-' &&\n DateTime.TryParseExact(timestampPart, \"yyyyMMdd-HHmmss\", null, \n System.Globalization.DateTimeStyles.None, out _);\n })\n .OrderByDescending(dir =>\n {\n var dirName = Path.GetFileName(dir);\n var timestampPart = dirName.Substring(\"postgres-data-\".Length);\n if (DateTime.TryParseExact(timestampPart, \"yyyyMMdd-HHmmss\", null,\n System.Globalization.DateTimeStyles.None, out var timestamp))\n {\n return timestamp;\n }\n return DateTime.MinValue;\n })\n .ToList();\n\n return snapshotDirectories.FirstOrDefault();\n }\n }\n}\n"}, "4fdfcceaef5a4b9d758c287b063f594ba5dc9f78": {"t": 1785748760.0058405, "n": 22204, "h": "4fb3fae2895a6be2", "c": "using Microsoft.Extensions.Hosting;\nusing Aspire.Hosting;\nusing Aspire.Hosting.ApplicationModel;\n\nnamespace STEMifyBackend.AppHost.Extensions\n{\n public static class ServiceExtensions\n {\n public static IDistributedApplicationBuilder AddApplicationServices(this IDistributedApplicationBuilder builder)\n {\n // Get or create snapshot-based data directory for Postgres persistence\n var postgresDataPath = GetOrCreatePostgresSnapshotPath(builder.Environment.IsDevelopment());\n\n var postgres = builder.AddPostgres(\"stemify-postgres\");\n if (builder.Environment.IsDevelopment())\n postgres.WithBindMount(postgresDataPath, \"/var/lib/postgresql/data\", isReadOnly: false);\n\n var identityDb = postgres.AddDatabase(\"stemifyidentity\");\n\n var classroomDb = postgres.AddDatabase(\"stemifyclassroom\");\n\n var resourceDb = postgres.AddDatabase(\"stemifyresource\");\n\n var notificationDb = postgres.AddDatabase(\"stemifynotification\");\n\n var productDb = postgres.AddDatabase(\"stemifyproduct\");\n\n var orderDb = postgres.AddDatabase(\"stemifyorder\");\n var paymentDb = postgres.AddDatabase(\"stemifypayment\");\n\n var cartDb = postgres.AddDatabase(\"stemifycart\");\n\n var hangfireDb = postgres.AddDatabase(\"stemifyhangfire\");\n\n var aiMemoryDb = postgres.AddDatabase(\"stemifyaimemory\");\n\n // // Add pgAdmin for database administration (Production)\n // var pgAdmin = builder\n // .AddContainer(\"pgadmin\", \"dpage/pgadmin4\")\n // .WithHttpEndpoint(targetPort: 5050, name: \"http\")\n // .WithExternalHttpEndpoints()\n // .WithEnvironment(\"PGADMIN_DEFAULT_EMAIL\", \"admin@stemify.com\")\n // .WithEnvironment(\"PGADMIN_DEFAULT_PASSWORD\", Environment.GetEnvironmentVariable(\"PGADMIN_PASSWORD\") ?? \"admin\")\n // .WithEnvironment(\"PGADMIN_CONFIG_SERVER_MODE\", \"False\")\n // .WithEnvironment(\"PGADMIN_CONFIG_MASTER_PASSWORD_REQUIRED\", \"False\")\n // .WithEnvironment(\"PGADMIN_LISTEN_PORT\", \"5050\")\n // .WaitFor(postgres);\n\n // Add RabbitMQ for messaging (required for Payment Service Saga pattern)\n IResourceBuilder<IResourceWithConnectionString> rabbitmqReference;\n if (builder.Environment.IsProduction())\n {\n var cloudAmqpUrl = builder.Configuration[\"RabbitMQ:Url\"]\n ?? Environment.GetEnvironmentVariable(\"RABBITMQ_URL\")\n ?? throw new InvalidOperationException(\"RabbitMQ URL not configured.\");\n\n rabbitmqReference = builder.AddConnectionString(\"messaging\", cloudAmqpUrl);\n }\n else\n {\n var rabbitmq = builder.AddRabbitMQ(\"messaging\")\n .WithManagementPlugin();\n rabbitmqReference = rabbitmq;\n }\n\n\n var manualFlag = Environment.GetEnvironmentVariable(\"ENABLE_OBSERVABILITY_STACK\");\n var externalObservabilityEnabled = builder.Environment.IsDevelopment() ||\n string.Equals(manualFlag, \"true\", StringComparison.OrdinalIgnoreCase);\n EndpointReference? grafanaEndpoint = null;\n externalObservabilityEnabled = false;\n if (externalObservabilityEnabled)\n {\n var prometheus = builder.AddContainer(\"prometheus\", \"prom/prometheus\", \"v3.5.0\")\n .WithBindMount(\"../../../../config/prometheus\", \"/etc/prometheus\", isReadOnly: true)\n .WithArgs(\n \"--web.enable-otlp-receiver\",\n \"--config.file=/etc/prometheus/prometheus.yml\",\n \"--storage.tsdb.retention.time=15d\",\n \"--storage.tsdb.retention.size=10GB\"\n )\n .WithHttpEndpoint(targetPort: 9090, name: \"http\");\n\n var alertmanager = builder.AddContainer(\"alertmanager\", \"prom/alertmanager\", \"v0.27.0\")\n .WithBindMount(\"../../../../config/alertmanager\", \"/etc/alertmanager\", isReadOnly: true)\n .WithArgs(\"--config.file=/etc/alertmanager/alertmanager.yml\", \"--storage.path=/alertmanager\")\n .WithHttpEndpoint(targetPort: 9093, name: \"http\");\n\n // Loki - Log aggregation system\n var loki = builder.AddContainer(\"loki\", \"grafana/loki\", \"2.9.0\")\n .WithBindMount(\"../../../../config/loki\", \"/etc/loki\", isReadOnly: true)\n .WithArgs(\"-config.file=/etc/loki/loki-config.yaml\")\n .WithHttpEndpoint(targetPort: 3100, name: \"http\")\n .WithEnvironment(\"LOKI_CONFIG_FILE\", \"/etc/loki/loki-config.yaml\");\n\n // Promtail - Log shipper for Loki\n var promtail = builder.AddContainer(\"promtail\", \"grafana/promtail\", \"2.9.0\")\n .WithBindMount(\"../../../../config/promtail\", \"/etc/promtail\", isReadOnly: true)\n .WithBindMount(\"/var/run/docker.sock\", \"/var/run/docker.sock\", isReadOnly: true)\n .WithArgs(\"-config.file=/etc/promtail/promtail-config.yaml\")\n .WithHttpEndpoint(targetPort: 9080, name: \"http\")\n .WithEnvironment(\"PROMTAIL_CONFIG_FILE\", \"/etc/promtail/promtail-config.yaml\")\n .WaitFor(loki);\n\n // Tempo - Distributed tracing backend\n var tempo = builder.AddContainer(\"tempo\", \"grafana/tempo\", \"2.3.0\")\n .WithBindMount(\"../../../../config/tempo\", \"/etc/tempo\", isReadOnly: true)\n .WithArgs(\"-config.file=/etc/tempo/tempo-config.yaml\")\n .WithHttpEndpoint(targetPort: 3200, name: \"http\")\n .WithHttpEndpoint(targetPort: 4317, name: \"otlp-grpc\")\n .WithHttpEndpoint(targetPort: 4318, name: \"otlp-http\")\n .WithHttpEndpoint(targetPort: 14268, name: \"jaeger-http\")\n .WithHttpEndpoint(targetPort: 14250, name: \"jaeger-grpc\")\n .WithHttpEndpoint(targetPort: 9411, name: \"zipkin\")\n .WithEnvironment(\"TEMPO_CONFIG_FILE\", \"/etc/tempo/tempo-config.yaml\");\n\n var grafana = builder.AddContainer(\"grafana\", \"grafana/grafana\")\n .WithBindMount(\"../../../../config/grafana/config\", \"/etc/grafana\", isReadOnly: true)\n .WithBindMount(\"../../../../config/grafana/dashboards\", \"/var/lib/grafana/dashboards\", isReadOnly: true)\n .WithEnvironment(\"PROMETHEUS_ENDPOINT\", prometheus.GetEndpoint(\"http\"))\n .WithEnvironment(\"LOKI_ENDPOINT\", loki.GetEndpoint(\"http\"))\n .WithEnvironment(\"TEMPO_ENDPOINT\", tempo.GetEndpoint(\"http\"))\n .WithHttpEndpoint(targetPort: 3000, name: \"http\")\n .WaitFor(prometheus)\n .WaitFor(loki)\n .WaitFor(tempo);\n\n grafanaEndpoint = grafana.GetEndpoint(\"http\");\n\n var configFile = \"../../../../config/otelcollector/config.dev.yaml\";\n\n builder.AddOpenTelemetryCollector(\"otelcollector\", configFile)\n .WithEnvironment(\"PROMETHEUS_ENDPOINT\", $\"{prometheus.GetEndpoint(\"http\")}/api/v1/otlp\")\n .WithEnvironment(\"ALERTMANAGER_ENDPOINT\", alertmanager.GetEndpoint(\"http\"))\n .WithEnvironment(\"LOKI_ENDPOINT\", loki.GetEndpoint(\"http\"))\n .WithEnvironment(\"TEMPO_ENDPOINT_GRPC\", tempo.GetEndpoint(\"otlp-grpc\"))\n .WaitFor(loki)\n .WaitFor(tempo)\n .WaitFor(tempo);\n }\n\n var pgAdmin = builder.Environment.IsProduction()\n ? builder\n .AddContainer(\"pgadmin\", \"dpage/pgadmin4:8.10\")\n .WithHttpEndpoint(targetPort: 5050, name: \"pgadmin-http\")\n .WithExternalHttpEndpoints()\n .WithEnvironment(\"PGADMIN_DEFAULT_EMAIL\", \"admin@stemify.com\")\n .WithEnvironment(\n \"PGADMIN_DEFAULT_PASSWORD\",\n Environment.GetEnvironmentVariable(\"PGADMIN_PASSWORD\") ?? \"admin\"\n )\n .WithEnvironment(\"PGADMIN_CONFIG_SERVER_MODE\", \"False\")\n .WithEnvironment(\"PGADMIN_CONFIG_MASTER_PASSWORD_REQUIRED\", \"False\")\n .WithEnvironment(\"PGADMIN_LISTEN_PORT\", \"5050\")\n .WithEnvironment(\"PGADMIN_CONFIG_CHECK_EMAIL_DELIVERABILITY\", \"False\")\n .WithEnvironment(\"GUNICORN_TIMEOUT\", \"120\")\n .WaitFor(postgres)\n : postgres.WithPgAdmin(x =>\n {\n x.WithHttpEndpoint(targetPort: 5050, name: \"pgadmin-dev\");\n x.WithExternalHttpEndpoints();\n x.WithEnvironment(\"PGADMIN_CONFIG_CHECK_EMAIL_DELIVERABILITY\", \"False\");\n x.WithEnvironment(\"GUNICORN_TIMEOUT\", \"120\");\n });\n\n var identityService = builder\n .AddProject<Projects.Identity_Web>(\"identity\")\n .WithExternalHttpEndpoints()\n .WithReference(identityDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(identityDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(identityService);\n\n var identityApiService = builder\n .AddProject<Projects.Identity_API>(\"identity-api\")\n .WithExternalHttpEndpoints()\n .WithReference(identityDb)\n .WithReference(identityService)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(identityDb)\n .WaitFor(identityService)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(identityApiService);\n\n // var sagaOrchestrator = builder\n // .AddProject<Projects.Saga_Orchestrator>(\"saga-orchestrator\")\n // .WithReference(identityService)\n // .WithReference(rabbitmqReference)\n // .WaitFor(identityService)\n // .WaitFor(rabbitmqReference)\n // ;\n // ConfigureObservability(sagaOrchestrator);\n\n var classroomService = builder\n .AddProject<Projects.Classroom_API>(\"classroom-api\")\n .WithExternalHttpEndpoints()\n .WithReference(classroomDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(classroomDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(classroomService);\n\n var resourceApiService = builder\n .AddProject<Projects.Resource_API>(\"resource-api\")\n .WithExternalHttpEndpoints()\n .WithReference(resourceDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(resourceDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(resourceApiService);\n\n var notificationApiService = builder\n .AddProject<Projects.Notification_API>(\"notification-api\")\n .WithExternalHttpEndpoints()\n .WithReference(notificationDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(notificationDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(notificationApiService);\n\n var productApiService = builder\n .AddProject<Projects.Product_API>(\"product-api\")\n .WithExternalHttpEndpoints()\n .WithReference(productDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(productDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(productApiService);\n\n var orderApiService = builder\n .AddProject<Projects.Order_API>(\"order-api\")\n .WithExternalHttpEndpoints()\n .WithReference(orderDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(orderDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(orderApiService);\n\n // var paymentApiService = builder\n // .AddProject<Projects.Payment_API>(\"payment-api\")\n // .WithExternalHttpEndpoints()\n // .WithReference(paymentDb)\n // .WithReference(rabbitmq)\n // .WaitFor(postgres)\n // .WaitFor(paymentDb)\n // .WaitFor(rabbitmq)\n // ;\n // ConfigureObservability(paymentApiService);\n\n // var cartApiService = builder\n // .AddProject<Projects.Cart_API>(\"cart-api\")\n // .WithExternalHttpEndpoints()\n // .WithReference(cartDb)\n // .WithReference(rabbitmq)\n // .WaitFor(postgres)\n // .WaitFor(cartDb)\n // .WaitFor(rabbitmq)\n // ;\n // ConfigureObservability(cartApiService);\n \n IResourceBuilder<IResourceWithConnectionString>? emulatorMongoDbReference = null;\n \n var mongo = builder.AddMongoDB(\"emulator-mongodb\")\n .WithLifetime(ContainerLifetime.Persistent);\n \n var emulatorMongoDb = mongo.AddDatabase(\"stemify-emulator\");\n emulatorMongoDbReference = emulatorMongoDb;\n \n\n var emulatorApiService = builder\n .AddProject<Projects.Emulator_API>(\"emulator-api\")\n .WithExternalHttpEndpoints();\n \n if (emulatorMongoDbReference is not null)\n {\n emulatorApiService\n .WithReference(emulatorMongoDbReference)\n .WaitFor(emulatorMongoDbReference);\n }\n \n ConfigureObservability(emulatorApiService);\n\n var hangfireApiService = builder\n .AddProject<Projects.Hangfire_API>(\"hangfire-api\")\n .WithExternalHttpEndpoints()\n .WithReference(hangfireDb)\n .WithReference(identityDb)\n .WithReference(orderDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(hangfireDb)\n .WaitFor(identityDb)\n .WaitFor(orderDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(hangfireApiService);\n\n \n var resourceGrpcPort = builder.Configuration[\"ResourceApi:GrpcPort\"] ?? \"5082\";\n var resourceGrpcEndpoint = $\"localhost:{resourceGrpcPort}\";\n \n var aiService = builder\n .AddUvicornApp(\"ai-service\", \"../../AIService\", \"app.main:app\")\n .WithExternalHttpEndpoints()\n .WithHttpHealthCheck(\"/health\")\n .WithReference(aiMemoryDb)\n .WithReference(rabbitmqReference)\n .WithEnvironment(\"AI_MEMORY_DB_CONNECTION\", aiMemoryDb.Resource.ConnectionStringExpression)\n .WithEnvironment(\"RABBITMQ_URL\", rabbitmqReference.Resource.ConnectionStringExpression)\n .WithEnvironment(\"RESOURCE_GRPC_ENDPOINT\", resourceGrpcEndpoint)\n .WithEnvironment(\"RESOURCE_GRPC_USE_TLS\", \"false\")\n .WithEnvironment(\"PYTHONUNBUFFERED\", \"1\")\n .WithEnvironment(\"PYTHONDONTWRITEBYTECODE\", \"1\")\n .WithEnvironment(\"PORT\", \"8000\")\n .WithEnvironment(\"OPENAI_API_KEY\", builder.Configuration[\"AIService:LLM:OpenAI:ApiKey\"]\n ?? builder.Configuration[\"OpenAI:ApiKey\"]\n ?? Environment.GetEnvironmentVariable(\"OPENAI_API_KEY\")\n ?? \"\")\n .WithEnvironment(\"DEEPSEEK_API_KEY\", builder.Configuration[\"AIService:LLM:DeepSeek:ApiKey\"]\n ?? builder.Configuration[\"DeepSeek:ApiKey\"]\n ?? Environment.GetEnvironmentVariable(\"DEEPSEEK_API_KEY\")\n ?? \"\")\n .WithReference(resourceApiService)\n .WaitFor(resourceApiService)\n .WaitFor(rabbitmqReference);\n ConfigureObservability(aiService);\n\n var apiGateway = builder\n .AddProject<Projects.ApiGateway>(\"apigateway\")\n .WithExternalHttpEndpoints()\n .WithReference(identityService)\n // .WithReference(identityApiService)\n .WithReference(classroomService)\n .WithReference(resourceApiService)\n .WithReference(notificationApiService)\n .WithReference(emulatorApiService)\n .WaitFor(identityService)\n .WaitFor(resourceApiService)\n .WaitFor(classroomService)\n .WaitFor(notificationApiService)\n .WaitFor(productApiService)\n .WaitFor(orderApiService)\n .WaitFor(emulatorApiService)\n .WaitFor(aiService)\n ;\n ConfigureObservability(apiGateway);\n\n void ConfigureObservability<TResource>(IResourceBuilder<TResource> resourceBuilder)\n where TResource : IResourceWithEnvironment\n {\n var enabledValue = externalObservabilityEnabled ? \"true\" : \"false\";\n resourceBuilder.WithEnvironment(\"Observability__ExternalStackEnabled\", enabledValue);\n\n if (grafanaEndpoint is not null)\n {\n resourceBuilder.WithEnvironment(\"GRAFANA_URL\", grafanaEndpoint);\n }\n }\n\n return builder;\n }\n\n \n private static string GetOrCreatePostgresSnapshotPath(bool isDevelopment)\n {\n // if (!isDevelopment)\n // {\n // var productionPath = Path.Combine(\n // Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),\n // \"STEMify\",\n // \"postgres-data\");\n // Directory.CreateDirectory(productionPath);\n // return productionPath;\n // }\n\n var baseSnapshotsPath = Path.Combine(\n Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),\n \"STEMify\",\n \"postgres-snapshots\");\n\n \n Directory.CreateDirectory(baseSnapshotsPath);\n\n var mostRecentSnapshot = FindMostRecentSnapshot(baseSnapshotsPath);\n\n var timestamp = DateTime.Now.ToString(\"yyyyMMdd-HHmmss\");\n var newSnapshotPath = Path.Combine(baseSnapshotsPath, $\"postgres-data-{timestamp}\");\n \n if (mostRecentSnapshot != null && Directory.Exists(mostRecentSnapshot))\n {\n CopyDirectory(mostRecentSnapshot, newSnapshotPath);\n }\n else\n {\n \n Directory.CreateDirectory(newSnapshotPath);\n }\n\n return newSnapshotPath;\n }\n\n private static void CopyDirectory(string sourceDir, string destinationDir)\n {\n var dir = new DirectoryInfo(sourceDir);\n DirectoryInfo[] dirs = dir.GetDirectories();\n\n Directory.CreateDirectory(destinationDir);\n\n foreach (FileInfo file in dir.GetFiles())\n {\n string targetFilePath = Path.Combine(destinationDir, file.Name);\n file.CopyTo(targetFilePath, overwrite: true);\n }\n\n foreach (DirectoryInfo subDir in dirs)\n {\n string newDestinationDir = Path.Combine(destinationDir, subDir.Name);\n CopyDirectory(subDir.FullName, newDestinationDir);\n }\n }\n\n private static string? FindMostRecentSnapshot(string snapshotsBasePath)\n {\n if (!Directory.Exists(snapshotsBasePath))\n return null;\n\n var snapshotDirectories = Directory.GetDirectories(snapshotsBasePath, \"postgres-data-*\")\n .Where(dir =>\n {\n var dirName = Path.GetFileName(dir);\n if (dirName.Length < 24 || !dirName.StartsWith(\"postgres-data-\"))\n return false;\n \n var timestampPart = dirName.Substring(\"postgres-data-\".Length);\n return timestampPart.Length == 15 && // YYYYMMDD-HHmmss = 15 chars\n timestampPart[8] == '-' &&\n DateTime.TryParseExact(timestampPart, \"yyyyMMdd-HHmmss\", null, \n System.Globalization.DateTimeStyles.None, out _);\n })\n .OrderByDescending(dir =>\n {\n var dirName = Path.GetFileName(dir);\n var timestampPart = dirName.Substring(\"postgres-data-\".Length);\n if (DateTime.TryParseExact(timestampPart, \"yyyyMMdd-HHmmss\", null,\n System.Globalization.DateTimeStyles.None, out var timestamp))\n {\n return timestamp;\n }\n return DateTime.MinValue;\n })\n .ToList();\n\n return snapshotDirectories.FirstOrDefault();\n }\n }\n}\n"}, "350ee5a67a6b169e8ec754b785b75312c8f9e27c": {"t": 1785748761.257946, "n": 22006, "h": "20d1d283af98aa50", "c": "using Microsoft.Extensions.Hosting;\nusing Aspire.Hosting;\nusing Aspire.Hosting.ApplicationModel;\n\nnamespace STEMifyBackend.AppHost.Extensions\n{\n public static class ServiceExtensions\n {\n public static IDistributedApplicationBuilder AddApplicationServices(this IDistributedApplicationBuilder builder)\n {\n // Get or create snapshot-based data directory for Postgres persistence\n var postgresDataPath = GetOrCreatePostgresSnapshotPath(builder.Environment.IsDevelopment());\n\n var postgres = builder.AddPostgres(\"stemify-postgres\");\n if (builder.Environment.IsDevelopment())\n postgres.WithBindMount(postgresDataPath, \"/var/lib/postgresql/data\", isReadOnly: false);\n\n var identityDb = postgres.AddDatabase(\"stemifyidentity\");\n\n var classroomDb = postgres.AddDatabase(\"stemifyclassroom\");\n\n var resourceDb = postgres.AddDatabase(\"stemifyresource\");\n\n var notificationDb = postgres.AddDatabase(\"stemifynotification\");\n\n var productDb = postgres.AddDatabase(\"stemifyproduct\");\n\n var orderDb = postgres.AddDatabase(\"stemifyorder\");\n var paymentDb = postgres.AddDatabase(\"stemifypayment\");\n\n var cartDb = postgres.AddDatabase(\"stemifycart\");\n\n var hangfireDb = postgres.AddDatabase(\"stemifyhangfire\");\n\n var aiMemoryDb = postgres.AddDatabase(\"stemifyaimemory\");\n\n // // Add pgAdmin for database administration (Production)\n // var pgAdmin = builder\n // .AddContainer(\"pgadmin\", \"dpage/pgadmin4\")\n // .WithHttpEndpoint(targetPort: 5050, name: \"http\")\n // .WithExternalHttpEndpoints()\n // .WithEnvironment(\"PGADMIN_DEFAULT_EMAIL\", \"admin@stemify.com\")\n // .WithEnvironment(\"PGADMIN_DEFAULT_PASSWORD\", Environment.GetEnvironmentVariable(\"PGADMIN_PASSWORD\") ?? \"admin\")\n // .WithEnvironment(\"PGADMIN_CONFIG_SERVER_MODE\", \"False\")\n // .WithEnvironment(\"PGADMIN_CONFIG_MASTER_PASSWORD_REQUIRED\", \"False\")\n // .WithEnvironment(\"PGADMIN_LISTEN_PORT\", \"5050\")\n // .WaitFor(postgres);\n\n // Add RabbitMQ for messaging (required for Payment Service Saga pattern)\n IResourceBuilder<IResourceWithConnectionString> rabbitmqReference;\n if (builder.Environment.IsProduction())\n {\n var cloudAmqpUrl = builder.Configuration[\"RabbitMQ:Url\"]\n ?? Environment.GetEnvironmentVariable(\"RABBITMQ_URL\")\n ?? throw new InvalidOperationException(\"RabbitMQ URL not configured.\");\n\n rabbitmqReference = builder.AddConnectionString(\"messaging\", cloudAmqpUrl);\n }\n else\n {\n var rabbitmq = builder.AddRabbitMQ(\"messaging\")\n .WithManagementPlugin();\n rabbitmqReference = rabbitmq;\n }\n\n\n var manualFlag = Environment.GetEnvironmentVariable(\"ENABLE_OBSERVABILITY_STACK\");\n var externalObservabilityEnabled = builder.Environment.IsDevelopment() ||\n string.Equals(manualFlag, \"true\", StringComparison.OrdinalIgnoreCase);\n EndpointReference? grafanaEndpoint = null;\n externalObservabilityEnabled = false;\n if (externalObservabilityEnabled)\n {\n var prometheus = builder.AddContainer(\"prometheus\", \"prom/prometheus\", \"v3.5.0\")\n .WithBindMount(\"../../../../config/prometheus\", \"/etc/prometheus\", isReadOnly: true)\n .WithArgs(\n \"--web.enable-otlp-receiver\",\n \"--config.file=/etc/prometheus/prometheus.yml\",\n \"--storage.tsdb.retention.time=15d\",\n \"--storage.tsdb.retention.size=10GB\"\n )\n .WithHttpEndpoint(targetPort: 9090, name: \"http\");\n\n var alertmanager = builder.AddContainer(\"alertmanager\", \"prom/alertmanager\", \"v0.27.0\")\n .WithBindMount(\"../../../../config/alertmanager\", \"/etc/alertmanager\", isReadOnly: true)\n .WithArgs(\"--config.file=/etc/alertmanager/alertmanager.yml\", \"--storage.path=/alertmanager\")\n .WithHttpEndpoint(targetPort: 9093, name: \"http\");\n\n // Loki - Log aggregation system\n var loki = builder.AddContainer(\"loki\", \"grafana/loki\", \"2.9.0\")\n .WithBindMount(\"../../../../config/loki\", \"/etc/loki\", isReadOnly: true)\n .WithArgs(\"-config.file=/etc/loki/loki-config.yaml\")\n .WithHttpEndpoint(targetPort: 3100, name: \"http\")\n .WithEnvironment(\"LOKI_CONFIG_FILE\", \"/etc/loki/loki-config.yaml\");\n\n // Promtail - Log shipper for Loki\n var promtail = builder.AddContainer(\"promtail\", \"grafana/promtail\", \"2.9.0\")\n .WithBindMount(\"../../../../config/promtail\", \"/etc/promtail\", isReadOnly: true)\n .WithBindMount(\"/var/run/docker.sock\", \"/var/run/docker.sock\", isReadOnly: true)\n .WithArgs(\"-config.file=/etc/promtail/promtail-config.yaml\")\n .WithHttpEndpoint(targetPort: 9080, name: \"http\")\n .WithEnvironment(\"PROMTAIL_CONFIG_FILE\", \"/etc/promtail/promtail-config.yaml\")\n .WaitFor(loki);\n\n // Tempo - Distributed tracing backend\n var tempo = builder.AddContainer(\"tempo\", \"grafana/tempo\", \"2.3.0\")\n .WithBindMount(\"../../../../config/tempo\", \"/etc/tempo\", isReadOnly: true)\n .WithArgs(\"-config.file=/etc/tempo/tempo-config.yaml\")\n .WithHttpEndpoint(targetPort: 3200, name: \"http\")\n .WithHttpEndpoint(targetPort: 4317, name: \"otlp-grpc\")\n .WithHttpEndpoint(targetPort: 4318, name: \"otlp-http\")\n .WithHttpEndpoint(targetPort: 14268, name: \"jaeger-http\")\n .WithHttpEndpoint(targetPort: 14250, name: \"jaeger-grpc\")\n .WithHttpEndpoint(targetPort: 9411, name: \"zipkin\")\n .WithEnvironment(\"TEMPO_CONFIG_FILE\", \"/etc/tempo/tempo-config.yaml\");\n\n var grafana = builder.AddContainer(\"grafana\", \"grafana/grafana\")\n .WithBindMount(\"../../../../config/grafana/config\", \"/etc/grafana\", isReadOnly: true)\n .WithBindMount(\"../../../../config/grafana/dashboards\", \"/var/lib/grafana/dashboards\", isReadOnly: true)\n .WithEnvironment(\"PROMETHEUS_ENDPOINT\", prometheus.GetEndpoint(\"http\"))\n .WithEnvironment(\"LOKI_ENDPOINT\", loki.GetEndpoint(\"http\"))\n .WithEnvironment(\"TEMPO_ENDPOINT\", tempo.GetEndpoint(\"http\"))\n .WithHttpEndpoint(targetPort: 3000, name: \"http\")\n .WaitFor(prometheus)\n .WaitFor(loki)\n .WaitFor(tempo);\n\n grafanaEndpoint = grafana.GetEndpoint(\"http\");\n\n var configFile = \"../../../../config/otelcollector/config.dev.yaml\";\n\n builder.AddOpenTelemetryCollector(\"otelcollector\", configFile)\n .WithEnvironment(\"PROMETHEUS_ENDPOINT\", $\"{prometheus.GetEndpoint(\"http\")}/api/v1/otlp\")\n .WithEnvironment(\"ALERTMANAGER_ENDPOINT\", alertmanager.GetEndpoint(\"http\"))\n .WithEnvironment(\"LOKI_ENDPOINT\", loki.GetEndpoint(\"http\"))\n .WithEnvironment(\"TEMPO_ENDPOINT_GRPC\", tempo.GetEndpoint(\"otlp-grpc\"))\n .WaitFor(loki)\n .WaitFor(tempo)\n .WaitFor(tempo);\n }\n\n var pgAdmin = builder.Environment.IsProduction()\n ? builder\n .AddContainer(\"pgadmin\", \"dpage/pgadmin4:8.10\")\n .WithHttpEndpoint(targetPort: 5050, name: \"pgadmin-http\")\n .WithExternalHttpEndpoints()\n .WithEnvironment(\"PGADMIN_DEFAULT_EMAIL\", \"admin@stemify.com\")\n .WithEnvironment(\n \"PGADMIN_DEFAULT_PASSWORD\",\n Environment.GetEnvironmentVariable(\"PGADMIN_PASSWORD\") ?? \"admin\"\n )\n .WithEnvironment(\"PGADMIN_CONFIG_SERVER_MODE\", \"False\")\n .WithEnvironment(\"PGADMIN_CONFIG_MASTER_PASSWORD_REQUIRED\", \"False\")\n .WithEnvironment(\"PGADMIN_LISTEN_PORT\", \"5050\")\n .WithEnvironment(\"PGADMIN_CONFIG_CHECK_EMAIL_DELIVERABILITY\", \"False\")\n .WithEnvironment(\"GUNICORN_TIMEOUT\", \"120\")\n .WaitFor(postgres)\n : postgres.WithPgAdmin(x =>\n {\n x.WithHttpEndpoint(targetPort: 5050, name: \"pgadmin-dev\");\n x.WithExternalHttpEndpoints();\n x.WithEnvironment(\"PGADMIN_CONFIG_CHECK_EMAIL_DELIVERABILITY\", \"False\");\n x.WithEnvironment(\"GUNICORN_TIMEOUT\", \"120\");\n });\n\n var identityService = builder\n .AddProject<Projects.Identity_Web>(\"identity\")\n .WithExternalHttpEndpoints()\n .WithReference(identityDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(identityDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(identityService);\n\n var identityApiService = builder\n .AddProject<Projects.Identity_API>(\"identity-api\")\n .WithExternalHttpEndpoints()\n .WithReference(identityDb)\n .WithReference(identityService)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(identityDb)\n .WaitFor(identityService)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(identityApiService);\n\n // var sagaOrchestrator = builder\n // .AddProject<Projects.Saga_Orchestrator>(\"saga-orchestrator\")\n // .WithReference(identityService)\n // .WithReference(rabbitmqReference)\n // .WaitFor(identityService)\n // .WaitFor(rabbitmqReference)\n // ;\n // ConfigureObservability(sagaOrchestrator);\n\n var classroomService = builder\n .AddProject<Projects.Classroom_API>(\"classroom-api\")\n .WithExternalHttpEndpoints()\n .WithReference(classroomDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(classroomDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(classroomService);\n\n var resourceApiService = builder\n .AddProject<Projects.Resource_API>(\"resource-api\")\n .WithExternalHttpEndpoints()\n .WithReference(resourceDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(resourceDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(resourceApiService);\n\n var notificationApiService = builder\n .AddProject<Projects.Notification_API>(\"notification-api\")\n .WithExternalHttpEndpoints()\n .WithReference(notificationDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(notificationDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(notificationApiService);\n\n var productApiService = builder\n .AddProject<Projects.Product_API>(\"product-api\")\n .WithExternalHttpEndpoints()\n .WithReference(productDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(productDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(productApiService);\n\n var orderApiService = builder\n .AddProject<Projects.Order_API>(\"order-api\")\n .WithExternalHttpEndpoints()\n .WithReference(orderDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(orderDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(orderApiService);\n\n // var paymentApiService = builder\n // .AddProject<Projects.Payment_API>(\"payment-api\")\n // .WithExternalHttpEndpoints()\n // .WithReference(paymentDb)\n // .WithReference(rabbitmq)\n // .WaitFor(postgres)\n // .WaitFor(paymentDb)\n // .WaitFor(rabbitmq)\n // ;\n // ConfigureObservability(paymentApiService);\n\n // var cartApiService = builder\n // .AddProject<Projects.Cart_API>(\"cart-api\")\n // .WithExternalHttpEndpoints()\n // .WithReference(cartDb)\n // .WithReference(rabbitmq)\n // .WaitFor(postgres)\n // .WaitFor(cartDb)\n // .WaitFor(rabbitmq)\n // ;\n // ConfigureObservability(cartApiService);\n \n IResourceBuilder<IResourceWithConnectionString>? emulatorMongoDbReference = null;\n \n var mongo = builder.AddMongoDB(\"emulator-mongodb\")\n .WithLifetime(ContainerLifetime.Persistent);\n \n var emulatorMongoDb = mongo.AddDatabase(\"stemify-emulator\");\n emulatorMongoDbReference = emulatorMongoDb;\n \n\n var emulatorApiService = builder\n .AddProject<Projects.Emulator_API>(\"emulator-api\")\n .WithExternalHttpEndpoints();\n \n if (emulatorMongoDbReference is not null)\n {\n emulatorApiService\n .WithReference(emulatorMongoDbReference)\n .WaitFor(emulatorMongoDbReference);\n }\n \n ConfigureObservability(emulatorApiService);\n\n var hangfireApiService = builder\n .AddProject<Projects.Hangfire_API>(\"hangfire-api\")\n .WithExternalHttpEndpoints()\n .WithReference(hangfireDb)\n .WithReference(identityDb)\n .WithReference(orderDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(hangfireDb)\n .WaitFor(identityDb)\n .WaitFor(orderDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(hangfireApiService);\n\n \n var resourceGrpcPort = builder.Configuration[\"ResourceApi:GrpcPort\"] ?? \"5082\";\n var resourceGrpcEndpoint = $\"localhost:{resourceGrpcPort}\";\n \n var aiService = builder\n .AddUvicornApp(\"ai-service\", \"../../AIService\", \"app.main:app\")\n .WithExternalHttpEndpoints()\n .WithHttpHealthCheck(\"/health\")\n .WithReference(aiMemoryDb)\n .WithEnvironment(\"AI_MEMORY_DB_CONNECTION\", aiMemoryDb.Resource.ConnectionStringExpression)\n .WithEnvironment(\"RESOURCE_GRPC_ENDPOINT\", resourceGrpcEndpoint)\n .WithEnvironment(\"RESOURCE_GRPC_USE_TLS\", \"false\")\n .WithEnvironment(\"PYTHONUNBUFFERED\", \"1\")\n .WithEnvironment(\"PYTHONDONTWRITEBYTECODE\", \"1\")\n .WithEnvironment(\"PORT\", \"8000\")\n .WithEnvironment(\"OPENAI_API_KEY\", builder.Configuration[\"AIService:LLM:OpenAI:ApiKey\"]\n ?? builder.Configuration[\"OpenAI:ApiKey\"]\n ?? Environment.GetEnvironmentVariable(\"OPENAI_API_KEY\")\n ?? \"\")\n .WithEnvironment(\"DEEPSEEK_API_KEY\", builder.Configuration[\"AIService:LLM:DeepSeek:ApiKey\"]\n ?? builder.Configuration[\"DeepSeek:ApiKey\"]\n ?? Environment.GetEnvironmentVariable(\"DEEPSEEK_API_KEY\")\n ?? \"\")\n .WithReference(resourceApiService)\n .WaitFor(resourceApiService);\n ConfigureObservability(aiService);\n\n var apiGateway = builder\n .AddProject<Projects.ApiGateway>(\"apigateway\")\n .WithExternalHttpEndpoints()\n .WithReference(identityService)\n // .WithReference(identityApiService)\n .WithReference(classroomService)\n .WithReference(resourceApiService)\n .WithReference(notificationApiService)\n .WithReference(emulatorApiService)\n .WaitFor(identityService)\n .WaitFor(resourceApiService)\n .WaitFor(classroomService)\n .WaitFor(notificationApiService)\n .WaitFor(productApiService)\n .WaitFor(orderApiService)\n .WaitFor(emulatorApiService)\n .WaitFor(aiService)\n ;\n ConfigureObservability(apiGateway);\n\n void ConfigureObservability<TResource>(IResourceBuilder<TResource> resourceBuilder)\n where TResource : IResourceWithEnvironment\n {\n var enabledValue = externalObservabilityEnabled ? \"true\" : \"false\";\n resourceBuilder.WithEnvironment(\"Observability__ExternalStackEnabled\", enabledValue);\n\n if (grafanaEndpoint is not null)\n {\n resourceBuilder.WithEnvironment(\"GRAFANA_URL\", grafanaEndpoint);\n }\n }\n\n return builder;\n }\n\n \n private static string GetOrCreatePostgresSnapshotPath(bool isDevelopment)\n {\n // if (!isDevelopment)\n // {\n // var productionPath = Path.Combine(\n // Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),\n // \"STEMify\",\n // \"postgres-data\");\n // Directory.CreateDirectory(productionPath);\n // return productionPath;\n // }\n\n var baseSnapshotsPath = Path.Combine(\n Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),\n \"STEMify\",\n \"postgres-snapshots\");\n\n \n Directory.CreateDirectory(baseSnapshotsPath);\n\n var mostRecentSnapshot = FindMostRecentSnapshot(baseSnapshotsPath);\n\n var timestamp = DateTime.Now.ToString(\"yyyyMMdd-HHmmss\");\n var newSnapshotPath = Path.Combine(baseSnapshotsPath, $\"postgres-data-{timestamp}\");\n \n if (mostRecentSnapshot != null && Directory.Exists(mostRecentSnapshot))\n {\n CopyDirectory(mostRecentSnapshot, newSnapshotPath);\n }\n else\n {\n \n Directory.CreateDirectory(newSnapshotPath);\n }\n\n return newSnapshotPath;\n }\n\n private static void CopyDirectory(string sourceDir, string destinationDir)\n {\n var dir = new DirectoryInfo(sourceDir);\n DirectoryInfo[] dirs = dir.GetDirectories();\n\n Directory.CreateDirectory(destinationDir);\n\n foreach (FileInfo file in dir.GetFiles())\n {\n string targetFilePath = Path.Combine(destinationDir, file.Name);\n file.CopyTo(targetFilePath, overwrite: true);\n }\n\n foreach (DirectoryInfo subDir in dirs)\n {\n string newDestinationDir = Path.Combine(destinationDir, subDir.Name);\n CopyDirectory(subDir.FullName, newDestinationDir);\n }\n }\n\n private static string? FindMostRecentSnapshot(string snapshotsBasePath)\n {\n if (!Directory.Exists(snapshotsBasePath))\n return null;\n\n var snapshotDirectories = Directory.GetDirectories(snapshotsBasePath, \"postgres-data-*\")\n .Where(dir =>\n {\n var dirName = Path.GetFileName(dir);\n if (dirName.Length < 24 || !dirName.StartsWith(\"postgres-data-\"))\n return false;\n \n var timestampPart = dirName.Substring(\"postgres-data-\".Length);\n return timestampPart.Length == 15 && // YYYYMMDD-HHmmss = 15 chars\n timestampPart[8] == '-' &&\n DateTime.TryParseExact(timestampPart, \"yyyyMMdd-HHmmss\", null, \n System.Globalization.DateTimeStyles.None, out _);\n })\n .OrderByDescending(dir =>\n {\n var dirName = Path.GetFileName(dir);\n var timestampPart = dirName.Substring(\"postgres-data-\".Length);\n if (DateTime.TryParseExact(timestampPart, \"yyyyMMdd-HHmmss\", null,\n System.Globalization.DateTimeStyles.None, out var timestamp))\n {\n return timestamp;\n }\n return DateTime.MinValue;\n })\n .ToList();\n\n return snapshotDirectories.FirstOrDefault();\n }\n }\n}\n"}, "f65b88ef1caeda5a08c053b67e75a3d12af2dcf8": {"t": 1785748762.4984, "n": 21957, "h": "a82e9143b0b246fe", "c": "using Microsoft.Extensions.Hosting;\nusing Aspire.Hosting;\nusing Aspire.Hosting.ApplicationModel;\n\nnamespace STEMifyBackend.AppHost.Extensions\n{\n public static class ServiceExtensions\n {\n public static IDistributedApplicationBuilder AddApplicationServices(this IDistributedApplicationBuilder builder)\n {\n // Get or create snapshot-based data directory for Postgres persistence\n var postgresDataPath = GetOrCreatePostgresSnapshotPath(builder.Environment.IsDevelopment());\n\n var postgres = builder.AddPostgres(\"stemify-postgres\");\n if (builder.Environment.IsDevelopment())\n postgres.WithBindMount(postgresDataPath, \"/var/lib/postgresql/data\", isReadOnly: false);\n\n var identityDb = postgres.AddDatabase(\"stemifyidentity\");\n\n var classroomDb = postgres.AddDatabase(\"stemifyclassroom\");\n\n var resourceDb = postgres.AddDatabase(\"stemifyresource\");\n\n var notificationDb = postgres.AddDatabase(\"stemifynotification\");\n\n var productDb = postgres.AddDatabase(\"stemifyproduct\");\n\n var orderDb = postgres.AddDatabase(\"stemifyorder\");\n var paymentDb = postgres.AddDatabase(\"stemifypayment\");\n\n var cartDb = postgres.AddDatabase(\"stemifycart\");\n\n var hangfireDb = postgres.AddDatabase(\"stemifyhangfire\");\n\n var aiMemoryDb = postgres.AddDatabase(\"stemifyaimemory\");\n\n // // Add pgAdmin for database administration (Production)\n // var pgAdmin = builder\n // .AddContainer(\"pgadmin\", \"dpage/pgadmin4\")\n // .WithHttpEndpoint(targetPort: 5050, name: \"http\")\n // .WithExternalHttpEndpoints()\n // .WithEnvironment(\"PGADMIN_DEFAULT_EMAIL\", \"admin@stemify.com\")\n // .WithEnvironment(\"PGADMIN_DEFAULT_PASSWORD\", Environment.GetEnvironmentVariable(\"PGADMIN_PASSWORD\") ?? \"admin\")\n // .WithEnvironment(\"PGADMIN_CONFIG_SERVER_MODE\", \"False\")\n // .WithEnvironment(\"PGADMIN_CONFIG_MASTER_PASSWORD_REQUIRED\", \"False\")\n // .WithEnvironment(\"PGADMIN_LISTEN_PORT\", \"5050\")\n // .WaitFor(postgres);\n\n // Add RabbitMQ for messaging (required for Payment Service Saga pattern)\n IResourceBuilder<IResourceWithConnectionString> rabbitmqReference;\n if (builder.Environment.IsProduction())\n {\n var cloudAmqpUrl = builder.Configuration[\"RabbitMQ:Url\"]\n ?? Environment.GetEnvironmentVariable(\"RABBITMQ_URL\")\n ?? throw new InvalidOperationException(\"RabbitMQ URL not configured.\");\n\n rabbitmqReference = builder.AddConnectionString(\"messaging\", cloudAmqpUrl);\n }\n else\n {\n var rabbitmq = builder.AddRabbitMQ(\"messaging\")\n .WithManagementPlugin();\n rabbitmqReference = rabbitmq;\n }\n\n\n var manualFlag = Environment.GetEnvironmentVariable(\"ENABLE_OBSERVABILITY_STACK\");\n var externalObservabilityEnabled = builder.Environment.IsDevelopment() ||\n string.Equals(manualFlag, \"true\", StringComparison.OrdinalIgnoreCase);\n EndpointReference? grafanaEndpoint = null;\n\n if (externalObservabilityEnabled)\n {\n var prometheus = builder.AddContainer(\"prometheus\", \"prom/prometheus\", \"v3.5.0\")\n .WithBindMount(\"../../../../config/prometheus\", \"/etc/prometheus\", isReadOnly: true)\n .WithArgs(\n \"--web.enable-otlp-receiver\",\n \"--config.file=/etc/prometheus/prometheus.yml\",\n \"--storage.tsdb.retention.time=15d\",\n \"--storage.tsdb.retention.size=10GB\"\n )\n .WithHttpEndpoint(targetPort: 9090, name: \"http\");\n\n var alertmanager = builder.AddContainer(\"alertmanager\", \"prom/alertmanager\", \"v0.27.0\")\n .WithBindMount(\"../../../../config/alertmanager\", \"/etc/alertmanager\", isReadOnly: true)\n .WithArgs(\"--config.file=/etc/alertmanager/alertmanager.yml\", \"--storage.path=/alertmanager\")\n .WithHttpEndpoint(targetPort: 9093, name: \"http\");\n\n // Loki - Log aggregation system\n var loki = builder.AddContainer(\"loki\", \"grafana/loki\", \"2.9.0\")\n .WithBindMount(\"../../../../config/loki\", \"/etc/loki\", isReadOnly: true)\n .WithArgs(\"-config.file=/etc/loki/loki-config.yaml\")\n .WithHttpEndpoint(targetPort: 3100, name: \"http\")\n .WithEnvironment(\"LOKI_CONFIG_FILE\", \"/etc/loki/loki-config.yaml\");\n\n // Promtail - Log shipper for Loki\n var promtail = builder.AddContainer(\"promtail\", \"grafana/promtail\", \"2.9.0\")\n .WithBindMount(\"../../../../config/promtail\", \"/etc/promtail\", isReadOnly: true)\n .WithBindMount(\"/var/run/docker.sock\", \"/var/run/docker.sock\", isReadOnly: true)\n .WithArgs(\"-config.file=/etc/promtail/promtail-config.yaml\")\n .WithHttpEndpoint(targetPort: 9080, name: \"http\")\n .WithEnvironment(\"PROMTAIL_CONFIG_FILE\", \"/etc/promtail/promtail-config.yaml\")\n .WaitFor(loki);\n\n // Tempo - Distributed tracing backend\n var tempo = builder.AddContainer(\"tempo\", \"grafana/tempo\", \"2.3.0\")\n .WithBindMount(\"../../../../config/tempo\", \"/etc/tempo\", isReadOnly: true)\n .WithArgs(\"-config.file=/etc/tempo/tempo-config.yaml\")\n .WithHttpEndpoint(targetPort: 3200, name: \"http\")\n .WithHttpEndpoint(targetPort: 4317, name: \"otlp-grpc\")\n .WithHttpEndpoint(targetPort: 4318, name: \"otlp-http\")\n .WithHttpEndpoint(targetPort: 14268, name: \"jaeger-http\")\n .WithHttpEndpoint(targetPort: 14250, name: \"jaeger-grpc\")\n .WithHttpEndpoint(targetPort: 9411, name: \"zipkin\")\n .WithEnvironment(\"TEMPO_CONFIG_FILE\", \"/etc/tempo/tempo-config.yaml\");\n\n var grafana = builder.AddContainer(\"grafana\", \"grafana/grafana\")\n .WithBindMount(\"../../../../config/grafana/config\", \"/etc/grafana\", isReadOnly: true)\n .WithBindMount(\"../../../../config/grafana/dashboards\", \"/var/lib/grafana/dashboards\", isReadOnly: true)\n .WithEnvironment(\"PROMETHEUS_ENDPOINT\", prometheus.GetEndpoint(\"http\"))\n .WithEnvironment(\"LOKI_ENDPOINT\", loki.GetEndpoint(\"http\"))\n .WithEnvironment(\"TEMPO_ENDPOINT\", tempo.GetEndpoint(\"http\"))\n .WithHttpEndpoint(targetPort: 3000, name: \"http\")\n .WaitFor(prometheus)\n .WaitFor(loki)\n .WaitFor(tempo);\n\n grafanaEndpoint = grafana.GetEndpoint(\"http\");\n\n var configFile = \"../../../../config/otelcollector/config.dev.yaml\";\n\n builder.AddOpenTelemetryCollector(\"otelcollector\", configFile)\n .WithEnvironment(\"PROMETHEUS_ENDPOINT\", $\"{prometheus.GetEndpoint(\"http\")}/api/v1/otlp\")\n .WithEnvironment(\"ALERTMANAGER_ENDPOINT\", alertmanager.GetEndpoint(\"http\"))\n .WithEnvironment(\"LOKI_ENDPOINT\", loki.GetEndpoint(\"http\"))\n .WithEnvironment(\"TEMPO_ENDPOINT_GRPC\", tempo.GetEndpoint(\"otlp-grpc\"))\n .WaitFor(loki)\n .WaitFor(tempo)\n .WaitFor(tempo);\n }\n\n var pgAdmin = builder.Environment.IsProduction()\n ? builder\n .AddContainer(\"pgadmin\", \"dpage/pgadmin4:8.10\")\n .WithHttpEndpoint(targetPort: 5050, name: \"pgadmin-http\")\n .WithExternalHttpEndpoints()\n .WithEnvironment(\"PGADMIN_DEFAULT_EMAIL\", \"admin@stemify.com\")\n .WithEnvironment(\n \"PGADMIN_DEFAULT_PASSWORD\",\n Environment.GetEnvironmentVariable(\"PGADMIN_PASSWORD\") ?? \"admin\"\n )\n .WithEnvironment(\"PGADMIN_CONFIG_SERVER_MODE\", \"False\")\n .WithEnvironment(\"PGADMIN_CONFIG_MASTER_PASSWORD_REQUIRED\", \"False\")\n .WithEnvironment(\"PGADMIN_LISTEN_PORT\", \"5050\")\n .WithEnvironment(\"PGADMIN_CONFIG_CHECK_EMAIL_DELIVERABILITY\", \"False\")\n .WithEnvironment(\"GUNICORN_TIMEOUT\", \"120\")\n .WaitFor(postgres)\n : postgres.WithPgAdmin(x =>\n {\n x.WithHttpEndpoint(targetPort: 5050, name: \"pgadmin-dev\");\n x.WithExternalHttpEndpoints();\n x.WithEnvironment(\"PGADMIN_CONFIG_CHECK_EMAIL_DELIVERABILITY\", \"False\");\n x.WithEnvironment(\"GUNICORN_TIMEOUT\", \"120\");\n });\n\n var identityService = builder\n .AddProject<Projects.Identity_Web>(\"identity\")\n .WithExternalHttpEndpoints()\n .WithReference(identityDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(identityDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(identityService);\n\n var identityApiService = builder\n .AddProject<Projects.Identity_API>(\"identity-api\")\n .WithExternalHttpEndpoints()\n .WithReference(identityDb)\n .WithReference(identityService)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(identityDb)\n .WaitFor(identityService)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(identityApiService);\n\n // var sagaOrchestrator = builder\n // .AddProject<Projects.Saga_Orchestrator>(\"saga-orchestrator\")\n // .WithReference(identityService)\n // .WithReference(rabbitmqReference)\n // .WaitFor(identityService)\n // .WaitFor(rabbitmqReference)\n // ;\n // ConfigureObservability(sagaOrchestrator);\n\n var classroomService = builder\n .AddProject<Projects.Classroom_API>(\"classroom-api\")\n .WithExternalHttpEndpoints()\n .WithReference(classroomDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(classroomDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(classroomService);\n\n var resourceApiService = builder\n .AddProject<Projects.Resource_API>(\"resource-api\")\n .WithExternalHttpEndpoints()\n .WithReference(resourceDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(resourceDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(resourceApiService);\n\n var notificationApiService = builder\n .AddProject<Projects.Notification_API>(\"notification-api\")\n .WithExternalHttpEndpoints()\n .WithReference(notificationDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(notificationDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(notificationApiService);\n\n var productApiService = builder\n .AddProject<Projects.Product_API>(\"product-api\")\n .WithExternalHttpEndpoints()\n .WithReference(productDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(productDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(productApiService);\n\n var orderApiService = builder\n .AddProject<Projects.Order_API>(\"order-api\")\n .WithExternalHttpEndpoints()\n .WithReference(orderDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(orderDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(orderApiService);\n\n // var paymentApiService = builder\n // .AddProject<Projects.Payment_API>(\"payment-api\")\n // .WithExternalHttpEndpoints()\n // .WithReference(paymentDb)\n // .WithReference(rabbitmq)\n // .WaitFor(postgres)\n // .WaitFor(paymentDb)\n // .WaitFor(rabbitmq)\n // ;\n // ConfigureObservability(paymentApiService);\n\n // var cartApiService = builder\n // .AddProject<Projects.Cart_API>(\"cart-api\")\n // .WithExternalHttpEndpoints()\n // .WithReference(cartDb)\n // .WithReference(rabbitmq)\n // .WaitFor(postgres)\n // .WaitFor(cartDb)\n // .WaitFor(rabbitmq)\n // ;\n // ConfigureObservability(cartApiService);\n \n IResourceBuilder<IResourceWithConnectionString>? emulatorMongoDbReference = null;\n \n var mongo = builder.AddMongoDB(\"emulator-mongodb\")\n .WithLifetime(ContainerLifetime.Persistent);\n \n var emulatorMongoDb = mongo.AddDatabase(\"stemify-emulator\");\n emulatorMongoDbReference = emulatorMongoDb;\n \n\n var emulatorApiService = builder\n .AddProject<Projects.Emulator_API>(\"emulator-api\")\n .WithExternalHttpEndpoints();\n \n if (emulatorMongoDbReference is not null)\n {\n emulatorApiService\n .WithReference(emulatorMongoDbReference)\n .WaitFor(emulatorMongoDbReference);\n }\n \n ConfigureObservability(emulatorApiService);\n\n var hangfireApiService = builder\n .AddProject<Projects.Hangfire_API>(\"hangfire-api\")\n .WithExternalHttpEndpoints()\n .WithReference(hangfireDb)\n .WithReference(identityDb)\n .WithReference(orderDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(hangfireDb)\n .WaitFor(identityDb)\n .WaitFor(orderDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(hangfireApiService);\n\n \n var resourceGrpcPort = builder.Configuration[\"ResourceApi:GrpcPort\"] ?? \"5082\";\n var resourceGrpcEndpoint = $\"localhost:{resourceGrpcPort}\";\n \n var aiService = builder\n .AddUvicornApp(\"ai-service\", \"../../AIService\", \"app.main:app\")\n .WithExternalHttpEndpoints()\n .WithHttpHealthCheck(\"/health\")\n .WithReference(aiMemoryDb)\n .WithEnvironment(\"AI_MEMORY_DB_CONNECTION\", aiMemoryDb.Resource.ConnectionStringExpression)\n .WithEnvironment(\"RESOURCE_GRPC_ENDPOINT\", resourceGrpcEndpoint)\n .WithEnvironment(\"RESOURCE_GRPC_USE_TLS\", \"false\")\n .WithEnvironment(\"PYTHONUNBUFFERED\", \"1\")\n .WithEnvironment(\"PYTHONDONTWRITEBYTECODE\", \"1\")\n .WithEnvironment(\"PORT\", \"8000\")\n .WithEnvironment(\"OPENAI_API_KEY\", builder.Configuration[\"AIService:LLM:OpenAI:ApiKey\"]\n ?? builder.Configuration[\"OpenAI:ApiKey\"]\n ?? Environment.GetEnvironmentVariable(\"OPENAI_API_KEY\")\n ?? \"\")\n .WithEnvironment(\"DEEPSEEK_API_KEY\", builder.Configuration[\"AIService:LLM:DeepSeek:ApiKey\"]\n ?? builder.Configuration[\"DeepSeek:ApiKey\"]\n ?? Environment.GetEnvironmentVariable(\"DEEPSEEK_API_KEY\")\n ?? \"\")\n .WithReference(resourceApiService)\n .WaitFor(resourceApiService);\n ConfigureObservability(aiService);\n\n var apiGateway = builder\n .AddProject<Projects.ApiGateway>(\"apigateway\")\n .WithExternalHttpEndpoints()\n .WithReference(identityService)\n // .WithReference(identityApiService)\n .WithReference(classroomService)\n .WithReference(resourceApiService)\n .WithReference(notificationApiService)\n .WithReference(emulatorApiService)\n .WaitFor(identityService)\n .WaitFor(resourceApiService)\n .WaitFor(classroomService)\n .WaitFor(notificationApiService)\n .WaitFor(productApiService)\n .WaitFor(orderApiService)\n .WaitFor(emulatorApiService)\n .WaitFor(aiService)\n ;\n ConfigureObservability(apiGateway);\n\n void ConfigureObservability<TResource>(IResourceBuilder<TResource> resourceBuilder)\n where TResource : IResourceWithEnvironment\n {\n var enabledValue = externalObservabilityEnabled ? \"true\" : \"false\";\n resourceBuilder.WithEnvironment(\"Observability__ExternalStackEnabled\", enabledValue);\n\n if (grafanaEndpoint is not null)\n {\n resourceBuilder.WithEnvironment(\"GRAFANA_URL\", grafanaEndpoint);\n }\n }\n\n return builder;\n }\n\n \n private static string GetOrCreatePostgresSnapshotPath(bool isDevelopment)\n {\n // if (!isDevelopment)\n // {\n // var productionPath = Path.Combine(\n // Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),\n // \"STEMify\",\n // \"postgres-data\");\n // Directory.CreateDirectory(productionPath);\n // return productionPath;\n // }\n\n var baseSnapshotsPath = Path.Combine(\n Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),\n \"STEMify\",\n \"postgres-snapshots\");\n\n \n Directory.CreateDirectory(baseSnapshotsPath);\n\n var mostRecentSnapshot = FindMostRecentSnapshot(baseSnapshotsPath);\n\n var timestamp = DateTime.Now.ToString(\"yyyyMMdd-HHmmss\");\n var newSnapshotPath = Path.Combine(baseSnapshotsPath, $\"postgres-data-{timestamp}\");\n \n if (mostRecentSnapshot != null && Directory.Exists(mostRecentSnapshot))\n {\n CopyDirectory(mostRecentSnapshot, newSnapshotPath);\n }\n else\n {\n \n Directory.CreateDirectory(newSnapshotPath);\n }\n\n return newSnapshotPath;\n }\n\n private static void CopyDirectory(string sourceDir, string destinationDir)\n {\n var dir = new DirectoryInfo(sourceDir);\n DirectoryInfo[] dirs = dir.GetDirectories();\n\n Directory.CreateDirectory(destinationDir);\n\n foreach (FileInfo file in dir.GetFiles())\n {\n string targetFilePath = Path.Combine(destinationDir, file.Name);\n file.CopyTo(targetFilePath, overwrite: true);\n }\n\n foreach (DirectoryInfo subDir in dirs)\n {\n string newDestinationDir = Path.Combine(destinationDir, subDir.Name);\n CopyDirectory(subDir.FullName, newDestinationDir);\n }\n }\n\n private static string? FindMostRecentSnapshot(string snapshotsBasePath)\n {\n if (!Directory.Exists(snapshotsBasePath))\n return null;\n\n var snapshotDirectories = Directory.GetDirectories(snapshotsBasePath, \"postgres-data-*\")\n .Where(dir =>\n {\n var dirName = Path.GetFileName(dir);\n if (dirName.Length < 24 || !dirName.StartsWith(\"postgres-data-\"))\n return false;\n \n var timestampPart = dirName.Substring(\"postgres-data-\".Length);\n return timestampPart.Length == 15 && // YYYYMMDD-HHmmss = 15 chars\n timestampPart[8] == '-' &&\n DateTime.TryParseExact(timestampPart, \"yyyyMMdd-HHmmss\", null, \n System.Globalization.DateTimeStyles.None, out _);\n })\n .OrderByDescending(dir =>\n {\n var dirName = Path.GetFileName(dir);\n var timestampPart = dirName.Substring(\"postgres-data-\".Length);\n if (DateTime.TryParseExact(timestampPart, \"yyyyMMdd-HHmmss\", null,\n System.Globalization.DateTimeStyles.None, out var timestamp))\n {\n return timestamp;\n }\n return DateTime.MinValue;\n })\n .ToList();\n\n return snapshotDirectories.FirstOrDefault();\n }\n }\n}\n"}, "f3d68c4347aaef210b871c018aa1332ba092751f": {"t": 1785748763.7296805, "n": 21674, "h": "04e1e0ac0e0865f3", "c": "using Microsoft.Extensions.Hosting;\n\nnamespace STEMifyBackend.AppHost.Extensions\n{\n public static class ServiceExtensions\n {\n public static IDistributedApplicationBuilder AddApplicationServices(this IDistributedApplicationBuilder builder)\n {\n // Get or create snapshot-based data directory for Postgres persistence\n var postgresDataPath = GetOrCreatePostgresSnapshotPath(builder.Environment.IsDevelopment());\n\n var postgres = builder.AddPostgres(\"stemify-postgres\");\n if (builder.Environment.IsDevelopment())\n postgres.WithBindMount(postgresDataPath, \"/var/lib/postgresql/data\", isReadOnly: false);\n\n var identityDb = postgres.AddDatabase(\"stemifyidentity\");\n\n var classroomDb = postgres.AddDatabase(\"stemifyclassroom\");\n\n var resourceDb = postgres.AddDatabase(\"stemifyresource\");\n\n var notificationDb = postgres.AddDatabase(\"stemifynotification\");\n\n var productDb = postgres.AddDatabase(\"stemifyproduct\");\n\n var orderDb = postgres.AddDatabase(\"stemifyorder\");\n var paymentDb = postgres.AddDatabase(\"stemifypayment\");\n\n var cartDb = postgres.AddDatabase(\"stemifycart\");\n\n var hangfireDb = postgres.AddDatabase(\"stemifyhangfire\");\n\n // // Add pgAdmin for database administration (Production)\n // var pgAdmin = builder\n // .AddContainer(\"pgadmin\", \"dpage/pgadmin4\")\n // .WithHttpEndpoint(targetPort: 5050, name: \"http\")\n // .WithExternalHttpEndpoints()\n // .WithEnvironment(\"PGADMIN_DEFAULT_EMAIL\", \"admin@stemify.com\")\n // .WithEnvironment(\"PGADMIN_DEFAULT_PASSWORD\", Environment.GetEnvironmentVariable(\"PGADMIN_PASSWORD\") ?? \"admin\")\n // .WithEnvironment(\"PGADMIN_CONFIG_SERVER_MODE\", \"False\")\n // .WithEnvironment(\"PGADMIN_CONFIG_MASTER_PASSWORD_REQUIRED\", \"False\")\n // .WithEnvironment(\"PGADMIN_LISTEN_PORT\", \"5050\")\n // .WaitFor(postgres);\n\n // Add RabbitMQ for messaging (required for Payment Service Saga pattern)\n IResourceBuilder<IResourceWithConnectionString> rabbitmqReference;\n if (builder.Environment.IsProduction())\n {\n var cloudAmqpUrl = builder.Configuration[\"RabbitMQ:Url\"]\n ?? Environment.GetEnvironmentVariable(\"RABBITMQ_URL\")\n ?? throw new InvalidOperationException(\"RabbitMQ URL not configured.\");\n\n rabbitmqReference = builder.AddConnectionString(\"messaging\", cloudAmqpUrl);\n }\n else\n {\n var rabbitmq = builder.AddRabbitMQ(\"messaging\")\n .WithManagementPlugin();\n rabbitmqReference = rabbitmq;\n }\n\n\n var manualFlag = Environment.GetEnvironmentVariable(\"ENABLE_OBSERVABILITY_STACK\");\n var externalObservabilityEnabled = builder.Environment.IsDevelopment() ||\n string.Equals(manualFlag, \"true\", StringComparison.OrdinalIgnoreCase);\n EndpointReference? grafanaEndpoint = null;\n\n if (externalObservabilityEnabled)\n {\n var prometheus = builder.AddContainer(\"prometheus\", \"prom/prometheus\", \"v3.5.0\")\n .WithBindMount(\"../../../../config/prometheus\", \"/etc/prometheus\", isReadOnly: true)\n .WithArgs(\n \"--web.enable-otlp-receiver\",\n \"--config.file=/etc/prometheus/prometheus.yml\",\n \"--storage.tsdb.retention.time=15d\",\n \"--storage.tsdb.retention.size=10GB\"\n )\n .WithHttpEndpoint(targetPort: 9090, name: \"http\");\n\n var alertmanager = builder.AddContainer(\"alertmanager\", \"prom/alertmanager\", \"v0.27.0\")\n .WithBindMount(\"../../../../config/alertmanager\", \"/etc/alertmanager\", isReadOnly: true)\n .WithArgs(\"--config.file=/etc/alertmanager/alertmanager.yml\", \"--storage.path=/alertmanager\")\n .WithHttpEndpoint(targetPort: 9093, name: \"http\");\n\n // Loki - Log aggregation system\n var loki = builder.AddContainer(\"loki\", \"grafana/loki\", \"2.9.0\")\n .WithBindMount(\"../../../../config/loki\", \"/etc/loki\", isReadOnly: true)\n .WithArgs(\"-config.file=/etc/loki/loki-config.yaml\")\n .WithHttpEndpoint(targetPort: 3100, name: \"http\")\n .WithEnvironment(\"LOKI_CONFIG_FILE\", \"/etc/loki/loki-config.yaml\");\n\n // Promtail - Log shipper for Loki\n var promtail = builder.AddContainer(\"promtail\", \"grafana/promtail\", \"2.9.0\")\n .WithBindMount(\"../../../../config/promtail\", \"/etc/promtail\", isReadOnly: true)\n .WithBindMount(\"/var/run/docker.sock\", \"/var/run/docker.sock\", isReadOnly: true)\n .WithArgs(\"-config.file=/etc/promtail/promtail-config.yaml\")\n .WithHttpEndpoint(targetPort: 9080, name: \"http\")\n .WithEnvironment(\"PROMTAIL_CONFIG_FILE\", \"/etc/promtail/promtail-config.yaml\")\n .WaitFor(loki);\n\n // Tempo - Distributed tracing backend\n var tempo = builder.AddContainer(\"tempo\", \"grafana/tempo\", \"2.3.0\")\n .WithBindMount(\"../../../../config/tempo\", \"/etc/tempo\", isReadOnly: true)\n .WithArgs(\"-config.file=/etc/tempo/tempo-config.yaml\")\n .WithHttpEndpoint(targetPort: 3200, name: \"http\")\n .WithHttpEndpoint(targetPort: 4317, name: \"otlp-grpc\")\n .WithHttpEndpoint(targetPort: 4318, name: \"otlp-http\")\n .WithHttpEndpoint(targetPort: 14268, name: \"jaeger-http\")\n .WithHttpEndpoint(targetPort: 14250, name: \"jaeger-grpc\")\n .WithHttpEndpoint(targetPort: 9411, name: \"zipkin\")\n .WithEnvironment(\"TEMPO_CONFIG_FILE\", \"/etc/tempo/tempo-config.yaml\");\n\n var grafana = builder.AddContainer(\"grafana\", \"grafana/grafana\")\n .WithBindMount(\"../../../../config/grafana/config\", \"/etc/grafana\", isReadOnly: true)\n .WithBindMount(\"../../../../config/grafana/dashboards\", \"/var/lib/grafana/dashboards\", isReadOnly: true)\n .WithEnvironment(\"PROMETHEUS_ENDPOINT\", prometheus.GetEndpoint(\"http\"))\n .WithEnvironment(\"LOKI_ENDPOINT\", loki.GetEndpoint(\"http\"))\n .WithEnvironment(\"TEMPO_ENDPOINT\", tempo.GetEndpoint(\"http\"))\n .WithHttpEndpoint(targetPort: 3000, name: \"http\")\n .WaitFor(prometheus)\n .WaitFor(loki)\n .WaitFor(tempo);\n\n grafanaEndpoint = grafana.GetEndpoint(\"http\");\n\n var configFile = \"../../../../config/otelcollector/config.dev.yaml\";\n\n builder.AddOpenTelemetryCollector(\"otelcollector\", configFile)\n .WithEnvironment(\"PROMETHEUS_ENDPOINT\", $\"{prometheus.GetEndpoint(\"http\")}/api/v1/otlp\")\n .WithEnvironment(\"ALERTMANAGER_ENDPOINT\", alertmanager.GetEndpoint(\"http\"))\n .WithEnvironment(\"LOKI_ENDPOINT\", loki.GetEndpoint(\"http\"))\n .WithEnvironment(\"TEMPO_ENDPOINT_GRPC\", tempo.GetEndpoint(\"otlp-grpc\"))\n .WaitFor(loki)\n .WaitFor(tempo)\n .WaitFor(tempo);\n }\n\n var pgAdmin = builder.Environment.IsProduction()\n ? builder\n .AddContainer(\"pgadmin\", \"dpage/pgadmin4:8.10\")\n .WithHttpEndpoint(targetPort: 5050, name: \"pgadmin-http\")\n .WithExternalHttpEndpoints()\n .WithEnvironment(\"PGADMIN_DEFAULT_EMAIL\", \"admin@stemify.com\")\n .WithEnvironment(\n \"PGADMIN_DEFAULT_PASSWORD\",\n Environment.GetEnvironmentVariable(\"PGADMIN_PASSWORD\") ?? \"admin\"\n )\n .WithEnvironment(\"PGADMIN_CONFIG_SERVER_MODE\", \"False\")\n .WithEnvironment(\"PGADMIN_CONFIG_MASTER_PASSWORD_REQUIRED\", \"False\")\n .WithEnvironment(\"PGADMIN_LISTEN_PORT\", \"5050\")\n .WithEnvironment(\"PGADMIN_CONFIG_CHECK_EMAIL_DELIVERABILITY\", \"False\")\n .WithEnvironment(\"GUNICORN_TIMEOUT\", \"120\")\n .WaitFor(postgres)\n : postgres.WithPgAdmin(x =>\n {\n x.WithHttpEndpoint(targetPort: 5050, name: \"pgadmin-dev\");\n x.WithExternalHttpEndpoints();\n x.WithEnvironment(\"PGADMIN_CONFIG_CHECK_EMAIL_DELIVERABILITY\", \"False\");\n x.WithEnvironment(\"GUNICORN_TIMEOUT\", \"120\");\n });\n\n var identityService = builder\n .AddProject<Projects.Identity_Web>(\"identity\")\n .WithExternalHttpEndpoints()\n .WithReference(identityDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(identityDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(identityService);\n\n var identityApiService = builder\n .AddProject<Projects.Identity_API>(\"identity-api\")\n .WithExternalHttpEndpoints()\n .WithReference(identityDb)\n .WithReference(identityService)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(identityDb)\n .WaitFor(identityService)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(identityApiService);\n\n // var sagaOrchestrator = builder\n // .AddProject<Projects.Saga_Orchestrator>(\"saga-orchestrator\")\n // .WithReference(identityService)\n // .WithReference(rabbitmqReference)\n // .WaitFor(identityService)\n // .WaitFor(rabbitmqReference)\n // ;\n // ConfigureObservability(sagaOrchestrator);\n\n var classroomService = builder\n .AddProject<Projects.Classroom_API>(\"classroom-api\")\n .WithExternalHttpEndpoints()\n .WithReference(classroomDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(classroomDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(classroomService);\n\n var resourceApiService = builder\n .AddProject<Projects.Resource_API>(\"resource-api\")\n .WithExternalHttpEndpoints()\n .WithReference(resourceDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(resourceDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(resourceApiService);\n\n var notificationApiService = builder\n .AddProject<Projects.Notification_API>(\"notification-api\")\n .WithExternalHttpEndpoints()\n .WithReference(notificationDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(notificationDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(notificationApiService);\n\n var productApiService = builder\n .AddProject<Projects.Product_API>(\"product-api\")\n .WithExternalHttpEndpoints()\n .WithReference(productDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(productDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(productApiService);\n\n var orderApiService = builder\n .AddProject<Projects.Order_API>(\"order-api\")\n .WithExternalHttpEndpoints()\n .WithReference(orderDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(orderDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(orderApiService);\n\n // var paymentApiService = builder\n // .AddProject<Projects.Payment_API>(\"payment-api\")\n // .WithExternalHttpEndpoints()\n // .WithReference(paymentDb)\n // .WithReference(rabbitmq)\n // .WaitFor(postgres)\n // .WaitFor(paymentDb)\n // .WaitFor(rabbitmq)\n // ;\n // ConfigureObservability(paymentApiService);\n\n // var cartApiService = builder\n // .AddProject<Projects.Cart_API>(\"cart-api\")\n // .WithExternalHttpEndpoints()\n // .WithReference(cartDb)\n // .WithReference(rabbitmq)\n // .WaitFor(postgres)\n // .WaitFor(cartDb)\n // .WaitFor(rabbitmq)\n // ;\n // ConfigureObservability(cartApiService);\n \n IResourceBuilder<IResourceWithConnectionString>? emulatorMongoDbReference = null;\n \n var mongo = builder.AddMongoDB(\"emulator-mongodb\")\n .WithLifetime(ContainerLifetime.Persistent);\n \n var emulatorMongoDb = mongo.AddDatabase(\"stemify-emulator\");\n emulatorMongoDbReference = emulatorMongoDb;\n \n\n var emulatorApiService = builder\n .AddProject<Projects.Emulator_API>(\"emulator-api\")\n .WithExternalHttpEndpoints();\n \n if (emulatorMongoDbReference is not null)\n {\n emulatorApiService\n .WithReference(emulatorMongoDbReference)\n .WaitFor(emulatorMongoDbReference);\n }\n \n ConfigureObservability(emulatorApiService);\n\n var hangfireApiService = builder\n .AddProject<Projects.Hangfire_API>(\"hangfire-api\")\n .WithExternalHttpEndpoints()\n .WithReference(hangfireDb)\n .WithReference(identityDb)\n .WithReference(orderDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(hangfireDb)\n .WaitFor(identityDb)\n .WaitFor(orderDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(hangfireApiService);\n\n \n var resourceGrpcPort = builder.Configuration[\"ResourceApi:GrpcPort\"] ?? \"5082\";\n var resourceGrpcEndpoint = $\"localhost:{resourceGrpcPort}\";\n \n var aiService = builder\n .AddUvicornApp(\"ai-service\", \"../../AIService\", \"app.main:app\")\n .WithExternalHttpEndpoints()\n .WithHttpHealthCheck(\"/health\")\n .WithEnvironment(\"RESOURCE_GRPC_ENDPOINT\", resourceGrpcEndpoint)\n .WithEnvironment(\"RESOURCE_GRPC_USE_TLS\", \"false\")\n .WithEnvironment(\"PYTHONUNBUFFERED\", \"1\")\n .WithEnvironment(\"PYTHONDONTWRITEBYTECODE\", \"1\")\n .WithEnvironment(\"PORT\", \"8000\")\n .WithEnvironment(\"OPENAI_API_KEY\", builder.Configuration[\"AIService:LLM:OpenAI:ApiKey\"]\n ?? builder.Configuration[\"OpenAI:ApiKey\"]\n ?? Environment.GetEnvironmentVariable(\"OPENAI_API_KEY\")\n ?? \"\")\n .WithEnvironment(\"DEEPSEEK_API_KEY\", builder.Configuration[\"AIService:LLM:DeepSeek:ApiKey\"]\n ?? builder.Configuration[\"DeepSeek:ApiKey\"]\n ?? Environment.GetEnvironmentVariable(\"DEEPSEEK_API_KEY\")\n ?? \"\")\n .WithReference(resourceApiService)\n .WaitFor(resourceApiService);\n ConfigureObservability(aiService);\n\n var apiGateway = builder\n .AddProject<Projects.ApiGateway>(\"apigateway\")\n .WithExternalHttpEndpoints()\n .WithReference(identityService)\n // .WithReference(identityApiService)\n .WithReference(classroomService)\n .WithReference(resourceApiService)\n .WithReference(notificationApiService)\n .WithReference(emulatorApiService)\n .WaitFor(identityService)\n .WaitFor(resourceApiService)\n .WaitFor(classroomService)\n .WaitFor(notificationApiService)\n .WaitFor(productApiService)\n .WaitFor(orderApiService)\n .WaitFor(emulatorApiService)\n .WaitFor(aiService)\n ;\n ConfigureObservability(apiGateway);\n\n void ConfigureObservability<TResource>(IResourceBuilder<TResource> resourceBuilder)\n where TResource : IResourceWithEnvironment\n {\n var enabledValue = externalObservabilityEnabled ? \"true\" : \"false\";\n resourceBuilder.WithEnvironment(\"Observability__ExternalStackEnabled\", enabledValue);\n\n if (grafanaEndpoint is not null)\n {\n resourceBuilder.WithEnvironment(\"GRAFANA_URL\", grafanaEndpoint);\n }\n }\n\n return builder;\n }\n\n \n private static string GetOrCreatePostgresSnapshotPath(bool isDevelopment)\n {\n // if (!isDevelopment)\n // {\n // var productionPath = Path.Combine(\n // Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),\n // \"STEMify\",\n // \"postgres-data\");\n // Directory.CreateDirectory(productionPath);\n // return productionPath;\n // }\n\n var baseSnapshotsPath = Path.Combine(\n Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),\n \"STEMify\",\n \"postgres-snapshots\");\n\n \n Directory.CreateDirectory(baseSnapshotsPath);\n\n var mostRecentSnapshot = FindMostRecentSnapshot(baseSnapshotsPath);\n\n var timestamp = DateTime.Now.ToString(\"yyyyMMdd-HHmmss\");\n var newSnapshotPath = Path.Combine(baseSnapshotsPath, $\"postgres-data-{timestamp}\");\n \n if (mostRecentSnapshot != null && Directory.Exists(mostRecentSnapshot))\n {\n CopyDirectory(mostRecentSnapshot, newSnapshotPath);\n }\n else\n {\n \n Directory.CreateDirectory(newSnapshotPath);\n }\n\n return newSnapshotPath;\n }\n\n private static void CopyDirectory(string sourceDir, string destinationDir)\n {\n var dir = new DirectoryInfo(sourceDir);\n DirectoryInfo[] dirs = dir.GetDirectories();\n\n Directory.CreateDirectory(destinationDir);\n\n foreach (FileInfo file in dir.GetFiles())\n {\n string targetFilePath = Path.Combine(destinationDir, file.Name);\n file.CopyTo(targetFilePath, overwrite: true);\n }\n\n foreach (DirectoryInfo subDir in dirs)\n {\n string newDestinationDir = Path.Combine(destinationDir, subDir.Name);\n CopyDirectory(subDir.FullName, newDestinationDir);\n }\n }\n\n private static string? FindMostRecentSnapshot(string snapshotsBasePath)\n {\n if (!Directory.Exists(snapshotsBasePath))\n return null;\n\n var snapshotDirectories = Directory.GetDirectories(snapshotsBasePath, \"postgres-data-*\")\n .Where(dir =>\n {\n var dirName = Path.GetFileName(dir);\n if (dirName.Length < 24 || !dirName.StartsWith(\"postgres-data-\"))\n return false;\n \n var timestampPart = dirName.Substring(\"postgres-data-\".Length);\n return timestampPart.Length == 15 && // YYYYMMDD-HHmmss = 15 chars\n timestampPart[8] == '-' &&\n DateTime.TryParseExact(timestampPart, \"yyyyMMdd-HHmmss\", null, \n System.Globalization.DateTimeStyles.None, out _);\n })\n .OrderByDescending(dir =>\n {\n var dirName = Path.GetFileName(dir);\n var timestampPart = dirName.Substring(\"postgres-data-\".Length);\n if (DateTime.TryParseExact(timestampPart, \"yyyyMMdd-HHmmss\", null,\n System.Globalization.DateTimeStyles.None, out var timestamp))\n {\n return timestamp;\n }\n return DateTime.MinValue;\n })\n .ToList();\n\n return snapshotDirectories.FirstOrDefault();\n }\n }\n}\n"}, "1930ae06f683c64db389ef4f9317151912a3fa4c": {"t": 1785748764.8787863, "n": 19084, "h": "345c081a794c4909", "c": "using Microsoft.Extensions.Hosting;\n\nnamespace STEMifyBackend.AppHost.Extensions\n{\n public static class ServiceExtensions\n {\n public static IDistributedApplicationBuilder AddApplicationServices(this IDistributedApplicationBuilder builder)\n {\n // Get or create snapshot-based data directory for Postgres persistence\n var postgresDataPath = GetOrCreatePostgresSnapshotPath(builder.Environment.IsDevelopment());\n\n var postgres = builder.AddPostgres(\"stemify-postgres\");\n if (builder.Environment.IsDevelopment())\n postgres.WithBindMount(postgresDataPath, \"/var/lib/postgresql/data\", isReadOnly: false);\n\n var identityDb = postgres.AddDatabase(\"stemifyidentity\");\n\n var classroomDb = postgres.AddDatabase(\"stemifyclassroom\");\n\n var resourceDb = postgres.AddDatabase(\"stemifyresource\");\n\n var notificationDb = postgres.AddDatabase(\"stemifynotification\");\n\n var productDb = postgres.AddDatabase(\"stemifyproduct\");\n\n var orderDb = postgres.AddDatabase(\"stemifyorder\");\n var paymentDb = postgres.AddDatabase(\"stemifypayment\");\n\n var cartDb = postgres.AddDatabase(\"stemifycart\");\n\n var hangfireDb = postgres.AddDatabase(\"stemifyhangfire\");\n\n // // Add pgAdmin for database administration (Production)\n // var pgAdmin = builder\n // .AddContainer(\"pgadmin\", \"dpage/pgadmin4\")\n // .WithHttpEndpoint(targetPort: 5050, name: \"http\")\n // .WithExternalHttpEndpoints()\n // .WithEnvironment(\"PGADMIN_DEFAULT_EMAIL\", \"admin@stemify.com\")\n // .WithEnvironment(\"PGADMIN_DEFAULT_PASSWORD\", Environment.GetEnvironmentVariable(\"PGADMIN_PASSWORD\") ?? \"admin\")\n // .WithEnvironment(\"PGADMIN_CONFIG_SERVER_MODE\", \"False\")\n // .WithEnvironment(\"PGADMIN_CONFIG_MASTER_PASSWORD_REQUIRED\", \"False\")\n // .WithEnvironment(\"PGADMIN_LISTEN_PORT\", \"5050\")\n // .WaitFor(postgres);\n\n // Add RabbitMQ for messaging (required for Payment Service Saga pattern)\n IResourceBuilder<IResourceWithConnectionString> rabbitmqReference;\n if (builder.Environment.IsProduction())\n {\n var cloudAmqpUrl = builder.Configuration[\"RabbitMQ:Url\"]\n ?? Environment.GetEnvironmentVariable(\"RABBITMQ_URL\")\n ?? throw new InvalidOperationException(\"RabbitMQ URL not configured.\");\n\n rabbitmqReference = builder.AddConnectionString(\"messaging\", cloudAmqpUrl);\n }\n else\n {\n var rabbitmq = builder.AddRabbitMQ(\"messaging\")\n .WithManagementPlugin();\n rabbitmqReference = rabbitmq;\n }\n\n\n var manualFlag = Environment.GetEnvironmentVariable(\"ENABLE_OBSERVABILITY_STACK\");\n var externalObservabilityEnabled = builder.Environment.IsDevelopment() ||\n string.Equals(manualFlag, \"true\", StringComparison.OrdinalIgnoreCase);\n EndpointReference? grafanaEndpoint = null;\n\n if (externalObservabilityEnabled)\n {\n var prometheus = builder.AddContainer(\"prometheus\", \"prom/prometheus\", \"v3.5.0\")\n .WithBindMount(\"../../../../config/prometheus\", \"/etc/prometheus\", isReadOnly: true)\n .WithArgs(\n \"--web.enable-otlp-receiver\",\n \"--config.file=/etc/prometheus/prometheus.yml\",\n \"--storage.tsdb.retention.time=15d\",\n \"--storage.tsdb.retention.size=10GB\"\n )\n .WithHttpEndpoint(targetPort: 9090, name: \"http\");\n\n var alertmanager = builder.AddContainer(\"alertmanager\", \"prom/alertmanager\", \"v0.27.0\")\n .WithBindMount(\"../../../../config/alertmanager\", \"/etc/alertmanager\", isReadOnly: true)\n .WithArgs(\"--config.file=/etc/alertmanager/alertmanager.yml\", \"--storage.path=/alertmanager\")\n .WithHttpEndpoint(targetPort: 9093, name: \"http\");\n\n var grafana = builder.AddContainer(\"grafana\", \"grafana/grafana\")\n .WithBindMount(\"../../../../config/grafana/config\", \"/etc/grafana\", isReadOnly: true)\n .WithBindMount(\"../../../../config/grafana/dashboards\", \"/var/lib/grafana/dashboards\", isReadOnly: true)\n .WithEnvironment(\"PROMETHEUS_ENDPOINT\", prometheus.GetEndpoint(\"http\"))\n .WithHttpEndpoint(targetPort: 3000, name: \"http\");\n\n grafanaEndpoint = grafana.GetEndpoint(\"http\");\n\n var configFile = \"../../../../config/otelcollector/config.dev.yaml\";\n\n builder.AddOpenTelemetryCollector(\"otelcollector\", configFile)\n .WithEnvironment(\"PROMETHEUS_ENDPOINT\", $\"{prometheus.GetEndpoint(\"http\")}/api/v1/otlp\")\n .WithEnvironment(\"ALERTMANAGER_ENDPOINT\", alertmanager.GetEndpoint(\"http\"));\n }\n\n var pgAdmin = builder.Environment.IsProduction()\n ? builder\n .AddContainer(\"pgadmin\", \"dpage/pgadmin4:8.10\")\n .WithHttpEndpoint(targetPort: 5050, name: \"pgadmin-http\")\n .WithExternalHttpEndpoints()\n .WithEnvironment(\"PGADMIN_DEFAULT_EMAIL\", \"admin@stemify.com\")\n .WithEnvironment(\n \"PGADMIN_DEFAULT_PASSWORD\",\n Environment.GetEnvironmentVariable(\"PGADMIN_PASSWORD\") ?? \"admin\"\n )\n .WithEnvironment(\"PGADMIN_CONFIG_SERVER_MODE\", \"False\")\n .WithEnvironment(\"PGADMIN_CONFIG_MASTER_PASSWORD_REQUIRED\", \"False\")\n .WithEnvironment(\"PGADMIN_LISTEN_PORT\", \"5050\")\n .WithEnvironment(\"PGADMIN_CONFIG_CHECK_EMAIL_DELIVERABILITY\", \"False\")\n .WithEnvironment(\"GUNICORN_TIMEOUT\", \"120\")\n .WaitFor(postgres)\n : postgres.WithPgAdmin(x =>\n {\n x.WithHttpEndpoint(targetPort: 5050, name: \"pgadmin-dev\");\n x.WithExternalHttpEndpoints();\n x.WithEnvironment(\"PGADMIN_CONFIG_CHECK_EMAIL_DELIVERABILITY\", \"False\");\n x.WithEnvironment(\"GUNICORN_TIMEOUT\", \"120\");\n });\n\n var identityService = builder\n .AddProject<Projects.Identity_Web>(\"identity\")\n .WithExternalHttpEndpoints()\n .WithReference(identityDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(identityDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(identityService);\n\n var identityApiService = builder\n .AddProject<Projects.Identity_API>(\"identity-api\")\n .WithExternalHttpEndpoints()\n .WithReference(identityDb)\n .WithReference(identityService)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(identityDb)\n .WaitFor(identityService)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(identityApiService);\n\n // var sagaOrchestrator = builder\n // .AddProject<Projects.Saga_Orchestrator>(\"saga-orchestrator\")\n // .WithReference(identityService)\n // .WithReference(rabbitmqReference)\n // .WaitFor(identityService)\n // .WaitFor(rabbitmqReference)\n // ;\n // ConfigureObservability(sagaOrchestrator);\n\n var classroomService = builder\n .AddProject<Projects.Classroom_API>(\"classroom-api\")\n .WithExternalHttpEndpoints()\n .WithReference(classroomDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(classroomDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(classroomService);\n\n var resourceApiService = builder\n .AddProject<Projects.Resource_API>(\"resource-api\")\n .WithExternalHttpEndpoints()\n .WithReference(resourceDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(resourceDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(resourceApiService);\n\n var notificationApiService = builder\n .AddProject<Projects.Notification_API>(\"notification-api\")\n .WithExternalHttpEndpoints()\n .WithReference(notificationDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(notificationDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(notificationApiService);\n\n var productApiService = builder\n .AddProject<Projects.Product_API>(\"product-api\")\n .WithExternalHttpEndpoints()\n .WithReference(productDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(productDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(productApiService);\n\n var orderApiService = builder\n .AddProject<Projects.Order_API>(\"order-api\")\n .WithExternalHttpEndpoints()\n .WithReference(orderDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(orderDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(orderApiService);\n\n // var paymentApiService = builder\n // .AddProject<Projects.Payment_API>(\"payment-api\")\n // .WithExternalHttpEndpoints()\n // .WithReference(paymentDb)\n // .WithReference(rabbitmq)\n // .WaitFor(postgres)\n // .WaitFor(paymentDb)\n // .WaitFor(rabbitmq)\n // ;\n // ConfigureObservability(paymentApiService);\n\n // var cartApiService = builder\n // .AddProject<Projects.Cart_API>(\"cart-api\")\n // .WithExternalHttpEndpoints()\n // .WithReference(cartDb)\n // .WithReference(rabbitmq)\n // .WaitFor(postgres)\n // .WaitFor(cartDb)\n // .WaitFor(rabbitmq)\n // ;\n // ConfigureObservability(cartApiService);\n \n IResourceBuilder<IResourceWithConnectionString>? emulatorMongoDbReference = null;\n \n var mongo = builder.AddMongoDB(\"emulator-mongodb\")\n .WithLifetime(ContainerLifetime.Persistent);\n \n var emulatorMongoDb = mongo.AddDatabase(\"stemify-emulator\");\n emulatorMongoDbReference = emulatorMongoDb;\n \n\n var emulatorApiService = builder\n .AddProject<Projects.Emulator_API>(\"emulator-api\")\n .WithExternalHttpEndpoints();\n \n if (emulatorMongoDbReference is not null)\n {\n emulatorApiService\n .WithReference(emulatorMongoDbReference)\n .WaitFor(emulatorMongoDbReference);\n }\n \n ConfigureObservability(emulatorApiService);\n\n var hangfireApiService = builder\n .AddProject<Projects.Hangfire_API>(\"hangfire-api\")\n .WithExternalHttpEndpoints()\n .WithReference(hangfireDb)\n .WithReference(identityDb)\n .WithReference(orderDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(hangfireDb)\n .WaitFor(identityDb)\n .WaitFor(orderDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(hangfireApiService);\n\n \n var resourceGrpcPort = builder.Configuration[\"ResourceApi:GrpcPort\"] ?? \"5082\";\n var resourceGrpcEndpoint = $\"localhost:{resourceGrpcPort}\";\n \n var aiService = builder\n .AddUvicornApp(\"ai-service\", \"../../AIService\", \"app.main:app\")\n .WithExternalHttpEndpoints()\n .WithHttpHealthCheck(\"/health\")\n .WithEnvironment(\"RESOURCE_GRPC_ENDPOINT\", resourceGrpcEndpoint)\n .WithEnvironment(\"RESOURCE_GRPC_USE_TLS\", \"false\")\n .WithEnvironment(\"PYTHONUNBUFFERED\", \"1\")\n .WithEnvironment(\"PYTHONDONTWRITEBYTECODE\", \"1\")\n .WithEnvironment(\"PORT\", \"8000\")\n .WithEnvironment(\"OPENAI_API_KEY\", builder.Configuration[\"AIService:LLM:OpenAI:ApiKey\"]\n ?? builder.Configuration[\"OpenAI:ApiKey\"]\n ?? Environment.GetEnvironmentVariable(\"OPENAI_API_KEY\")\n ?? \"\")\n .WithEnvironment(\"DEEPSEEK_API_KEY\", builder.Configuration[\"AIService:LLM:DeepSeek:ApiKey\"]\n ?? builder.Configuration[\"DeepSeek:ApiKey\"]\n ?? Environment.GetEnvironmentVariable(\"DEEPSEEK_API_KEY\")\n ?? \"\")\n .WithReference(resourceApiService)\n .WaitFor(resourceApiService);\n ConfigureObservability(aiService);\n\n var apiGateway = builder\n .AddProject<Projects.ApiGateway>(\"apigateway\")\n .WithExternalHttpEndpoints()\n .WithReference(identityService)\n // .WithReference(identityApiService)\n .WithReference(classroomService)\n .WithReference(resourceApiService)\n .WithReference(notificationApiService)\n .WithReference(emulatorApiService)\n .WaitFor(identityService)\n .WaitFor(resourceApiService)\n .WaitFor(classroomService)\n .WaitFor(notificationApiService)\n .WaitFor(productApiService)\n .WaitFor(orderApiService)\n .WaitFor(emulatorApiService)\n .WaitFor(aiService)\n ;\n ConfigureObservability(apiGateway);\n\n void ConfigureObservability<TResource>(IResourceBuilder<TResource> resourceBuilder)\n where TResource : IResourceWithEnvironment\n {\n var enabledValue = externalObservabilityEnabled ? \"true\" : \"false\";\n resourceBuilder.WithEnvironment(\"Observability__ExternalStackEnabled\", enabledValue);\n\n if (grafanaEndpoint is not null)\n {\n resourceBuilder.WithEnvironment(\"GRAFANA_URL\", grafanaEndpoint);\n }\n }\n\n return builder;\n }\n\n \n private static string GetOrCreatePostgresSnapshotPath(bool isDevelopment)\n {\n // if (!isDevelopment)\n // {\n // var productionPath = Path.Combine(\n // Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),\n // \"STEMify\",\n // \"postgres-data\");\n // Directory.CreateDirectory(productionPath);\n // return productionPath;\n // }\n\n var baseSnapshotsPath = Path.Combine(\n Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),\n \"STEMify\",\n \"postgres-snapshots\");\n\n \n Directory.CreateDirectory(baseSnapshotsPath);\n\n var mostRecentSnapshot = FindMostRecentSnapshot(baseSnapshotsPath);\n\n var timestamp = DateTime.Now.ToString(\"yyyyMMdd-HHmmss\");\n var newSnapshotPath = Path.Combine(baseSnapshotsPath, $\"postgres-data-{timestamp}\");\n \n if (mostRecentSnapshot != null && Directory.Exists(mostRecentSnapshot))\n {\n CopyDirectory(mostRecentSnapshot, newSnapshotPath);\n }\n else\n {\n \n Directory.CreateDirectory(newSnapshotPath);\n }\n\n return newSnapshotPath;\n }\n\n private static void CopyDirectory(string sourceDir, string destinationDir)\n {\n var dir = new DirectoryInfo(sourceDir);\n DirectoryInfo[] dirs = dir.GetDirectories();\n\n Directory.CreateDirectory(destinationDir);\n\n foreach (FileInfo file in dir.GetFiles())\n {\n string targetFilePath = Path.Combine(destinationDir, file.Name);\n file.CopyTo(targetFilePath, overwrite: true);\n }\n\n foreach (DirectoryInfo subDir in dirs)\n {\n string newDestinationDir = Path.Combine(destinationDir, subDir.Name);\n CopyDirectory(subDir.FullName, newDestinationDir);\n }\n }\n\n private static string? FindMostRecentSnapshot(string snapshotsBasePath)\n {\n if (!Directory.Exists(snapshotsBasePath))\n return null;\n\n var snapshotDirectories = Directory.GetDirectories(snapshotsBasePath, \"postgres-data-*\")\n .Where(dir =>\n {\n var dirName = Path.GetFileName(dir);\n if (dirName.Length < 24 || !dirName.StartsWith(\"postgres-data-\"))\n return false;\n \n var timestampPart = dirName.Substring(\"postgres-data-\".Length);\n return timestampPart.Length == 15 && // YYYYMMDD-HHmmss = 15 chars\n timestampPart[8] == '-' &&\n DateTime.TryParseExact(timestampPart, \"yyyyMMdd-HHmmss\", null, \n System.Globalization.DateTimeStyles.None, out _);\n })\n .OrderByDescending(dir =>\n {\n var dirName = Path.GetFileName(dir);\n var timestampPart = dirName.Substring(\"postgres-data-\".Length);\n if (DateTime.TryParseExact(timestampPart, \"yyyyMMdd-HHmmss\", null,\n System.Globalization.DateTimeStyles.None, out var timestamp))\n {\n return timestamp;\n }\n return DateTime.MinValue;\n })\n .ToList();\n\n return snapshotDirectories.FirstOrDefault();\n }\n }\n}\n"}, "4e76077b4dff72be47fcdb8c6cff4b49ec88f52f": {"t": 1785748766.0129204, "n": 19126, "h": "2ad919b3cde3e18f", "c": "using Microsoft.Extensions.Hosting;\n\nnamespace STEMifyBackend.AppHost.Extensions\n{\n public static class ServiceExtensions\n {\n public static IDistributedApplicationBuilder AddApplicationServices(this IDistributedApplicationBuilder builder)\n {\n // Get or create snapshot-based data directory for Postgres persistence\n var postgresDataPath = GetOrCreatePostgresSnapshotPath(builder.Environment.IsDevelopment());\n\n var postgres = builder.AddPostgres(\"stemify-postgres\");\n if (builder.Environment.IsDevelopment())\n postgres.WithBindMount(postgresDataPath, \"/var/lib/postgresql/data\", isReadOnly: false);\n\n var identityDb = postgres.AddDatabase(\"stemifyidentity\");\n\n var classroomDb = postgres.AddDatabase(\"stemifyclassroom\");\n\n var resourceDb = postgres.AddDatabase(\"stemifyresource\");\n\n var notificationDb = postgres.AddDatabase(\"stemifynotification\");\n\n var productDb = postgres.AddDatabase(\"stemifyproduct\");\n\n var orderDb = postgres.AddDatabase(\"stemifyorder\");\n var paymentDb = postgres.AddDatabase(\"stemifypayment\");\n\n var cartDb = postgres.AddDatabase(\"stemifycart\");\n\n var hangfireDb = postgres.AddDatabase(\"stemifyhangfire\");\n\n // // Add pgAdmin for database administration (Production)\n // var pgAdmin = builder\n // .AddContainer(\"pgadmin\", \"dpage/pgadmin4\")\n // .WithHttpEndpoint(targetPort: 5050, name: \"http\")\n // .WithExternalHttpEndpoints()\n // .WithEnvironment(\"PGADMIN_DEFAULT_EMAIL\", \"admin@stemify.com\")\n // .WithEnvironment(\"PGADMIN_DEFAULT_PASSWORD\", Environment.GetEnvironmentVariable(\"PGADMIN_PASSWORD\") ?? \"admin\")\n // .WithEnvironment(\"PGADMIN_CONFIG_SERVER_MODE\", \"False\")\n // .WithEnvironment(\"PGADMIN_CONFIG_MASTER_PASSWORD_REQUIRED\", \"False\")\n // .WithEnvironment(\"PGADMIN_LISTEN_PORT\", \"5050\")\n // .WaitFor(postgres);\n\n // Add RabbitMQ for messaging (required for Payment Service Saga pattern)\n IResourceBuilder<IResourceWithConnectionString> rabbitmqReference;\n if (builder.Environment.IsProduction())\n {\n var cloudAmqpUrl = builder.Configuration[\"RabbitMQ:Url\"]\n ?? Environment.GetEnvironmentVariable(\"RABBITMQ_URL\")\n ?? throw new InvalidOperationException(\"RabbitMQ URL not configured.\");\n\n rabbitmqReference = builder.AddConnectionString(\"messaging\", cloudAmqpUrl);\n }\n else\n {\n var rabbitmq = builder.AddRabbitMQ(\"messaging\")\n .WithManagementPlugin();\n rabbitmqReference = rabbitmq;\n }\n\n\n var manualFlag = Environment.GetEnvironmentVariable(\"ENABLE_OBSERVABILITY_STACK\");\n var externalObservabilityEnabled = builder.Environment.IsDevelopment() ||\n string.Equals(manualFlag, \"true\", StringComparison.OrdinalIgnoreCase);\n EndpointReference? grafanaEndpoint = null;\n\n if (externalObservabilityEnabled)\n {\n var prometheus = builder.AddContainer(\"prometheus\", \"prom/prometheus\", \"v3.5.0\")\n .WithBindMount(\"../../../../config/prometheus\", \"/etc/prometheus\", isReadOnly: true)\n .WithArgs(\n \"--web.enable-otlp-receiver\",\n \"--config.file=/etc/prometheus/prometheus.yml\",\n \"--storage.tsdb.retention.time=15d\",\n \"--storage.tsdb.retention.size=10GB\"\n )\n .WithHttpEndpoint(targetPort: 9090, name: \"http\");\n\n var alertmanager = builder.AddContainer(\"alertmanager\", \"prom/alertmanager\", \"v0.27.0\")\n .WithBindMount(\"../../../../config/alertmanager\", \"/etc/alertmanager\", isReadOnly: true)\n .WithArgs(\"--config.file=/etc/alertmanager/alertmanager.yml\", \"--storage.path=/alertmanager\")\n .WithHttpEndpoint(targetPort: 9093, name: \"http\");\n\n var grafana = builder.AddContainer(\"grafana\", \"grafana/grafana\")\n .WithBindMount(\"../../../../config/grafana/config\", \"/etc/grafana\", isReadOnly: true)\n .WithBindMount(\"../../../../config/grafana/dashboards\", \"/var/lib/grafana/dashboards\", isReadOnly: true)\n .WithEnvironment(\"PROMETHEUS_ENDPOINT\", prometheus.GetEndpoint(\"http\"))\n .WithHttpEndpoint(targetPort: 3000, name: \"http\");\n\n grafanaEndpoint = grafana.GetEndpoint(\"http\");\n\n var configFile = \"../../../../config/otelcollector/config.dev.yaml\";\n\n builder.AddOpenTelemetryCollector(\"otelcollector\", configFile)\n .WithEnvironment(\"PROMETHEUS_ENDPOINT\", $\"{prometheus.GetEndpoint(\"http\")}/api/v1/otlp\")\n .WithEnvironment(\"ALERTMANAGER_ENDPOINT\", alertmanager.GetEndpoint(\"http\"));\n }\n\n var pgAdmin = builder.Environment.IsProduction()\n ? builder\n .AddContainer(\"pgadmin\", \"dpage/pgadmin4:8.10\")\n .WithHttpEndpoint(targetPort: 5050, name: \"pgadmin-http\")\n .WithExternalHttpEndpoints()\n .WithEnvironment(\"PGADMIN_DEFAULT_EMAIL\", \"admin@stemify.com\")\n .WithEnvironment(\n \"PGADMIN_DEFAULT_PASSWORD\",\n Environment.GetEnvironmentVariable(\"PGADMIN_PASSWORD\") ?? \"admin\"\n )\n .WithEnvironment(\"PGADMIN_CONFIG_SERVER_MODE\", \"False\")\n .WithEnvironment(\"PGADMIN_CONFIG_MASTER_PASSWORD_REQUIRED\", \"False\")\n .WithEnvironment(\"PGADMIN_LISTEN_PORT\", \"5050\")\n .WithEnvironment(\"PGADMIN_CONFIG_CHECK_EMAIL_DELIVERABILITY\", \"False\")\n .WithEnvironment(\"GUNICORN_TIMEOUT\", \"120\")\n .WaitFor(postgres)\n : postgres.WithPgAdmin(x =>\n {\n x.WithHttpEndpoint(targetPort: 5050, name: \"pgadmin-dev\");\n x.WithExternalHttpEndpoints();\n x.WithEnvironment(\"PGADMIN_CONFIG_CHECK_EMAIL_DELIVERABILITY\", \"False\");\n x.WithEnvironment(\"GUNICORN_TIMEOUT\", \"120\");\n });\n\n var identityService = builder\n .AddProject<Projects.Identity_Web>(\"identity\")\n .WithExternalHttpEndpoints()\n .WithReference(identityDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(identityDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(identityService);\n\n var identityApiService = builder\n .AddProject<Projects.Identity_API>(\"identity-api\")\n .WithExternalHttpEndpoints()\n .WithReference(identityDb)\n .WithReference(identityService)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(identityDb)\n .WaitFor(identityService)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(identityApiService);\n\n // var sagaOrchestrator = builder\n // .AddProject<Projects.Saga_Orchestrator>(\"saga-orchestrator\")\n // .WithReference(identityService)\n // .WithReference(rabbitmqReference)\n // .WaitFor(identityService)\n // .WaitFor(rabbitmqReference)\n // ;\n // ConfigureObservability(sagaOrchestrator);\n\n var classroomService = builder\n .AddProject<Projects.Classroom_API>(\"classroom-api\")\n .WithExternalHttpEndpoints()\n .WithReference(classroomDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(classroomDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(classroomService);\n\n var resourceApiService = builder\n .AddProject<Projects.Resource_API>(\"resource-api\")\n .WithExternalHttpEndpoints()\n .WithReference(resourceDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(resourceDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(resourceApiService);\n\n var notificationApiService = builder\n .AddProject<Projects.Notification_API>(\"notification-api\")\n .WithExternalHttpEndpoints()\n .WithReference(notificationDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(notificationDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(notificationApiService);\n\n var productApiService = builder\n .AddProject<Projects.Product_API>(\"product-api\")\n .WithExternalHttpEndpoints()\n .WithReference(productDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(productDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(productApiService);\n\n var orderApiService = builder\n .AddProject<Projects.Order_API>(\"order-api\")\n .WithExternalHttpEndpoints()\n .WithReference(orderDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(orderDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(orderApiService);\n\n // var paymentApiService = builder\n // .AddProject<Projects.Payment_API>(\"payment-api\")\n // .WithExternalHttpEndpoints()\n // .WithReference(paymentDb)\n // .WithReference(rabbitmq)\n // .WaitFor(postgres)\n // .WaitFor(paymentDb)\n // .WaitFor(rabbitmq)\n // ;\n // ConfigureObservability(paymentApiService);\n\n // var cartApiService = builder\n // .AddProject<Projects.Cart_API>(\"cart-api\")\n // .WithExternalHttpEndpoints()\n // .WithReference(cartDb)\n // .WithReference(rabbitmq)\n // .WaitFor(postgres)\n // .WaitFor(cartDb)\n // .WaitFor(rabbitmq)\n // ;\n // ConfigureObservability(cartApiService);\n \n IResourceBuilder<IResourceWithConnectionString>? emulatorMongoDbReference = null;\n \n var mongo = builder.AddMongoDB(\"emulator-mongodb\")\n .WithLifetime(ContainerLifetime.Persistent);\n \n var emulatorMongoDb = mongo.AddDatabase(\"stemify-emulator\");\n emulatorMongoDbReference = emulatorMongoDb;\n \n\n var emulatorApiService = builder\n .AddProject<Projects.Emulator_API>(\"emulator-api\")\n .WithExternalHttpEndpoints();\n \n if (emulatorMongoDbReference is not null)\n {\n emulatorApiService\n .WithReference(emulatorMongoDbReference)\n .WaitFor(emulatorMongoDbReference);\n }\n \n ConfigureObservability(emulatorApiService);\n\n var hangfireApiService = builder\n .AddProject<Projects.Hangfire_API>(\"hangfire-api\")\n .WithExternalHttpEndpoints()\n .WithReference(hangfireDb)\n .WithReference(identityDb)\n .WithReference(orderDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(hangfireDb)\n .WaitFor(identityDb)\n .WaitFor(orderDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(hangfireApiService);\n\n \n var resourceGrpcPort = builder.Configuration[\"ResourceApi:GrpcPort\"] ?? \"5082\";\n var resourceGrpcEndpoint = $\"localhost:{resourceGrpcPort}\";\n \n //var aiService = builder\n // .AddUvicornApp(\"ai-service\", \"../../AIService\", \"app.main:app\")\n // .WithExternalHttpEndpoints()\n // .WithHttpHealthCheck(\"/health\")\n // .WithEnvironment(\"RESOURCE_GRPC_ENDPOINT\", resourceGrpcEndpoint)\n // .WithEnvironment(\"RESOURCE_GRPC_USE_TLS\", \"false\")\n // .WithEnvironment(\"PYTHONUNBUFFERED\", \"1\")\n // .WithEnvironment(\"PYTHONDONTWRITEBYTECODE\", \"1\")\n // .WithEnvironment(\"PORT\", \"8000\")\n // .WithEnvironment(\"OPENAI_API_KEY\", builder.Configuration[\"AIService:LLM:OpenAI:ApiKey\"]\n // ?? builder.Configuration[\"OpenAI:ApiKey\"]\n // ?? Environment.GetEnvironmentVariable(\"OPENAI_API_KEY\")\n // ?? \"\")\n // .WithEnvironment(\"DEEPSEEK_API_KEY\", builder.Configuration[\"AIService:LLM:DeepSeek:ApiKey\"]\n // ?? builder.Configuration[\"DeepSeek:ApiKey\"]\n // ?? Environment.GetEnvironmentVariable(\"DEEPSEEK_API_KEY\")\n // ?? \"\")\n // .WithReference(resourceApiService)\n // .WaitFor(resourceApiService);\n //ConfigureObservability(aiService);\n\n var apiGateway = builder\n .AddProject<Projects.ApiGateway>(\"apigateway\")\n .WithExternalHttpEndpoints()\n .WithReference(identityService)\n // .WithReference(identityApiService)\n .WithReference(classroomService)\n .WithReference(resourceApiService)\n .WithReference(notificationApiService)\n .WithReference(emulatorApiService)\n .WaitFor(identityService)\n .WaitFor(resourceApiService)\n .WaitFor(classroomService)\n .WaitFor(notificationApiService)\n .WaitFor(productApiService)\n .WaitFor(orderApiService)\n .WaitFor(emulatorApiService)\n //.WaitFor(aiService)\n ;\n ConfigureObservability(apiGateway);\n\n void ConfigureObservability<TResource>(IResourceBuilder<TResource> resourceBuilder)\n where TResource : IResourceWithEnvironment\n {\n var enabledValue = externalObservabilityEnabled ? \"true\" : \"false\";\n resourceBuilder.WithEnvironment(\"Observability__ExternalStackEnabled\", enabledValue);\n\n if (grafanaEndpoint is not null)\n {\n resourceBuilder.WithEnvironment(\"GRAFANA_URL\", grafanaEndpoint);\n }\n }\n\n return builder;\n }\n\n \n private static string GetOrCreatePostgresSnapshotPath(bool isDevelopment)\n {\n // if (!isDevelopment)\n // {\n // var productionPath = Path.Combine(\n // Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),\n // \"STEMify\",\n // \"postgres-data\");\n // Directory.CreateDirectory(productionPath);\n // return productionPath;\n // }\n\n var baseSnapshotsPath = Path.Combine(\n Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),\n \"STEMify\",\n \"postgres-snapshots\");\n\n \n Directory.CreateDirectory(baseSnapshotsPath);\n\n var mostRecentSnapshot = FindMostRecentSnapshot(baseSnapshotsPath);\n\n var timestamp = DateTime.Now.ToString(\"yyyyMMdd-HHmmss\");\n var newSnapshotPath = Path.Combine(baseSnapshotsPath, $\"postgres-data-{timestamp}\");\n \n if (mostRecentSnapshot != null && Directory.Exists(mostRecentSnapshot))\n {\n CopyDirectory(mostRecentSnapshot, newSnapshotPath);\n }\n else\n {\n \n Directory.CreateDirectory(newSnapshotPath);\n }\n\n return newSnapshotPath;\n }\n\n private static void CopyDirectory(string sourceDir, string destinationDir)\n {\n var dir = new DirectoryInfo(sourceDir);\n DirectoryInfo[] dirs = dir.GetDirectories();\n\n Directory.CreateDirectory(destinationDir);\n\n foreach (FileInfo file in dir.GetFiles())\n {\n string targetFilePath = Path.Combine(destinationDir, file.Name);\n file.CopyTo(targetFilePath, overwrite: true);\n }\n\n foreach (DirectoryInfo subDir in dirs)\n {\n string newDestinationDir = Path.Combine(destinationDir, subDir.Name);\n CopyDirectory(subDir.FullName, newDestinationDir);\n }\n }\n\n private static string? FindMostRecentSnapshot(string snapshotsBasePath)\n {\n if (!Directory.Exists(snapshotsBasePath))\n return null;\n\n var snapshotDirectories = Directory.GetDirectories(snapshotsBasePath, \"postgres-data-*\")\n .Where(dir =>\n {\n var dirName = Path.GetFileName(dir);\n if (dirName.Length < 24 || !dirName.StartsWith(\"postgres-data-\"))\n return false;\n \n var timestampPart = dirName.Substring(\"postgres-data-\".Length);\n return timestampPart.Length == 15 && // YYYYMMDD-HHmmss = 15 chars\n timestampPart[8] == '-' &&\n DateTime.TryParseExact(timestampPart, \"yyyyMMdd-HHmmss\", null, \n System.Globalization.DateTimeStyles.None, out _);\n })\n .OrderByDescending(dir =>\n {\n var dirName = Path.GetFileName(dir);\n var timestampPart = dirName.Substring(\"postgres-data-\".Length);\n if (DateTime.TryParseExact(timestampPart, \"yyyyMMdd-HHmmss\", null,\n System.Globalization.DateTimeStyles.None, out var timestamp))\n {\n return timestamp;\n }\n return DateTime.MinValue;\n })\n .ToList();\n\n return snapshotDirectories.FirstOrDefault();\n }\n }\n}\n"}, "a45399f54030898cd22e7e8a373819a4fcd2144f": {"t": 1785748767.1452136, "n": 19084, "h": "345c081a794c4909", "c": "using Microsoft.Extensions.Hosting;\n\nnamespace STEMifyBackend.AppHost.Extensions\n{\n public static class ServiceExtensions\n {\n public static IDistributedApplicationBuilder AddApplicationServices(this IDistributedApplicationBuilder builder)\n {\n // Get or create snapshot-based data directory for Postgres persistence\n var postgresDataPath = GetOrCreatePostgresSnapshotPath(builder.Environment.IsDevelopment());\n\n var postgres = builder.AddPostgres(\"stemify-postgres\");\n if (builder.Environment.IsDevelopment())\n postgres.WithBindMount(postgresDataPath, \"/var/lib/postgresql/data\", isReadOnly: false);\n\n var identityDb = postgres.AddDatabase(\"stemifyidentity\");\n\n var classroomDb = postgres.AddDatabase(\"stemifyclassroom\");\n\n var resourceDb = postgres.AddDatabase(\"stemifyresource\");\n\n var notificationDb = postgres.AddDatabase(\"stemifynotification\");\n\n var productDb = postgres.AddDatabase(\"stemifyproduct\");\n\n var orderDb = postgres.AddDatabase(\"stemifyorder\");\n var paymentDb = postgres.AddDatabase(\"stemifypayment\");\n\n var cartDb = postgres.AddDatabase(\"stemifycart\");\n\n var hangfireDb = postgres.AddDatabase(\"stemifyhangfire\");\n\n // // Add pgAdmin for database administration (Production)\n // var pgAdmin = builder\n // .AddContainer(\"pgadmin\", \"dpage/pgadmin4\")\n // .WithHttpEndpoint(targetPort: 5050, name: \"http\")\n // .WithExternalHttpEndpoints()\n // .WithEnvironment(\"PGADMIN_DEFAULT_EMAIL\", \"admin@stemify.com\")\n // .WithEnvironment(\"PGADMIN_DEFAULT_PASSWORD\", Environment.GetEnvironmentVariable(\"PGADMIN_PASSWORD\") ?? \"admin\")\n // .WithEnvironment(\"PGADMIN_CONFIG_SERVER_MODE\", \"False\")\n // .WithEnvironment(\"PGADMIN_CONFIG_MASTER_PASSWORD_REQUIRED\", \"False\")\n // .WithEnvironment(\"PGADMIN_LISTEN_PORT\", \"5050\")\n // .WaitFor(postgres);\n\n // Add RabbitMQ for messaging (required for Payment Service Saga pattern)\n IResourceBuilder<IResourceWithConnectionString> rabbitmqReference;\n if (builder.Environment.IsProduction())\n {\n var cloudAmqpUrl = builder.Configuration[\"RabbitMQ:Url\"]\n ?? Environment.GetEnvironmentVariable(\"RABBITMQ_URL\")\n ?? throw new InvalidOperationException(\"RabbitMQ URL not configured.\");\n\n rabbitmqReference = builder.AddConnectionString(\"messaging\", cloudAmqpUrl);\n }\n else\n {\n var rabbitmq = builder.AddRabbitMQ(\"messaging\")\n .WithManagementPlugin();\n rabbitmqReference = rabbitmq;\n }\n\n\n var manualFlag = Environment.GetEnvironmentVariable(\"ENABLE_OBSERVABILITY_STACK\");\n var externalObservabilityEnabled = builder.Environment.IsDevelopment() ||\n string.Equals(manualFlag, \"true\", StringComparison.OrdinalIgnoreCase);\n EndpointReference? grafanaEndpoint = null;\n\n if (externalObservabilityEnabled)\n {\n var prometheus = builder.AddContainer(\"prometheus\", \"prom/prometheus\", \"v3.5.0\")\n .WithBindMount(\"../../../../config/prometheus\", \"/etc/prometheus\", isReadOnly: true)\n .WithArgs(\n \"--web.enable-otlp-receiver\",\n \"--config.file=/etc/prometheus/prometheus.yml\",\n \"--storage.tsdb.retention.time=15d\",\n \"--storage.tsdb.retention.size=10GB\"\n )\n .WithHttpEndpoint(targetPort: 9090, name: \"http\");\n\n var alertmanager = builder.AddContainer(\"alertmanager\", \"prom/alertmanager\", \"v0.27.0\")\n .WithBindMount(\"../../../../config/alertmanager\", \"/etc/alertmanager\", isReadOnly: true)\n .WithArgs(\"--config.file=/etc/alertmanager/alertmanager.yml\", \"--storage.path=/alertmanager\")\n .WithHttpEndpoint(targetPort: 9093, name: \"http\");\n\n var grafana = builder.AddContainer(\"grafana\", \"grafana/grafana\")\n .WithBindMount(\"../../../../config/grafana/config\", \"/etc/grafana\", isReadOnly: true)\n .WithBindMount(\"../../../../config/grafana/dashboards\", \"/var/lib/grafana/dashboards\", isReadOnly: true)\n .WithEnvironment(\"PROMETHEUS_ENDPOINT\", prometheus.GetEndpoint(\"http\"))\n .WithHttpEndpoint(targetPort: 3000, name: \"http\");\n\n grafanaEndpoint = grafana.GetEndpoint(\"http\");\n\n var configFile = \"../../../../config/otelcollector/config.dev.yaml\";\n\n builder.AddOpenTelemetryCollector(\"otelcollector\", configFile)\n .WithEnvironment(\"PROMETHEUS_ENDPOINT\", $\"{prometheus.GetEndpoint(\"http\")}/api/v1/otlp\")\n .WithEnvironment(\"ALERTMANAGER_ENDPOINT\", alertmanager.GetEndpoint(\"http\"));\n }\n\n var pgAdmin = builder.Environment.IsProduction()\n ? builder\n .AddContainer(\"pgadmin\", \"dpage/pgadmin4:8.10\")\n .WithHttpEndpoint(targetPort: 5050, name: \"pgadmin-http\")\n .WithExternalHttpEndpoints()\n .WithEnvironment(\"PGADMIN_DEFAULT_EMAIL\", \"admin@stemify.com\")\n .WithEnvironment(\n \"PGADMIN_DEFAULT_PASSWORD\",\n Environment.GetEnvironmentVariable(\"PGADMIN_PASSWORD\") ?? \"admin\"\n )\n .WithEnvironment(\"PGADMIN_CONFIG_SERVER_MODE\", \"False\")\n .WithEnvironment(\"PGADMIN_CONFIG_MASTER_PASSWORD_REQUIRED\", \"False\")\n .WithEnvironment(\"PGADMIN_LISTEN_PORT\", \"5050\")\n .WithEnvironment(\"PGADMIN_CONFIG_CHECK_EMAIL_DELIVERABILITY\", \"False\")\n .WithEnvironment(\"GUNICORN_TIMEOUT\", \"120\")\n .WaitFor(postgres)\n : postgres.WithPgAdmin(x =>\n {\n x.WithHttpEndpoint(targetPort: 5050, name: \"pgadmin-dev\");\n x.WithExternalHttpEndpoints();\n x.WithEnvironment(\"PGADMIN_CONFIG_CHECK_EMAIL_DELIVERABILITY\", \"False\");\n x.WithEnvironment(\"GUNICORN_TIMEOUT\", \"120\");\n });\n\n var identityService = builder\n .AddProject<Projects.Identity_Web>(\"identity\")\n .WithExternalHttpEndpoints()\n .WithReference(identityDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(identityDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(identityService);\n\n var identityApiService = builder\n .AddProject<Projects.Identity_API>(\"identity-api\")\n .WithExternalHttpEndpoints()\n .WithReference(identityDb)\n .WithReference(identityService)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(identityDb)\n .WaitFor(identityService)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(identityApiService);\n\n // var sagaOrchestrator = builder\n // .AddProject<Projects.Saga_Orchestrator>(\"saga-orchestrator\")\n // .WithReference(identityService)\n // .WithReference(rabbitmqReference)\n // .WaitFor(identityService)\n // .WaitFor(rabbitmqReference)\n // ;\n // ConfigureObservability(sagaOrchestrator);\n\n var classroomService = builder\n .AddProject<Projects.Classroom_API>(\"classroom-api\")\n .WithExternalHttpEndpoints()\n .WithReference(classroomDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(classroomDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(classroomService);\n\n var resourceApiService = builder\n .AddProject<Projects.Resource_API>(\"resource-api\")\n .WithExternalHttpEndpoints()\n .WithReference(resourceDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(resourceDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(resourceApiService);\n\n var notificationApiService = builder\n .AddProject<Projects.Notification_API>(\"notification-api\")\n .WithExternalHttpEndpoints()\n .WithReference(notificationDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(notificationDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(notificationApiService);\n\n var productApiService = builder\n .AddProject<Projects.Product_API>(\"product-api\")\n .WithExternalHttpEndpoints()\n .WithReference(productDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(productDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(productApiService);\n\n var orderApiService = builder\n .AddProject<Projects.Order_API>(\"order-api\")\n .WithExternalHttpEndpoints()\n .WithReference(orderDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(orderDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(orderApiService);\n\n // var paymentApiService = builder\n // .AddProject<Projects.Payment_API>(\"payment-api\")\n // .WithExternalHttpEndpoints()\n // .WithReference(paymentDb)\n // .WithReference(rabbitmq)\n // .WaitFor(postgres)\n // .WaitFor(paymentDb)\n // .WaitFor(rabbitmq)\n // ;\n // ConfigureObservability(paymentApiService);\n\n // var cartApiService = builder\n // .AddProject<Projects.Cart_API>(\"cart-api\")\n // .WithExternalHttpEndpoints()\n // .WithReference(cartDb)\n // .WithReference(rabbitmq)\n // .WaitFor(postgres)\n // .WaitFor(cartDb)\n // .WaitFor(rabbitmq)\n // ;\n // ConfigureObservability(cartApiService);\n \n IResourceBuilder<IResourceWithConnectionString>? emulatorMongoDbReference = null;\n \n var mongo = builder.AddMongoDB(\"emulator-mongodb\")\n .WithLifetime(ContainerLifetime.Persistent);\n \n var emulatorMongoDb = mongo.AddDatabase(\"stemify-emulator\");\n emulatorMongoDbReference = emulatorMongoDb;\n \n\n var emulatorApiService = builder\n .AddProject<Projects.Emulator_API>(\"emulator-api\")\n .WithExternalHttpEndpoints();\n \n if (emulatorMongoDbReference is not null)\n {\n emulatorApiService\n .WithReference(emulatorMongoDbReference)\n .WaitFor(emulatorMongoDbReference);\n }\n \n ConfigureObservability(emulatorApiService);\n\n var hangfireApiService = builder\n .AddProject<Projects.Hangfire_API>(\"hangfire-api\")\n .WithExternalHttpEndpoints()\n .WithReference(hangfireDb)\n .WithReference(identityDb)\n .WithReference(orderDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(hangfireDb)\n .WaitFor(identityDb)\n .WaitFor(orderDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(hangfireApiService);\n\n \n var resourceGrpcPort = builder.Configuration[\"ResourceApi:GrpcPort\"] ?? \"5082\";\n var resourceGrpcEndpoint = $\"localhost:{resourceGrpcPort}\";\n \n var aiService = builder\n .AddUvicornApp(\"ai-service\", \"../../AIService\", \"app.main:app\")\n .WithExternalHttpEndpoints()\n .WithHttpHealthCheck(\"/health\")\n .WithEnvironment(\"RESOURCE_GRPC_ENDPOINT\", resourceGrpcEndpoint)\n .WithEnvironment(\"RESOURCE_GRPC_USE_TLS\", \"false\")\n .WithEnvironment(\"PYTHONUNBUFFERED\", \"1\")\n .WithEnvironment(\"PYTHONDONTWRITEBYTECODE\", \"1\")\n .WithEnvironment(\"PORT\", \"8000\")\n .WithEnvironment(\"OPENAI_API_KEY\", builder.Configuration[\"AIService:LLM:OpenAI:ApiKey\"]\n ?? builder.Configuration[\"OpenAI:ApiKey\"]\n ?? Environment.GetEnvironmentVariable(\"OPENAI_API_KEY\")\n ?? \"\")\n .WithEnvironment(\"DEEPSEEK_API_KEY\", builder.Configuration[\"AIService:LLM:DeepSeek:ApiKey\"]\n ?? builder.Configuration[\"DeepSeek:ApiKey\"]\n ?? Environment.GetEnvironmentVariable(\"DEEPSEEK_API_KEY\")\n ?? \"\")\n .WithReference(resourceApiService)\n .WaitFor(resourceApiService);\n ConfigureObservability(aiService);\n\n var apiGateway = builder\n .AddProject<Projects.ApiGateway>(\"apigateway\")\n .WithExternalHttpEndpoints()\n .WithReference(identityService)\n // .WithReference(identityApiService)\n .WithReference(classroomService)\n .WithReference(resourceApiService)\n .WithReference(notificationApiService)\n .WithReference(emulatorApiService)\n .WaitFor(identityService)\n .WaitFor(resourceApiService)\n .WaitFor(classroomService)\n .WaitFor(notificationApiService)\n .WaitFor(productApiService)\n .WaitFor(orderApiService)\n .WaitFor(emulatorApiService)\n .WaitFor(aiService)\n ;\n ConfigureObservability(apiGateway);\n\n void ConfigureObservability<TResource>(IResourceBuilder<TResource> resourceBuilder)\n where TResource : IResourceWithEnvironment\n {\n var enabledValue = externalObservabilityEnabled ? \"true\" : \"false\";\n resourceBuilder.WithEnvironment(\"Observability__ExternalStackEnabled\", enabledValue);\n\n if (grafanaEndpoint is not null)\n {\n resourceBuilder.WithEnvironment(\"GRAFANA_URL\", grafanaEndpoint);\n }\n }\n\n return builder;\n }\n\n \n private static string GetOrCreatePostgresSnapshotPath(bool isDevelopment)\n {\n // if (!isDevelopment)\n // {\n // var productionPath = Path.Combine(\n // Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),\n // \"STEMify\",\n // \"postgres-data\");\n // Directory.CreateDirectory(productionPath);\n // return productionPath;\n // }\n\n var baseSnapshotsPath = Path.Combine(\n Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),\n \"STEMify\",\n \"postgres-snapshots\");\n\n \n Directory.CreateDirectory(baseSnapshotsPath);\n\n var mostRecentSnapshot = FindMostRecentSnapshot(baseSnapshotsPath);\n\n var timestamp = DateTime.Now.ToString(\"yyyyMMdd-HHmmss\");\n var newSnapshotPath = Path.Combine(baseSnapshotsPath, $\"postgres-data-{timestamp}\");\n \n if (mostRecentSnapshot != null && Directory.Exists(mostRecentSnapshot))\n {\n CopyDirectory(mostRecentSnapshot, newSnapshotPath);\n }\n else\n {\n \n Directory.CreateDirectory(newSnapshotPath);\n }\n\n return newSnapshotPath;\n }\n\n private static void CopyDirectory(string sourceDir, string destinationDir)\n {\n var dir = new DirectoryInfo(sourceDir);\n DirectoryInfo[] dirs = dir.GetDirectories();\n\n Directory.CreateDirectory(destinationDir);\n\n foreach (FileInfo file in dir.GetFiles())\n {\n string targetFilePath = Path.Combine(destinationDir, file.Name);\n file.CopyTo(targetFilePath, overwrite: true);\n }\n\n foreach (DirectoryInfo subDir in dirs)\n {\n string newDestinationDir = Path.Combine(destinationDir, subDir.Name);\n CopyDirectory(subDir.FullName, newDestinationDir);\n }\n }\n\n private static string? FindMostRecentSnapshot(string snapshotsBasePath)\n {\n if (!Directory.Exists(snapshotsBasePath))\n return null;\n\n var snapshotDirectories = Directory.GetDirectories(snapshotsBasePath, \"postgres-data-*\")\n .Where(dir =>\n {\n var dirName = Path.GetFileName(dir);\n if (dirName.Length < 24 || !dirName.StartsWith(\"postgres-data-\"))\n return false;\n \n var timestampPart = dirName.Substring(\"postgres-data-\".Length);\n return timestampPart.Length == 15 && // YYYYMMDD-HHmmss = 15 chars\n timestampPart[8] == '-' &&\n DateTime.TryParseExact(timestampPart, \"yyyyMMdd-HHmmss\", null, \n System.Globalization.DateTimeStyles.None, out _);\n })\n .OrderByDescending(dir =>\n {\n var dirName = Path.GetFileName(dir);\n var timestampPart = dirName.Substring(\"postgres-data-\".Length);\n if (DateTime.TryParseExact(timestampPart, \"yyyyMMdd-HHmmss\", null,\n System.Globalization.DateTimeStyles.None, out var timestamp))\n {\n return timestamp;\n }\n return DateTime.MinValue;\n })\n .ToList();\n\n return snapshotDirectories.FirstOrDefault();\n }\n }\n}\n"}, "557b49d8da63f3d8129ac4f781abee551bceb124": {"t": 1785748768.281565, "n": 19054, "h": "fd0c9aad2000d99a", "c": "using Microsoft.Extensions.Hosting;\n\nnamespace STEMifyBackend.AppHost.Extensions\n{\n public static class ServiceExtensions\n {\n public static IDistributedApplicationBuilder AddApplicationServices(this IDistributedApplicationBuilder builder)\n {\n // Get or create snapshot-based data directory for Postgres persistence\n var postgresDataPath = GetOrCreatePostgresSnapshotPath(builder.Environment.IsDevelopment());\n\n var postgres = builder.AddPostgres(\"stemify-postgres\");\n if (builder.Environment.IsDevelopment())\n postgres.WithBindMount(postgresDataPath, \"/var/lib/postgresql/data\", isReadOnly: false);\n\n var identityDb = postgres.AddDatabase(\"stemifyidentity\");\n\n var classroomDb = postgres.AddDatabase(\"stemifyclassroom\");\n\n var resourceDb = postgres.AddDatabase(\"stemifyresource\");\n\n var notificationDb = postgres.AddDatabase(\"stemifynotification\");\n\n var productDb = postgres.AddDatabase(\"stemifyproduct\");\n\n var orderDb = postgres.AddDatabase(\"stemifyorder\");\n var paymentDb = postgres.AddDatabase(\"stemifypayment\");\n\n var cartDb = postgres.AddDatabase(\"stemifycart\");\n\n var hangfireDb = postgres.AddDatabase(\"stemifyhangfire\");\n\n // // Add pgAdmin for database administration (Production)\n // var pgAdmin = builder\n // .AddContainer(\"pgadmin\", \"dpage/pgadmin4\")\n // .WithHttpEndpoint(targetPort: 5050, name: \"http\")\n // .WithExternalHttpEndpoints()\n // .WithEnvironment(\"PGADMIN_DEFAULT_EMAIL\", \"admin@stemify.com\")\n // .WithEnvironment(\"PGADMIN_DEFAULT_PASSWORD\", Environment.GetEnvironmentVariable(\"PGADMIN_PASSWORD\") ?? \"admin\")\n // .WithEnvironment(\"PGADMIN_CONFIG_SERVER_MODE\", \"False\")\n // .WithEnvironment(\"PGADMIN_CONFIG_MASTER_PASSWORD_REQUIRED\", \"False\")\n // .WithEnvironment(\"PGADMIN_LISTEN_PORT\", \"5050\")\n // .WaitFor(postgres);\n\n // Add RabbitMQ for messaging (required for Payment Service Saga pattern)\n IResourceBuilder<IResourceWithConnectionString> rabbitmqReference;\n if (builder.Environment.IsProduction())\n {\n var cloudAmqpUrl = builder.Configuration[\"RabbitMQ:Url\"]\n ?? Environment.GetEnvironmentVariable(\"RABBITMQ_URL\")\n ?? throw new InvalidOperationException(\"RabbitMQ URL not configured.\");\n\n rabbitmqReference = builder.AddConnectionString(\"messaging\", cloudAmqpUrl);\n }\n else\n {\n var rabbitmq = builder.AddRabbitMQ(\"messaging\")\n .WithManagementPlugin();\n rabbitmqReference = rabbitmq;\n }\n\n\n var manualFlag = Environment.GetEnvironmentVariable(\"ENABLE_OBSERVABILITY_STACK\");\n var externalObservabilityEnabled = builder.Environment.IsDevelopment() ||\n string.Equals(manualFlag, \"true\", StringComparison.OrdinalIgnoreCase);\n EndpointReference? grafanaEndpoint = null;\n\n if (externalObservabilityEnabled)\n {\n var prometheus = builder.AddContainer(\"prometheus\", \"prom/prometheus\", \"v3.5.0\")\n .WithBindMount(\"../../../../config/prometheus\", \"/etc/prometheus\", isReadOnly: true)\n .WithArgs(\n \"--web.enable-otlp-receiver\",\n \"--config.file=/etc/prometheus/prometheus.yml\",\n \"--storage.tsdb.retention.time=15d\",\n \"--storage.tsdb.retention.size=10GB\"\n )\n .WithHttpEndpoint(targetPort: 9090, name: \"http\");\n\n var alertmanager = builder.AddContainer(\"alertmanager\", \"prom/alertmanager\", \"v0.27.0\")\n .WithBindMount(\"../../../../config/alertmanager\", \"/etc/alertmanager\", isReadOnly: true)\n .WithArgs(\"--config.file=/etc/alertmanager/alertmanager.yml\", \"--storage.path=/alertmanager\")\n .WithHttpEndpoint(targetPort: 9093, name: \"http\");\n\n var grafana = builder.AddContainer(\"grafana\", \"grafana/grafana\")\n .WithBindMount(\"../../../../config/grafana/config\", \"/etc/grafana\", isReadOnly: true)\n .WithBindMount(\"../../../../config/grafana/dashboards\", \"/var/lib/grafana/dashboards\", isReadOnly: true)\n .WithEnvironment(\"PROMETHEUS_ENDPOINT\", prometheus.GetEndpoint(\"http\"))\n .WithHttpEndpoint(targetPort: 3000, name: \"http\");\n\n grafanaEndpoint = grafana.GetEndpoint(\"http\");\n\n var configFile = \"../../../../config/otelcollector/config.dev.yaml\";\n\n builder.AddOpenTelemetryCollector(\"otelcollector\", configFile)\n .WithEnvironment(\"PROMETHEUS_ENDPOINT\", $\"{prometheus.GetEndpoint(\"http\")}/api/v1/otlp\")\n .WithEnvironment(\"ALERTMANAGER_ENDPOINT\", alertmanager.GetEndpoint(\"http\"));\n }\n\n var pgAdmin = builder.Environment.IsProduction()\n ? builder\n .AddContainer(\"pgadmin\", \"dpage/pgadmin4:8.10\")\n .WithHttpEndpoint(targetPort: 5050, name: \"pgadmin-http\")\n .WithExternalHttpEndpoints()\n .WithEnvironment(\"PGADMIN_DEFAULT_EMAIL\", \"admin@stemify.com\")\n .WithEnvironment(\n \"PGADMIN_DEFAULT_PASSWORD\",\n Environment.GetEnvironmentVariable(\"PGADMIN_PASSWORD\") ?? \"admin\"\n )\n .WithEnvironment(\"PGADMIN_CONFIG_SERVER_MODE\", \"False\")\n .WithEnvironment(\"PGADMIN_CONFIG_MASTER_PASSWORD_REQUIRED\", \"False\")\n .WithEnvironment(\"PGADMIN_LISTEN_PORT\", \"5050\")\n .WithEnvironment(\"PGADMIN_CONFIG_CHECK_EMAIL_DELIVERABILITY\", \"False\")\n .WithEnvironment(\"GUNICORN_TIMEOUT\", \"120\")\n .WaitFor(postgres)\n : postgres.WithPgAdmin(x =>\n {\n x.WithHttpEndpoint(targetPort: 5050, name: \"pgadmin-dev\");\n x.WithExternalHttpEndpoints();\n x.WithEnvironment(\"PGADMIN_CONFIG_CHECK_EMAIL_DELIVERABILITY\", \"False\");\n x.WithEnvironment(\"GUNICORN_TIMEOUT\", \"120\");\n });\n\n var identityService = builder\n .AddProject<Projects.Identity_Web>(\"identity\")\n .WithExternalHttpEndpoints()\n .WithReference(identityDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(identityDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(identityService);\n\n var identityApiService = builder\n .AddProject<Projects.Identity_API>(\"identity-api\")\n .WithExternalHttpEndpoints()\n .WithReference(identityDb)\n .WithReference(identityService)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(identityDb)\n .WaitFor(identityService)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(identityApiService);\n\n // var sagaOrchestrator = builder\n // .AddProject<Projects.Saga_Orchestrator>(\"saga-orchestrator\")\n // .WithReference(identityService)\n // .WithReference(rabbitmqReference)\n // .WaitFor(identityService)\n // .WaitFor(rabbitmqReference)\n // ;\n // ConfigureObservability(sagaOrchestrator);\n\n var classroomService = builder\n .AddProject<Projects.Classroom_API>(\"classroom-api\")\n .WithExternalHttpEndpoints()\n .WithReference(classroomDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(classroomDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(classroomService);\n\n var resourceApiService = builder\n .AddProject<Projects.Resource_API>(\"resource-api\")\n .WithExternalHttpEndpoints()\n .WithReference(resourceDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(resourceDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(resourceApiService);\n\n var notificationApiService = builder\n .AddProject<Projects.Notification_API>(\"notification-api\")\n .WithExternalHttpEndpoints()\n .WithReference(notificationDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(notificationDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(notificationApiService);\n\n var productApiService = builder\n .AddProject<Projects.Product_API>(\"product-api\")\n .WithExternalHttpEndpoints()\n .WithReference(productDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(productDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(productApiService);\n\n var orderApiService = builder\n .AddProject<Projects.Order_API>(\"order-api\")\n .WithExternalHttpEndpoints()\n .WithReference(orderDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(orderDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(orderApiService);\n\n // var paymentApiService = builder\n // .AddProject<Projects.Payment_API>(\"payment-api\")\n // .WithExternalHttpEndpoints()\n // .WithReference(paymentDb)\n // .WithReference(rabbitmq)\n // .WaitFor(postgres)\n // .WaitFor(paymentDb)\n // .WaitFor(rabbitmq)\n // ;\n // ConfigureObservability(paymentApiService);\n\n // var cartApiService = builder\n // .AddProject<Projects.Cart_API>(\"cart-api\")\n // .WithExternalHttpEndpoints()\n // .WithReference(cartDb)\n // .WithReference(rabbitmq)\n // .WaitFor(postgres)\n // .WaitFor(cartDb)\n // .WaitFor(rabbitmq)\n // ;\n // ConfigureObservability(cartApiService);\n \n IResourceBuilder<IResourceWithConnectionString>? emulatorMongoDbReference = null;\n \n var mongo = builder.AddMongoDB(\"emulator-mongodb\")\n .WithLifetime(ContainerLifetime.Persistent);\n \n var emulatorMongoDb = mongo.AddDatabase(\"stemify-emulator\");\n emulatorMongoDbReference = emulatorMongoDb;\n \n\n var emulatorApiService = builder\n .AddProject<Projects.Emulator_API>(\"emulator-api\")\n .WithExternalHttpEndpoints();\n \n if (emulatorMongoDbReference is not null)\n {\n emulatorApiService\n .WithReference(emulatorMongoDbReference)\n .WaitFor(emulatorMongoDbReference);\n }\n \n ConfigureObservability(emulatorApiService);\n\n var hangfireApiService = builder\n .AddProject<Projects.Hangfire_API>(\"hangfire-api\")\n .WithExternalHttpEndpoints()\n .WithReference(hangfireDb)\n .WithReference(identityDb)\n .WithReference(orderDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(hangfireDb)\n .WaitFor(identityDb)\n .WaitFor(orderDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(hangfireApiService);\n\n \n var resourceGrpcPort = builder.Configuration[\"ResourceApi:GrpcPort\"] ?? \"5082\";\n var resourceGrpcEndpoint = $\"localhost:{resourceGrpcPort}\";\n \n var aiService = builder\n .AddUvicornApp(\"ai-service\", \"../../AIService\", \"app.main:app\")\n .WithHttpEndpoint(port: 8000, targetPort: 8000)\n .WithHttpHealthCheck(\"/health\")\n .WithEnvironment(\"RESOURCE_GRPC_ENDPOINT\", resourceGrpcEndpoint)\n .WithEnvironment(\"RESOURCE_GRPC_USE_TLS\", \"false\")\n .WithEnvironment(\"PYTHONUNBUFFERED\", \"1\")\n .WithEnvironment(\"PYTHONDONTWRITEBYTECODE\", \"1\")\n .WithEnvironment(\"OPENAI_API_KEY\", builder.Configuration[\"AIService:LLM:OpenAI:ApiKey\"]\n ?? builder.Configuration[\"OpenAI:ApiKey\"]\n ?? Environment.GetEnvironmentVariable(\"OPENAI_API_KEY\")\n ?? \"\")\n .WithEnvironment(\"DEEPSEEK_API_KEY\", builder.Configuration[\"AIService:LLM:DeepSeek:ApiKey\"]\n ?? builder.Configuration[\"DeepSeek:ApiKey\"]\n ?? Environment.GetEnvironmentVariable(\"DEEPSEEK_API_KEY\")\n ?? \"\")\n .WithReference(resourceApiService)\n .WaitFor(resourceApiService);\n ConfigureObservability(aiService);\n\n var apiGateway = builder\n .AddProject<Projects.ApiGateway>(\"apigateway\")\n .WithExternalHttpEndpoints()\n .WithReference(identityService)\n // .WithReference(identityApiService)\n .WithReference(classroomService)\n .WithReference(resourceApiService)\n .WithReference(notificationApiService)\n .WithReference(emulatorApiService)\n .WaitFor(identityService)\n .WaitFor(resourceApiService)\n .WaitFor(classroomService)\n .WaitFor(notificationApiService)\n .WaitFor(productApiService)\n .WaitFor(orderApiService)\n .WaitFor(emulatorApiService)\n .WaitFor(aiService)\n ;\n ConfigureObservability(apiGateway);\n\n void ConfigureObservability<TResource>(IResourceBuilder<TResource> resourceBuilder)\n where TResource : IResourceWithEnvironment\n {\n var enabledValue = externalObservabilityEnabled ? \"true\" : \"false\";\n resourceBuilder.WithEnvironment(\"Observability__ExternalStackEnabled\", enabledValue);\n\n if (grafanaEndpoint is not null)\n {\n resourceBuilder.WithEnvironment(\"GRAFANA_URL\", grafanaEndpoint);\n }\n }\n\n return builder;\n }\n\n \n private static string GetOrCreatePostgresSnapshotPath(bool isDevelopment)\n {\n // if (!isDevelopment)\n // {\n // var productionPath = Path.Combine(\n // Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),\n // \"STEMify\",\n // \"postgres-data\");\n // Directory.CreateDirectory(productionPath);\n // return productionPath;\n // }\n\n var baseSnapshotsPath = Path.Combine(\n Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),\n \"STEMify\",\n \"postgres-snapshots\");\n\n \n Directory.CreateDirectory(baseSnapshotsPath);\n\n var mostRecentSnapshot = FindMostRecentSnapshot(baseSnapshotsPath);\n\n var timestamp = DateTime.Now.ToString(\"yyyyMMdd-HHmmss\");\n var newSnapshotPath = Path.Combine(baseSnapshotsPath, $\"postgres-data-{timestamp}\");\n \n if (mostRecentSnapshot != null && Directory.Exists(mostRecentSnapshot))\n {\n CopyDirectory(mostRecentSnapshot, newSnapshotPath);\n }\n else\n {\n \n Directory.CreateDirectory(newSnapshotPath);\n }\n\n return newSnapshotPath;\n }\n\n private static void CopyDirectory(string sourceDir, string destinationDir)\n {\n var dir = new DirectoryInfo(sourceDir);\n DirectoryInfo[] dirs = dir.GetDirectories();\n\n Directory.CreateDirectory(destinationDir);\n\n foreach (FileInfo file in dir.GetFiles())\n {\n string targetFilePath = Path.Combine(destinationDir, file.Name);\n file.CopyTo(targetFilePath, overwrite: true);\n }\n\n foreach (DirectoryInfo subDir in dirs)\n {\n string newDestinationDir = Path.Combine(destinationDir, subDir.Name);\n CopyDirectory(subDir.FullName, newDestinationDir);\n }\n }\n\n private static string? FindMostRecentSnapshot(string snapshotsBasePath)\n {\n if (!Directory.Exists(snapshotsBasePath))\n return null;\n\n var snapshotDirectories = Directory.GetDirectories(snapshotsBasePath, \"postgres-data-*\")\n .Where(dir =>\n {\n var dirName = Path.GetFileName(dir);\n if (dirName.Length < 24 || !dirName.StartsWith(\"postgres-data-\"))\n return false;\n \n var timestampPart = dirName.Substring(\"postgres-data-\".Length);\n return timestampPart.Length == 15 && // YYYYMMDD-HHmmss = 15 chars\n timestampPart[8] == '-' &&\n DateTime.TryParseExact(timestampPart, \"yyyyMMdd-HHmmss\", null, \n System.Globalization.DateTimeStyles.None, out _);\n })\n .OrderByDescending(dir =>\n {\n var dirName = Path.GetFileName(dir);\n var timestampPart = dirName.Substring(\"postgres-data-\".Length);\n if (DateTime.TryParseExact(timestampPart, \"yyyyMMdd-HHmmss\", null,\n System.Globalization.DateTimeStyles.None, out var timestamp))\n {\n return timestamp;\n }\n return DateTime.MinValue;\n })\n .ToList();\n\n return snapshotDirectories.FirstOrDefault();\n }\n }\n}\n"}, "e886d94a93ab019aa3484cb7b0e77b2dc6c067ee": {"t": 1785748769.407015, "n": 19113, "h": "199cf20325919da5", "c": "using Microsoft.Extensions.Hosting;\n\nnamespace STEMifyBackend.AppHost.Extensions\n{\n public static class ServiceExtensions\n {\n public static IDistributedApplicationBuilder AddApplicationServices(this IDistributedApplicationBuilder builder)\n {\n // Get or create snapshot-based data directory for Postgres persistence\n var postgresDataPath = GetOrCreatePostgresSnapshotPath(builder.Environment.IsDevelopment());\n\n var postgres = builder.AddPostgres(\"stemify-postgres\");\n if (builder.Environment.IsDevelopment())\n postgres.WithBindMount(postgresDataPath, \"/var/lib/postgresql/data\", isReadOnly: false);\n\n var identityDb = postgres.AddDatabase(\"stemifyidentity\");\n\n var classroomDb = postgres.AddDatabase(\"stemifyclassroom\");\n\n var resourceDb = postgres.AddDatabase(\"stemifyresource\");\n\n var notificationDb = postgres.AddDatabase(\"stemifynotification\");\n\n var productDb = postgres.AddDatabase(\"stemifyproduct\");\n\n var orderDb = postgres.AddDatabase(\"stemifyorder\");\n var paymentDb = postgres.AddDatabase(\"stemifypayment\");\n\n var cartDb = postgres.AddDatabase(\"stemifycart\");\n\n var hangfireDb = postgres.AddDatabase(\"stemifyhangfire\");\n\n // // Add pgAdmin for database administration (Production)\n // var pgAdmin = builder\n // .AddContainer(\"pgadmin\", \"dpage/pgadmin4\")\n // .WithHttpEndpoint(targetPort: 5050, name: \"http\")\n // .WithExternalHttpEndpoints()\n // .WithEnvironment(\"PGADMIN_DEFAULT_EMAIL\", \"admin@stemify.com\")\n // .WithEnvironment(\"PGADMIN_DEFAULT_PASSWORD\", Environment.GetEnvironmentVariable(\"PGADMIN_PASSWORD\") ?? \"admin\")\n // .WithEnvironment(\"PGADMIN_CONFIG_SERVER_MODE\", \"False\")\n // .WithEnvironment(\"PGADMIN_CONFIG_MASTER_PASSWORD_REQUIRED\", \"False\")\n // .WithEnvironment(\"PGADMIN_LISTEN_PORT\", \"5050\")\n // .WaitFor(postgres);\n\n // Add RabbitMQ for messaging (required for Payment Service Saga pattern)\n IResourceBuilder<IResourceWithConnectionString> rabbitmqReference;\n if (builder.Environment.IsProduction())\n {\n var cloudAmqpUrl = builder.Configuration[\"RabbitMQ:Url\"]\n ?? Environment.GetEnvironmentVariable(\"RABBITMQ_URL\")\n ?? throw new InvalidOperationException(\"RabbitMQ URL not configured.\");\n\n rabbitmqReference = builder.AddConnectionString(\"messaging\", cloudAmqpUrl);\n }\n else\n {\n var rabbitmq = builder.AddRabbitMQ(\"messaging\")\n .WithManagementPlugin();\n rabbitmqReference = rabbitmq;\n }\n\n\n var manualFlag = Environment.GetEnvironmentVariable(\"ENABLE_OBSERVABILITY_STACK\");\n var externalObservabilityEnabled = builder.Environment.IsDevelopment() ||\n string.Equals(manualFlag, \"true\", StringComparison.OrdinalIgnoreCase);\n EndpointReference? grafanaEndpoint = null;\n\n if (externalObservabilityEnabled)\n {\n var prometheus = builder.AddContainer(\"prometheus\", \"prom/prometheus\", \"v3.5.0\")\n .WithBindMount(\"../../../../config/prometheus\", \"/etc/prometheus\", isReadOnly: true)\n .WithArgs(\n \"--web.enable-otlp-receiver\",\n \"--config.file=/etc/prometheus/prometheus.yml\",\n \"--storage.tsdb.retention.time=15d\",\n \"--storage.tsdb.retention.size=10GB\"\n )\n .WithHttpEndpoint(targetPort: 9090, name: \"http\");\n\n var alertmanager = builder.AddContainer(\"alertmanager\", \"prom/alertmanager\", \"v0.27.0\")\n .WithBindMount(\"../../../../config/alertmanager\", \"/etc/alertmanager\", isReadOnly: true)\n .WithArgs(\"--config.file=/etc/alertmanager/alertmanager.yml\", \"--storage.path=/alertmanager\")\n .WithHttpEndpoint(targetPort: 9093, name: \"http\");\n\n var grafana = builder.AddContainer(\"grafana\", \"grafana/grafana\")\n .WithBindMount(\"../../../../config/grafana/config\", \"/etc/grafana\", isReadOnly: true)\n .WithBindMount(\"../../../../config/grafana/dashboards\", \"/var/lib/grafana/dashboards\", isReadOnly: true)\n .WithEnvironment(\"PROMETHEUS_ENDPOINT\", prometheus.GetEndpoint(\"http\"))\n .WithHttpEndpoint(targetPort: 3000, name: \"http\");\n\n grafanaEndpoint = grafana.GetEndpoint(\"http\");\n\n var configFile = \"../../../../config/otelcollector/config.dev.yaml\";\n\n builder.AddOpenTelemetryCollector(\"otelcollector\", configFile)\n .WithEnvironment(\"PROMETHEUS_ENDPOINT\", $\"{prometheus.GetEndpoint(\"http\")}/api/v1/otlp\")\n .WithEnvironment(\"ALERTMANAGER_ENDPOINT\", alertmanager.GetEndpoint(\"http\"));\n }\n\n var pgAdmin = builder.Environment.IsProduction()\n ? builder\n .AddContainer(\"pgadmin\", \"dpage/pgadmin4:8.10\")\n .WithHttpEndpoint(targetPort: 5050, name: \"pgadmin-http\")\n .WithExternalHttpEndpoints()\n .WithEnvironment(\"PGADMIN_DEFAULT_EMAIL\", \"admin@stemify.com\")\n .WithEnvironment(\n \"PGADMIN_DEFAULT_PASSWORD\",\n Environment.GetEnvironmentVariable(\"PGADMIN_PASSWORD\") ?? \"admin\"\n )\n .WithEnvironment(\"PGADMIN_CONFIG_SERVER_MODE\", \"False\")\n .WithEnvironment(\"PGADMIN_CONFIG_MASTER_PASSWORD_REQUIRED\", \"False\")\n .WithEnvironment(\"PGADMIN_LISTEN_PORT\", \"5050\")\n .WithEnvironment(\"PGADMIN_CONFIG_CHECK_EMAIL_DELIVERABILITY\", \"False\")\n .WithEnvironment(\"GUNICORN_TIMEOUT\", \"120\")\n .WaitFor(postgres)\n : postgres.WithPgAdmin(x =>\n {\n x.WithHttpEndpoint(targetPort: 5050, name: \"pgadmin-dev\");\n x.WithExternalHttpEndpoints();\n x.WithEnvironment(\"PGADMIN_CONFIG_CHECK_EMAIL_DELIVERABILITY\", \"False\");\n x.WithEnvironment(\"GUNICORN_TIMEOUT\", \"120\");\n });\n\n var identityService = builder\n .AddProject<Projects.Identity_Web>(\"identity\")\n .WithExternalHttpEndpoints()\n .WithReference(identityDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(identityDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(identityService);\n\n var identityApiService = builder\n .AddProject<Projects.Identity_API>(\"identity-api\")\n .WithExternalHttpEndpoints()\n .WithReference(identityDb)\n .WithReference(identityService)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(identityDb)\n .WaitFor(identityService)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(identityApiService);\n\n // var sagaOrchestrator = builder\n // .AddProject<Projects.Saga_Orchestrator>(\"saga-orchestrator\")\n // .WithReference(identityService)\n // .WithReference(rabbitmqReference)\n // .WaitFor(identityService)\n // .WaitFor(rabbitmqReference)\n // ;\n // ConfigureObservability(sagaOrchestrator);\n\n var classroomService = builder\n .AddProject<Projects.Classroom_API>(\"classroom-api\")\n .WithExternalHttpEndpoints()\n .WithReference(classroomDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(classroomDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(classroomService);\n\n var resourceApiService = builder\n .AddProject<Projects.Resource_API>(\"resource-api\")\n .WithExternalHttpEndpoints()\n .WithReference(resourceDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(resourceDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(resourceApiService);\n\n var notificationApiService = builder\n .AddProject<Projects.Notification_API>(\"notification-api\")\n .WithExternalHttpEndpoints()\n .WithReference(notificationDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(notificationDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(notificationApiService);\n\n var productApiService = builder\n .AddProject<Projects.Product_API>(\"product-api\")\n .WithExternalHttpEndpoints()\n .WithReference(productDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(productDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(productApiService);\n\n var orderApiService = builder\n .AddProject<Projects.Order_API>(\"order-api\")\n .WithExternalHttpEndpoints()\n .WithReference(orderDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(orderDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(orderApiService);\n\n // var paymentApiService = builder\n // .AddProject<Projects.Payment_API>(\"payment-api\")\n // .WithExternalHttpEndpoints()\n // .WithReference(paymentDb)\n // .WithReference(rabbitmq)\n // .WaitFor(postgres)\n // .WaitFor(paymentDb)\n // .WaitFor(rabbitmq)\n // ;\n // ConfigureObservability(paymentApiService);\n\n // var cartApiService = builder\n // .AddProject<Projects.Cart_API>(\"cart-api\")\n // .WithExternalHttpEndpoints()\n // .WithReference(cartDb)\n // .WithReference(rabbitmq)\n // .WaitFor(postgres)\n // .WaitFor(cartDb)\n // .WaitFor(rabbitmq)\n // ;\n // ConfigureObservability(cartApiService);\n \n IResourceBuilder<IResourceWithConnectionString>? emulatorMongoDbReference = null;\n \n var mongo = builder.AddMongoDB(\"emulator-mongodb\")\n .WithLifetime(ContainerLifetime.Persistent);\n \n var emulatorMongoDb = mongo.AddDatabase(\"stemify-emulator\");\n emulatorMongoDbReference = emulatorMongoDb;\n \n\n var emulatorApiService = builder\n .AddProject<Projects.Emulator_API>(\"emulator-api\")\n .WithExternalHttpEndpoints();\n \n if (emulatorMongoDbReference is not null)\n {\n emulatorApiService\n .WithReference(emulatorMongoDbReference)\n .WaitFor(emulatorMongoDbReference);\n }\n \n ConfigureObservability(emulatorApiService);\n\n var hangfireApiService = builder\n .AddProject<Projects.Hangfire_API>(\"hangfire-api\")\n .WithExternalHttpEndpoints()\n .WithReference(hangfireDb)\n .WithReference(identityDb)\n .WithReference(orderDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(hangfireDb)\n .WaitFor(identityDb)\n .WaitFor(orderDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(hangfireApiService);\n\n \n var resourceGrpcPort = builder.Configuration[\"ResourceApi:GrpcPort\"] ?? \"5082\";\n var resourceGrpcEndpoint = $\"localhost:{resourceGrpcPort}\";\n \n var aiService = builder\n .AddUvicornApp(\"ai-service\", \"../../AIService\", \"app.main:app\")\n .WithHttpEndpoint(port: 8000, targetPort: 8000, name: \"http\")\n .WithExternalHttpEndpoints()\n .WithHttpHealthCheck(\"/health\")\n .WithEnvironment(\"RESOURCE_GRPC_ENDPOINT\", resourceGrpcEndpoint)\n .WithEnvironment(\"RESOURCE_GRPC_USE_TLS\", \"false\")\n .WithEnvironment(\"PYTHONUNBUFFERED\", \"1\")\n .WithEnvironment(\"PYTHONDONTWRITEBYTECODE\", \"1\")\n .WithEnvironment(\"OPENAI_API_KEY\", builder.Configuration[\"AIService:LLM:OpenAI:ApiKey\"]\n ?? builder.Configuration[\"OpenAI:ApiKey\"]\n ?? Environment.GetEnvironmentVariable(\"OPENAI_API_KEY\")\n ?? \"\")\n .WithEnvironment(\"DEEPSEEK_API_KEY\", builder.Configuration[\"AIService:LLM:DeepSeek:ApiKey\"]\n ?? builder.Configuration[\"DeepSeek:ApiKey\"]\n ?? Environment.GetEnvironmentVariable(\"DEEPSEEK_API_KEY\")\n ?? \"\")\n .WithReference(resourceApiService)\n .WaitFor(resourceApiService);\n ConfigureObservability(aiService);\n\n var apiGateway = builder\n .AddProject<Projects.ApiGateway>(\"apigateway\")\n .WithExternalHttpEndpoints()\n .WithReference(identityService)\n // .WithReference(identityApiService)\n .WithReference(classroomService)\n .WithReference(resourceApiService)\n .WithReference(notificationApiService)\n .WithReference(emulatorApiService)\n .WaitFor(identityService)\n .WaitFor(resourceApiService)\n .WaitFor(classroomService)\n .WaitFor(notificationApiService)\n .WaitFor(productApiService)\n .WaitFor(orderApiService)\n .WaitFor(emulatorApiService)\n .WaitFor(aiService)\n ;\n ConfigureObservability(apiGateway);\n\n void ConfigureObservability<TResource>(IResourceBuilder<TResource> resourceBuilder)\n where TResource : IResourceWithEnvironment\n {\n var enabledValue = externalObservabilityEnabled ? \"true\" : \"false\";\n resourceBuilder.WithEnvironment(\"Observability__ExternalStackEnabled\", enabledValue);\n\n if (grafanaEndpoint is not null)\n {\n resourceBuilder.WithEnvironment(\"GRAFANA_URL\", grafanaEndpoint);\n }\n }\n\n return builder;\n }\n\n \n private static string GetOrCreatePostgresSnapshotPath(bool isDevelopment)\n {\n // if (!isDevelopment)\n // {\n // var productionPath = Path.Combine(\n // Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),\n // \"STEMify\",\n // \"postgres-data\");\n // Directory.CreateDirectory(productionPath);\n // return productionPath;\n // }\n\n var baseSnapshotsPath = Path.Combine(\n Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),\n \"STEMify\",\n \"postgres-snapshots\");\n\n \n Directory.CreateDirectory(baseSnapshotsPath);\n\n var mostRecentSnapshot = FindMostRecentSnapshot(baseSnapshotsPath);\n\n var timestamp = DateTime.Now.ToString(\"yyyyMMdd-HHmmss\");\n var newSnapshotPath = Path.Combine(baseSnapshotsPath, $\"postgres-data-{timestamp}\");\n \n if (mostRecentSnapshot != null && Directory.Exists(mostRecentSnapshot))\n {\n CopyDirectory(mostRecentSnapshot, newSnapshotPath);\n }\n else\n {\n \n Directory.CreateDirectory(newSnapshotPath);\n }\n\n return newSnapshotPath;\n }\n\n private static void CopyDirectory(string sourceDir, string destinationDir)\n {\n var dir = new DirectoryInfo(sourceDir);\n DirectoryInfo[] dirs = dir.GetDirectories();\n\n Directory.CreateDirectory(destinationDir);\n\n foreach (FileInfo file in dir.GetFiles())\n {\n string targetFilePath = Path.Combine(destinationDir, file.Name);\n file.CopyTo(targetFilePath, overwrite: true);\n }\n\n foreach (DirectoryInfo subDir in dirs)\n {\n string newDestinationDir = Path.Combine(destinationDir, subDir.Name);\n CopyDirectory(subDir.FullName, newDestinationDir);\n }\n }\n\n private static string? FindMostRecentSnapshot(string snapshotsBasePath)\n {\n if (!Directory.Exists(snapshotsBasePath))\n return null;\n\n var snapshotDirectories = Directory.GetDirectories(snapshotsBasePath, \"postgres-data-*\")\n .Where(dir =>\n {\n var dirName = Path.GetFileName(dir);\n if (dirName.Length < 24 || !dirName.StartsWith(\"postgres-data-\"))\n return false;\n \n var timestampPart = dirName.Substring(\"postgres-data-\".Length);\n return timestampPart.Length == 15 && // YYYYMMDD-HHmmss = 15 chars\n timestampPart[8] == '-' &&\n DateTime.TryParseExact(timestampPart, \"yyyyMMdd-HHmmss\", null, \n System.Globalization.DateTimeStyles.None, out _);\n })\n .OrderByDescending(dir =>\n {\n var dirName = Path.GetFileName(dir);\n var timestampPart = dirName.Substring(\"postgres-data-\".Length);\n if (DateTime.TryParseExact(timestampPart, \"yyyyMMdd-HHmmss\", null,\n System.Globalization.DateTimeStyles.None, out var timestamp))\n {\n return timestamp;\n }\n return DateTime.MinValue;\n })\n .ToList();\n\n return snapshotDirectories.FirstOrDefault();\n }\n }\n}\n"}, "c087a988db53d6056766b9c5566bac500f46781c": {"t": 1785748771.0141292, "n": 19035, "h": "55926f4d90eb6086", "c": "using Microsoft.Extensions.Hosting;\n\nnamespace STEMifyBackend.AppHost.Extensions\n{\n public static class ServiceExtensions\n {\n public static IDistributedApplicationBuilder AddApplicationServices(this IDistributedApplicationBuilder builder)\n {\n // Get or create snapshot-based data directory for Postgres persistence\n var postgresDataPath = GetOrCreatePostgresSnapshotPath(builder.Environment.IsDevelopment());\n\n var postgres = builder.AddPostgres(\"stemify-postgres\");\n if (builder.Environment.IsDevelopment())\n postgres.WithBindMount(postgresDataPath, \"/var/lib/postgresql/data\", isReadOnly: false);\n\n var identityDb = postgres.AddDatabase(\"stemifyidentity\");\n\n var classroomDb = postgres.AddDatabase(\"stemifyclassroom\");\n\n var resourceDb = postgres.AddDatabase(\"stemifyresource\");\n\n var notificationDb = postgres.AddDatabase(\"stemifynotification\");\n\n var productDb = postgres.AddDatabase(\"stemifyproduct\");\n\n var orderDb = postgres.AddDatabase(\"stemifyorder\");\n var paymentDb = postgres.AddDatabase(\"stemifypayment\");\n\n var cartDb = postgres.AddDatabase(\"stemifycart\");\n\n var hangfireDb = postgres.AddDatabase(\"stemifyhangfire\");\n\n // // Add pgAdmin for database administration (Production)\n // var pgAdmin = builder\n // .AddContainer(\"pgadmin\", \"dpage/pgadmin4\")\n // .WithHttpEndpoint(targetPort: 5050, name: \"http\")\n // .WithExternalHttpEndpoints()\n // .WithEnvironment(\"PGADMIN_DEFAULT_EMAIL\", \"admin@stemify.com\")\n // .WithEnvironment(\"PGADMIN_DEFAULT_PASSWORD\", Environment.GetEnvironmentVariable(\"PGADMIN_PASSWORD\") ?? \"admin\")\n // .WithEnvironment(\"PGADMIN_CONFIG_SERVER_MODE\", \"False\")\n // .WithEnvironment(\"PGADMIN_CONFIG_MASTER_PASSWORD_REQUIRED\", \"False\")\n // .WithEnvironment(\"PGADMIN_LISTEN_PORT\", \"5050\")\n // .WaitFor(postgres);\n\n // Add RabbitMQ for messaging (required for Payment Service Saga pattern)\n IResourceBuilder<IResourceWithConnectionString> rabbitmqReference;\n if (builder.Environment.IsProduction())\n {\n var cloudAmqpUrl = builder.Configuration[\"RabbitMQ:Url\"]\n ?? Environment.GetEnvironmentVariable(\"RABBITMQ_URL\")\n ?? throw new InvalidOperationException(\"RabbitMQ URL not configured.\");\n\n rabbitmqReference = builder.AddConnectionString(\"messaging\", cloudAmqpUrl);\n }\n else\n {\n var rabbitmq = builder.AddRabbitMQ(\"messaging\")\n .WithManagementPlugin();\n rabbitmqReference = rabbitmq;\n }\n\n\n var manualFlag = Environment.GetEnvironmentVariable(\"ENABLE_OBSERVABILITY_STACK\");\n var externalObservabilityEnabled = builder.Environment.IsDevelopment() ||\n string.Equals(manualFlag, \"true\", StringComparison.OrdinalIgnoreCase);\n EndpointReference? grafanaEndpoint = null;\n\n if (externalObservabilityEnabled)\n {\n var prometheus = builder.AddContainer(\"prometheus\", \"prom/prometheus\", \"v3.5.0\")\n .WithBindMount(\"../../../../config/prometheus\", \"/etc/prometheus\", isReadOnly: true)\n .WithArgs(\n \"--web.enable-otlp-receiver\",\n \"--config.file=/etc/prometheus/prometheus.yml\",\n \"--storage.tsdb.retention.time=15d\",\n \"--storage.tsdb.retention.size=10GB\"\n )\n .WithHttpEndpoint(targetPort: 9090, name: \"http\");\n\n var alertmanager = builder.AddContainer(\"alertmanager\", \"prom/alertmanager\", \"v0.27.0\")\n .WithBindMount(\"../../../../config/alertmanager\", \"/etc/alertmanager\", isReadOnly: true)\n .WithArgs(\"--config.file=/etc/alertmanager/alertmanager.yml\", \"--storage.path=/alertmanager\")\n .WithHttpEndpoint(targetPort: 9093, name: \"http\");\n\n var grafana = builder.AddContainer(\"grafana\", \"grafana/grafana\")\n .WithBindMount(\"../../../../config/grafana/config\", \"/etc/grafana\", isReadOnly: true)\n .WithBindMount(\"../../../../config/grafana/dashboards\", \"/var/lib/grafana/dashboards\", isReadOnly: true)\n .WithEnvironment(\"PROMETHEUS_ENDPOINT\", prometheus.GetEndpoint(\"http\"))\n .WithHttpEndpoint(targetPort: 3000, name: \"http\");\n\n grafanaEndpoint = grafana.GetEndpoint(\"http\");\n\n var configFile = \"../../../../config/otelcollector/config.dev.yaml\";\n\n builder.AddOpenTelemetryCollector(\"otelcollector\", configFile)\n .WithEnvironment(\"PROMETHEUS_ENDPOINT\", $\"{prometheus.GetEndpoint(\"http\")}/api/v1/otlp\")\n .WithEnvironment(\"ALERTMANAGER_ENDPOINT\", alertmanager.GetEndpoint(\"http\"));\n }\n\n var pgAdmin = builder.Environment.IsProduction()\n ? builder\n .AddContainer(\"pgadmin\", \"dpage/pgadmin4:8.10\")\n .WithHttpEndpoint(targetPort: 5050, name: \"pgadmin-http\")\n .WithExternalHttpEndpoints()\n .WithEnvironment(\"PGADMIN_DEFAULT_EMAIL\", \"admin@stemify.com\")\n .WithEnvironment(\n \"PGADMIN_DEFAULT_PASSWORD\",\n Environment.GetEnvironmentVariable(\"PGADMIN_PASSWORD\") ?? \"admin\"\n )\n .WithEnvironment(\"PGADMIN_CONFIG_SERVER_MODE\", \"False\")\n .WithEnvironment(\"PGADMIN_CONFIG_MASTER_PASSWORD_REQUIRED\", \"False\")\n .WithEnvironment(\"PGADMIN_LISTEN_PORT\", \"5050\")\n .WithEnvironment(\"PGADMIN_CONFIG_CHECK_EMAIL_DELIVERABILITY\", \"False\")\n .WithEnvironment(\"GUNICORN_TIMEOUT\", \"120\")\n .WaitFor(postgres)\n : postgres.WithPgAdmin(x =>\n {\n x.WithHttpEndpoint(targetPort: 5050, name: \"pgadmin-dev\");\n x.WithExternalHttpEndpoints();\n x.WithEnvironment(\"PGADMIN_CONFIG_CHECK_EMAIL_DELIVERABILITY\", \"False\");\n x.WithEnvironment(\"GUNICORN_TIMEOUT\", \"120\");\n });\n\n var identityService = builder\n .AddProject<Projects.Identity_Web>(\"identity\")\n .WithExternalHttpEndpoints()\n .WithReference(identityDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(identityDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(identityService);\n\n var identityApiService = builder\n .AddProject<Projects.Identity_API>(\"identity-api\")\n .WithExternalHttpEndpoints()\n .WithReference(identityDb)\n .WithReference(identityService)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(identityDb)\n .WaitFor(identityService)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(identityApiService);\n\n // var sagaOrchestrator = builder\n // .AddProject<Projects.Saga_Orchestrator>(\"saga-orchestrator\")\n // .WithReference(identityService)\n // .WithReference(rabbitmqReference)\n // .WaitFor(identityService)\n // .WaitFor(rabbitmqReference)\n // ;\n // ConfigureObservability(sagaOrchestrator);\n\n var classroomService = builder\n .AddProject<Projects.Classroom_API>(\"classroom-api\")\n .WithExternalHttpEndpoints()\n .WithReference(classroomDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(classroomDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(classroomService);\n\n var resourceApiService = builder\n .AddProject<Projects.Resource_API>(\"resource-api\")\n .WithExternalHttpEndpoints()\n .WithReference(resourceDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(resourceDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(resourceApiService);\n\n var notificationApiService = builder\n .AddProject<Projects.Notification_API>(\"notification-api\")\n .WithExternalHttpEndpoints()\n .WithReference(notificationDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(notificationDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(notificationApiService);\n\n var productApiService = builder\n .AddProject<Projects.Product_API>(\"product-api\")\n .WithExternalHttpEndpoints()\n .WithReference(productDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(productDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(productApiService);\n\n var orderApiService = builder\n .AddProject<Projects.Order_API>(\"order-api\")\n .WithExternalHttpEndpoints()\n .WithReference(orderDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(orderDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(orderApiService);\n\n // var paymentApiService = builder\n // .AddProject<Projects.Payment_API>(\"payment-api\")\n // .WithExternalHttpEndpoints()\n // .WithReference(paymentDb)\n // .WithReference(rabbitmq)\n // .WaitFor(postgres)\n // .WaitFor(paymentDb)\n // .WaitFor(rabbitmq)\n // ;\n // ConfigureObservability(paymentApiService);\n\n // var cartApiService = builder\n // .AddProject<Projects.Cart_API>(\"cart-api\")\n // .WithExternalHttpEndpoints()\n // .WithReference(cartDb)\n // .WithReference(rabbitmq)\n // .WaitFor(postgres)\n // .WaitFor(cartDb)\n // .WaitFor(rabbitmq)\n // ;\n // ConfigureObservability(cartApiService);\n \n IResourceBuilder<IResourceWithConnectionString>? emulatorMongoDbReference = null;\n \n var mongo = builder.AddMongoDB(\"emulator-mongodb\")\n .WithLifetime(ContainerLifetime.Persistent);\n \n var emulatorMongoDb = mongo.AddDatabase(\"stemify-emulator\");\n emulatorMongoDbReference = emulatorMongoDb;\n \n\n var emulatorApiService = builder\n .AddProject<Projects.Emulator_API>(\"emulator-api\")\n .WithExternalHttpEndpoints();\n \n if (emulatorMongoDbReference is not null)\n {\n emulatorApiService\n .WithReference(emulatorMongoDbReference)\n .WaitFor(emulatorMongoDbReference);\n }\n \n ConfigureObservability(emulatorApiService);\n\n var hangfireApiService = builder\n .AddProject<Projects.Hangfire_API>(\"hangfire-api\")\n .WithExternalHttpEndpoints()\n .WithReference(hangfireDb)\n .WithReference(identityDb)\n .WithReference(orderDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(hangfireDb)\n .WaitFor(identityDb)\n .WaitFor(orderDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(hangfireApiService);\n\n \n var resourceGrpcPort = builder.Configuration[\"ResourceApi:GrpcPort\"] ?? \"5082\";\n var resourceGrpcEndpoint = $\"localhost:{resourceGrpcPort}\";\n \n var aiService = builder\n .AddUvicornApp(\"ai-service\", \"../../AIService\", \"app.main:app\")\n .WithExternalHttpEndpoints()\n .WithHttpHealthCheck(\"/health\")\n .WithEnvironment(\"RESOURCE_GRPC_ENDPOINT\", resourceGrpcEndpoint)\n .WithEnvironment(\"RESOURCE_GRPC_USE_TLS\", \"false\")\n .WithEnvironment(\"PYTHONUNBUFFERED\", \"1\")\n .WithEnvironment(\"PYTHONDONTWRITEBYTECODE\", \"1\")\n .WithEnvironment(\"OPENAI_API_KEY\", builder.Configuration[\"AIService:LLM:OpenAI:ApiKey\"]\n ?? builder.Configuration[\"OpenAI:ApiKey\"]\n ?? Environment.GetEnvironmentVariable(\"OPENAI_API_KEY\")\n ?? \"\")\n .WithEnvironment(\"DEEPSEEK_API_KEY\", builder.Configuration[\"AIService:LLM:DeepSeek:ApiKey\"]\n ?? builder.Configuration[\"DeepSeek:ApiKey\"]\n ?? Environment.GetEnvironmentVariable(\"DEEPSEEK_API_KEY\")\n ?? \"\")\n .WithReference(resourceApiService)\n .WaitFor(resourceApiService);\n ConfigureObservability(aiService);\n\n var apiGateway = builder\n .AddProject<Projects.ApiGateway>(\"apigateway\")\n .WithExternalHttpEndpoints()\n .WithReference(identityService)\n // .WithReference(identityApiService)\n .WithReference(classroomService)\n .WithReference(resourceApiService)\n .WithReference(notificationApiService)\n .WithReference(emulatorApiService)\n .WaitFor(identityService)\n .WaitFor(resourceApiService)\n .WaitFor(classroomService)\n .WaitFor(notificationApiService)\n .WaitFor(productApiService)\n .WaitFor(orderApiService)\n .WaitFor(emulatorApiService)\n .WaitFor(aiService)\n ;\n ConfigureObservability(apiGateway);\n\n void ConfigureObservability<TResource>(IResourceBuilder<TResource> resourceBuilder)\n where TResource : IResourceWithEnvironment\n {\n var enabledValue = externalObservabilityEnabled ? \"true\" : \"false\";\n resourceBuilder.WithEnvironment(\"Observability__ExternalStackEnabled\", enabledValue);\n\n if (grafanaEndpoint is not null)\n {\n resourceBuilder.WithEnvironment(\"GRAFANA_URL\", grafanaEndpoint);\n }\n }\n\n return builder;\n }\n\n \n private static string GetOrCreatePostgresSnapshotPath(bool isDevelopment)\n {\n // if (!isDevelopment)\n // {\n // var productionPath = Path.Combine(\n // Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),\n // \"STEMify\",\n // \"postgres-data\");\n // Directory.CreateDirectory(productionPath);\n // return productionPath;\n // }\n\n var baseSnapshotsPath = Path.Combine(\n Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),\n \"STEMify\",\n \"postgres-snapshots\");\n\n \n Directory.CreateDirectory(baseSnapshotsPath);\n\n var mostRecentSnapshot = FindMostRecentSnapshot(baseSnapshotsPath);\n\n var timestamp = DateTime.Now.ToString(\"yyyyMMdd-HHmmss\");\n var newSnapshotPath = Path.Combine(baseSnapshotsPath, $\"postgres-data-{timestamp}\");\n \n if (mostRecentSnapshot != null && Directory.Exists(mostRecentSnapshot))\n {\n CopyDirectory(mostRecentSnapshot, newSnapshotPath);\n }\n else\n {\n \n Directory.CreateDirectory(newSnapshotPath);\n }\n\n return newSnapshotPath;\n }\n\n private static void CopyDirectory(string sourceDir, string destinationDir)\n {\n var dir = new DirectoryInfo(sourceDir);\n DirectoryInfo[] dirs = dir.GetDirectories();\n\n Directory.CreateDirectory(destinationDir);\n\n foreach (FileInfo file in dir.GetFiles())\n {\n string targetFilePath = Path.Combine(destinationDir, file.Name);\n file.CopyTo(targetFilePath, overwrite: true);\n }\n\n foreach (DirectoryInfo subDir in dirs)\n {\n string newDestinationDir = Path.Combine(destinationDir, subDir.Name);\n CopyDirectory(subDir.FullName, newDestinationDir);\n }\n }\n\n private static string? FindMostRecentSnapshot(string snapshotsBasePath)\n {\n if (!Directory.Exists(snapshotsBasePath))\n return null;\n\n var snapshotDirectories = Directory.GetDirectories(snapshotsBasePath, \"postgres-data-*\")\n .Where(dir =>\n {\n var dirName = Path.GetFileName(dir);\n if (dirName.Length < 24 || !dirName.StartsWith(\"postgres-data-\"))\n return false;\n \n var timestampPart = dirName.Substring(\"postgres-data-\".Length);\n return timestampPart.Length == 15 && // YYYYMMDD-HHmmss = 15 chars\n timestampPart[8] == '-' &&\n DateTime.TryParseExact(timestampPart, \"yyyyMMdd-HHmmss\", null, \n System.Globalization.DateTimeStyles.None, out _);\n })\n .OrderByDescending(dir =>\n {\n var dirName = Path.GetFileName(dir);\n var timestampPart = dirName.Substring(\"postgres-data-\".Length);\n if (DateTime.TryParseExact(timestampPart, \"yyyyMMdd-HHmmss\", null,\n System.Globalization.DateTimeStyles.None, out var timestamp))\n {\n return timestamp;\n }\n return DateTime.MinValue;\n })\n .ToList();\n\n return snapshotDirectories.FirstOrDefault();\n }\n }\n}\n"}, "e53597c84c31dc7fbd276f537ec87ca6cdce436e": {"t": 1785748772.63631, "n": 18966, "h": "5acbb32ddf6d6335", "c": "using Microsoft.Extensions.Hosting;\n\nnamespace STEMifyBackend.AppHost.Extensions\n{\n public static class ServiceExtensions\n {\n public static IDistributedApplicationBuilder AddApplicationServices(this IDistributedApplicationBuilder builder)\n {\n // Get or create snapshot-based data directory for Postgres persistence\n var postgresDataPath = GetOrCreatePostgresSnapshotPath(builder.Environment.IsDevelopment());\n\n var postgres = builder.AddPostgres(\"stemify-postgres\");\n if (builder.Environment.IsDevelopment())\n postgres.WithBindMount(postgresDataPath, \"/var/lib/postgresql/data\", isReadOnly: false);\n\n var identityDb = postgres.AddDatabase(\"stemifyidentity\");\n\n var classroomDb = postgres.AddDatabase(\"stemifyclassroom\");\n\n var resourceDb = postgres.AddDatabase(\"stemifyresource\");\n\n var notificationDb = postgres.AddDatabase(\"stemifynotification\");\n\n var productDb = postgres.AddDatabase(\"stemifyproduct\");\n\n var orderDb = postgres.AddDatabase(\"stemifyorder\");\n var paymentDb = postgres.AddDatabase(\"stemifypayment\");\n\n var cartDb = postgres.AddDatabase(\"stemifycart\");\n\n var hangfireDb = postgres.AddDatabase(\"stemifyhangfire\");\n\n // // Add pgAdmin for database administration (Production)\n // var pgAdmin = builder\n // .AddContainer(\"pgadmin\", \"dpage/pgadmin4\")\n // .WithHttpEndpoint(targetPort: 5050, name: \"http\")\n // .WithExternalHttpEndpoints()\n // .WithEnvironment(\"PGADMIN_DEFAULT_EMAIL\", \"admin@stemify.com\")\n // .WithEnvironment(\"PGADMIN_DEFAULT_PASSWORD\", Environment.GetEnvironmentVariable(\"PGADMIN_PASSWORD\") ?? \"admin\")\n // .WithEnvironment(\"PGADMIN_CONFIG_SERVER_MODE\", \"False\")\n // .WithEnvironment(\"PGADMIN_CONFIG_MASTER_PASSWORD_REQUIRED\", \"False\")\n // .WithEnvironment(\"PGADMIN_LISTEN_PORT\", \"5050\")\n // .WaitFor(postgres);\n\n // Add RabbitMQ for messaging (required for Payment Service Saga pattern)\n IResourceBuilder<IResourceWithConnectionString> rabbitmqReference;\n if (builder.Environment.IsProduction())\n {\n var cloudAmqpUrl = builder.Configuration[\"RabbitMQ:Url\"]\n ?? Environment.GetEnvironmentVariable(\"RABBITMQ_URL\")\n ?? throw new InvalidOperationException(\"RabbitMQ URL not configured.\");\n\n rabbitmqReference = builder.AddConnectionString(\"messaging\", cloudAmqpUrl);\n }\n else\n {\n var rabbitmq = builder.AddRabbitMQ(\"messaging\")\n .WithManagementPlugin();\n rabbitmqReference = rabbitmq;\n }\n\n\n var manualFlag = Environment.GetEnvironmentVariable(\"ENABLE_OBSERVABILITY_STACK\");\n var externalObservabilityEnabled = builder.Environment.IsDevelopment() ||\n string.Equals(manualFlag, \"true\", StringComparison.OrdinalIgnoreCase);\n EndpointReference? grafanaEndpoint = null;\n\n if (externalObservabilityEnabled)\n {\n var prometheus = builder.AddContainer(\"prometheus\", \"prom/prometheus\", \"v3.5.0\")\n .WithBindMount(\"../../../../config/prometheus\", \"/etc/prometheus\", isReadOnly: true)\n .WithArgs(\n \"--web.enable-otlp-receiver\",\n \"--config.file=/etc/prometheus/prometheus.yml\",\n \"--storage.tsdb.retention.time=15d\",\n \"--storage.tsdb.retention.size=10GB\"\n )\n .WithHttpEndpoint(targetPort: 9090, name: \"http\");\n\n var alertmanager = builder.AddContainer(\"alertmanager\", \"prom/alertmanager\", \"v0.27.0\")\n .WithBindMount(\"../../../../config/alertmanager\", \"/etc/alertmanager\", isReadOnly: true)\n .WithArgs(\"--config.file=/etc/alertmanager/alertmanager.yml\", \"--storage.path=/alertmanager\")\n .WithHttpEndpoint(targetPort: 9093, name: \"http\");\n\n var grafana = builder.AddContainer(\"grafana\", \"grafana/grafana\")\n .WithBindMount(\"../../../../config/grafana/config\", \"/etc/grafana\", isReadOnly: true)\n .WithBindMount(\"../../../../config/grafana/dashboards\", \"/var/lib/grafana/dashboards\", isReadOnly: true)\n .WithEnvironment(\"PROMETHEUS_ENDPOINT\", prometheus.GetEndpoint(\"http\"))\n .WithHttpEndpoint(targetPort: 3000, name: \"http\");\n\n grafanaEndpoint = grafana.GetEndpoint(\"http\");\n\n var configFile = \"../../../../config/otelcollector/config.dev.yaml\";\n\n builder.AddOpenTelemetryCollector(\"otelcollector\", configFile)\n .WithEnvironment(\"PROMETHEUS_ENDPOINT\", $\"{prometheus.GetEndpoint(\"http\")}/api/v1/otlp\")\n .WithEnvironment(\"ALERTMANAGER_ENDPOINT\", alertmanager.GetEndpoint(\"http\"));\n }\n\n var pgAdmin = builder.Environment.IsProduction()\n ? builder\n .AddContainer(\"pgadmin\", \"dpage/pgadmin4:8.10\")\n .WithHttpEndpoint(targetPort: 5050, name: \"pgadmin-http\")\n .WithExternalHttpEndpoints()\n .WithEnvironment(\"PGADMIN_DEFAULT_EMAIL\", \"admin@stemify.com\")\n .WithEnvironment(\n \"PGADMIN_DEFAULT_PASSWORD\",\n Environment.GetEnvironmentVariable(\"PGADMIN_PASSWORD\") ?? \"admin\"\n )\n .WithEnvironment(\"PGADMIN_CONFIG_SERVER_MODE\", \"False\")\n .WithEnvironment(\"PGADMIN_CONFIG_MASTER_PASSWORD_REQUIRED\", \"False\")\n .WithEnvironment(\"PGADMIN_LISTEN_PORT\", \"5050\")\n .WithEnvironment(\"PGADMIN_CONFIG_CHECK_EMAIL_DELIVERABILITY\", \"False\")\n .WithEnvironment(\"GUNICORN_TIMEOUT\", \"120\")\n .WaitFor(postgres)\n : postgres.WithPgAdmin(x =>\n {\n x.WithHttpEndpoint(targetPort: 5050, name: \"pgadmin-dev\");\n x.WithExternalHttpEndpoints();\n x.WithEnvironment(\"PGADMIN_CONFIG_CHECK_EMAIL_DELIVERABILITY\", \"False\");\n x.WithEnvironment(\"GUNICORN_TIMEOUT\", \"120\");\n });\n\n var identityService = builder\n .AddProject<Projects.Identity_Web>(\"identity\")\n .WithExternalHttpEndpoints()\n .WithReference(identityDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(identityDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(identityService);\n\n var identityApiService = builder\n .AddProject<Projects.Identity_API>(\"identity-api\")\n .WithExternalHttpEndpoints()\n .WithReference(identityDb)\n .WithReference(identityService)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(identityDb)\n .WaitFor(identityService)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(identityApiService);\n\n // var sagaOrchestrator = builder\n // .AddProject<Projects.Saga_Orchestrator>(\"saga-orchestrator\")\n // .WithReference(identityService)\n // .WithReference(rabbitmqReference)\n // .WaitFor(identityService)\n // .WaitFor(rabbitmqReference)\n // ;\n // ConfigureObservability(sagaOrchestrator);\n\n var classroomService = builder\n .AddProject<Projects.Classroom_API>(\"classroom-api\")\n .WithExternalHttpEndpoints()\n .WithReference(classroomDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(classroomDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(classroomService);\n\n var resourceApiService = builder\n .AddProject<Projects.Resource_API>(\"resource-api\")\n .WithExternalHttpEndpoints()\n .WithReference(resourceDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(resourceDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(resourceApiService);\n\n var notificationApiService = builder\n .AddProject<Projects.Notification_API>(\"notification-api\")\n .WithExternalHttpEndpoints()\n .WithReference(notificationDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(notificationDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(notificationApiService);\n\n var productApiService = builder\n .AddProject<Projects.Product_API>(\"product-api\")\n .WithExternalHttpEndpoints()\n .WithReference(productDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(productDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(productApiService);\n\n var orderApiService = builder\n .AddProject<Projects.Order_API>(\"order-api\")\n .WithExternalHttpEndpoints()\n .WithReference(orderDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(orderDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(orderApiService);\n\n // var paymentApiService = builder\n // .AddProject<Projects.Payment_API>(\"payment-api\")\n // .WithExternalHttpEndpoints()\n // .WithReference(paymentDb)\n // .WithReference(rabbitmq)\n // .WaitFor(postgres)\n // .WaitFor(paymentDb)\n // .WaitFor(rabbitmq)\n // ;\n // ConfigureObservability(paymentApiService);\n\n // var cartApiService = builder\n // .AddProject<Projects.Cart_API>(\"cart-api\")\n // .WithExternalHttpEndpoints()\n // .WithReference(cartDb)\n // .WithReference(rabbitmq)\n // .WaitFor(postgres)\n // .WaitFor(cartDb)\n // .WaitFor(rabbitmq)\n // ;\n // ConfigureObservability(cartApiService);\n \n IResourceBuilder<IResourceWithConnectionString>? emulatorMongoDbReference = null;\n \n var mongo = builder.AddMongoDB(\"emulator-mongodb\")\n .WithLifetime(ContainerLifetime.Persistent);\n \n var emulatorMongoDb = mongo.AddDatabase(\"stemify-emulator\");\n emulatorMongoDbReference = emulatorMongoDb;\n \n\n var emulatorApiService = builder\n .AddProject<Projects.Emulator_API>(\"emulator-api\")\n .WithExternalHttpEndpoints();\n \n if (emulatorMongoDbReference is not null)\n {\n emulatorApiService\n .WithReference(emulatorMongoDbReference)\n .WaitFor(emulatorMongoDbReference);\n }\n \n ConfigureObservability(emulatorApiService);\n\n var hangfireApiService = builder\n .AddProject<Projects.Hangfire_API>(\"hangfire-api\")\n .WithExternalHttpEndpoints()\n .WithReference(hangfireDb)\n .WithReference(identityDb)\n .WithReference(orderDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(hangfireDb)\n .WaitFor(identityDb)\n .WaitFor(orderDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(hangfireApiService);\n\n \n var resourceGrpcPort = builder.Configuration[\"ResourceApi:GrpcPort\"] ?? \"5082\";\n var resourceGrpcEndpoint = $\"localhost:{resourceGrpcPort}\";\n \n var aiService = builder\n .AddUvicornApp(\"ai-service\", \"../../AIService\", \"app.main:app\")\n .WithExternalHttpEndpoints()\n .WithHttpHealthCheck(\"/health\")\n .WithEnvironment(\"RESOURCE_GRPC_ENDPOINT\", resourceGrpcEndpoint)\n .WithEnvironment(\"RESOURCE_GRPC_USE_TLS\", \"false\")\n .WithEnvironment(\"PYTHONUNBUFFERED\", \"1\")\n .WithEnvironment(\"PYTHONDONTWRITEBYTECODE\", \"1\")\n // LLM API Keys: get from configuration or environment variables\n .WithEnvironment(\"OPENAI_API_KEY\", builder.Configuration[\"OpenAI:ApiKey\"] \n ?? Environment.GetEnvironmentVariable(\"OPENAI_API_KEY\") \n ?? \"\")\n .WithEnvironment(\"DEEPSEEK_API_KEY\", builder.Configuration[\"DeepSeek:ApiKey\"] \n ?? Environment.GetEnvironmentVariable(\"DEEPSEEK_API_KEY\") \n ?? \"\")\n .WithReference(resourceApiService)\n .WaitFor(resourceApiService);\n ConfigureObservability(aiService);\n\n var apiGateway = builder\n .AddProject<Projects.ApiGateway>(\"apigateway\")\n .WithExternalHttpEndpoints()\n .WithReference(identityService)\n // .WithReference(identityApiService)\n .WithReference(classroomService)\n .WithReference(resourceApiService)\n .WithReference(notificationApiService)\n .WithReference(emulatorApiService)\n .WaitFor(identityService)\n .WaitFor(resourceApiService)\n .WaitFor(classroomService)\n .WaitFor(notificationApiService)\n .WaitFor(productApiService)\n .WaitFor(orderApiService)\n .WaitFor(emulatorApiService)\n .WaitFor(aiService)\n ;\n ConfigureObservability(apiGateway);\n\n void ConfigureObservability<TResource>(IResourceBuilder<TResource> resourceBuilder)\n where TResource : IResourceWithEnvironment\n {\n var enabledValue = externalObservabilityEnabled ? \"true\" : \"false\";\n resourceBuilder.WithEnvironment(\"Observability__ExternalStackEnabled\", enabledValue);\n\n if (grafanaEndpoint is not null)\n {\n resourceBuilder.WithEnvironment(\"GRAFANA_URL\", grafanaEndpoint);\n }\n }\n\n return builder;\n }\n\n \n private static string GetOrCreatePostgresSnapshotPath(bool isDevelopment)\n {\n // if (!isDevelopment)\n // {\n // var productionPath = Path.Combine(\n // Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),\n // \"STEMify\",\n // \"postgres-data\");\n // Directory.CreateDirectory(productionPath);\n // return productionPath;\n // }\n\n var baseSnapshotsPath = Path.Combine(\n Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),\n \"STEMify\",\n \"postgres-snapshots\");\n\n \n Directory.CreateDirectory(baseSnapshotsPath);\n\n var mostRecentSnapshot = FindMostRecentSnapshot(baseSnapshotsPath);\n\n var timestamp = DateTime.Now.ToString(\"yyyyMMdd-HHmmss\");\n var newSnapshotPath = Path.Combine(baseSnapshotsPath, $\"postgres-data-{timestamp}\");\n \n if (mostRecentSnapshot != null && Directory.Exists(mostRecentSnapshot))\n {\n CopyDirectory(mostRecentSnapshot, newSnapshotPath);\n }\n else\n {\n \n Directory.CreateDirectory(newSnapshotPath);\n }\n\n return newSnapshotPath;\n }\n\n private static void CopyDirectory(string sourceDir, string destinationDir)\n {\n var dir = new DirectoryInfo(sourceDir);\n DirectoryInfo[] dirs = dir.GetDirectories();\n\n Directory.CreateDirectory(destinationDir);\n\n foreach (FileInfo file in dir.GetFiles())\n {\n string targetFilePath = Path.Combine(destinationDir, file.Name);\n file.CopyTo(targetFilePath, overwrite: true);\n }\n\n foreach (DirectoryInfo subDir in dirs)\n {\n string newDestinationDir = Path.Combine(destinationDir, subDir.Name);\n CopyDirectory(subDir.FullName, newDestinationDir);\n }\n }\n\n private static string? FindMostRecentSnapshot(string snapshotsBasePath)\n {\n if (!Directory.Exists(snapshotsBasePath))\n return null;\n\n var snapshotDirectories = Directory.GetDirectories(snapshotsBasePath, \"postgres-data-*\")\n .Where(dir =>\n {\n var dirName = Path.GetFileName(dir);\n if (dirName.Length < 24 || !dirName.StartsWith(\"postgres-data-\"))\n return false;\n \n var timestampPart = dirName.Substring(\"postgres-data-\".Length);\n return timestampPart.Length == 15 && // YYYYMMDD-HHmmss = 15 chars\n timestampPart[8] == '-' &&\n DateTime.TryParseExact(timestampPart, \"yyyyMMdd-HHmmss\", null, \n System.Globalization.DateTimeStyles.None, out _);\n })\n .OrderByDescending(dir =>\n {\n var dirName = Path.GetFileName(dir);\n var timestampPart = dirName.Substring(\"postgres-data-\".Length);\n if (DateTime.TryParseExact(timestampPart, \"yyyyMMdd-HHmmss\", null,\n System.Globalization.DateTimeStyles.None, out var timestamp))\n {\n return timestamp;\n }\n return DateTime.MinValue;\n })\n .ToList();\n\n return snapshotDirectories.FirstOrDefault();\n }\n }\n}\n"}, "48d41ac066536184d6eb011cf39a3659e26fb5ff": {"t": 1785748773.777324, "n": 15482, "h": "72154fb4f975f669", "c": "using Microsoft.Extensions.Hosting;\n\nnamespace STEMifyBackend.AppHost.Extensions\n{\n public static class ServiceExtensions\n {\n public static IDistributedApplicationBuilder AddApplicationServices(this IDistributedApplicationBuilder builder)\n {\n // Create data directory for Postgres persistence (relative to AppHost project)\n var postgresDataPath = Path.Combine(\n Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),\n \"STEMify\",\n \"postgres-data\");\n\n // Ensure directory exists\n Directory.CreateDirectory(postgresDataPath);\n\n var postgres = builder.AddPostgres(\"stemify-postgres\");\n if (builder.Environment.IsDevelopment())\n postgres.WithBindMount(postgresDataPath, \"/var/lib/postgresql/data\", isReadOnly: false);\n\n var identityDb = postgres.AddDatabase(\"stemifyidentity\");\n\n var classroomDb = postgres.AddDatabase(\"stemifyclassroom\");\n\n var resourceDb = postgres.AddDatabase(\"stemifyresource\");\n\n var notificationDb = postgres.AddDatabase(\"stemifynotification\");\n\n var productDb = postgres.AddDatabase(\"stemifyproduct\");\n\n var orderDb = postgres.AddDatabase(\"stemifyorder\");\n var paymentDb = postgres.AddDatabase(\"stemifypayment\");\n\n var cartDb = postgres.AddDatabase(\"stemifycart\");\n\n var hangfireDb = postgres.AddDatabase(\"stemifyhangfire\");\n\n // // Add pgAdmin for database administration (Production)\n // var pgAdmin = builder\n // .AddContainer(\"pgadmin\", \"dpage/pgadmin4\")\n // .WithHttpEndpoint(targetPort: 5050, name: \"http\")\n // .WithExternalHttpEndpoints()\n // .WithEnvironment(\"PGADMIN_DEFAULT_EMAIL\", \"admin@stemify.com\")\n // .WithEnvironment(\"PGADMIN_DEFAULT_PASSWORD\", Environment.GetEnvironmentVariable(\"PGADMIN_PASSWORD\") ?? \"admin\")\n // .WithEnvironment(\"PGADMIN_CONFIG_SERVER_MODE\", \"False\")\n // .WithEnvironment(\"PGADMIN_CONFIG_MASTER_PASSWORD_REQUIRED\", \"False\")\n // .WithEnvironment(\"PGADMIN_LISTEN_PORT\", \"5050\")\n // .WaitFor(postgres);\n\n // Add RabbitMQ for messaging (required for Payment Service Saga pattern)\n IResourceBuilder<IResourceWithConnectionString> rabbitmqReference;\n if (builder.Environment.IsProduction())\n {\n var cloudAmqpUrl = builder.Configuration[\"RabbitMQ:Url\"]\n ?? Environment.GetEnvironmentVariable(\"RABBITMQ_URL\")\n ?? throw new InvalidOperationException(\"RabbitMQ URL not configured.\");\n\n rabbitmqReference = builder.AddConnectionString(\"messaging\", cloudAmqpUrl);\n }\n else\n {\n var rabbitmq = builder.AddRabbitMQ(\"messaging\")\n .WithManagementPlugin();\n rabbitmqReference = rabbitmq;\n }\n\n\n var manualFlag = Environment.GetEnvironmentVariable(\"ENABLE_OBSERVABILITY_STACK\");\n var externalObservabilityEnabled = builder.Environment.IsDevelopment() ||\n string.Equals(manualFlag, \"true\", StringComparison.OrdinalIgnoreCase);\n EndpointReference? grafanaEndpoint = null;\n\n if (externalObservabilityEnabled)\n {\n var prometheus = builder.AddContainer(\"prometheus\", \"prom/prometheus\", \"v3.5.0\")\n .WithBindMount(\"../../../../config/prometheus\", \"/etc/prometheus\", isReadOnly: true)\n .WithArgs(\n \"--web.enable-otlp-receiver\",\n \"--config.file=/etc/prometheus/prometheus.yml\",\n \"--storage.tsdb.retention.time=15d\",\n \"--storage.tsdb.retention.size=10GB\"\n )\n .WithHttpEndpoint(targetPort: 9090, name: \"http\");\n\n var alertmanager = builder.AddContainer(\"alertmanager\", \"prom/alertmanager\", \"v0.27.0\")\n .WithBindMount(\"../../../../config/alertmanager\", \"/etc/alertmanager\", isReadOnly: true)\n .WithArgs(\"--config.file=/etc/alertmanager/alertmanager.yml\", \"--storage.path=/alertmanager\")\n .WithHttpEndpoint(targetPort: 9093, name: \"http\");\n\n var grafana = builder.AddContainer(\"grafana\", \"grafana/grafana\")\n .WithBindMount(\"../../../../config/grafana/config\", \"/etc/grafana\", isReadOnly: true)\n .WithBindMount(\"../../../../config/grafana/dashboards\", \"/var/lib/grafana/dashboards\", isReadOnly: true)\n .WithEnvironment(\"PROMETHEUS_ENDPOINT\", prometheus.GetEndpoint(\"http\"))\n .WithHttpEndpoint(targetPort: 3000, name: \"http\");\n\n grafanaEndpoint = grafana.GetEndpoint(\"http\");\n\n var configFile = \"../../../../config/otelcollector/config.dev.yaml\";\n\n builder.AddOpenTelemetryCollector(\"otelcollector\", configFile)\n .WithEnvironment(\"PROMETHEUS_ENDPOINT\", $\"{prometheus.GetEndpoint(\"http\")}/api/v1/otlp\")\n .WithEnvironment(\"ALERTMANAGER_ENDPOINT\", alertmanager.GetEndpoint(\"http\"));\n }\n\n var pgAdmin = builder.Environment.IsProduction()\n ? builder\n .AddContainer(\"pgadmin\", \"dpage/pgadmin4:8.10\")\n .WithHttpEndpoint(targetPort: 5050, name: \"pgadmin-http\")\n .WithExternalHttpEndpoints()\n .WithEnvironment(\"PGADMIN_DEFAULT_EMAIL\", \"admin@stemify.com\")\n .WithEnvironment(\n \"PGADMIN_DEFAULT_PASSWORD\",\n Environment.GetEnvironmentVariable(\"PGADMIN_PASSWORD\") ?? \"admin\"\n )\n .WithEnvironment(\"PGADMIN_CONFIG_SERVER_MODE\", \"False\")\n .WithEnvironment(\"PGADMIN_CONFIG_MASTER_PASSWORD_REQUIRED\", \"False\")\n .WithEnvironment(\"PGADMIN_LISTEN_PORT\", \"5050\")\n .WithEnvironment(\"PGADMIN_CONFIG_CHECK_EMAIL_DELIVERABILITY\", \"False\")\n .WithEnvironment(\"GUNICORN_TIMEOUT\", \"120\")\n .WaitFor(postgres)\n : postgres.WithPgAdmin(x =>\n {\n x.WithHttpEndpoint(targetPort: 5050, name: \"pgadmin-dev\");\n x.WithExternalHttpEndpoints();\n x.WithEnvironment(\"PGADMIN_CONFIG_CHECK_EMAIL_DELIVERABILITY\", \"False\");\n x.WithEnvironment(\"GUNICORN_TIMEOUT\", \"120\");\n });\n\n var identityService = builder\n .AddProject<Projects.Identity_Web>(\"identity\")\n .WithExternalHttpEndpoints()\n .WithReference(identityDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(identityDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(identityService);\n\n var identityApiService = builder\n .AddProject<Projects.Identity_API>(\"identity-api\")\n .WithExternalHttpEndpoints()\n .WithReference(identityDb)\n .WithReference(identityService)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(identityDb)\n .WaitFor(identityService)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(identityApiService);\n\n // var sagaOrchestrator = builder\n // .AddProject<Projects.Saga_Orchestrator>(\"saga-orchestrator\")\n // .WithReference(identityService)\n // .WithReference(rabbitmqReference)\n // .WaitFor(identityService)\n // .WaitFor(rabbitmqReference)\n // ;\n // ConfigureObservability(sagaOrchestrator);\n\n var classroomService = builder\n .AddProject<Projects.Classroom_API>(\"classroom-api\")\n .WithExternalHttpEndpoints()\n .WithReference(classroomDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(classroomDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(classroomService);\n\n var resourceApiService = builder\n .AddProject<Projects.Resource_API>(\"resource-api\")\n .WithExternalHttpEndpoints()\n .WithReference(resourceDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(resourceDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(resourceApiService);\n\n var notificationApiService = builder\n .AddProject<Projects.Notification_API>(\"notification-api\")\n .WithExternalHttpEndpoints()\n .WithReference(notificationDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(notificationDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(notificationApiService);\n\n var productApiService = builder\n .AddProject<Projects.Product_API>(\"product-api\")\n .WithExternalHttpEndpoints()\n .WithReference(productDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(productDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(productApiService);\n\n var orderApiService = builder\n .AddProject<Projects.Order_API>(\"order-api\")\n .WithExternalHttpEndpoints()\n .WithReference(orderDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(orderDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(orderApiService);\n\n // var paymentApiService = builder\n // .AddProject<Projects.Payment_API>(\"payment-api\")\n // .WithExternalHttpEndpoints()\n // .WithReference(paymentDb)\n // .WithReference(rabbitmq)\n // .WaitFor(postgres)\n // .WaitFor(paymentDb)\n // .WaitFor(rabbitmq)\n // ;\n // ConfigureObservability(paymentApiService);\n\n // var cartApiService = builder\n // .AddProject<Projects.Cart_API>(\"cart-api\")\n // .WithExternalHttpEndpoints()\n // .WithReference(cartDb)\n // .WithReference(rabbitmq)\n // .WaitFor(postgres)\n // .WaitFor(cartDb)\n // .WaitFor(rabbitmq)\n // ;\n // ConfigureObservability(cartApiService);\n \n IResourceBuilder<IResourceWithConnectionString>? emulatorMongoDbReference = null;\n \n var mongo = builder.AddMongoDB(\"emulator-mongodb\")\n .WithLifetime(ContainerLifetime.Persistent);\n \n var emulatorMongoDb = mongo.AddDatabase(\"stemify-emulator\");\n emulatorMongoDbReference = emulatorMongoDb;\n \n\n var emulatorApiService = builder\n .AddProject<Projects.Emulator_API>(\"emulator-api\")\n .WithExternalHttpEndpoints();\n \n if (emulatorMongoDbReference is not null)\n {\n emulatorApiService\n .WithReference(emulatorMongoDbReference)\n .WaitFor(emulatorMongoDbReference);\n }\n \n ConfigureObservability(emulatorApiService);\n\n var hangfireApiService = builder\n .AddProject<Projects.Hangfire_API>(\"hangfire-api\")\n .WithExternalHttpEndpoints()\n .WithReference(hangfireDb)\n .WithReference(identityDb)\n .WithReference(orderDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(hangfireDb)\n .WaitFor(identityDb)\n .WaitFor(orderDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(hangfireApiService);\n\n \n var resourceGrpcPort = builder.Configuration[\"ResourceApi:GrpcPort\"] ?? \"5082\";\n var resourceGrpcEndpoint = $\"localhost:{resourceGrpcPort}\";\n \n var aiService = builder\n .AddUvicornApp(\"ai-service\", \"../../AIService\", \"app.main:app\")\n .WithExternalHttpEndpoints()\n .WithHttpHealthCheck(\"/health\")\n .WithEnvironment(\"RESOURCE_GRPC_ENDPOINT\", resourceGrpcEndpoint)\n .WithEnvironment(\"RESOURCE_GRPC_USE_TLS\", \"false\")\n .WithEnvironment(\"PYTHONUNBUFFERED\", \"1\")\n .WithEnvironment(\"PYTHONDONTWRITEBYTECODE\", \"1\")\n // LLM API Keys: get from configuration or environment variables\n .WithEnvironment(\"OPENAI_API_KEY\", builder.Configuration[\"OpenAI:ApiKey\"] \n ?? Environment.GetEnvironmentVariable(\"OPENAI_API_KEY\") \n ?? \"\")\n .WithEnvironment(\"DEEPSEEK_API_KEY\", builder.Configuration[\"DeepSeek:ApiKey\"] \n ?? Environment.GetEnvironmentVariable(\"DEEPSEEK_API_KEY\") \n ?? \"\")\n .WithReference(resourceApiService)\n .WaitFor(resourceApiService);\n ConfigureObservability(aiService);\n\n var apiGateway = builder\n .AddProject<Projects.ApiGateway>(\"apigateway\")\n .WithExternalHttpEndpoints()\n .WithReference(identityService)\n // .WithReference(identityApiService)\n .WithReference(classroomService)\n .WithReference(resourceApiService)\n .WithReference(notificationApiService)\n .WithReference(emulatorApiService)\n .WaitFor(identityService)\n .WaitFor(resourceApiService)\n .WaitFor(classroomService)\n .WaitFor(notificationApiService)\n .WaitFor(productApiService)\n .WaitFor(orderApiService)\n .WaitFor(emulatorApiService)\n .WaitFor(aiService)\n ;\n ConfigureObservability(apiGateway);\n\n void ConfigureObservability<TResource>(IResourceBuilder<TResource> resourceBuilder)\n where TResource : IResourceWithEnvironment\n {\n var enabledValue = externalObservabilityEnabled ? \"true\" : \"false\";\n resourceBuilder.WithEnvironment(\"Observability__ExternalStackEnabled\", enabledValue);\n\n if (grafanaEndpoint is not null)\n {\n resourceBuilder.WithEnvironment(\"GRAFANA_URL\", grafanaEndpoint);\n }\n }\n\n return builder;\n }\n }\n}\n"}, "e00764a033e76feaf4f20055241091230dbc0f41": {"t": 1785748774.9029844, "n": 14933, "h": "bfeb97e4c1c84acb", "c": "using Microsoft.Extensions.Hosting;\n\nnamespace STEMifyBackend.AppHost.Extensions\n{\n public static class ServiceExtensions\n {\n public static IDistributedApplicationBuilder AddApplicationServices(this IDistributedApplicationBuilder builder)\n {\n var postgres = builder.AddPostgres(\"stemify-postgres\");\n\n var identityDb = postgres.AddDatabase(\"stemifyidentity\");\n\n var classroomDb = postgres.AddDatabase(\"stemifyclassroom\");\n\n var resourceDb = postgres.AddDatabase(\"stemifyresource\");\n\n var notificationDb = postgres.AddDatabase(\"stemifynotification\");\n\n var productDb = postgres.AddDatabase(\"stemifyproduct\");\n\n var orderDb = postgres.AddDatabase(\"stemifyorder\");\n var paymentDb = postgres.AddDatabase(\"stemifypayment\");\n\n var cartDb = postgres.AddDatabase(\"stemifycart\");\n\n var hangfireDb = postgres.AddDatabase(\"stemifyhangfire\");\n\n // // Add pgAdmin for database administration (Production)\n // var pgAdmin = builder\n // .AddContainer(\"pgadmin\", \"dpage/pgadmin4\")\n // .WithHttpEndpoint(targetPort: 5050, name: \"http\")\n // .WithExternalHttpEndpoints()\n // .WithEnvironment(\"PGADMIN_DEFAULT_EMAIL\", \"admin@stemify.com\")\n // .WithEnvironment(\"PGADMIN_DEFAULT_PASSWORD\", Environment.GetEnvironmentVariable(\"PGADMIN_PASSWORD\") ?? \"admin\")\n // .WithEnvironment(\"PGADMIN_CONFIG_SERVER_MODE\", \"False\")\n // .WithEnvironment(\"PGADMIN_CONFIG_MASTER_PASSWORD_REQUIRED\", \"False\")\n // .WithEnvironment(\"PGADMIN_LISTEN_PORT\", \"5050\")\n // .WaitFor(postgres);\n\n // Add RabbitMQ for messaging (required for Payment Service Saga pattern)\n IResourceBuilder<IResourceWithConnectionString> rabbitmqReference;\n if (builder.Environment.IsProduction())\n {\n var cloudAmqpUrl = builder.Configuration[\"RabbitMQ:Url\"]\n ?? Environment.GetEnvironmentVariable(\"RABBITMQ_URL\")\n ?? throw new InvalidOperationException(\"RabbitMQ URL not configured.\");\n\n rabbitmqReference = builder.AddConnectionString(\"messaging\", cloudAmqpUrl);\n }\n else\n {\n var rabbitmq = builder.AddRabbitMQ(\"messaging\")\n .WithManagementPlugin();\n rabbitmqReference = rabbitmq;\n }\n\n\n var manualFlag = Environment.GetEnvironmentVariable(\"ENABLE_OBSERVABILITY_STACK\");\n var externalObservabilityEnabled = builder.Environment.IsDevelopment() ||\n string.Equals(manualFlag, \"true\", StringComparison.OrdinalIgnoreCase);\n EndpointReference? grafanaEndpoint = null;\n\n if (externalObservabilityEnabled)\n {\n var prometheus = builder.AddContainer(\"prometheus\", \"prom/prometheus\", \"v3.5.0\")\n .WithBindMount(\"../../../../config/prometheus\", \"/etc/prometheus\", isReadOnly: true)\n .WithArgs(\n \"--web.enable-otlp-receiver\",\n \"--config.file=/etc/prometheus/prometheus.yml\",\n \"--storage.tsdb.retention.time=15d\",\n \"--storage.tsdb.retention.size=10GB\"\n )\n .WithHttpEndpoint(targetPort: 9090, name: \"http\");\n\n var alertmanager = builder.AddContainer(\"alertmanager\", \"prom/alertmanager\", \"v0.27.0\")\n .WithBindMount(\"../../../../config/alertmanager\", \"/etc/alertmanager\", isReadOnly: true)\n .WithArgs(\"--config.file=/etc/alertmanager/alertmanager.yml\", \"--storage.path=/alertmanager\")\n .WithHttpEndpoint(targetPort: 9093, name: \"http\");\n\n var grafana = builder.AddContainer(\"grafana\", \"grafana/grafana\")\n .WithBindMount(\"../../../../config/grafana/config\", \"/etc/grafana\", isReadOnly: true)\n .WithBindMount(\"../../../../config/grafana/dashboards\", \"/var/lib/grafana/dashboards\", isReadOnly: true)\n .WithEnvironment(\"PROMETHEUS_ENDPOINT\", prometheus.GetEndpoint(\"http\"))\n .WithHttpEndpoint(targetPort: 3000, name: \"http\");\n\n grafanaEndpoint = grafana.GetEndpoint(\"http\");\n\n var configFile = \"../../../../config/otelcollector/config.dev.yaml\";\n\n builder.AddOpenTelemetryCollector(\"otelcollector\", configFile)\n .WithEnvironment(\"PROMETHEUS_ENDPOINT\", $\"{prometheus.GetEndpoint(\"http\")}/api/v1/otlp\")\n .WithEnvironment(\"ALERTMANAGER_ENDPOINT\", alertmanager.GetEndpoint(\"http\"));\n }\n\n var pgAdmin = builder.Environment.IsProduction()\n ? builder\n .AddContainer(\"pgadmin\", \"dpage/pgadmin4:8.10\")\n .WithHttpEndpoint(targetPort: 5050, name: \"pgadmin-http\")\n .WithExternalHttpEndpoints()\n .WithEnvironment(\"PGADMIN_DEFAULT_EMAIL\", \"admin@stemify.com\")\n .WithEnvironment(\n \"PGADMIN_DEFAULT_PASSWORD\",\n Environment.GetEnvironmentVariable(\"PGADMIN_PASSWORD\") ?? \"admin\"\n )\n .WithEnvironment(\"PGADMIN_CONFIG_SERVER_MODE\", \"False\")\n .WithEnvironment(\"PGADMIN_CONFIG_MASTER_PASSWORD_REQUIRED\", \"False\")\n .WithEnvironment(\"PGADMIN_LISTEN_PORT\", \"5050\")\n .WithEnvironment(\"PGADMIN_CONFIG_CHECK_EMAIL_DELIVERABILITY\", \"False\")\n .WithEnvironment(\"GUNICORN_TIMEOUT\", \"120\")\n .WaitFor(postgres)\n : postgres.WithPgAdmin(x =>\n {\n x.WithHttpEndpoint(targetPort: 5050, name: \"pgadmin-dev\");\n x.WithExternalHttpEndpoints();\n x.WithEnvironment(\"PGADMIN_CONFIG_CHECK_EMAIL_DELIVERABILITY\", \"False\");\n x.WithEnvironment(\"GUNICORN_TIMEOUT\", \"120\");\n });\n\n var identityService = builder\n .AddProject<Projects.Identity_Web>(\"identity\")\n .WithExternalHttpEndpoints()\n .WithReference(identityDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(identityDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(identityService);\n\n var identityApiService = builder\n .AddProject<Projects.Identity_API>(\"identity-api\")\n .WithExternalHttpEndpoints()\n .WithReference(identityDb)\n .WithReference(identityService)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(identityDb)\n .WaitFor(identityService)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(identityApiService);\n\n // var sagaOrchestrator = builder\n // .AddProject<Projects.Saga_Orchestrator>(\"saga-orchestrator\")\n // .WithReference(identityService)\n // .WithReference(rabbitmqReference)\n // .WaitFor(identityService)\n // .WaitFor(rabbitmqReference)\n // ;\n // ConfigureObservability(sagaOrchestrator);\n\n var classroomService = builder\n .AddProject<Projects.Classroom_API>(\"classroom-api\")\n .WithExternalHttpEndpoints()\n .WithReference(classroomDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(classroomDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(classroomService);\n\n var resourceApiService = builder\n .AddProject<Projects.Resource_API>(\"resource-api\")\n .WithExternalHttpEndpoints()\n .WithReference(resourceDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(resourceDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(resourceApiService);\n\n var notificationApiService = builder\n .AddProject<Projects.Notification_API>(\"notification-api\")\n .WithExternalHttpEndpoints()\n .WithReference(notificationDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(notificationDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(notificationApiService);\n\n var productApiService = builder\n .AddProject<Projects.Product_API>(\"product-api\")\n .WithExternalHttpEndpoints()\n .WithReference(productDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(productDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(productApiService);\n\n var orderApiService = builder\n .AddProject<Projects.Order_API>(\"order-api\")\n .WithExternalHttpEndpoints()\n .WithReference(orderDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(orderDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(orderApiService);\n\n // var paymentApiService = builder\n // .AddProject<Projects.Payment_API>(\"payment-api\")\n // .WithExternalHttpEndpoints()\n // .WithReference(paymentDb)\n // .WithReference(rabbitmq)\n // .WaitFor(postgres)\n // .WaitFor(paymentDb)\n // .WaitFor(rabbitmq)\n // ;\n // ConfigureObservability(paymentApiService);\n\n // var cartApiService = builder\n // .AddProject<Projects.Cart_API>(\"cart-api\")\n // .WithExternalHttpEndpoints()\n // .WithReference(cartDb)\n // .WithReference(rabbitmq)\n // .WaitFor(postgres)\n // .WaitFor(cartDb)\n // .WaitFor(rabbitmq)\n // ;\n // ConfigureObservability(cartApiService);\n \n IResourceBuilder<IResourceWithConnectionString>? emulatorMongoDbReference = null;\n \n var mongo = builder.AddMongoDB(\"emulator-mongodb\")\n .WithLifetime(ContainerLifetime.Persistent);\n \n var emulatorMongoDb = mongo.AddDatabase(\"stemify-emulator\");\n emulatorMongoDbReference = emulatorMongoDb;\n \n\n var emulatorApiService = builder\n .AddProject<Projects.Emulator_API>(\"emulator-api\")\n .WithExternalHttpEndpoints();\n \n if (emulatorMongoDbReference is not null)\n {\n emulatorApiService\n .WithReference(emulatorMongoDbReference)\n .WaitFor(emulatorMongoDbReference);\n }\n \n ConfigureObservability(emulatorApiService);\n\n var hangfireApiService = builder\n .AddProject<Projects.Hangfire_API>(\"hangfire-api\")\n .WithExternalHttpEndpoints()\n .WithReference(hangfireDb)\n .WithReference(identityDb)\n .WithReference(orderDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(hangfireDb)\n .WaitFor(identityDb)\n .WaitFor(orderDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(hangfireApiService);\n\n \n var resourceGrpcPort = builder.Configuration[\"ResourceApi:GrpcPort\"] ?? \"5082\";\n var resourceGrpcEndpoint = $\"localhost:{resourceGrpcPort}\";\n \n var aiService = builder\n .AddUvicornApp(\"ai-service\", \"../../AIService\", \"app.main:app\")\n .WithExternalHttpEndpoints()\n .WithHttpHealthCheck(\"/health\")\n .WithEnvironment(\"RESOURCE_GRPC_ENDPOINT\", resourceGrpcEndpoint)\n .WithEnvironment(\"RESOURCE_GRPC_USE_TLS\", \"false\")\n .WithEnvironment(\"PYTHONUNBUFFERED\", \"1\")\n .WithEnvironment(\"PYTHONDONTWRITEBYTECODE\", \"1\")\n // LLM API Keys: get from configuration or environment variables\n .WithEnvironment(\"OPENAI_API_KEY\", builder.Configuration[\"OpenAI:ApiKey\"] \n ?? Environment.GetEnvironmentVariable(\"OPENAI_API_KEY\") \n ?? \"\")\n .WithEnvironment(\"DEEPSEEK_API_KEY\", builder.Configuration[\"DeepSeek:ApiKey\"] \n ?? Environment.GetEnvironmentVariable(\"DEEPSEEK_API_KEY\") \n ?? \"\")\n .WithReference(resourceApiService)\n .WaitFor(resourceApiService);\n ConfigureObservability(aiService);\n\n var apiGateway = builder\n .AddProject<Projects.ApiGateway>(\"apigateway\")\n .WithExternalHttpEndpoints()\n .WithReference(identityService)\n // .WithReference(identityApiService)\n .WithReference(classroomService)\n .WithReference(resourceApiService)\n .WithReference(notificationApiService)\n .WithReference(emulatorApiService)\n .WaitFor(identityService)\n .WaitFor(resourceApiService)\n .WaitFor(classroomService)\n .WaitFor(notificationApiService)\n .WaitFor(productApiService)\n .WaitFor(orderApiService)\n .WaitFor(emulatorApiService)\n .WaitFor(aiService)\n ;\n ConfigureObservability(apiGateway);\n\n void ConfigureObservability<TResource>(IResourceBuilder<TResource> resourceBuilder)\n where TResource : IResourceWithEnvironment\n {\n var enabledValue = externalObservabilityEnabled ? \"true\" : \"false\";\n resourceBuilder.WithEnvironment(\"Observability__ExternalStackEnabled\", enabledValue);\n\n if (grafanaEndpoint is not null)\n {\n resourceBuilder.WithEnvironment(\"GRAFANA_URL\", grafanaEndpoint);\n }\n }\n\n return builder;\n }\n }\n}\n"}, "802ae17c3f9fc23237af12855b84e2fbea8ee991": {"t": 1785748776.0174842, "n": 14991, "h": "727698253519b42e", "c": "using Microsoft.Extensions.Hosting;\n\nnamespace STEMifyBackend.AppHost.Extensions\n{\n public static class ServiceExtensions\n {\n public static IDistributedApplicationBuilder AddApplicationServices(this IDistributedApplicationBuilder builder)\n {\n var postgres = builder.AddPostgres(\"stemify-postgres\");\n\n var identityDb = postgres.AddDatabase(\"stemifyidentity\");\n\n var classroomDb = postgres.AddDatabase(\"stemifyclassroom\");\n\n var resourceDb = postgres.AddDatabase(\"stemifyresource\");\n\n var notificationDb = postgres.AddDatabase(\"stemifynotification\");\n\n var productDb = postgres.AddDatabase(\"stemifyproduct\");\n\n var orderDb = postgres.AddDatabase(\"stemifyorder\");\n var paymentDb = postgres.AddDatabase(\"stemifypayment\");\n\n var cartDb = postgres.AddDatabase(\"stemifycart\");\n\n var hangfireDb = postgres.AddDatabase(\"stemifyhangfire\");\n\n // // Add pgAdmin for database administration (Production)\n // var pgAdmin = builder\n // .AddContainer(\"pgadmin\", \"dpage/pgadmin4\")\n // .WithHttpEndpoint(targetPort: 5050, name: \"http\")\n // .WithExternalHttpEndpoints()\n // .WithEnvironment(\"PGADMIN_DEFAULT_EMAIL\", \"admin@stemify.com\")\n // .WithEnvironment(\"PGADMIN_DEFAULT_PASSWORD\", Environment.GetEnvironmentVariable(\"PGADMIN_PASSWORD\") ?? \"admin\")\n // .WithEnvironment(\"PGADMIN_CONFIG_SERVER_MODE\", \"False\")\n // .WithEnvironment(\"PGADMIN_CONFIG_MASTER_PASSWORD_REQUIRED\", \"False\")\n // .WithEnvironment(\"PGADMIN_LISTEN_PORT\", \"5050\")\n // .WaitFor(postgres);\n\n // Add RabbitMQ for messaging (required for Payment Service Saga pattern)\n IResourceBuilder<IResourceWithConnectionString> rabbitmqReference;\n if (builder.Environment.IsProduction())\n {\n var cloudAmqpUrl = builder.Configuration[\"RabbitMQ:Url\"]\n ?? Environment.GetEnvironmentVariable(\"RABBITMQ_URL\")\n ?? throw new InvalidOperationException(\"RabbitMQ URL not configured.\");\n\n rabbitmqReference = builder.AddConnectionString(\"messaging\", cloudAmqpUrl);\n }\n else\n {\n var rabbitmq = builder.AddRabbitMQ(\"messaging\")\n .WithManagementPlugin();\n rabbitmqReference = rabbitmq;\n }\n\n\n var manualFlag = Environment.GetEnvironmentVariable(\"ENABLE_OBSERVABILITY_STACK\");\n var externalObservabilityEnabled = builder.Environment.IsDevelopment() ||\n string.Equals(manualFlag, \"true\", StringComparison.OrdinalIgnoreCase);\n EndpointReference? grafanaEndpoint = null;\n\n if (externalObservabilityEnabled)\n {\n var prometheus = builder.AddContainer(\"prometheus\", \"prom/prometheus\", \"v3.5.0\")\n .WithBindMount(\"../../../../config/prometheus\", \"/etc/prometheus\", isReadOnly: true)\n .WithArgs(\n \"--web.enable-otlp-receiver\",\n \"--config.file=/etc/prometheus/prometheus.yml\",\n \"--storage.tsdb.retention.time=15d\",\n \"--storage.tsdb.retention.size=10GB\"\n )\n .WithHttpEndpoint(targetPort: 9090, name: \"http\");\n\n var alertmanager = builder.AddContainer(\"alertmanager\", \"prom/alertmanager\", \"v0.27.0\")\n .WithBindMount(\"../../../../config/alertmanager\", \"/etc/alertmanager\", isReadOnly: true)\n .WithArgs(\"--config.file=/etc/alertmanager/alertmanager.yml\", \"--storage.path=/alertmanager\")\n .WithHttpEndpoint(targetPort: 9093, name: \"http\");\n\n var grafana = builder.AddContainer(\"grafana\", \"grafana/grafana\")\n .WithBindMount(\"../../../../config/grafana/config\", \"/etc/grafana\", isReadOnly: true)\n .WithBindMount(\"../../../../config/grafana/dashboards\", \"/var/lib/grafana/dashboards\", isReadOnly: true)\n .WithEnvironment(\"PROMETHEUS_ENDPOINT\", prometheus.GetEndpoint(\"http\"))\n .WithHttpEndpoint(targetPort: 3000, name: \"http\");\n\n grafanaEndpoint = grafana.GetEndpoint(\"http\");\n\n var configFile = \"../../../../config/otelcollector/config.dev.yaml\";\n\n builder.AddOpenTelemetryCollector(\"otelcollector\", configFile)\n .WithEnvironment(\"PROMETHEUS_ENDPOINT\", $\"{prometheus.GetEndpoint(\"http\")}/api/v1/otlp\")\n .WithEnvironment(\"ALERTMANAGER_ENDPOINT\", alertmanager.GetEndpoint(\"http\"));\n }\n\n var pgAdmin = builder.Environment.IsProduction()\n ? builder\n .AddContainer(\"pgadmin\", \"dpage/pgadmin4:8.10\")\n .WithHttpEndpoint(targetPort: 5050, name: \"pgadmin-http\")\n .WithExternalHttpEndpoints()\n .WithEnvironment(\"PGADMIN_DEFAULT_EMAIL\", \"admin@stemify.com\")\n .WithEnvironment(\n \"PGADMIN_DEFAULT_PASSWORD\",\n Environment.GetEnvironmentVariable(\"PGADMIN_PASSWORD\") ?? \"admin\"\n )\n .WithEnvironment(\"PGADMIN_CONFIG_SERVER_MODE\", \"False\")\n .WithEnvironment(\"PGADMIN_CONFIG_MASTER_PASSWORD_REQUIRED\", \"False\")\n .WithEnvironment(\"PGADMIN_LISTEN_PORT\", \"5050\")\n .WithEnvironment(\"PGADMIN_CONFIG_CHECK_EMAIL_DELIVERABILITY\", \"False\")\n .WithEnvironment(\"GUNICORN_TIMEOUT\", \"120\")\n .WaitFor(postgres)\n : postgres.WithPgAdmin(x =>\n {\n x.WithHttpEndpoint(targetPort: 5050, name: \"pgadmin-dev\");\n x.WithExternalHttpEndpoints();\n x.WithEnvironment(\"PGADMIN_CONFIG_CHECK_EMAIL_DELIVERABILITY\", \"False\");\n x.WithEnvironment(\"GUNICORN_TIMEOUT\", \"120\");\n });\n\n var identityService = builder\n .AddProject<Projects.Identity_Web>(\"identity\")\n .WithExternalHttpEndpoints()\n .WithReference(identityDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(identityDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(identityService);\n\n var identityApiService = builder\n .AddProject<Projects.Identity_API>(\"identity-api\")\n .WithExternalHttpEndpoints()\n .WithReference(identityDb)\n .WithReference(identityService)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(identityDb)\n .WaitFor(identityService)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(identityApiService);\n\n // var sagaOrchestrator = builder\n // .AddProject<Projects.Saga_Orchestrator>(\"saga-orchestrator\")\n // .WithReference(identityService)\n // .WithReference(rabbitmqReference)\n // .WaitFor(identityService)\n // .WaitFor(rabbitmqReference)\n // ;\n // ConfigureObservability(sagaOrchestrator);\n\n var classroomService = builder\n .AddProject<Projects.Classroom_API>(\"classroom-api\")\n .WithExternalHttpEndpoints()\n .WithReference(classroomDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(classroomDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(classroomService);\n\n var resourceApiService = builder\n .AddProject<Projects.Resource_API>(\"resource-api\")\n .WithExternalHttpEndpoints()\n .WithReference(resourceDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(resourceDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(resourceApiService);\n\n var notificationApiService = builder\n .AddProject<Projects.Notification_API>(\"notification-api\")\n .WithExternalHttpEndpoints()\n .WithReference(notificationDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(notificationDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(notificationApiService);\n\n var productApiService = builder\n .AddProject<Projects.Product_API>(\"product-api\")\n .WithExternalHttpEndpoints()\n .WithReference(productDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(productDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(productApiService);\n\n var orderApiService = builder\n .AddProject<Projects.Order_API>(\"order-api\")\n .WithExternalHttpEndpoints()\n .WithReference(orderDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(orderDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(orderApiService);\n\n // var paymentApiService = builder\n // .AddProject<Projects.Payment_API>(\"payment-api\")\n // .WithExternalHttpEndpoints()\n // .WithReference(paymentDb)\n // .WithReference(rabbitmq)\n // .WaitFor(postgres)\n // .WaitFor(paymentDb)\n // .WaitFor(rabbitmq)\n // ;\n // ConfigureObservability(paymentApiService);\n\n // var cartApiService = builder\n // .AddProject<Projects.Cart_API>(\"cart-api\")\n // .WithExternalHttpEndpoints()\n // .WithReference(cartDb)\n // .WithReference(rabbitmq)\n // .WaitFor(postgres)\n // .WaitFor(cartDb)\n // .WaitFor(rabbitmq)\n // ;\n // ConfigureObservability(cartApiService);\n \n IResourceBuilder<IResourceWithConnectionString>? emulatorMongoDbReference = null;\n if (!builder.Environment.IsDevelopment())\n {\n var mongo = builder.AddMongoDB(\"mongo\")\n .WithLifetime(ContainerLifetime.Persistent);\n \n var emulatorMongoDb = mongo.AddDatabase(\"stemify-emulator\");\n emulatorMongoDbReference = emulatorMongoDb;\n }\n \n\n var emulatorApiService = builder\n .AddProject<Projects.Emulator_API>(\"emulator-api\")\n .WithExternalHttpEndpoints();\n \n if (emulatorMongoDbReference is not null)\n {\n emulatorApiService\n .WithReference(emulatorMongoDbReference)\n .WaitFor(emulatorMongoDbReference);\n }\n \n ConfigureObservability(emulatorApiService);\n\n var hangfireApiService = builder\n .AddProject<Projects.Hangfire_API>(\"hangfire-api\")\n .WithExternalHttpEndpoints()\n .WithReference(hangfireDb)\n .WithReference(identityDb)\n .WithReference(orderDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(hangfireDb)\n .WaitFor(identityDb)\n .WaitFor(orderDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(hangfireApiService);\n\n \n var resourceGrpcPort = builder.Configuration[\"ResourceApi:GrpcPort\"] ?? \"5082\";\n var resourceGrpcEndpoint = $\"localhost:{resourceGrpcPort}\";\n \n var aiService = builder\n .AddUvicornApp(\"ai-service\", \"../../AIService\", \"app.main:app\")\n .WithExternalHttpEndpoints()\n .WithHttpHealthCheck(\"/health\")\n .WithEnvironment(\"RESOURCE_GRPC_ENDPOINT\", resourceGrpcEndpoint)\n .WithEnvironment(\"RESOURCE_GRPC_USE_TLS\", \"false\")\n .WithEnvironment(\"PYTHONUNBUFFERED\", \"1\")\n .WithEnvironment(\"PYTHONDONTWRITEBYTECODE\", \"1\")\n // LLM API Keys: get from configuration or environment variables\n .WithEnvironment(\"OPENAI_API_KEY\", builder.Configuration[\"OpenAI:ApiKey\"] \n ?? Environment.GetEnvironmentVariable(\"OPENAI_API_KEY\") \n ?? \"\")\n .WithEnvironment(\"DEEPSEEK_API_KEY\", builder.Configuration[\"DeepSeek:ApiKey\"] \n ?? Environment.GetEnvironmentVariable(\"DEEPSEEK_API_KEY\") \n ?? \"\")\n .WithReference(resourceApiService)\n .WaitFor(resourceApiService);\n ConfigureObservability(aiService);\n\n var apiGateway = builder\n .AddProject<Projects.ApiGateway>(\"apigateway\")\n .WithExternalHttpEndpoints()\n .WithReference(identityService)\n // .WithReference(identityApiService)\n .WithReference(classroomService)\n .WithReference(resourceApiService)\n .WithReference(notificationApiService)\n .WithReference(emulatorApiService)\n .WaitFor(identityService)\n .WaitFor(resourceApiService)\n .WaitFor(classroomService)\n .WaitFor(notificationApiService)\n .WaitFor(productApiService)\n .WaitFor(orderApiService)\n .WaitFor(emulatorApiService)\n .WaitFor(aiService)\n ;\n ConfigureObservability(apiGateway);\n\n void ConfigureObservability<TResource>(IResourceBuilder<TResource> resourceBuilder)\n where TResource : IResourceWithEnvironment\n {\n var enabledValue = externalObservabilityEnabled ? \"true\" : \"false\";\n resourceBuilder.WithEnvironment(\"Observability__ExternalStackEnabled\", enabledValue);\n\n if (grafanaEndpoint is not null)\n {\n resourceBuilder.WithEnvironment(\"GRAFANA_URL\", grafanaEndpoint);\n }\n }\n\n return builder;\n }\n }\n}\n"}, "c762fc03427dd10e921936e4ecfb91b425c30cc6": {"t": 1785748777.1318715, "n": 14662, "h": "22c3415148e4c346", "c": "using Microsoft.Extensions.Hosting;\n\nnamespace STEMifyBackend.AppHost.Extensions\n{\n public static class ServiceExtensions\n {\n public static IDistributedApplicationBuilder AddApplicationServices(this IDistributedApplicationBuilder builder)\n {\n var postgres = builder.AddPostgres(\"stemify-postgres\");\n\n var identityDb = postgres.AddDatabase(\"stemifyidentity\");\n\n var classroomDb = postgres.AddDatabase(\"stemifyclassroom\");\n\n var resourceDb = postgres.AddDatabase(\"stemifyresource\");\n\n var notificationDb = postgres.AddDatabase(\"stemifynotification\");\n\n var productDb = postgres.AddDatabase(\"stemifyproduct\");\n\n var orderDb = postgres.AddDatabase(\"stemifyorder\");\n var paymentDb = postgres.AddDatabase(\"stemifypayment\");\n\n var cartDb = postgres.AddDatabase(\"stemifycart\");\n\n var hangfireDb = postgres.AddDatabase(\"stemifyhangfire\");\n\n // // Add pgAdmin for database administration (Production)\n // var pgAdmin = builder\n // .AddContainer(\"pgadmin\", \"dpage/pgadmin4\")\n // .WithHttpEndpoint(targetPort: 5050, name: \"http\")\n // .WithExternalHttpEndpoints()\n // .WithEnvironment(\"PGADMIN_DEFAULT_EMAIL\", \"admin@stemify.com\")\n // .WithEnvironment(\"PGADMIN_DEFAULT_PASSWORD\", Environment.GetEnvironmentVariable(\"PGADMIN_PASSWORD\") ?? \"admin\")\n // .WithEnvironment(\"PGADMIN_CONFIG_SERVER_MODE\", \"False\")\n // .WithEnvironment(\"PGADMIN_CONFIG_MASTER_PASSWORD_REQUIRED\", \"False\")\n // .WithEnvironment(\"PGADMIN_LISTEN_PORT\", \"5050\")\n // .WaitFor(postgres);\n\n // Add RabbitMQ for messaging (required for Payment Service Saga pattern)\n IResourceBuilder<IResourceWithConnectionString> rabbitmqReference;\n if (builder.Environment.IsProduction())\n {\n var cloudAmqpUrl = builder.Configuration[\"RabbitMQ:Url\"]\n ?? Environment.GetEnvironmentVariable(\"RABBITMQ_URL\")\n ?? throw new InvalidOperationException(\"RabbitMQ URL not configured.\");\n\n rabbitmqReference = builder.AddConnectionString(\"messaging\", cloudAmqpUrl);\n }\n else\n {\n var rabbitmq = builder.AddRabbitMQ(\"messaging\")\n .WithManagementPlugin();\n rabbitmqReference = rabbitmq;\n }\n\n\n var manualFlag = Environment.GetEnvironmentVariable(\"ENABLE_OBSERVABILITY_STACK\");\n var externalObservabilityEnabled = builder.Environment.IsDevelopment() ||\n string.Equals(manualFlag, \"true\", StringComparison.OrdinalIgnoreCase);\n EndpointReference? grafanaEndpoint = null;\n\n if (externalObservabilityEnabled)\n {\n var prometheus = builder.AddContainer(\"prometheus\", \"prom/prometheus\", \"v3.5.0\")\n .WithBindMount(\"../../../../config/prometheus\", \"/etc/prometheus\", isReadOnly: true)\n .WithArgs(\n \"--web.enable-otlp-receiver\",\n \"--config.file=/etc/prometheus/prometheus.yml\",\n \"--storage.tsdb.retention.time=15d\",\n \"--storage.tsdb.retention.size=10GB\"\n )\n .WithHttpEndpoint(targetPort: 9090, name: \"http\");\n\n var alertmanager = builder.AddContainer(\"alertmanager\", \"prom/alertmanager\", \"v0.27.0\")\n .WithBindMount(\"../../../../config/alertmanager\", \"/etc/alertmanager\", isReadOnly: true)\n .WithArgs(\"--config.file=/etc/alertmanager/alertmanager.yml\", \"--storage.path=/alertmanager\")\n .WithHttpEndpoint(targetPort: 9093, name: \"http\");\n\n var grafana = builder.AddContainer(\"grafana\", \"grafana/grafana\")\n .WithBindMount(\"../../../../config/grafana/config\", \"/etc/grafana\", isReadOnly: true)\n .WithBindMount(\"../../../../config/grafana/dashboards\", \"/var/lib/grafana/dashboards\", isReadOnly: true)\n .WithEnvironment(\"PROMETHEUS_ENDPOINT\", prometheus.GetEndpoint(\"http\"))\n .WithHttpEndpoint(targetPort: 3000, name: \"http\");\n\n grafanaEndpoint = grafana.GetEndpoint(\"http\");\n\n var configFile = \"../../../../config/otelcollector/config.dev.yaml\";\n\n builder.AddOpenTelemetryCollector(\"otelcollector\", configFile)\n .WithEnvironment(\"PROMETHEUS_ENDPOINT\", $\"{prometheus.GetEndpoint(\"http\")}/api/v1/otlp\")\n .WithEnvironment(\"ALERTMANAGER_ENDPOINT\", alertmanager.GetEndpoint(\"http\"));\n }\n\n var pgAdmin = builder.Environment.IsProduction()\n ? builder\n .AddContainer(\"pgadmin\", \"dpage/pgadmin4:8.10\")\n .WithHttpEndpoint(targetPort: 5050, name: \"pgadmin-http\")\n .WithExternalHttpEndpoints()\n .WithEnvironment(\"PGADMIN_DEFAULT_EMAIL\", \"admin@stemify.com\")\n .WithEnvironment(\n \"PGADMIN_DEFAULT_PASSWORD\",\n Environment.GetEnvironmentVariable(\"PGADMIN_PASSWORD\") ?? \"admin\"\n )\n .WithEnvironment(\"PGADMIN_CONFIG_SERVER_MODE\", \"False\")\n .WithEnvironment(\"PGADMIN_CONFIG_MASTER_PASSWORD_REQUIRED\", \"False\")\n .WithEnvironment(\"PGADMIN_LISTEN_PORT\", \"5050\")\n .WithEnvironment(\"PGADMIN_CONFIG_CHECK_EMAIL_DELIVERABILITY\", \"False\")\n .WithEnvironment(\"GUNICORN_TIMEOUT\", \"120\")\n .WaitFor(postgres)\n : postgres.WithPgAdmin(x =>\n {\n x.WithHttpEndpoint(targetPort: 5050, name: \"pgadmin-dev\");\n x.WithExternalHttpEndpoints();\n x.WithEnvironment(\"PGADMIN_CONFIG_CHECK_EMAIL_DELIVERABILITY\", \"False\");\n x.WithEnvironment(\"GUNICORN_TIMEOUT\", \"120\");\n });\n\n var identityService = builder\n .AddProject<Projects.Identity_Web>(\"identity\")\n .WithExternalHttpEndpoints()\n .WithReference(identityDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(identityDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(identityService);\n\n var identityApiService = builder\n .AddProject<Projects.Identity_API>(\"identity-api\")\n .WithExternalHttpEndpoints()\n .WithReference(identityDb)\n .WithReference(identityService)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(identityDb)\n .WaitFor(identityService)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(identityApiService);\n\n // var sagaOrchestrator = builder\n // .AddProject<Projects.Saga_Orchestrator>(\"saga-orchestrator\")\n // .WithReference(identityService)\n // .WithReference(rabbitmqReference)\n // .WaitFor(identityService)\n // .WaitFor(rabbitmqReference)\n // ;\n // ConfigureObservability(sagaOrchestrator);\n\n var classroomService = builder\n .AddProject<Projects.Classroom_API>(\"classroom-api\")\n .WithExternalHttpEndpoints()\n .WithReference(classroomDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(classroomDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(classroomService);\n\n var resourceApiService = builder\n .AddProject<Projects.Resource_API>(\"resource-api\")\n .WithExternalHttpEndpoints()\n .WithReference(resourceDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(resourceDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(resourceApiService);\n\n var notificationApiService = builder\n .AddProject<Projects.Notification_API>(\"notification-api\")\n .WithExternalHttpEndpoints()\n .WithReference(notificationDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(notificationDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(notificationApiService);\n\n var productApiService = builder\n .AddProject<Projects.Product_API>(\"product-api\")\n .WithExternalHttpEndpoints()\n .WithReference(productDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(productDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(productApiService);\n\n var orderApiService = builder\n .AddProject<Projects.Order_API>(\"order-api\")\n .WithExternalHttpEndpoints()\n .WithReference(orderDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(orderDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(orderApiService);\n\n // var paymentApiService = builder\n // .AddProject<Projects.Payment_API>(\"payment-api\")\n // .WithExternalHttpEndpoints()\n // .WithReference(paymentDb)\n // .WithReference(rabbitmq)\n // .WaitFor(postgres)\n // .WaitFor(paymentDb)\n // .WaitFor(rabbitmq)\n // ;\n // ConfigureObservability(paymentApiService);\n\n // var cartApiService = builder\n // .AddProject<Projects.Cart_API>(\"cart-api\")\n // .WithExternalHttpEndpoints()\n // .WithReference(cartDb)\n // .WithReference(rabbitmq)\n // .WaitFor(postgres)\n // .WaitFor(cartDb)\n // .WaitFor(rabbitmq)\n // ;\n // ConfigureObservability(cartApiService);\n \n IResourceBuilder<IResourceWithConnectionString>? emulatorMongoDbReference = null;\n if (!builder.Environment.IsDevelopment())\n {\n var mongo = builder.AddMongoDB(\"mongo\")\n .WithLifetime(ContainerLifetime.Persistent);\n \n var emulatorMongoDb = mongo.AddDatabase(\"stemify-emulator\");\n emulatorMongoDbReference = emulatorMongoDb;\n }\n \n\n var emulatorApiService = builder\n .AddProject<Projects.Emulator_API>(\"emulator-api\")\n .WithExternalHttpEndpoints();\n \n if (emulatorMongoDbReference is not null)\n {\n emulatorApiService\n .WithReference(emulatorMongoDbReference)\n .WaitFor(emulatorMongoDbReference);\n }\n \n ConfigureObservability(emulatorApiService);\n\n var hangfireApiService = builder\n .AddProject<Projects.Hangfire_API>(\"hangfire-api\")\n .WithExternalHttpEndpoints()\n .WithReference(hangfireDb)\n .WithReference(identityDb)\n .WithReference(orderDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(hangfireDb)\n .WaitFor(identityDb)\n .WaitFor(orderDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(hangfireApiService);\n\n var aiService = builder\n .AddDockerfile(\"ai-service\", \"../../AIService\")\n .WithHttpEndpoint(targetPort: 80, name: \"http\")\n .WithExternalHttpEndpoints()\n // gRPC endpoint: use service name in container, port 5003 for gRPC\n .WithEnvironment(\"RESOURCE_GRPC_ENDPOINT\", \"resource-api:5003\")\n .WithEnvironment(\"RESOURCE_GRPC_USE_TLS\", \"false\")\n .WithEnvironment(\"PYTHONUNBUFFERED\", \"1\")\n .WithEnvironment(\"PYTHONDONTWRITEBYTECODE\", \"1\")\n // LLM API Keys (set via environment variables or user secrets)\n .WithEnvironment(\"OPENAI_API_KEY\", Environment.GetEnvironmentVariable(\"OPENAI_API_KEY\") ?? \"\")\n .WithEnvironment(\"DEEPSEEK_API_KEY\", Environment.GetEnvironmentVariable(\"DEEPSEEK_API_KEY\") ?? \"\")\n .WaitFor(resourceApiService);\n ConfigureObservability(aiService);\n\n var apiGateway = builder\n .AddProject<Projects.ApiGateway>(\"apigateway\")\n .WithExternalHttpEndpoints()\n .WithReference(identityService)\n // .WithReference(identityApiService)\n .WithReference(classroomService)\n .WithReference(resourceApiService)\n .WithReference(notificationApiService)\n .WithReference(emulatorApiService)\n .WaitFor(identityService)\n .WaitFor(resourceApiService)\n .WaitFor(classroomService)\n .WaitFor(notificationApiService)\n .WaitFor(productApiService)\n .WaitFor(orderApiService)\n .WaitFor(emulatorApiService)\n .WaitFor(aiService)\n ;\n ConfigureObservability(apiGateway);\n\n void ConfigureObservability<TResource>(IResourceBuilder<TResource> resourceBuilder)\n where TResource : IResourceWithEnvironment\n {\n var enabledValue = externalObservabilityEnabled ? \"true\" : \"false\";\n resourceBuilder.WithEnvironment(\"Observability__ExternalStackEnabled\", enabledValue);\n\n if (grafanaEndpoint is not null)\n {\n resourceBuilder.WithEnvironment(\"GRAFANA_URL\", grafanaEndpoint);\n }\n }\n\n return builder;\n }\n }\n}\n"}, "480b0742ea77d1c72ca847eaf1e78690f2621bb2": {"t": 1785748778.2543168, "n": 13911, "h": "d6bf0f5ca2c8399d", "c": "using Microsoft.Extensions.Hosting;\n\nnamespace STEMifyBackend.AppHost.Extensions\n{\n public static class ServiceExtensions\n {\n public static IDistributedApplicationBuilder AddApplicationServices(this IDistributedApplicationBuilder builder)\n {\n var postgres = builder.AddPostgres(\"stemify-postgres\");\n\n var identityDb = postgres.AddDatabase(\"stemifyidentity\");\n\n var classroomDb = postgres.AddDatabase(\"stemifyclassroom\");\n\n var resourceDb = postgres.AddDatabase(\"stemifyresource\");\n\n var notificationDb = postgres.AddDatabase(\"stemifynotification\");\n\n var productDb = postgres.AddDatabase(\"stemifyproduct\");\n\n var orderDb = postgres.AddDatabase(\"stemifyorder\");\n var paymentDb = postgres.AddDatabase(\"stemifypayment\");\n\n var cartDb = postgres.AddDatabase(\"stemifycart\");\n\n var hangfireDb = postgres.AddDatabase(\"stemifyhangfire\");\n\n // // Add pgAdmin for database administration (Production)\n // var pgAdmin = builder\n // .AddContainer(\"pgadmin\", \"dpage/pgadmin4\")\n // .WithHttpEndpoint(targetPort: 5050, name: \"http\")\n // .WithExternalHttpEndpoints()\n // .WithEnvironment(\"PGADMIN_DEFAULT_EMAIL\", \"admin@stemify.com\")\n // .WithEnvironment(\"PGADMIN_DEFAULT_PASSWORD\", Environment.GetEnvironmentVariable(\"PGADMIN_PASSWORD\") ?? \"admin\")\n // .WithEnvironment(\"PGADMIN_CONFIG_SERVER_MODE\", \"False\")\n // .WithEnvironment(\"PGADMIN_CONFIG_MASTER_PASSWORD_REQUIRED\", \"False\")\n // .WithEnvironment(\"PGADMIN_LISTEN_PORT\", \"5050\")\n // .WaitFor(postgres);\n\n // Add RabbitMQ for messaging (required for Payment Service Saga pattern)\n IResourceBuilder<IResourceWithConnectionString> rabbitmqReference;\n if (builder.Environment.IsProduction())\n {\n var cloudAmqpUrl = builder.Configuration[\"RabbitMQ:Url\"]\n ?? Environment.GetEnvironmentVariable(\"RABBITMQ_URL\")\n ?? throw new InvalidOperationException(\"RabbitMQ URL not configured.\");\n\n rabbitmqReference = builder.AddConnectionString(\"messaging\", cloudAmqpUrl);\n }\n else\n {\n var rabbitmq = builder.AddRabbitMQ(\"messaging\")\n .WithManagementPlugin();\n rabbitmqReference = rabbitmq;\n }\n\n\n var manualFlag = Environment.GetEnvironmentVariable(\"ENABLE_OBSERVABILITY_STACK\");\n var externalObservabilityEnabled = builder.Environment.IsDevelopment() ||\n string.Equals(manualFlag, \"true\", StringComparison.OrdinalIgnoreCase);\n EndpointReference? grafanaEndpoint = null;\n\n if (externalObservabilityEnabled)\n {\n var prometheus = builder.AddContainer(\"prometheus\", \"prom/prometheus\", \"v3.5.0\")\n .WithBindMount(\"../../../../config/prometheus\", \"/etc/prometheus\", isReadOnly: true)\n .WithArgs(\n \"--web.enable-otlp-receiver\",\n \"--config.file=/etc/prometheus/prometheus.yml\",\n \"--storage.tsdb.retention.time=15d\",\n \"--storage.tsdb.retention.size=10GB\"\n )\n .WithHttpEndpoint(targetPort: 9090, name: \"http\");\n\n var alertmanager = builder.AddContainer(\"alertmanager\", \"prom/alertmanager\", \"v0.27.0\")\n .WithBindMount(\"../../../../config/alertmanager\", \"/etc/alertmanager\", isReadOnly: true)\n .WithArgs(\"--config.file=/etc/alertmanager/alertmanager.yml\", \"--storage.path=/alertmanager\")\n .WithHttpEndpoint(targetPort: 9093, name: \"http\");\n\n var grafana = builder.AddContainer(\"grafana\", \"grafana/grafana\")\n .WithBindMount(\"../../../../config/grafana/config\", \"/etc/grafana\", isReadOnly: true)\n .WithBindMount(\"../../../../config/grafana/dashboards\", \"/var/lib/grafana/dashboards\", isReadOnly: true)\n .WithEnvironment(\"PROMETHEUS_ENDPOINT\", prometheus.GetEndpoint(\"http\"))\n .WithHttpEndpoint(targetPort: 3000, name: \"http\");\n\n grafanaEndpoint = grafana.GetEndpoint(\"http\");\n\n var configFile = \"../../../../config/otelcollector/config.dev.yaml\";\n\n builder.AddOpenTelemetryCollector(\"otelcollector\", configFile)\n .WithEnvironment(\"PROMETHEUS_ENDPOINT\", $\"{prometheus.GetEndpoint(\"http\")}/api/v1/otlp\")\n .WithEnvironment(\"ALERTMANAGER_ENDPOINT\", alertmanager.GetEndpoint(\"http\"));\n }\n\n var pgAdmin = builder.Environment.IsProduction()\n ? builder\n .AddContainer(\"pgadmin\", \"dpage/pgadmin4:8.10\")\n .WithHttpEndpoint(targetPort: 5050, name: \"pgadmin-http\")\n .WithExternalHttpEndpoints()\n .WithEnvironment(\"PGADMIN_DEFAULT_EMAIL\", \"admin@stemify.com\")\n .WithEnvironment(\n \"PGADMIN_DEFAULT_PASSWORD\",\n Environment.GetEnvironmentVariable(\"PGADMIN_PASSWORD\") ?? \"admin\"\n )\n .WithEnvironment(\"PGADMIN_CONFIG_SERVER_MODE\", \"False\")\n .WithEnvironment(\"PGADMIN_CONFIG_MASTER_PASSWORD_REQUIRED\", \"False\")\n .WithEnvironment(\"PGADMIN_LISTEN_PORT\", \"5050\")\n .WithEnvironment(\"PGADMIN_CONFIG_CHECK_EMAIL_DELIVERABILITY\", \"False\")\n .WithEnvironment(\"GUNICORN_TIMEOUT\", \"120\")\n .WaitFor(postgres)\n : postgres.WithPgAdmin(x =>\n {\n x.WithHttpEndpoint(targetPort: 5050, name: \"pgadmin-dev\");\n x.WithExternalHttpEndpoints();\n x.WithEnvironment(\"PGADMIN_CONFIG_CHECK_EMAIL_DELIVERABILITY\", \"False\");\n x.WithEnvironment(\"GUNICORN_TIMEOUT\", \"120\");\n });\n\n var identityService = builder\n .AddProject<Projects.Identity_Web>(\"identity\")\n .WithExternalHttpEndpoints()\n .WithReference(identityDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(identityDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(identityService);\n\n var identityApiService = builder\n .AddProject<Projects.Identity_API>(\"identity-api\")\n .WithExternalHttpEndpoints()\n .WithReference(identityDb)\n .WithReference(identityService)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(identityDb)\n .WaitFor(identityService)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(identityApiService);\n\n // var sagaOrchestrator = builder\n // .AddProject<Projects.Saga_Orchestrator>(\"saga-orchestrator\")\n // .WithReference(identityService)\n // .WithReference(rabbitmqReference)\n // .WaitFor(identityService)\n // .WaitFor(rabbitmqReference)\n // ;\n // ConfigureObservability(sagaOrchestrator);\n\n var classroomService = builder\n .AddProject<Projects.Classroom_API>(\"classroom-api\")\n .WithExternalHttpEndpoints()\n .WithReference(classroomDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(classroomDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(classroomService);\n\n var resourceApiService = builder\n .AddProject<Projects.Resource_API>(\"resource-api\")\n .WithExternalHttpEndpoints()\n .WithReference(resourceDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(resourceDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(resourceApiService);\n\n var notificationApiService = builder\n .AddProject<Projects.Notification_API>(\"notification-api\")\n .WithExternalHttpEndpoints()\n .WithReference(notificationDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(notificationDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(notificationApiService);\n\n var productApiService = builder\n .AddProject<Projects.Product_API>(\"product-api\")\n .WithExternalHttpEndpoints()\n .WithReference(productDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(productDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(productApiService);\n\n var orderApiService = builder\n .AddProject<Projects.Order_API>(\"order-api\")\n .WithExternalHttpEndpoints()\n .WithReference(orderDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(orderDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(orderApiService);\n\n // var paymentApiService = builder\n // .AddProject<Projects.Payment_API>(\"payment-api\")\n // .WithExternalHttpEndpoints()\n // .WithReference(paymentDb)\n // .WithReference(rabbitmq)\n // .WaitFor(postgres)\n // .WaitFor(paymentDb)\n // .WaitFor(rabbitmq)\n // ;\n // ConfigureObservability(paymentApiService);\n\n // var cartApiService = builder\n // .AddProject<Projects.Cart_API>(\"cart-api\")\n // .WithExternalHttpEndpoints()\n // .WithReference(cartDb)\n // .WithReference(rabbitmq)\n // .WaitFor(postgres)\n // .WaitFor(cartDb)\n // .WaitFor(rabbitmq)\n // ;\n // ConfigureObservability(cartApiService);\n\n var emulatorApiService = builder\n .AddProject<Projects.Emulator_API>(\"emulator-api\")\n .WithExternalHttpEndpoints();\n ConfigureObservability(emulatorApiService);\n\n var hangfireApiService = builder\n .AddProject<Projects.Hangfire_API>(\"hangfire-api\")\n .WithExternalHttpEndpoints()\n .WithReference(hangfireDb)\n .WithReference(identityDb)\n .WithReference(orderDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(hangfireDb)\n .WaitFor(identityDb)\n .WaitFor(orderDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(hangfireApiService);\n\n var aiService = builder\n .AddDockerfile(\"ai-service\", \"../../AIService\")\n .WithHttpEndpoint(targetPort: 80, name: \"http\")\n .WithExternalHttpEndpoints()\n // gRPC endpoint: use service name in container, port 5003 for gRPC\n .WithEnvironment(\"RESOURCE_GRPC_ENDPOINT\", \"resource-api:5003\")\n .WithEnvironment(\"RESOURCE_GRPC_USE_TLS\", \"false\")\n .WithEnvironment(\"PYTHONUNBUFFERED\", \"1\")\n .WithEnvironment(\"PYTHONDONTWRITEBYTECODE\", \"1\")\n // LLM API Keys (set via environment variables or user secrets)\n .WithEnvironment(\"OPENAI_API_KEY\", Environment.GetEnvironmentVariable(\"OPENAI_API_KEY\") ?? \"\")\n .WithEnvironment(\"DEEPSEEK_API_KEY\", Environment.GetEnvironmentVariable(\"DEEPSEEK_API_KEY\") ?? \"\")\n .WaitFor(resourceApiService);\n ConfigureObservability(aiService);\n\n var apiGateway = builder\n .AddProject<Projects.ApiGateway>(\"apigateway\")\n .WithExternalHttpEndpoints()\n .WithReference(identityService)\n // .WithReference(identityApiService)\n .WithReference(classroomService)\n .WithReference(resourceApiService)\n .WithReference(notificationApiService)\n .WithReference(emulatorApiService)\n .WaitFor(identityService)\n .WaitFor(resourceApiService)\n .WaitFor(classroomService)\n .WaitFor(notificationApiService)\n .WaitFor(productApiService)\n .WaitFor(orderApiService)\n .WaitFor(emulatorApiService)\n .WaitFor(aiService)\n ;\n ConfigureObservability(apiGateway);\n\n void ConfigureObservability<TResource>(IResourceBuilder<TResource> resourceBuilder)\n where TResource : IResourceWithEnvironment\n {\n var enabledValue = externalObservabilityEnabled ? \"true\" : \"false\";\n resourceBuilder.WithEnvironment(\"Observability__ExternalStackEnabled\", enabledValue);\n\n if (grafanaEndpoint is not null)\n {\n resourceBuilder.WithEnvironment(\"GRAFANA_URL\", grafanaEndpoint);\n }\n }\n\n return builder;\n }\n }\n}\n"}, "6c79d7bf0e620c08338fcbc2553a2ec232e21a68": {"t": 1785748779.473358, "n": 14137, "h": "e634f9031d5f68a9", "c": "using Aspire.Hosting;\nusing Aspire.Hosting.ApplicationModel;\nusing Microsoft.Extensions.Hosting;\nusing Projects;\nusing System;\nusing System.Collections.Generic;\nusing System.IO;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\n\nnamespace STEMifyBackend.AppHost.Extensions\n{\n public static class ServiceExtensions\n{\n public static IDistributedApplicationBuilder AddApplicationServices(this IDistributedApplicationBuilder builder)\n {\n var postgres = builder.AddPostgres(\"stemify-postgres\");\n\n var identityDb = postgres.AddDatabase(\"stemifyidentity\");\n\n var classroomDb = postgres.AddDatabase(\"stemifyclassroom\");\n\n var resourceDb = postgres.AddDatabase(\"stemifyresource\");\n\n var notificationDb = postgres.AddDatabase(\"stemifynotification\");\n\n var productDb = postgres.AddDatabase(\"stemifyproduct\");\n\n var orderDb = postgres.AddDatabase(\"stemifyorder\");\n var paymentDb = postgres.AddDatabase(\"stemifypayment\");\n\n var cartDb = postgres.AddDatabase(\"stemifycart\");\n\n var hangfireDb = postgres.AddDatabase(\"stemifyhangfire\");\n\n // // Add pgAdmin for database administration (Production)\n // var pgAdmin = builder\n // .AddContainer(\"pgadmin\", \"dpage/pgadmin4\")\n // .WithHttpEndpoint(targetPort: 5050, name: \"http\")\n // .WithExternalHttpEndpoints()\n // .WithEnvironment(\"PGADMIN_DEFAULT_EMAIL\", \"admin@stemify.com\")\n // .WithEnvironment(\"PGADMIN_DEFAULT_PASSWORD\", Environment.GetEnvironmentVariable(\"PGADMIN_PASSWORD\") ?? \"admin\")\n // .WithEnvironment(\"PGADMIN_CONFIG_SERVER_MODE\", \"False\")\n // .WithEnvironment(\"PGADMIN_CONFIG_MASTER_PASSWORD_REQUIRED\", \"False\")\n // .WithEnvironment(\"PGADMIN_LISTEN_PORT\", \"5050\")\n // .WaitFor(postgres);\n\n // Add RabbitMQ for messaging (required for Payment Service Saga pattern)\n IResourceBuilder<IResourceWithConnectionString> rabbitmqReference;\n if ( builder.Environment.IsProduction() )\n {\n var cloudAmqpUrl = builder.Configuration[\"RabbitMQ:Url\"]\n ?? Environment.GetEnvironmentVariable(\"RABBITMQ_URL\")\n ?? throw new InvalidOperationException(\"RabbitMQ URL not configured.\");\n\n rabbitmqReference = builder.AddConnectionString(\"messaging\", cloudAmqpUrl);\n }\n else\n {\n var rabbitmq = builder.AddRabbitMQ(\"messaging\")\n .WithManagementPlugin(); \n rabbitmqReference = rabbitmq;\n }\n \n\n var manualFlag = Environment.GetEnvironmentVariable(\"ENABLE_OBSERVABILITY_STACK\");\n var externalObservabilityEnabled = builder.Environment.IsDevelopment() ||\n string.Equals(manualFlag, \"true\", StringComparison.OrdinalIgnoreCase);\n EndpointReference? grafanaEndpoint = null;\n\n if (externalObservabilityEnabled)\n {\n var prometheus = builder.AddContainer(\"prometheus\", \"prom/prometheus\", \"v3.5.0\")\n .WithBindMount(\"../../../../config/prometheus\", \"/etc/prometheus\", isReadOnly: true)\n .WithArgs(\n \"--web.enable-otlp-receiver\",\n \"--config.file=/etc/prometheus/prometheus.yml\",\n \"--storage.tsdb.retention.time=15d\",\n \"--storage.tsdb.retention.size=10GB\"\n )\n .WithHttpEndpoint(targetPort: 9090, name: \"http\");\n\n var alertmanager = builder.AddContainer(\"alertmanager\", \"prom/alertmanager\", \"v0.27.0\")\n .WithBindMount(\"../../../../config/alertmanager\", \"/etc/alertmanager\", isReadOnly: true)\n .WithArgs(\"--config.file=/etc/alertmanager/alertmanager.yml\", \"--storage.path=/alertmanager\")\n .WithHttpEndpoint(targetPort: 9093, name: \"http\");\n\n var grafana = builder.AddContainer(\"grafana\", \"grafana/grafana\")\n .WithBindMount(\"../../../../config/grafana/config\", \"/etc/grafana\", isReadOnly: true)\n .WithBindMount(\"../../../../config/grafana/dashboards\", \"/var/lib/grafana/dashboards\", isReadOnly: true)\n .WithEnvironment(\"PROMETHEUS_ENDPOINT\", prometheus.GetEndpoint(\"http\"))\n .WithHttpEndpoint(targetPort: 3000, name: \"http\");\n\n grafanaEndpoint = grafana.GetEndpoint(\"http\");\n\n var configFile = \"../../../../config/otelcollector/config.dev.yaml\";\n\n builder.AddOpenTelemetryCollector(\"otelcollector\", configFile)\n .WithEnvironment(\"PROMETHEUS_ENDPOINT\", $\"{prometheus.GetEndpoint(\"http\")}/api/v1/otlp\")\n .WithEnvironment(\"ALERTMANAGER_ENDPOINT\", alertmanager.GetEndpoint(\"http\"));\n }\n\n var pgAdmin = builder.Environment.IsProduction()\n ? builder\n .AddContainer(\"pgadmin\", \"dpage/pgadmin4:8.10\")\n .WithHttpEndpoint(targetPort: 5050, name: \"pgadmin-http\")\n .WithExternalHttpEndpoints()\n .WithEnvironment(\"PGADMIN_DEFAULT_EMAIL\", \"admin@stemify.com\")\n .WithEnvironment(\n \"PGADMIN_DEFAULT_PASSWORD\",\n Environment.GetEnvironmentVariable(\"PGADMIN_PASSWORD\") ?? \"admin\"\n )\n .WithEnvironment(\"PGADMIN_CONFIG_SERVER_MODE\", \"False\")\n .WithEnvironment(\"PGADMIN_CONFIG_MASTER_PASSWORD_REQUIRED\", \"False\")\n .WithEnvironment(\"PGADMIN_LISTEN_PORT\", \"5050\")\n .WithEnvironment(\"PGADMIN_CONFIG_CHECK_EMAIL_DELIVERABILITY\", \"False\")\n .WithEnvironment(\"GUNICORN_TIMEOUT\", \"120\")\n .WaitFor(postgres)\n : postgres.WithPgAdmin(x =>\n {\n x.WithHttpEndpoint(targetPort: 5050, name: \"pgadmin-dev\");\n x.WithExternalHttpEndpoints();\n x.WithEnvironment(\"PGADMIN_CONFIG_CHECK_EMAIL_DELIVERABILITY\", \"False\");\n x.WithEnvironment(\"GUNICORN_TIMEOUT\", \"120\");\n });\n\n var identityService = builder\n .AddProject<Projects.Identity_Web>(\"identity\")\n .WithExternalHttpEndpoints()\n .WithReference(identityDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres) \n .WaitFor(identityDb)\n .WaitFor(rabbitmqReference) \n ;\n ConfigureObservability(identityService);\n\n var identityApiService = builder\n .AddProject<Projects.Identity_API>(\"identity-api\")\n .WithExternalHttpEndpoints()\n .WithReference(identityDb)\n .WithReference(identityService)\n .WithReference(rabbitmqReference) \n .WaitFor(postgres)\n .WaitFor(identityDb)\n .WaitFor(identityService)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(identityApiService);\n\n // var sagaOrchestrator = builder\n // .AddProject<Projects.Saga_Orchestrator>(\"saga-orchestrator\")\n // .WithReference(identityService)\n // .WithReference(rabbitmqReference)\n // .WaitFor(identityService)\n // .WaitFor(rabbitmqReference)\n // ;\n // ConfigureObservability(sagaOrchestrator);\n\n var classroomService = builder\n .AddProject<Projects.Classroom_API>(\"classroom-api\")\n .WithExternalHttpEndpoints()\n .WithReference(classroomDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(classroomDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(classroomService);\n\n var resourceApiService = builder\n .AddProject<Projects.Resource_API>(\"resource-api\")\n .WithExternalHttpEndpoints()\n .WithReference(resourceDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(resourceDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(resourceApiService);\n\n var notificationApiService = builder\n .AddProject<Projects.Notification_API>(\"notification-api\")\n .WithExternalHttpEndpoints()\n .WithReference(notificationDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(notificationDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(notificationApiService);\n\n var productApiService = builder\n .AddProject<Projects.Product_API>(\"product-api\")\n .WithExternalHttpEndpoints()\n .WithReference(productDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(productDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(productApiService);\n\n var orderApiService = builder\n .AddProject<Projects.Order_API>(\"order-api\")\n .WithExternalHttpEndpoints()\n .WithReference(orderDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(orderDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(orderApiService);\n\n // var paymentApiService = builder\n // .AddProject<Projects.Payment_API>(\"payment-api\")\n // .WithExternalHttpEndpoints()\n // .WithReference(paymentDb)\n // .WithReference(rabbitmq)\n // .WaitFor(postgres)\n // .WaitFor(paymentDb)\n // .WaitFor(rabbitmq)\n // ;\n // ConfigureObservability(paymentApiService);\n\n // var cartApiService = builder\n // .AddProject<Projects.Cart_API>(\"cart-api\")\n // .WithExternalHttpEndpoints()\n // .WithReference(cartDb)\n // .WithReference(rabbitmq)\n // .WaitFor(postgres)\n // .WaitFor(cartDb)\n // .WaitFor(rabbitmq)\n // ;\n // ConfigureObservability(cartApiService);\n\n var emulatorApiService = builder\n .AddProject<Projects.Emulator_API>(\"emulator-api\")\n .WithExternalHttpEndpoints();\n ConfigureObservability(emulatorApiService);\n\n var hangfireApiService = builder\n .AddProject<Projects.Hangfire_API>(\"hangfire-api\")\n .WithExternalHttpEndpoints()\n .WithReference(hangfireDb)\n .WithReference(identityDb)\n .WithReference(orderDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(hangfireDb)\n .WaitFor(identityDb)\n .WaitFor(orderDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(hangfireApiService);\n\n var aiService = builder\n .AddDockerfile(\"ai-service\", \"../../AIService\")\n .WithHttpEndpoint(targetPort: 80, name: \"http\")\n .WithExternalHttpEndpoints()\n // gRPC endpoint: use service name in container, port 5003 for gRPC\n .WithEnvironment(\"RESOURCE_GRPC_ENDPOINT\", \"resource-api:5003\")\n .WithEnvironment(\"RESOURCE_GRPC_USE_TLS\", \"false\")\n .WithEnvironment(\"PYTHONUNBUFFERED\", \"1\")\n .WithEnvironment(\"PYTHONDONTWRITEBYTECODE\", \"1\")\n // LLM API Keys (set via environment variables or user secrets)\n .WithEnvironment(\"OPENAI_API_KEY\", Environment.GetEnvironmentVariable(\"OPENAI_API_KEY\") ?? \"\")\n .WithEnvironment(\"DEEPSEEK_API_KEY\", Environment.GetEnvironmentVariable(\"DEEPSEEK_API_KEY\") ?? \"\")\n .WaitFor(resourceApiService);\n ConfigureObservability(aiService);\n\n var apiGateway = builder\n .AddProject<Projects.ApiGateway>(\"apigateway\")\n .WithExternalHttpEndpoints()\n .WithReference(identityService)\n // .WithReference(identityApiService)\n .WithReference(classroomService)\n .WithReference(resourceApiService)\n .WithReference(notificationApiService)\n .WithReference(emulatorApiService)\n .WaitFor(identityService)\n .WaitFor(resourceApiService)\n .WaitFor(classroomService)\n .WaitFor(notificationApiService)\n .WaitFor(productApiService)\n .WaitFor(orderApiService)\n .WaitFor(emulatorApiService)\n .WaitFor(aiService)\n ;\n ConfigureObservability(apiGateway);\n\n void ConfigureObservability<TResource>(IResourceBuilder<TResource> resourceBuilder)\n where TResource : IResourceWithEnvironment\n {\n var enabledValue = externalObservabilityEnabled ? \"true\" : \"false\";\n resourceBuilder.WithEnvironment(\"Observability__ExternalStackEnabled\", enabledValue);\n\n if (grafanaEndpoint is not null)\n {\n resourceBuilder.WithEnvironment(\"GRAFANA_URL\", grafanaEndpoint);\n }\n }\n\n return builder;\n }\n }\n}\n"}, "3cb06bd733fc3869313fbd8ea12614bd23121d76": {"t": 1785748784.864295, "n": 14133, "h": "b90dda9ccba02c80", "c": "using Aspire.Hosting;\nusing Aspire.Hosting.ApplicationModel;\nusing Microsoft.Extensions.Hosting;\nusing Projects;\nusing System;\nusing System.Collections.Generic;\nusing System.IO;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\n\nnamespace STEMifyBackend.AppHost.Extensions\n{\n public static class ServiceExtensions\n{\n public static IDistributedApplicationBuilder AddApplicationServices(this IDistributedApplicationBuilder builder)\n {\n var postgres = builder.AddPostgres(\"stemify-postgres\");\n\n var identityDb = postgres.AddDatabase(\"stemifyidentity\");\n\n var classroomDb = postgres.AddDatabase(\"stemifyclassroom\");\n\n var resourceDb = postgres.AddDatabase(\"stemifyresource\");\n\n var notificationDb = postgres.AddDatabase(\"stemifynotification\");\n\n var productDb = postgres.AddDatabase(\"stemifyproduct\");\n\n var orderDb = postgres.AddDatabase(\"stemifyorder\");\n var paymentDb = postgres.AddDatabase(\"stemifypayment\");\n\n var cartDb = postgres.AddDatabase(\"stemifycart\");\n\n var hangfireDb = postgres.AddDatabase(\"stemifyhangfire\");\n\n // // Add pgAdmin for database administration (Production)\n // var pgAdmin = builder\n // .AddContainer(\"pgadmin\", \"dpage/pgadmin4\")\n // .WithHttpEndpoint(targetPort: 5050, name: \"http\")\n // .WithExternalHttpEndpoints()\n // .WithEnvironment(\"PGADMIN_DEFAULT_EMAIL\", \"admin@stemify.com\")\n // .WithEnvironment(\"PGADMIN_DEFAULT_PASSWORD\", Environment.GetEnvironmentVariable(\"PGADMIN_PASSWORD\") ?? \"admin\")\n // .WithEnvironment(\"PGADMIN_CONFIG_SERVER_MODE\", \"False\")\n // .WithEnvironment(\"PGADMIN_CONFIG_MASTER_PASSWORD_REQUIRED\", \"False\")\n // .WithEnvironment(\"PGADMIN_LISTEN_PORT\", \"5050\")\n // .WaitFor(postgres);\n\n // Add RabbitMQ for messaging (required for Payment Service Saga pattern)\n if ( builder.Environment.IsProduction() )\n {\n var cloudAmqpUrl = builder.Configuration[\"RabbitMQ:Url\"]\n ?? Environment.GetEnvironmentVariable(\"RABBITMQ_URL\")\n ?? throw new InvalidOperationException(\"RabbitMQ URL not configured.\");\n\n rabbitmqReference = builder.AddConnectionString(\"messaging\", cloudAmqpUrl);\n }\n else\n {\n var rabbitmq = builder.AddRabbitMQ(\"messaging\")\n .WithManagementPlugin(); \n rabbitmqReference = rabbitmq;\n }\n \n\n var manualFlag = Environment.GetEnvironmentVariable(\"ENABLE_OBSERVABILITY_STACK\");\n var externalObservabilityEnabled = builder.Environment.IsDevelopment() ||\n string.Equals(manualFlag, \"true\", StringComparison.OrdinalIgnoreCase);\n EndpointReference? grafanaEndpoint = null;\n\n if (externalObservabilityEnabled)\n {\n var prometheus = builder.AddContainer(\"prometheus\", \"prom/prometheus\", \"v3.5.0\")\n .WithBindMount(\"../../../../config/prometheus\", \"/etc/prometheus\", isReadOnly: true)\n .WithArgs(\n \"--web.enable-otlp-receiver\",\n \"--config.file=/etc/prometheus/prometheus.yml\",\n \"--storage.tsdb.retention.time=15d\",\n \"--storage.tsdb.retention.size=10GB\"\n )\n .WithHttpEndpoint(targetPort: 9090, name: \"http\");\n\n var alertmanager = builder.AddContainer(\"alertmanager\", \"prom/alertmanager\", \"v0.27.0\")\n .WithBindMount(\"../../../../config/alertmanager\", \"/etc/alertmanager\", isReadOnly: true)\n .WithArgs(\"--config.file=/etc/alertmanager/alertmanager.yml\", \"--storage.path=/alertmanager\")\n .WithHttpEndpoint(targetPort: 9093, name: \"http\");\n\n var grafana = builder.AddContainer(\"grafana\", \"grafana/grafana\")\n .WithBindMount(\"../../../../config/grafana/config\", \"/etc/grafana\", isReadOnly: true)\n .WithBindMount(\"../../../../config/grafana/dashboards\", \"/var/lib/grafana/dashboards\", isReadOnly: true)\n .WithEnvironment(\"PROMETHEUS_ENDPOINT\", prometheus.GetEndpoint(\"http\"))\n .WithHttpEndpoint(targetPort: 3000, name: \"http\");\n\n grafanaEndpoint = grafana.GetEndpoint(\"http\");\n\n var configFile = \"../../../../config/otelcollector/config.dev.yaml\";\n\n builder.AddOpenTelemetryCollector(\"otelcollector\", configFile)\n .WithEnvironment(\"PROMETHEUS_ENDPOINT\", $\"{prometheus.GetEndpoint(\"http\")}/api/v1/otlp\")\n .WithEnvironment(\"ALERTMANAGER_ENDPOINT\", alertmanager.GetEndpoint(\"http\"));\n }\n\n var pgAdmin = builder.Environment.IsProduction()\n ? builder\n .AddContainer(\"pgadmin\", \"dpage/pgadmin4:8.10\")\n .WithHttpEndpoint(targetPort: 5050, name: \"pgadmin-http\")\n .WithExternalHttpEndpoints()\n .WithEnvironment(\"PGADMIN_DEFAULT_EMAIL\", \"admin@stemify.com\")\n .WithEnvironment(\n \"PGADMIN_DEFAULT_PASSWORD\",\n Environment.GetEnvironmentVariable(\"PGADMIN_PASSWORD\") ?? \"admin\"\n )\n .WithEnvironment(\"PGADMIN_CONFIG_SERVER_MODE\", \"False\")\n .WithEnvironment(\"PGADMIN_CONFIG_MASTER_PASSWORD_REQUIRED\", \"False\")\n .WithEnvironment(\"PGADMIN_LISTEN_PORT\", \"5050\")\n .WithEnvironment(\"PGADMIN_CONFIG_CHECK_EMAIL_DELIVERABILITY\", \"False\")\n .WithEnvironment(\"GUNICORN_TIMEOUT\", \"120\")\n .WaitFor(postgres)\n : postgres.WithPgAdmin(x =>\n {\n x.WithHttpEndpoint(targetPort: 5050, name: \"pgadmin-dev\");\n x.WithExternalHttpEndpoints();\n x.WithEnvironment(\"PGADMIN_CONFIG_CHECK_EMAIL_DELIVERABILITY\", \"False\");\n x.WithEnvironment(\"GUNICORN_TIMEOUT\", \"120\");\n });\n\n var identityService = builder\n .AddProject<Projects.Identity_Web>(\"identity\")\n .WithExternalHttpEndpoints()\n .WithReference(identityDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres) \n .WaitFor(identityDb)\n .WaitFor(rabbitmqReference) \n ;\n ConfigureObservability(identityService);\n\n var identityApiService = builder\n .AddProject<Projects.Identity_API>(\"identity-api\")\n .WithExternalHttpEndpoints()\n .WithReference(identityDb)\n .WithReference(identityService)\n .WithReference(rabbitmqReference) \n .WaitFor(postgres)\n .WaitFor(identityDb)\n .WaitFor(identityService)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(identityApiService);\n\n // var sagaOrchestrator = builder\n // .AddProject<Projects.Saga_Orchestrator>(\"saga-orchestrator\")\n // .WithReference(identityService)\n // .WithReference(rabbitmqReference)\n // .WaitFor(identityService)\n // .WaitFor(rabbitmqReference)\n // ;\n // ConfigureObservability(sagaOrchestrator);\n\n var classroomService = builder\n .AddProject<Projects.Classroom_API>(\"classroom-api\")\n .WithExternalHttpEndpoints()\n .WithReference(classroomDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(classroomDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(classroomService);\n\n var resourceApiService = builder\n .AddProject<Projects.Resource_API>(\"resource-api\")\n .WithExternalHttpEndpoints()\n .WithReference(resourceDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(resourceDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(resourceApiService);\n\n var notificationApiService = builder\n .AddProject<Projects.Notification_API>(\"notification-api\")\n .WithExternalHttpEndpoints()\n .WithReference(notificationDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(notificationDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(notificationApiService);\n\n var productApiService = builder\n .AddProject<Projects.Product_API>(\"product-api\")\n .WithExternalHttpEndpoints()\n .WithReference(productDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(productDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(productApiService);\n\n var orderApiService = builder\n .AddProject<Projects.Order_API>(\"order-api\")\n .WithExternalHttpEndpoints()\n .WithReference(orderDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(orderDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(orderApiService);\n\n // var paymentApiService = builder\n // .AddProject<Projects.Payment_API>(\"payment-api\")\n // .WithExternalHttpEndpoints()\n // .WithReference(paymentDb)\n // .WithReference(rabbitmq)\n // .WaitFor(postgres)\n // .WaitFor(paymentDb)\n // .WaitFor(rabbitmq)\n // ;\n // ConfigureObservability(paymentApiService);\n\n // var cartApiService = builder\n // .AddProject<Projects.Cart_API>(\"cart-api\")\n // .WithExternalHttpEndpoints()\n // .WithReference(cartDb)\n // .WithReference(rabbitmq)\n // .WaitFor(postgres)\n // .WaitFor(cartDb)\n // .WaitFor(rabbitmq)\n // ;\n // ConfigureObservability(cartApiService);\n\n var emulatorApiService = builder\n .AddProject<Projects.Emulator_API>(\"emulator-api\")\n .WithExternalHttpEndpoints();\n ConfigureObservability(emulatorApiService);\n\n var hangfireApiService = builder\n .AddProject<Projects.Hangfire_API>(\"hangfire-api\")\n .WithExternalHttpEndpoints()\n .WithReference(hangfireDb)\n .WithReference(identityDb)\n .WithReference(orderDb)\n .WithReference(rabbitmqReference)\n .WaitFor(postgres)\n .WaitFor(hangfireDb)\n .WaitFor(identityDb)\n .WaitFor(orderDb)\n .WaitFor(rabbitmqReference)\n ;\n ConfigureObservability(hangfireApiService);\n\n var aiService = builder\n .AddDockerfile(\"ai-service\", \"../../AIService\")\n .WithHttpEndpoint(targetPort: 80, name: \"http\")\n .WithExternalHttpEndpoints()\n // gRPC endpoint: use service name in container, port 5003 for gRPC\n .WithEnvironment(\"RESOURCE_GRPC_ENDPOINT\", \"resource-api:5003\")\n .WithEnvironment(\"RESOURCE_GRPC_USE_TLS\", \"false\")\n .WithEnvironment(\"PYTHONUNBUFFERED\", \"1\")\n .WithEnvironment(\"PYTHONDONTWRITEBYTECODE\", \"1\")\n // LLM API Keys (set via environment variables or user secrets)\n .WithEnvironment(\"OPENAI_API_KEY\", Environment.GetEnvironmentVariable(\"OPENAI_API_KEY\") ?? \"\")\n .WithEnvironment(\"DEEPSEEK_API_KEY\", Environment.GetEnvironmentVariable(\"DEEPSEEK_API_KEY\") ?? \"\")\n .WaitFor(resourceApiService);\n ConfigureObservability(aiService);\n\n var apiGateway = builder\n .AddProject<Projects.ApiGateway>(\"apigateway\")\n .WithExternalHttpEndpoints()\n .WithReference(identityService)\n // .WithReference(identityApiService)\n .WithReference(classroomService)\n .WithReference(resourceApiService)\n .WithReference(notificationApiService)\n .WithReference(emulatorApiService)\n .WaitFor(identityService)\n .WaitFor(resourceApiService)\n .WaitFor(classroomService)\n .WaitFor(notificationApiService)\n .WaitFor(productApiService)\n .WaitFor(orderApiService)\n .WaitFor(cartApiService)\n .WaitFor(emulatorApiService)\n .WaitFor(paymentApiService)\n .WaitFor(aiService)\n ;\n ConfigureObservability(apiGateway);\n\n void ConfigureObservability<TResource>(IResourceBuilder<TResource> resourceBuilder)\n where TResource : IResourceWithEnvironment\n {\n var enabledValue = externalObservabilityEnabled ? \"true\" : \"false\";\n resourceBuilder.WithEnvironment(\"Observability__ExternalStackEnabled\", enabledValue);\n\n if (grafanaEndpoint is not null)\n {\n resourceBuilder.WithEnvironment(\"GRAFANA_URL\", grafanaEndpoint);\n }\n }\n\n return builder;\n }\n }\n}\n"}, "98cddecb4c97e7abde6665e3e534bfd8808fe2a2": {"t": 1785748786.4111316, "n": 13401, "h": "774e70037d62a5ff", "c": "using Aspire.Hosting;\nusing Aspire.Hosting.ApplicationModel;\nusing Microsoft.Extensions.Hosting;\nusing Projects;\nusing System;\nusing System.Collections.Generic;\nusing System.IO;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\n\nnamespace STEMifyBackend.AppHost.Extensions\n{\n public static class ServiceExtensions\n{\n public static IDistributedApplicationBuilder AddApplicationServices(this IDistributedApplicationBuilder builder)\n {\n var postgres = builder.AddPostgres(\"stemify-postgres\");\n\n var identityDb = postgres.AddDatabase(\"stemifyidentity\");\n\n var classroomDb = postgres.AddDatabase(\"stemifyclassroom\");\n\n var resourceDb = postgres.AddDatabase(\"stemifyresource\");\n\n var notificationDb = postgres.AddDatabase(\"stemifynotification\");\n\n var productDb = postgres.AddDatabase(\"stemifyproduct\");\n\n var orderDb = postgres.AddDatabase(\"stemifyorder\");\n var paymentDb = postgres.AddDatabase(\"stemifypayment\");\n\n var cartDb = postgres.AddDatabase(\"stemifycart\");\n\n var hangfireDb = postgres.AddDatabase(\"stemifyhangfire\");\n\n // // Add pgAdmin for database administration (Production)\n // var pgAdmin = builder\n // .AddContainer(\"pgadmin\", \"dpage/pgadmin4\")\n // .WithHttpEndpoint(targetPort: 5050, name: \"http\")\n // .WithExternalHttpEndpoints()\n // .WithEnvironment(\"PGADMIN_DEFAULT_EMAIL\", \"admin@stemify.com\")\n // .WithEnvironment(\"PGADMIN_DEFAULT_PASSWORD\", Environment.GetEnvironmentVariable(\"PGADMIN_PASSWORD\") ?? \"admin\")\n // .WithEnvironment(\"PGADMIN_CONFIG_SERVER_MODE\", \"False\")\n // .WithEnvironment(\"PGADMIN_CONFIG_MASTER_PASSWORD_REQUIRED\", \"False\")\n // .WithEnvironment(\"PGADMIN_LISTEN_PORT\", \"5050\")\n // .WaitFor(postgres);\n\n // Add RabbitMQ for messaging (required for Payment Service Saga pattern)\n var rabbitmq = builder.AddRabbitMQ(\"messaging\")\n .WithManagementPlugin(); \n\n var manualFlag = Environment.GetEnvironmentVariable(\"ENABLE_OBSERVABILITY_STACK\");\n var externalObservabilityEnabled = builder.Environment.IsDevelopment() ||\n string.Equals(manualFlag, \"true\", StringComparison.OrdinalIgnoreCase);\n EndpointReference? grafanaEndpoint = null;\n\n if (externalObservabilityEnabled)\n {\n var prometheus = builder.AddContainer(\"prometheus\", \"prom/prometheus\", \"v3.5.0\")\n .WithBindMount(\"../../../../config/prometheus\", \"/etc/prometheus\", isReadOnly: true)\n .WithArgs(\n \"--web.enable-otlp-receiver\",\n \"--config.file=/etc/prometheus/prometheus.yml\",\n \"--storage.tsdb.retention.time=15d\",\n \"--storage.tsdb.retention.size=10GB\"\n )\n .WithHttpEndpoint(targetPort: 9090, name: \"http\");\n\n var alertmanager = builder.AddContainer(\"alertmanager\", \"prom/alertmanager\", \"v0.27.0\")\n .WithBindMount(\"../../../../config/alertmanager\", \"/etc/alertmanager\", isReadOnly: true)\n .WithArgs(\"--config.file=/etc/alertmanager/alertmanager.yml\", \"--storage.path=/alertmanager\")\n .WithHttpEndpoint(targetPort: 9093, name: \"http\");\n\n var grafana = builder.AddContainer(\"grafana\", \"grafana/grafana\")\n .WithBindMount(\"../../../../config/grafana/config\", \"/etc/grafana\", isReadOnly: true)\n .WithBindMount(\"../../../../config/grafana/dashboards\", \"/var/lib/grafana/dashboards\", isReadOnly: true)\n .WithEnvironment(\"PROMETHEUS_ENDPOINT\", prometheus.GetEndpoint(\"http\"))\n .WithHttpEndpoint(targetPort: 3000, name: \"http\");\n\n grafanaEndpoint = grafana.GetEndpoint(\"http\");\n\n var configFile = \"../../../../config/otelcollector/config.dev.yaml\";\n\n builder.AddOpenTelemetryCollector(\"otelcollector\", configFile)\n .WithEnvironment(\"PROMETHEUS_ENDPOINT\", $\"{prometheus.GetEndpoint(\"http\")}/api/v1/otlp\")\n .WithEnvironment(\"ALERTMANAGER_ENDPOINT\", alertmanager.GetEndpoint(\"http\"));\n }\n\n var pgAdmin = builder.Environment.IsProduction()\n ? builder\n .AddContainer(\"pgadmin\", \"dpage/pgadmin4:8.10\")\n .WithHttpEndpoint(targetPort: 5050, name: \"pgadmin-http\")\n .WithExternalHttpEndpoints()\n .WithEnvironment(\"PGADMIN_DEFAULT_EMAIL\", \"admin@stemify.com\")\n .WithEnvironment(\n \"PGADMIN_DEFAULT_PASSWORD\",\n Environment.GetEnvironmentVariable(\"PGADMIN_PASSWORD\") ?? \"admin\"\n )\n .WithEnvironment(\"PGADMIN_CONFIG_SERVER_MODE\", \"False\")\n .WithEnvironment(\"PGADMIN_CONFIG_MASTER_PASSWORD_REQUIRED\", \"False\")\n .WithEnvironment(\"PGADMIN_LISTEN_PORT\", \"5050\")\n .WithEnvironment(\"PGADMIN_CONFIG_CHECK_EMAIL_DELIVERABILITY\", \"False\")\n .WithEnvironment(\"GUNICORN_TIMEOUT\", \"120\")\n .WaitFor(postgres)\n : postgres.WithPgAdmin(x =>\n {\n x.WithHttpEndpoint(targetPort: 5050, name: \"pgadmin-dev\");\n x.WithExternalHttpEndpoints();\n x.WithEnvironment(\"PGADMIN_CONFIG_CHECK_EMAIL_DELIVERABILITY\", \"False\");\n x.WithEnvironment(\"GUNICORN_TIMEOUT\", \"120\");\n });\n\n var identityService = builder\n .AddProject<Projects.Identity_Web>(\"identity\")\n .WithExternalHttpEndpoints()\n .WithReference(identityDb)\n .WithReference(rabbitmq)\n .WaitFor(postgres) \n .WaitFor(identityDb)\n .WaitFor(rabbitmq) \n ;\n ConfigureObservability(identityService);\n\n var identityApiService = builder\n .AddProject<Projects.Identity_API>(\"identity-api\")\n .WithExternalHttpEndpoints()\n .WithReference(identityDb)\n .WithReference(identityService)\n .WithReference(rabbitmq) \n .WaitFor(postgres)\n .WaitFor(identityDb)\n .WaitFor(identityService)\n .WaitFor(rabbitmq)\n ;\n ConfigureObservability(identityApiService);\n\n var sagaOrchestrator = builder\n .AddProject<Projects.Saga_Orchestrator>(\"saga-orchestrator\")\n .WithReference(identityService)\n .WithReference(rabbitmq)\n .WaitFor(identityService)\n .WaitFor(rabbitmq)\n ;\n ConfigureObservability(sagaOrchestrator);\n\n var classroomService = builder\n .AddProject<Projects.Classroom_API>(\"classroom-api\")\n .WithExternalHttpEndpoints()\n .WithReference(classroomDb)\n .WithReference(rabbitmq)\n .WaitFor(postgres)\n .WaitFor(classroomDb)\n .WaitFor(rabbitmq)\n ;\n ConfigureObservability(classroomService);\n\n var resourceApiService = builder\n .AddProject<Projects.Resource_API>(\"resource-api\")\n .WithExternalHttpEndpoints()\n .WithReference(resourceDb)\n .WithReference(rabbitmq)\n .WaitFor(postgres)\n .WaitFor(resourceDb)\n .WaitFor(rabbitmq)\n ;\n ConfigureObservability(resourceApiService);\n\n var notificationApiService = builder\n .AddProject<Projects.Notification_API>(\"notification-api\")\n .WithExternalHttpEndpoints()\n .WithReference(notificationDb)\n .WithReference(rabbitmq)\n .WaitFor(postgres)\n .WaitFor(notificationDb)\n .WaitFor(rabbitmq)\n ;\n ConfigureObservability(notificationApiService);\n\n var productApiService = builder\n .AddProject<Projects.Product_API>(\"product-api\")\n .WithExternalHttpEndpoints()\n .WithReference(productDb)\n .WithReference(rabbitmq)\n .WaitFor(postgres)\n .WaitFor(productDb)\n .WaitFor(rabbitmq)\n ;\n ConfigureObservability(productApiService);\n\n var orderApiService = builder\n .AddProject<Projects.Order_API>(\"order-api\")\n .WithExternalHttpEndpoints()\n .WithReference(orderDb)\n .WithReference(rabbitmq)\n .WaitFor(postgres)\n .WaitFor(orderDb)\n .WaitFor(rabbitmq)\n ;\n ConfigureObservability(orderApiService);\n\n var paymentApiService = builder\n .AddProject<Projects.Payment_API>(\"payment-api\")\n .WithExternalHttpEndpoints()\n .WithReference(paymentDb)\n .WithReference(rabbitmq)\n .WaitFor(postgres)\n .WaitFor(paymentDb)\n .WaitFor(rabbitmq)\n ;\n ConfigureObservability(paymentApiService);\n\n var cartApiService = builder\n .AddProject<Projects.Cart_API>(\"cart-api\")\n .WithExternalHttpEndpoints()\n .WithReference(cartDb)\n .WithReference(rabbitmq)\n .WaitFor(postgres)\n .WaitFor(cartDb)\n .WaitFor(rabbitmq)\n ;\n ConfigureObservability(cartApiService);\n\n var emulatorApiService = builder\n .AddProject<Projects.Emulator_API>(\"emulator-api\")\n .WithExternalHttpEndpoints();\n ConfigureObservability(emulatorApiService);\n\n var hangfireApiService = builder\n .AddProject<Projects.Hangfire_API>(\"hangfire-api\")\n .WithExternalHttpEndpoints()\n .WithReference(hangfireDb)\n .WithReference(identityDb)\n .WithReference(orderDb)\n .WithReference(rabbitmq)\n .WaitFor(postgres)\n .WaitFor(hangfireDb)\n .WaitFor(identityDb)\n .WaitFor(orderDb)\n .WaitFor(rabbitmq)\n ;\n ConfigureObservability(hangfireApiService);\n\n var aiService = builder\n .AddDockerfile(\"ai-service\", \"../../AIService\")\n .WithHttpEndpoint(targetPort: 80, name: \"http\")\n .WithExternalHttpEndpoints()\n // gRPC endpoint: use service name in container, port 5003 for gRPC\n .WithEnvironment(\"RESOURCE_GRPC_ENDPOINT\", \"resource-api:5003\")\n .WithEnvironment(\"RESOURCE_GRPC_USE_TLS\", \"false\")\n .WithEnvironment(\"PYTHONUNBUFFERED\", \"1\")\n .WithEnvironment(\"PYTHONDONTWRITEBYTECODE\", \"1\")\n // LLM API Keys (set via environment variables or user secrets)\n .WithEnvironment(\"OPENAI_API_KEY\", Environment.GetEnvironmentVariable(\"OPENAI_API_KEY\") ?? \"\")\n .WithEnvironment(\"DEEPSEEK_API_KEY\", Environment.GetEnvironmentVariable(\"DEEPSEEK_API_KEY\") ?? \"\")\n .WaitFor(resourceApiService);\n ConfigureObservability(aiService);\n\n var apiGateway = builder\n .AddProject<Projects.ApiGateway>(\"apigateway\")\n .WithExternalHttpEndpoints()\n .WithReference(identityService)\n // .WithReference(identityApiService)\n .WithReference(classroomService)\n .WithReference(resourceApiService)\n .WithReference(notificationApiService)\n .WithReference(emulatorApiService)\n .WaitFor(identityService)\n .WaitFor(resourceApiService)\n .WaitFor(classroomService)\n .WaitFor(notificationApiService)\n .WaitFor(productApiService)\n .WaitFor(orderApiService)\n .WaitFor(cartApiService)\n .WaitFor(emulatorApiService)\n .WaitFor(paymentApiService)\n .WaitFor(aiService)\n ;\n ConfigureObservability(apiGateway);\n\n void ConfigureObservability<TResource>(IResourceBuilder<TResource> resourceBuilder)\n where TResource : IResourceWithEnvironment\n {\n var enabledValue = externalObservabilityEnabled ? \"true\" : \"false\";\n resourceBuilder.WithEnvironment(\"Observability__ExternalStackEnabled\", enabledValue);\n\n if (grafanaEndpoint is not null)\n {\n resourceBuilder.WithEnvironment(\"GRAFANA_URL\", grafanaEndpoint);\n }\n }\n\n return builder;\n }\n }\n}\n"}, "d1a2cf9a2cb796ece9bd2775df695e1dedb03aa9": {"t": 1785748787.6769233, "n": 13082, "h": "4c4a720a94528e98", "c": "using Aspire.Hosting;\nusing Aspire.Hosting.ApplicationModel;\nusing Microsoft.Extensions.Hosting;\nusing Projects;\nusing System;\nusing System.Collections.Generic;\nusing System.IO;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\n\nnamespace STEMifyBackend.AppHost.Extensions\n{\n public static class ServiceExtensions\n{\n public static IDistributedApplicationBuilder AddApplicationServices(this IDistributedApplicationBuilder builder)\n {\n var postgres = builder.AddPostgres(\"stemify-postgres\");\n\n var identityDb = postgres.AddDatabase(\"stemifyidentity\");\n\n var classroomDb = postgres.AddDatabase(\"stemifyclassroom\");\n\n var resourceDb = postgres.AddDatabase(\"stemifyresource\");\n\n var notificationDb = postgres.AddDatabase(\"stemifynotification\");\n\n var productDb = postgres.AddDatabase(\"stemifyproduct\");\n\n var orderDb = postgres.AddDatabase(\"stemifyorder\");\n var paymentDb = postgres.AddDatabase(\"stemifypayment\");\n\n var cartDb = postgres.AddDatabase(\"stemifycart\");\n\n var hangfireDb = postgres.AddDatabase(\"stemifyhangfire\");\n\n // // Add pgAdmin for database administration (Production)\n // var pgAdmin = builder\n // .AddContainer(\"pgadmin\", \"dpage/pgadmin4\")\n // .WithHttpEndpoint(targetPort: 5050, name: \"http\")\n // .WithExternalHttpEndpoints()\n // .WithEnvironment(\"PGADMIN_DEFAULT_EMAIL\", \"admin@stemify.com\")\n // .WithEnvironment(\"PGADMIN_DEFAULT_PASSWORD\", Environment.GetEnvironmentVariable(\"PGADMIN_PASSWORD\") ?? \"admin\")\n // .WithEnvironment(\"PGADMIN_CONFIG_SERVER_MODE\", \"False\")\n // .WithEnvironment(\"PGADMIN_CONFIG_MASTER_PASSWORD_REQUIRED\", \"False\")\n // .WithEnvironment(\"PGADMIN_LISTEN_PORT\", \"5050\")\n // .WaitFor(postgres);\n\n // Add RabbitMQ for messaging (required for Payment Service Saga pattern)\n var rabbitmq = builder.AddRabbitMQ(\"messaging\")\n .WithManagementPlugin(); \n\n var manualFlag = Environment.GetEnvironmentVariable(\"ENABLE_OBSERVABILITY_STACK\");\n var externalObservabilityEnabled = builder.Environment.IsDevelopment() ||\n string.Equals(manualFlag, \"true\", StringComparison.OrdinalIgnoreCase);\n EndpointReference? grafanaEndpoint = null;\n\n if (externalObservabilityEnabled)\n {\n var prometheus = builder.AddContainer(\"prometheus\", \"prom/prometheus\", \"v3.5.0\")\n .WithBindMount(\"../../../../config/prometheus\", \"/etc/prometheus\", isReadOnly: true)\n .WithArgs(\n \"--web.enable-otlp-receiver\",\n \"--config.file=/etc/prometheus/prometheus.yml\",\n \"--storage.tsdb.retention.time=15d\",\n \"--storage.tsdb.retention.size=10GB\"\n )\n .WithHttpEndpoint(targetPort: 9090, name: \"http\");\n\n var alertmanager = builder.AddContainer(\"alertmanager\", \"prom/alertmanager\", \"v0.27.0\")\n .WithBindMount(\"../../../../config/alertmanager\", \"/etc/alertmanager\", isReadOnly: true)\n .WithArgs(\"--config.file=/etc/alertmanager/alertmanager.yml\", \"--storage.path=/alertmanager\")\n .WithHttpEndpoint(targetPort: 9093, name: \"http\");\n\n var grafana = builder.AddContainer(\"grafana\", \"grafana/grafana\")\n .WithBindMount(\"../../../../config/grafana/config\", \"/etc/grafana\", isReadOnly: true)\n .WithBindMount(\"../../../../config/grafana/dashboards\", \"/var/lib/grafana/dashboards\", isReadOnly: true)\n .WithEnvironment(\"PROMETHEUS_ENDPOINT\", prometheus.GetEndpoint(\"http\"))\n .WithHttpEndpoint(targetPort: 3000, name: \"http\");\n\n grafanaEndpoint = grafana.GetEndpoint(\"http\");\n\n var configFile = \"../../../../config/otelcollector/config.dev.yaml\";\n\n builder.AddOpenTelemetryCollector(\"otelcollector\", configFile)\n .WithEnvironment(\"PROMETHEUS_ENDPOINT\", $\"{prometheus.GetEndpoint(\"http\")}/api/v1/otlp\")\n .WithEnvironment(\"ALERTMANAGER_ENDPOINT\", alertmanager.GetEndpoint(\"http\"));\n }\n\n var pgAdmin = builder.Environment.IsProduction()\n ? builder\n .AddContainer(\"pgadmin\", \"dpage/pgadmin4:8.10\")\n .WithHttpEndpoint(targetPort: 5050, name: \"pgadmin-http\")\n .WithExternalHttpEndpoints()\n .WithEnvironment(\"PGADMIN_DEFAULT_EMAIL\", \"admin@stemify.com\")\n .WithEnvironment(\n \"PGADMIN_DEFAULT_PASSWORD\",\n Environment.GetEnvironmentVariable(\"PGADMIN_PASSWORD\") ?? \"admin\"\n )\n .WithEnvironment(\"PGADMIN_CONFIG_SERVER_MODE\", \"False\")\n .WithEnvironment(\"PGADMIN_CONFIG_MASTER_PASSWORD_REQUIRED\", \"False\")\n .WithEnvironment(\"PGADMIN_LISTEN_PORT\", \"5050\")\n .WithEnvironment(\"PGADMIN_CONFIG_CHECK_EMAIL_DELIVERABILITY\", \"False\")\n .WithEnvironment(\"GUNICORN_TIMEOUT\", \"120\")\n .WaitFor(postgres)\n : postgres.WithPgAdmin(x =>\n {\n x.WithHttpEndpoint(targetPort: 5050, name: \"pgadmin-dev\");\n x.WithExternalHttpEndpoints();\n x.WithEnvironment(\"PGADMIN_CONFIG_CHECK_EMAIL_DELIVERABILITY\", \"False\");\n x.WithEnvironment(\"GUNICORN_TIMEOUT\", \"120\");\n });\n\n var identityService = builder\n .AddProject<Projects.Identity_Web>(\"identity\")\n .WithExternalHttpEndpoints()\n .WithReference(identityDb)\n .WithReference(rabbitmq)\n .WaitFor(postgres) \n .WaitFor(identityDb)\n .WaitFor(rabbitmq) \n ;\n ConfigureObservability(identityService);\n\n var identityApiService = builder\n .AddProject<Projects.Identity_API>(\"identity-api\")\n .WithExternalHttpEndpoints()\n .WithReference(identityDb)\n .WithReference(identityService)\n .WithReference(rabbitmq) \n .WaitFor(postgres)\n .WaitFor(identityDb)\n .WaitFor(identityService)\n .WaitFor(rabbitmq)\n ;\n ConfigureObservability(identityApiService);\n\n var sagaOrchestrator = builder\n .AddProject<Projects.Saga_Orchestrator>(\"saga-orchestrator\")\n .WithReference(identityService)\n .WithReference(rabbitmq)\n .WaitFor(identityService)\n .WaitFor(rabbitmq)\n ;\n ConfigureObservability(sagaOrchestrator);\n\n var classroomService = builder\n .AddProject<Projects.Classroom_API>(\"classroom-api\")\n .WithExternalHttpEndpoints()\n .WithReference(classroomDb)\n .WithReference(rabbitmq)\n .WaitFor(postgres)\n .WaitFor(classroomDb)\n .WaitFor(rabbitmq)\n ;\n ConfigureObservability(classroomService);\n\n var resourceApiService = builder\n .AddProject<Projects.Resource_API>(\"resource-api\")\n .WithExternalHttpEndpoints()\n .WithReference(resourceDb)\n .WithReference(rabbitmq)\n .WaitFor(postgres)\n .WaitFor(resourceDb)\n .WaitFor(rabbitmq)\n ;\n ConfigureObservability(resourceApiService);\n\n var notificationApiService = builder\n .AddProject<Projects.Notification_API>(\"notification-api\")\n .WithExternalHttpEndpoints()\n .WithReference(notificationDb)\n .WithReference(rabbitmq)\n .WaitFor(postgres)\n .WaitFor(notificationDb)\n .WaitFor(rabbitmq)\n ;\n ConfigureObservability(notificationApiService);\n\n var productApiService = builder\n .AddProject<Projects.Product_API>(\"product-api\")\n .WithExternalHttpEndpoints()\n .WithReference(productDb)\n .WithReference(rabbitmq)\n .WaitFor(postgres)\n .WaitFor(productDb)\n .WaitFor(rabbitmq)\n ;\n ConfigureObservability(productApiService);\n\n var orderApiService = builder\n .AddProject<Projects.Order_API>(\"order-api\")\n .WithExternalHttpEndpoints()\n .WithReference(orderDb)\n .WithReference(rabbitmq)\n .WaitFor(postgres)\n .WaitFor(orderDb)\n .WaitFor(rabbitmq)\n ;\n ConfigureObservability(orderApiService);\n\n var paymentApiService = builder\n .AddProject<Projects.Payment_API>(\"payment-api\")\n .WithExternalHttpEndpoints()\n .WithReference(paymentDb)\n .WithReference(rabbitmq)\n .WaitFor(postgres)\n .WaitFor(paymentDb)\n .WaitFor(rabbitmq)\n ;\n ConfigureObservability(paymentApiService);\n\n var cartApiService = builder\n .AddProject<Projects.Cart_API>(\"cart-api\")\n .WithExternalHttpEndpoints()\n .WithReference(cartDb)\n .WithReference(rabbitmq)\n .WaitFor(postgres)\n .WaitFor(cartDb)\n .WaitFor(rabbitmq)\n ;\n ConfigureObservability(cartApiService);\n\n var emulatorApiService = builder\n .AddProject<Projects.Emulator_API>(\"emulator-api\")\n .WithExternalHttpEndpoints();\n ConfigureObservability(emulatorApiService);\n\n var hangfireApiService = builder\n .AddProject<Projects.Hangfire_API>(\"hangfire-api\")\n .WithExternalHttpEndpoints()\n .WithReference(hangfireDb)\n .WithReference(identityDb)\n .WithReference(orderDb)\n .WithReference(rabbitmq)\n .WaitFor(postgres)\n .WaitFor(hangfireDb)\n .WaitFor(identityDb)\n .WaitFor(orderDb)\n .WaitFor(rabbitmq)\n ;\n ConfigureObservability(hangfireApiService);\n\n var aiService = builder\n .AddDockerfile(\"ai-service\", \"../../AIService\")\n .WithHttpEndpoint(targetPort: 80, name: \"http\")\n .WithExternalHttpEndpoints()\n .WithEnvironment(\"RESOURCE_GRPC_ENDPOINT\", $\"{resourceApiService.GetEndpoint(\"http\")}\".Replace(\"http://\", \"\").Replace(\"https://\", \"\"))\n .WithEnvironment(\"RESOURCE_GRPC_USE_TLS\", \"false\")\n .WithEnvironment(\"PYTHONUNBUFFERED\", \"1\")\n .WithEnvironment(\"PYTHONDONTWRITEBYTECODE\", \"1\")\n .WaitFor(resourceApiService);\n ConfigureObservability(aiService);\n\n var apiGateway = builder\n .AddProject<Projects.ApiGateway>(\"apigateway\")\n .WithExternalHttpEndpoints()\n .WithReference(identityService)\n // .WithReference(identityApiService)\n .WithReference(classroomService)\n .WithReference(resourceApiService)\n .WithReference(notificationApiService)\n .WithReference(emulatorApiService)\n .WaitFor(identityService)\n .WaitFor(resourceApiService)\n .WaitFor(classroomService)\n .WaitFor(notificationApiService)\n .WaitFor(productApiService)\n .WaitFor(orderApiService)\n .WaitFor(cartApiService)\n .WaitFor(emulatorApiService)\n .WaitFor(paymentApiService)\n .WaitFor(aiService)\n ;\n ConfigureObservability(apiGateway);\n\n void ConfigureObservability<TResource>(IResourceBuilder<TResource> resourceBuilder)\n where TResource : IResourceWithEnvironment\n {\n var enabledValue = externalObservabilityEnabled ? \"true\" : \"false\";\n resourceBuilder.WithEnvironment(\"Observability__ExternalStackEnabled\", enabledValue);\n\n if (grafanaEndpoint is not null)\n {\n resourceBuilder.WithEnvironment(\"GRAFANA_URL\", grafanaEndpoint);\n }\n }\n\n return builder;\n }\n }\n}\n"}, "bb6647c9eeae5d19be8863d34bfc4be33e106db2": {"t": 1785748788.8213608, "n": 13237, "h": "72808fc3efb489f5", "c": "using Aspire.Hosting;\nusing Aspire.Hosting.ApplicationModel;\nusing Microsoft.Extensions.Hosting;\nusing Projects;\nusing System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\n\nnamespace STEMifyBackend.AppHost.Extensions\n{\n public static class ServiceExtensions\n{\n public static IDistributedApplicationBuilder AddApplicationServices(this IDistributedApplicationBuilder builder)\n {\n var postgres = builder.AddPostgres(\"stemify-postgres\");\n\n var identityDb = postgres.AddDatabase(\"stemifyidentity\");\n\n var classroomDb = postgres.AddDatabase(\"stemifyclassroom\");\n\n var resourceDb = postgres.AddDatabase(\"stemifyresource\");\n\n var notificationDb = postgres.AddDatabase(\"stemifynotification\");\n\n var productDb = postgres.AddDatabase(\"stemifyproduct\");\n\n var orderDb = postgres.AddDatabase(\"stemifyorder\");\n var paymentDb = postgres.AddDatabase(\"stemifypayment\");\n\n var cartDb = postgres.AddDatabase(\"stemifycart\");\n\n var hangfireDb = postgres.AddDatabase(\"stemifyhangfire\");\n\n // // Add pgAdmin for database administration (Production)\n // var pgAdmin = builder\n // .AddContainer(\"pgadmin\", \"dpage/pgadmin4\")\n // .WithHttpEndpoint(targetPort: 5050, name: \"http\")\n // .WithExternalHttpEndpoints()\n // .WithEnvironment(\"PGADMIN_DEFAULT_EMAIL\", \"admin@stemify.com\")\n // .WithEnvironment(\"PGADMIN_DEFAULT_PASSWORD\", Environment.GetEnvironmentVariable(\"PGADMIN_PASSWORD\") ?? \"admin\")\n // .WithEnvironment(\"PGADMIN_CONFIG_SERVER_MODE\", \"False\")\n // .WithEnvironment(\"PGADMIN_CONFIG_MASTER_PASSWORD_REQUIRED\", \"False\")\n // .WithEnvironment(\"PGADMIN_LISTEN_PORT\", \"5050\")\n // .WaitFor(postgres);\n\n // Add RabbitMQ for messaging (required for Payment Service Saga pattern)\n var rabbitmq = builder.AddRabbitMQ(\"messaging\")\n .WithManagementPlugin(); \n\n var manualFlag = Environment.GetEnvironmentVariable(\"ENABLE_OBSERVABILITY_STACK\");\n var externalObservabilityEnabled = builder.Environment.IsDevelopment() ||\n string.Equals(manualFlag, \"true\", StringComparison.OrdinalIgnoreCase);\n EndpointReference? grafanaEndpoint = null;\n\n if (externalObservabilityEnabled)\n {\n var prometheus = builder.AddContainer(\"prometheus\", \"prom/prometheus\", \"v3.5.0\")\n .WithBindMount(\"../../../../config/prometheus\", \"/etc/prometheus\", isReadOnly: true)\n .WithArgs(\n \"--web.enable-otlp-receiver\",\n \"--config.file=/etc/prometheus/prometheus.yml\",\n \"--storage.tsdb.retention.time=15d\",\n \"--storage.tsdb.retention.size=10GB\"\n )\n .WithHttpEndpoint(targetPort: 9090, name: \"http\");\n\n var alertmanager = builder.AddContainer(\"alertmanager\", \"prom/alertmanager\", \"v0.27.0\")\n .WithBindMount(\"../../../../config/alertmanager\", \"/etc/alertmanager\", isReadOnly: true)\n .WithArgs(\"--config.file=/etc/alertmanager/alertmanager.yml\", \"--storage.path=/alertmanager\")\n .WithHttpEndpoint(targetPort: 9093, name: \"http\");\n\n var grafana = builder.AddContainer(\"grafana\", \"grafana/grafana\")\n .WithBindMount(\"../../../../config/grafana/config\", \"/etc/grafana\", isReadOnly: true)\n .WithBindMount(\"../../../../config/grafana/dashboards\", \"/var/lib/grafana/dashboards\", isReadOnly: true)\n .WithEnvironment(\"PROMETHEUS_ENDPOINT\", prometheus.GetEndpoint(\"http\"))\n .WithHttpEndpoint(targetPort: 3000, name: \"http\");\n\n grafanaEndpoint = grafana.GetEndpoint(\"http\");\n\n var configFile = \"../../../../config/otelcollector/config.dev.yaml\";\n\n builder.AddOpenTelemetryCollector(\"otelcollector\", configFile)\n .WithEnvironment(\"PROMETHEUS_ENDPOINT\", $\"{prometheus.GetEndpoint(\"http\")}/api/v1/otlp\")\n .WithEnvironment(\"ALERTMANAGER_ENDPOINT\", alertmanager.GetEndpoint(\"http\"));\n }\n\n var pgAdmin = builder.Environment.IsProduction()\n ? builder\n .AddContainer(\"pgadmin\", \"dpage/pgadmin4:8.10\")\n .WithHttpEndpoint(targetPort: 5050, name: \"pgadmin-http\")\n .WithExternalHttpEndpoints()\n .WithEnvironment(\"PGADMIN_DEFAULT_EMAIL\", \"admin@stemify.com\")\n .WithEnvironment(\n \"PGADMIN_DEFAULT_PASSWORD\",\n Environment.GetEnvironmentVariable(\"PGADMIN_PASSWORD\") ?? \"admin\"\n )\n .WithEnvironment(\"PGADMIN_CONFIG_SERVER_MODE\", \"False\")\n .WithEnvironment(\"PGADMIN_CONFIG_MASTER_PASSWORD_REQUIRED\", \"False\")\n .WithEnvironment(\"PGADMIN_LISTEN_PORT\", \"5050\")\n .WithEnvironment(\"PGADMIN_CONFIG_CHECK_EMAIL_DELIVERABILITY\", \"False\")\n .WithEnvironment(\"GUNICORN_TIMEOUT\", \"120\")\n .WaitFor(postgres)\n : postgres.WithPgAdmin(x =>\n {\n x.WithHttpEndpoint(targetPort: 5050, name: \"pgadmin-dev\");\n x.WithExternalHttpEndpoints();\n x.WithEnvironment(\"PGADMIN_CONFIG_CHECK_EMAIL_DELIVERABILITY\", \"False\");\n x.WithEnvironment(\"GUNICORN_TIMEOUT\", \"120\");\n });\n\n var identityService = builder\n .AddProject<Projects.Identity_Web>(\"identity\")\n .WithExternalHttpEndpoints()\n .WithReference(identityDb)\n .WithReference(rabbitmq)\n .WaitFor(postgres) \n .WaitFor(identityDb)\n .WaitFor(rabbitmq) \n ;\n ConfigureObservability(identityService);\n\n var identityApiService = builder\n .AddProject<Projects.Identity_API>(\"identity-api\")\n .WithExternalHttpEndpoints()\n .WithReference(identityDb)\n .WithReference(identityService)\n .WithReference(rabbitmq) \n .WaitFor(postgres)\n .WaitFor(identityDb)\n .WaitFor(identityService)\n .WaitFor(rabbitmq)\n ;\n ConfigureObservability(identityApiService);\n\n var sagaOrchestrator = builder\n .AddProject<Projects.Saga_Orchestrator>(\"saga-orchestrator\")\n .WithReference(identityService)\n .WithReference(rabbitmq)\n .WaitFor(identityService)\n .WaitFor(rabbitmq)\n ;\n ConfigureObservability(sagaOrchestrator);\n\n var classroomService = builder\n .AddProject<Projects.Classroom_API>(\"classroom-api\")\n .WithExternalHttpEndpoints()\n .WithReference(classroomDb)\n .WithReference(rabbitmq)\n .WaitFor(postgres)\n .WaitFor(classroomDb)\n .WaitFor(rabbitmq)\n ;\n ConfigureObservability(classroomService);\n\n var resourceApiService = builder\n .AddProject<Projects.Resource_API>(\"resource-api\")\n .WithExternalHttpEndpoints()\n .WithReference(resourceDb)\n .WithReference(rabbitmq)\n .WaitFor(postgres)\n .WaitFor(resourceDb)\n .WaitFor(rabbitmq)\n ;\n ConfigureObservability(resourceApiService);\n\n var notificationApiService = builder\n .AddProject<Projects.Notification_API>(\"notification-api\")\n .WithExternalHttpEndpoints()\n .WithReference(notificationDb)\n .WithReference(rabbitmq)\n .WaitFor(postgres)\n .WaitFor(notificationDb)\n .WaitFor(rabbitmq)\n ;\n ConfigureObservability(notificationApiService);\n\n var productApiService = builder\n .AddProject<Projects.Product_API>(\"product-api\")\n .WithExternalHttpEndpoints()\n .WithReference(productDb)\n .WithReference(rabbitmq)\n .WaitFor(postgres)\n .WaitFor(productDb)\n .WaitFor(rabbitmq)\n ;\n ConfigureObservability(productApiService);\n\n var orderApiService = builder\n .AddProject<Projects.Order_API>(\"order-api\")\n .WithExternalHttpEndpoints()\n .WithReference(orderDb)\n .WithReference(rabbitmq)\n .WaitFor(postgres)\n .WaitFor(orderDb)\n .WaitFor(rabbitmq)\n ;\n ConfigureObservability(orderApiService);\n\n var paymentApiService = builder\n .AddProject<Projects.Payment_API>(\"payment-api\")\n .WithExternalHttpEndpoints()\n .WithReference(paymentDb)\n .WithReference(rabbitmq)\n .WaitFor(postgres)\n .WaitFor(paymentDb)\n .WaitFor(rabbitmq)\n ;\n ConfigureObservability(paymentApiService);\n\n var cartApiService = builder\n .AddProject<Projects.Cart_API>(\"cart-api\")\n .WithExternalHttpEndpoints()\n .WithReference(cartDb)\n .WithReference(rabbitmq)\n .WaitFor(postgres)\n .WaitFor(cartDb)\n .WaitFor(rabbitmq)\n ;\n ConfigureObservability(cartApiService);\n\n var emulatorApiService = builder\n .AddProject<Projects.Emulator_API>(\"emulator-api\")\n .WithExternalHttpEndpoints();\n ConfigureObservability(emulatorApiService);\n\n var hangfireApiService = builder\n .AddProject<Projects.Hangfire_API>(\"hangfire-api\")\n .WithExternalHttpEndpoints()\n .WithReference(hangfireDb)\n .WithReference(identityDb)\n .WithReference(orderDb)\n .WithReference(rabbitmq)\n .WaitFor(postgres)\n .WaitFor(hangfireDb)\n .WaitFor(identityDb)\n .WaitFor(orderDb)\n .WaitFor(rabbitmq)\n ;\n ConfigureObservability(hangfireApiService);\n\n // Add AI Service (Python FastAPI) as container\n var aiService = builder\n .AddContainer(\"ai-service\", \"ai-service\")\n .WithDockerfile(\"../../AIService/Dockerfile\", \"../../AIService\")\n .WithHttpEndpoint(targetPort: 80, name: \"http\")\n .WithExternalHttpEndpoints()\n .WithEnvironment(\"RESOURCE_GRPC_ENDPOINT\", resourceApiService.GetEndpoint(\"http\").Replace(\"http://\", \"\").Replace(\"https://\", \"\"))\n .WithEnvironment(\"RESOURCE_GRPC_USE_TLS\", \"false\")\n .WithEnvironment(\"PYTHONUNBUFFERED\", \"1\")\n .WithEnvironment(\"PYTHONDONTWRITEBYTECODE\", \"1\")\n .WaitFor(resourceApiService);\n ConfigureObservability(aiService);\n\n var apiGateway = builder\n .AddProject<Projects.ApiGateway>(\"apigateway\")\n .WithExternalHttpEndpoints()\n .WithReference(identityService)\n // .WithReference(identityApiService)\n .WithReference(classroomService)\n .WithReference(resourceApiService)\n .WithReference(notificationApiService)\n .WithReference(emulatorApiService)\n .WithReference(aiService)\n .WaitFor(identityService)\n .WaitFor(resourceApiService)\n .WaitFor(classroomService)\n .WaitFor(notificationApiService)\n .WaitFor(productApiService)\n .WaitFor(orderApiService)\n .WaitFor(cartApiService)\n .WaitFor(emulatorApiService)\n .WaitFor(paymentApiService)\n .WaitFor(aiService)\n ;\n ConfigureObservability(apiGateway);\n\n void ConfigureObservability<TResource>(IResourceBuilder<TResource> resourceBuilder)\n where TResource : IResourceWithEnvironment\n {\n var enabledValue = externalObservabilityEnabled ? \"true\" : \"false\";\n resourceBuilder.WithEnvironment(\"Observability__ExternalStackEnabled\", enabledValue);\n\n if (grafanaEndpoint is not null)\n {\n resourceBuilder.WithEnvironment(\"GRAFANA_URL\", grafanaEndpoint);\n }\n }\n\n return builder;\n }\n }\n}\n"}, "95e279429c9b998fcabb8117d04c5e20466b1af7": {"t": 1785748789.918201, "n": 12385, "h": "bb67439a1dc07481", "c": "using Aspire.Hosting;\nusing Aspire.Hosting.ApplicationModel;\nusing Microsoft.Extensions.Hosting;\nusing Projects;\nusing System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\n\nnamespace STEMifyBackend.AppHost.Extensions\n{\n public static class ServiceExtensions\n{\n public static IDistributedApplicationBuilder AddApplicationServices(this IDistributedApplicationBuilder builder)\n {\n var postgres = builder.AddPostgres(\"stemify-postgres\");\n\n var identityDb = postgres.AddDatabase(\"stemifyidentity\");\n\n var classroomDb = postgres.AddDatabase(\"stemifyclassroom\");\n\n var resourceDb = postgres.AddDatabase(\"stemifyresource\");\n\n var notificationDb = postgres.AddDatabase(\"stemifynotification\");\n\n var productDb = postgres.AddDatabase(\"stemifyproduct\");\n\n var orderDb = postgres.AddDatabase(\"stemifyorder\");\n var paymentDb = postgres.AddDatabase(\"stemifypayment\");\n\n var cartDb = postgres.AddDatabase(\"stemifycart\");\n\n var hangfireDb = postgres.AddDatabase(\"stemifyhangfire\");\n\n // // Add pgAdmin for database administration (Production)\n // var pgAdmin = builder\n // .AddContainer(\"pgadmin\", \"dpage/pgadmin4\")\n // .WithHttpEndpoint(targetPort: 5050, name: \"http\")\n // .WithExternalHttpEndpoints()\n // .WithEnvironment(\"PGADMIN_DEFAULT_EMAIL\", \"admin@stemify.com\")\n // .WithEnvironment(\"PGADMIN_DEFAULT_PASSWORD\", Environment.GetEnvironmentVariable(\"PGADMIN_PASSWORD\") ?? \"admin\")\n // .WithEnvironment(\"PGADMIN_CONFIG_SERVER_MODE\", \"False\")\n // .WithEnvironment(\"PGADMIN_CONFIG_MASTER_PASSWORD_REQUIRED\", \"False\")\n // .WithEnvironment(\"PGADMIN_LISTEN_PORT\", \"5050\")\n // .WaitFor(postgres);\n\n // Add RabbitMQ for messaging (required for Payment Service Saga pattern)\n var rabbitmq = builder.AddRabbitMQ(\"messaging\")\n .WithManagementPlugin(); \n\n var manualFlag = Environment.GetEnvironmentVariable(\"ENABLE_OBSERVABILITY_STACK\");\n var externalObservabilityEnabled = builder.Environment.IsDevelopment() ||\n string.Equals(manualFlag, \"true\", StringComparison.OrdinalIgnoreCase);\n EndpointReference? grafanaEndpoint = null;\n\n if (externalObservabilityEnabled)\n {\n var prometheus = builder.AddContainer(\"prometheus\", \"prom/prometheus\", \"v3.5.0\")\n .WithBindMount(\"../../../../config/prometheus\", \"/etc/prometheus\", isReadOnly: true)\n .WithArgs(\n \"--web.enable-otlp-receiver\",\n \"--config.file=/etc/prometheus/prometheus.yml\",\n \"--storage.tsdb.retention.time=15d\",\n \"--storage.tsdb.retention.size=10GB\"\n )\n .WithHttpEndpoint(targetPort: 9090, name: \"http\");\n\n var alertmanager = builder.AddContainer(\"alertmanager\", \"prom/alertmanager\", \"v0.27.0\")\n .WithBindMount(\"../../../../config/alertmanager\", \"/etc/alertmanager\", isReadOnly: true)\n .WithArgs(\"--config.file=/etc/alertmanager/alertmanager.yml\", \"--storage.path=/alertmanager\")\n .WithHttpEndpoint(targetPort: 9093, name: \"http\");\n\n var grafana = builder.AddContainer(\"grafana\", \"grafana/grafana\")\n .WithBindMount(\"../../../../config/grafana/config\", \"/etc/grafana\", isReadOnly: true)\n .WithBindMount(\"../../../../config/grafana/dashboards\", \"/var/lib/grafana/dashboards\", isReadOnly: true)\n .WithEnvironment(\"PROMETHEUS_ENDPOINT\", prometheus.GetEndpoint(\"http\"))\n .WithHttpEndpoint(targetPort: 3000, name: \"http\");\n\n grafanaEndpoint = grafana.GetEndpoint(\"http\");\n\n var configFile = \"../../../../config/otelcollector/config.dev.yaml\";\n\n builder.AddOpenTelemetryCollector(\"otelcollector\", configFile)\n .WithEnvironment(\"PROMETHEUS_ENDPOINT\", $\"{prometheus.GetEndpoint(\"http\")}/api/v1/otlp\")\n .WithEnvironment(\"ALERTMANAGER_ENDPOINT\", alertmanager.GetEndpoint(\"http\"));\n }\n\n var pgAdmin = builder.Environment.IsProduction()\n ? builder\n .AddContainer(\"pgadmin\", \"dpage/pgadmin4:8.10\")\n .WithHttpEndpoint(targetPort: 5050, name: \"pgadmin-http\")\n .WithExternalHttpEndpoints()\n .WithEnvironment(\"PGADMIN_DEFAULT_EMAIL\", \"admin@stemify.com\")\n .WithEnvironment(\n \"PGADMIN_DEFAULT_PASSWORD\",\n Environment.GetEnvironmentVariable(\"PGADMIN_PASSWORD\") ?? \"admin\"\n )\n .WithEnvironment(\"PGADMIN_CONFIG_SERVER_MODE\", \"False\")\n .WithEnvironment(\"PGADMIN_CONFIG_MASTER_PASSWORD_REQUIRED\", \"False\")\n .WithEnvironment(\"PGADMIN_LISTEN_PORT\", \"5050\")\n .WithEnvironment(\"PGADMIN_CONFIG_CHECK_EMAIL_DELIVERABILITY\", \"False\")\n .WithEnvironment(\"GUNICORN_TIMEOUT\", \"120\")\n .WaitFor(postgres)\n : postgres.WithPgAdmin(x =>\n {\n x.WithHttpEndpoint(targetPort: 5050, name: \"pgadmin-dev\");\n x.WithExternalHttpEndpoints();\n x.WithEnvironment(\"PGADMIN_CONFIG_CHECK_EMAIL_DELIVERABILITY\", \"False\");\n x.WithEnvironment(\"GUNICORN_TIMEOUT\", \"120\");\n });\n\n var identityService = builder\n .AddProject<Projects.Identity_Web>(\"identity\")\n .WithExternalHttpEndpoints()\n .WithReference(identityDb)\n .WithReference(rabbitmq)\n .WaitFor(postgres) \n .WaitFor(identityDb)\n .WaitFor(rabbitmq) \n ;\n ConfigureObservability(identityService);\n\n var identityApiService = builder\n .AddProject<Projects.Identity_API>(\"identity-api\")\n .WithExternalHttpEndpoints()\n .WithReference(identityDb)\n .WithReference(identityService)\n .WithReference(rabbitmq) \n .WaitFor(postgres)\n .WaitFor(identityDb)\n .WaitFor(identityService)\n .WaitFor(rabbitmq)\n ;\n ConfigureObservability(identityApiService);\n\n var sagaOrchestrator = builder\n .AddProject<Projects.Saga_Orchestrator>(\"saga-orchestrator\")\n .WithReference(identityService)\n .WithReference(rabbitmq)\n .WaitFor(identityService)\n .WaitFor(rabbitmq)\n ;\n ConfigureObservability(sagaOrchestrator);\n\n var classroomService = builder\n .AddProject<Projects.Classroom_API>(\"classroom-api\")\n .WithExternalHttpEndpoints()\n .WithReference(classroomDb)\n .WithReference(rabbitmq)\n .WaitFor(postgres)\n .WaitFor(classroomDb)\n .WaitFor(rabbitmq)\n ;\n ConfigureObservability(classroomService);\n\n var resourceApiService = builder\n .AddProject<Projects.Resource_API>(\"resource-api\")\n .WithExternalHttpEndpoints()\n .WithReference(resourceDb)\n .WithReference(rabbitmq)\n .WaitFor(postgres)\n .WaitFor(resourceDb)\n .WaitFor(rabbitmq)\n ;\n ConfigureObservability(resourceApiService);\n\n var notificationApiService = builder\n .AddProject<Projects.Notification_API>(\"notification-api\")\n .WithExternalHttpEndpoints()\n .WithReference(notificationDb)\n .WithReference(rabbitmq)\n .WaitFor(postgres)\n .WaitFor(notificationDb)\n .WaitFor(rabbitmq)\n ;\n ConfigureObservability(notificationApiService);\n\n var productApiService = builder\n .AddProject<Projects.Product_API>(\"product-api\")\n .WithExternalHttpEndpoints()\n .WithReference(productDb)\n .WithReference(rabbitmq)\n .WaitFor(postgres)\n .WaitFor(productDb)\n .WaitFor(rabbitmq)\n ;\n ConfigureObservability(productApiService);\n\n var orderApiService = builder\n .AddProject<Projects.Order_API>(\"order-api\")\n .WithExternalHttpEndpoints()\n .WithReference(orderDb)\n .WithReference(rabbitmq)\n .WaitFor(postgres)\n .WaitFor(orderDb)\n .WaitFor(rabbitmq)\n ;\n ConfigureObservability(orderApiService);\n\n var paymentApiService = builder\n .AddProject<Projects.Payment_API>(\"payment-api\")\n .WithExternalHttpEndpoints()\n .WithReference(paymentDb)\n .WithReference(rabbitmq)\n .WaitFor(postgres)\n .WaitFor(paymentDb)\n .WaitFor(rabbitmq)\n ;\n ConfigureObservability(paymentApiService);\n\n var cartApiService = builder\n .AddProject<Projects.Cart_API>(\"cart-api\")\n .WithExternalHttpEndpoints()\n .WithReference(cartDb)\n .WithReference(rabbitmq)\n .WaitFor(postgres)\n .WaitFor(cartDb)\n .WaitFor(rabbitmq)\n ;\n ConfigureObservability(cartApiService);\n\n var emulatorApiService = builder\n .AddProject<Projects.Emulator_API>(\"emulator-api\")\n .WithExternalHttpEndpoints();\n ConfigureObservability(emulatorApiService);\n\n var hangfireApiService = builder\n .AddProject<Projects.Hangfire_API>(\"hangfire-api\")\n .WithExternalHttpEndpoints()\n .WithReference(hangfireDb)\n .WithReference(identityDb)\n .WithReference(orderDb)\n .WithReference(rabbitmq)\n .WaitFor(postgres)\n .WaitFor(hangfireDb)\n .WaitFor(identityDb)\n .WaitFor(orderDb)\n .WaitFor(rabbitmq)\n ;\n ConfigureObservability(hangfireApiService);\n\n var apiGateway = builder\n .AddProject<Projects.ApiGateway>(\"apigateway\")\n .WithExternalHttpEndpoints()\n .WithReference(identityService)\n // .WithReference(identityApiService)\n .WithReference(classroomService)\n .WithReference(resourceApiService)\n .WithReference(notificationApiService)\n .WithReference(emulatorApiService)\n .WaitFor(identityService)\n .WaitFor(resourceApiService)\n .WaitFor(classroomService)\n .WaitFor(notificationApiService)\n .WaitFor(productApiService)\n .WaitFor(orderApiService)\n .WaitFor(cartApiService)\n .WaitFor(emulatorApiService)\n .WaitFor(paymentApiService)\n ;\n ConfigureObservability(apiGateway);\n\n void ConfigureObservability<TResource>(IResourceBuilder<TResource> resourceBuilder)\n where TResource : IResourceWithEnvironment\n {\n var enabledValue = externalObservabilityEnabled ? \"true\" : \"false\";\n resourceBuilder.WithEnvironment(\"Observability__ExternalStackEnabled\", enabledValue);\n\n if (grafanaEndpoint is not null)\n {\n resourceBuilder.WithEnvironment(\"GRAFANA_URL\", grafanaEndpoint);\n }\n }\n\n return builder;\n }\n }\n}\n"}, "f868e723650af55164288f025bb35dd068bfdf82": {"t": 1785748791.0355375, "n": 12170, "h": "e49caa975e4d1889", "c": "using Aspire.Hosting;\nusing Aspire.Hosting.ApplicationModel;\nusing Microsoft.Extensions.Hosting;\nusing Projects;\nusing System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\n\nnamespace STEMifyBackend.AppHost.Extensions\n{\n public static class ServiceExtensions\n{\n public static IDistributedApplicationBuilder AddApplicationServices(this IDistributedApplicationBuilder builder)\n {\n var postgres = builder.AddPostgres(\"stemify-postgres\");\n\n var identityDb = postgres.AddDatabase(\"stemifyidentity\");\n\n var classroomDb = postgres.AddDatabase(\"stemifyclassroom\");\n\n var resourceDb = postgres.AddDatabase(\"stemifyresource\");\n\n var notificationDb = postgres.AddDatabase(\"stemifynotification\");\n\n var productDb = postgres.AddDatabase(\"stemifyproduct\");\n\n var orderDb = postgres.AddDatabase(\"stemifyorder\");\n var paymentDb = postgres.AddDatabase(\"stemifypayment\");\n\n var cartDb = postgres.AddDatabase(\"stemifycart\");\n\n var hangfireDb = postgres.AddDatabase(\"stemifyhangfire\");\n\n // // Add pgAdmin for database administration (Production)\n // var pgAdmin = builder\n // .AddContainer(\"pgadmin\", \"dpage/pgadmin4\")\n // .WithHttpEndpoint(targetPort: 5050, name: \"http\")\n // .WithExternalHttpEndpoints()\n // .WithEnvironment(\"PGADMIN_DEFAULT_EMAIL\", \"admin@stemify.com\")\n // .WithEnvironment(\"PGADMIN_DEFAULT_PASSWORD\", Environment.GetEnvironmentVariable(\"PGADMIN_PASSWORD\") ?? \"admin\")\n // .WithEnvironment(\"PGADMIN_CONFIG_SERVER_MODE\", \"False\")\n // .WithEnvironment(\"PGADMIN_CONFIG_MASTER_PASSWORD_REQUIRED\", \"False\")\n // .WithEnvironment(\"PGADMIN_LISTEN_PORT\", \"5050\")\n // .WaitFor(postgres);\n\n // Add RabbitMQ for messaging (required for Payment Service Saga pattern)\n var rabbitmq = builder.AddRabbitMQ(\"messaging\")\n .WithManagementPlugin(); \n\n var externalObservabilityEnabled = builder.Environment.IsDevelopment();\n EndpointReference? grafanaEndpoint = null;\n\n if (externalObservabilityEnabled)\n {\n var prometheus = builder.AddContainer(\"prometheus\", \"prom/prometheus\", \"v3.5.0\")\n .WithBindMount(\"../../../../config/prometheus\", \"/etc/prometheus\", isReadOnly: true)\n .WithArgs(\n \"--web.enable-otlp-receiver\",\n \"--config.file=/etc/prometheus/prometheus.yml\",\n \"--storage.tsdb.retention.time=15d\",\n \"--storage.tsdb.retention.size=10GB\"\n )\n .WithHttpEndpoint(targetPort: 9090, name: \"http\");\n\n var alertmanager = builder.AddContainer(\"alertmanager\", \"prom/alertmanager\", \"v0.27.0\")\n .WithBindMount(\"../../../../config/alertmanager\", \"/etc/alertmanager\", isReadOnly: true)\n .WithArgs(\"--config.file=/etc/alertmanager/alertmanager.yml\", \"--storage.path=/alertmanager\")\n .WithHttpEndpoint(targetPort: 9093, name: \"http\");\n\n var grafana = builder.AddContainer(\"grafana\", \"grafana/grafana\")\n .WithBindMount(\"../../../../config/grafana/config\", \"/etc/grafana\", isReadOnly: true)\n .WithBindMount(\"../../../../config/grafana/dashboards\", \"/var/lib/grafana/dashboards\", isReadOnly: true)\n .WithEnvironment(\"PROMETHEUS_ENDPOINT\", prometheus.GetEndpoint(\"http\"))\n .WithHttpEndpoint(targetPort: 3000, name: \"http\");\n\n grafanaEndpoint = grafana.GetEndpoint(\"http\");\n\n var configFile = \"../../../../config/otelcollector/config.dev.yaml\";\n\n builder.AddOpenTelemetryCollector(\"otelcollector\", configFile)\n .WithEnvironment(\"PROMETHEUS_ENDPOINT\", $\"{prometheus.GetEndpoint(\"http\")}/api/v1/otlp\")\n .WithEnvironment(\"ALERTMANAGER_ENDPOINT\", alertmanager.GetEndpoint(\"http\"));\n }\n\n var pgAdmin = builder.Environment.IsProduction()\n ? builder\n .AddContainer(\"pgadmin\", \"dpage/pgadmin4:8.10\")\n .WithHttpEndpoint(targetPort: 5050, name: \"pgadmin-http\")\n .WithExternalHttpEndpoints()\n .WithEnvironment(\"PGADMIN_DEFAULT_EMAIL\", \"admin@stemify.com\")\n .WithEnvironment(\n \"PGADMIN_DEFAULT_PASSWORD\",\n Environment.GetEnvironmentVariable(\"PGADMIN_PASSWORD\") ?? \"admin\"\n )\n .WithEnvironment(\"PGADMIN_CONFIG_SERVER_MODE\", \"False\")\n .WithEnvironment(\"PGADMIN_CONFIG_MASTER_PASSWORD_REQUIRED\", \"False\")\n .WithEnvironment(\"PGADMIN_LISTEN_PORT\", \"5050\")\n .WithEnvironment(\"PGADMIN_CONFIG_CHECK_EMAIL_DELIVERABILITY\", \"False\")\n .WithEnvironment(\"GUNICORN_TIMEOUT\", \"120\")\n .WaitFor(postgres)\n : postgres.WithPgAdmin(x =>\n {\n x.WithHttpEndpoint(targetPort: 5050, name: \"pgadmin-dev\");\n x.WithExternalHttpEndpoints();\n x.WithEnvironment(\"PGADMIN_CONFIG_CHECK_EMAIL_DELIVERABILITY\", \"False\");\n x.WithEnvironment(\"GUNICORN_TIMEOUT\", \"120\");\n });\n\n var identityService = builder\n .AddProject<Projects.Identity_Web>(\"identity\")\n .WithExternalHttpEndpoints()\n .WithReference(identityDb)\n .WithReference(rabbitmq)\n .WaitFor(postgres) \n .WaitFor(identityDb)\n .WaitFor(rabbitmq) \n ;\n ConfigureObservability(identityService);\n\n var identityApiService = builder\n .AddProject<Projects.Identity_API>(\"identity-api\")\n .WithExternalHttpEndpoints()\n .WithReference(identityDb)\n .WithReference(identityService)\n .WithReference(rabbitmq) \n .WaitFor(postgres)\n .WaitFor(identityDb)\n .WaitFor(identityService)\n .WaitFor(rabbitmq)\n ;\n ConfigureObservability(identityApiService);\n\n var sagaOrchestrator = builder\n .AddProject<Projects.Saga_Orchestrator>(\"saga-orchestrator\")\n .WithReference(identityService)\n .WithReference(rabbitmq)\n .WaitFor(identityService)\n .WaitFor(rabbitmq)\n ;\n ConfigureObservability(sagaOrchestrator);\n\n var classroomService = builder\n .AddProject<Projects.Classroom_API>(\"classroom-api\")\n .WithExternalHttpEndpoints()\n .WithReference(classroomDb)\n .WithReference(rabbitmq)\n .WaitFor(postgres)\n .WaitFor(classroomDb)\n .WaitFor(rabbitmq)\n ;\n ConfigureObservability(classroomService);\n\n var resourceApiService = builder\n .AddProject<Projects.Resource_API>(\"resource-api\")\n .WithExternalHttpEndpoints()\n .WithReference(resourceDb)\n .WithReference(rabbitmq)\n .WaitFor(postgres)\n .WaitFor(resourceDb)\n .WaitFor(rabbitmq)\n ;\n ConfigureObservability(resourceApiService);\n\n var notificationApiService = builder\n .AddProject<Projects.Notification_API>(\"notification-api\")\n .WithExternalHttpEndpoints()\n .WithReference(notificationDb)\n .WithReference(rabbitmq)\n .WaitFor(postgres)\n .WaitFor(notificationDb)\n .WaitFor(rabbitmq)\n ;\n ConfigureObservability(notificationApiService);\n\n var productApiService = builder\n .AddProject<Projects.Product_API>(\"product-api\")\n .WithExternalHttpEndpoints()\n .WithReference(productDb)\n .WithReference(rabbitmq)\n .WaitFor(postgres)\n .WaitFor(productDb)\n .WaitFor(rabbitmq)\n ;\n ConfigureObservability(productApiService);\n\n var orderApiService = builder\n .AddProject<Projects.Order_API>(\"order-api\")\n .WithExternalHttpEndpoints()\n .WithReference(orderDb)\n .WithReference(rabbitmq)\n .WaitFor(postgres)\n .WaitFor(orderDb)\n .WaitFor(rabbitmq)\n ;\n ConfigureObservability(orderApiService);\n\n var paymentApiService = builder\n .AddProject<Projects.Payment_API>(\"payment-api\")\n .WithExternalHttpEndpoints()\n .WithReference(paymentDb)\n .WithReference(rabbitmq)\n .WaitFor(postgres)\n .WaitFor(paymentDb)\n .WaitFor(rabbitmq)\n ;\n ConfigureObservability(paymentApiService);\n\n var cartApiService = builder\n .AddProject<Projects.Cart_API>(\"cart-api\")\n .WithExternalHttpEndpoints()\n .WithReference(cartDb)\n .WithReference(rabbitmq)\n .WaitFor(postgres)\n .WaitFor(cartDb)\n .WaitFor(rabbitmq)\n ;\n ConfigureObservability(cartApiService);\n\n var emulatorApiService = builder\n .AddProject<Projects.Emulator_API>(\"emulator-api\")\n .WithExternalHttpEndpoints();\n ConfigureObservability(emulatorApiService);\n\n var hangfireApiService = builder\n .AddProject<Projects.Hangfire_API>(\"hangfire-api\")\n .WithExternalHttpEndpoints()\n .WithReference(hangfireDb)\n .WithReference(identityDb)\n .WithReference(orderDb)\n .WithReference(rabbitmq)\n .WaitFor(postgres)\n .WaitFor(hangfireDb)\n .WaitFor(identityDb)\n .WaitFor(orderDb)\n .WaitFor(rabbitmq)\n ;\n ConfigureObservability(hangfireApiService);\n\n var apiGateway = builder\n .AddProject<Projects.ApiGateway>(\"apigateway\")\n .WithExternalHttpEndpoints()\n .WithReference(identityService)\n // .WithReference(identityApiService)\n .WithReference(classroomService)\n .WithReference(resourceApiService)\n .WithReference(notificationApiService)\n .WithReference(emulatorApiService)\n .WaitFor(identityService)\n .WaitFor(resourceApiService)\n .WaitFor(classroomService)\n .WaitFor(notificationApiService)\n .WaitFor(productApiService)\n .WaitFor(orderApiService)\n .WaitFor(cartApiService)\n .WaitFor(emulatorApiService)\n .WaitFor(paymentApiService)\n ;\n ConfigureObservability(apiGateway);\n\n void ConfigureObservability<TResource>(IResourceBuilder<TResource> resourceBuilder)\n where TResource : IResourceWithEnvironment\n {\n var enabledValue = externalObservabilityEnabled ? \"true\" : \"false\";\n resourceBuilder.WithEnvironment(\"Observability__ExternalStackEnabled\", enabledValue);\n\n if (grafanaEndpoint is not null)\n {\n resourceBuilder.WithEnvironment(\"GRAFANA_URL\", grafanaEndpoint);\n }\n }\n\n return builder;\n }\n }\n}\n"}, "631225ab920a7d440612216fb98e2512946ca49a": {"t": 1785748792.146313, "n": 12608, "h": "804686c7cdb2f948", "c": "using Microsoft.Extensions.Hosting;\nusing Projects;\nusing System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\n\nnamespace STEMifyBackend.AppHost.Extensions\n{\n public static class ServiceExtensions\n{\n public static IDistributedApplicationBuilder AddApplicationServices(this IDistributedApplicationBuilder builder)\n {\n var postgres = builder.AddPostgres(\"stemify-postgres\");\n\n var identityDb = postgres.AddDatabase(\"stemifyidentity\");\n\n var classroomDb = postgres.AddDatabase(\"stemifyclassroom\");\n\n var resourceDb = postgres.AddDatabase(\"stemifyresource\");\n\n var notificationDb = postgres.AddDatabase(\"stemifynotification\");\n\n var productDb = postgres.AddDatabase(\"stemifyproduct\");\n\n var orderDb = postgres.AddDatabase(\"stemifyorder\");\n var paymentDb = postgres.AddDatabase(\"stemifypayment\");\n\n var cartDb = postgres.AddDatabase(\"stemifycart\");\n\n var hangfireDb = postgres.AddDatabase(\"stemifyhangfire\");\n\n // // Add pgAdmin for database administration (Production)\n // var pgAdmin = builder\n // .AddContainer(\"pgadmin\", \"dpage/pgadmin4\")\n // .WithHttpEndpoint(targetPort: 5050, name: \"http\")\n // .WithExternalHttpEndpoints()\n // .WithEnvironment(\"PGADMIN_DEFAULT_EMAIL\", \"admin@stemify.com\")\n // .WithEnvironment(\"PGADMIN_DEFAULT_PASSWORD\", Environment.GetEnvironmentVariable(\"PGADMIN_PASSWORD\") ?? \"admin\")\n // .WithEnvironment(\"PGADMIN_CONFIG_SERVER_MODE\", \"False\")\n // .WithEnvironment(\"PGADMIN_CONFIG_MASTER_PASSWORD_REQUIRED\", \"False\")\n // .WithEnvironment(\"PGADMIN_LISTEN_PORT\", \"5050\")\n // .WaitFor(postgres);\n\n // Add RabbitMQ for messaging (required for Payment Service Saga pattern)\n var rabbitmq = builder.AddRabbitMQ(\"messaging\")\n .WithManagementPlugin(); \n\n // External observability stack (production)\n var prometheus = builder.AddContainer(\"prometheus\", \"prom/prometheus\", \"v3.5.0\")\n .WithBindMount(\"../../../../config/prometheus\", \"/etc/prometheus\", isReadOnly: true)\n .WithArgs(\n \"--web.enable-otlp-receiver\", \n \"--config.file=/etc/prometheus/prometheus.yml\",\n \"--storage.tsdb.retention.time=15d\",\n \"--storage.tsdb.retention.size=10GB\"\n )\n .WithHttpEndpoint(targetPort: 9090, name: \"http\");\n\n // Add AlertManager for alerting\n var alertmanager = builder.AddContainer(\"alertmanager\", \"prom/alertmanager\", \"v0.27.0\")\n .WithBindMount(\"../../../../config/alertmanager\", \"/etc/alertmanager\", isReadOnly: true)\n .WithArgs(\"--config.file=/etc/alertmanager/alertmanager.yml\", \"--storage.path=/alertmanager\")\n .WithHttpEndpoint(targetPort: 9093, name: \"http\");\n\n var grafana = builder.AddContainer(\"grafana\", \"grafana/grafana\")\n .WithBindMount(\"../../../../config/grafana/config\", \"/etc/grafana\", isReadOnly: true)\n .WithBindMount(\"../../../../config/grafana/dashboards\", \"/var/lib/grafana/dashboards\", isReadOnly: true)\n .WithEnvironment(\"PROMETHEUS_ENDPOINT\", prometheus.GetEndpoint(\"http\"))\n .WithHttpEndpoint(targetPort: 3000, name: \"http\");\n\n // OpenTelemetry Collector configuration based on environment\n var configFile = builder.Environment.IsDevelopment() \n ? \"../../../../config/otelcollector/config.dev.yaml\"\n : \"../../../../config/otelcollector/config.yaml\";\n\n builder.AddOpenTelemetryCollector(\"otelcollector\", configFile)\n .WithEnvironment(\"PROMETHEUS_ENDPOINT\", $\"{prometheus.GetEndpoint(\"http\")}/api/v1/otlp\")\n .WithEnvironment(\"ALERTMANAGER_ENDPOINT\", alertmanager.GetEndpoint(\"http\"));\n\n\n\n var pgAdmin = builder.Environment.IsProduction()\n ? builder\n .AddContainer(\"pgadmin\", \"dpage/pgadmin4:8.10\")\n .WithHttpEndpoint(targetPort: 5050, name: \"pgadmin-http\")\n .WithExternalHttpEndpoints()\n .WithEnvironment(\"PGADMIN_DEFAULT_EMAIL\", \"admin@stemify.com\")\n .WithEnvironment(\n \"PGADMIN_DEFAULT_PASSWORD\",\n Environment.GetEnvironmentVariable(\"PGADMIN_PASSWORD\") ?? \"admin\"\n )\n .WithEnvironment(\"PGADMIN_CONFIG_SERVER_MODE\", \"False\")\n .WithEnvironment(\"PGADMIN_CONFIG_MASTER_PASSWORD_REQUIRED\", \"False\")\n .WithEnvironment(\"PGADMIN_LISTEN_PORT\", \"5050\")\n .WithEnvironment(\"PGADMIN_CONFIG_CHECK_EMAIL_DELIVERABILITY\", \"False\")\n .WithEnvironment(\"GUNICORN_TIMEOUT\", \"120\")\n .WaitFor(postgres)\n : postgres.WithPgAdmin(x =>\n {\n x.WithHttpEndpoint(targetPort: 5050, name: \"pgadmin-dev\");\n x.WithExternalHttpEndpoints();\n x.WithEnvironment(\"PGADMIN_CONFIG_CHECK_EMAIL_DELIVERABILITY\", \"False\");\n x.WithEnvironment(\"GUNICORN_TIMEOUT\", \"120\");\n });\n\n var identityService = builder\n .AddProject<Projects.Identity_Web>(\"identity\")\n .WithExternalHttpEndpoints()\n .WithReference(identityDb)\n .WithReference(rabbitmq)\n .WaitFor(postgres) \n .WaitFor(identityDb)\n .WaitFor(rabbitmq) \n .WithEnvironment(\"GRAFANA_URL\", grafana.GetEndpoint(\"http\"))\n .WithEnvironment(\"Observability__ExternalStackEnabled\", \"true\"); \n\n var identityApiService = builder\n .AddProject<Projects.Identity_API>(\"identity-api\")\n .WithExternalHttpEndpoints()\n .WithReference(identityDb)\n .WithReference(identityService)\n .WithReference(rabbitmq) \n .WaitFor(postgres)\n .WaitFor(identityDb)\n .WaitFor(identityService)\n .WaitFor(rabbitmq)\n .WithEnvironment(\"GRAFANA_URL\", grafana.GetEndpoint(\"http\"))\n .WithEnvironment(\"Observability__ExternalStackEnabled\", \"true\");\n\n builder\n .AddProject<Projects.Saga_Orchestrator>(\"saga-orchestrator\")\n .WithReference(identityService)\n .WithReference(rabbitmq)\n .WaitFor(identityService)\n .WaitFor(rabbitmq)\n .WithEnvironment(\"GRAFANA_URL\", grafana.GetEndpoint(\"http\"))\n .WithEnvironment(\"Observability__ExternalStackEnabled\", \"true\");\n\n var classroomService = builder\n .AddProject<Projects.Classroom_API>(\"classroom-api\")\n .WithExternalHttpEndpoints()\n .WithReference(classroomDb)\n .WithReference(rabbitmq)\n .WaitFor(postgres)\n .WaitFor(classroomDb)\n .WaitFor(rabbitmq)\n .WithEnvironment(\"GRAFANA_URL\", grafana.GetEndpoint(\"http\"))\n .WithEnvironment(\"Observability__ExternalStackEnabled\", \"true\");\n\n var resourceApiService = builder\n .AddProject<Projects.Resource_API>(\"resource-api\")\n .WithExternalHttpEndpoints()\n .WithReference(resourceDb)\n .WithReference(rabbitmq)\n .WaitFor(postgres)\n .WaitFor(resourceDb)\n .WaitFor(rabbitmq)\n .WithEnvironment(\"GRAFANA_URL\", grafana.GetEndpoint(\"http\"))\n .WithEnvironment(\"Observability__ExternalStackEnabled\", \"true\");\n\n var notificationApiService = builder\n .AddProject<Projects.Notification_API>(\"notification-api\")\n .WithExternalHttpEndpoints()\n .WithReference(notificationDb)\n .WithReference(rabbitmq)\n .WaitFor(postgres)\n .WaitFor(notificationDb)\n .WaitFor(rabbitmq)\n .WithEnvironment(\"GRAFANA_URL\", grafana.GetEndpoint(\"http\"))\n .WithEnvironment(\"Observability__ExternalStackEnabled\", \"true\");\n\n var productApiService = builder\n .AddProject<Projects.Product_API>(\"product-api\")\n .WithExternalHttpEndpoints()\n .WithReference(productDb)\n .WithReference(rabbitmq)\n .WaitFor(postgres)\n .WaitFor(productDb)\n .WaitFor(rabbitmq)\n .WithEnvironment(\"GRAFANA_URL\", grafana.GetEndpoint(\"http\"))\n .WithEnvironment(\"Observability__ExternalStackEnabled\", \"true\");\n\n var orderApiService = builder\n .AddProject<Projects.Order_API>(\"order-api\")\n .WithExternalHttpEndpoints()\n .WithReference(orderDb)\n .WithReference(rabbitmq)\n .WaitFor(postgres)\n .WaitFor(orderDb)\n .WaitFor(rabbitmq)\n .WithEnvironment(\"GRAFANA_URL\", grafana.GetEndpoint(\"http\"))\n .WithEnvironment(\"Observability__ExternalStackEnabled\", \"true\");\n\n var paymentApiService = builder\n .AddProject<Projects.Payment_API>(\"payment-api\")\n .WithExternalHttpEndpoints()\n .WithReference(paymentDb)\n .WithReference(rabbitmq)\n .WaitFor(postgres)\n .WaitFor(paymentDb)\n .WaitFor(rabbitmq)\n .WithEnvironment(\"GRAFANA_URL\", grafana.GetEndpoint(\"http\"))\n .WithEnvironment(\"Observability__ExternalStackEnabled\", \"true\");\n\n var cartApiService = builder\n .AddProject<Projects.Cart_API>(\"cart-api\")\n .WithExternalHttpEndpoints()\n .WithReference(cartDb)\n .WithReference(rabbitmq)\n .WaitFor(postgres)\n .WaitFor(cartDb)\n .WaitFor(rabbitmq)\n .WithEnvironment(\"GRAFANA_URL\", grafana.GetEndpoint(\"http\"))\n .WithEnvironment(\"Observability__ExternalStackEnabled\", \"true\");\n\n var emulatorApiService = builder\n .AddProject<Projects.Emulator_API>(\"emulator-api\")\n .WithExternalHttpEndpoints()\n .WithEnvironment(\"GRAFANA_URL\", grafana.GetEndpoint(\"http\"))\n .WithEnvironment(\"Observability__ExternalStackEnabled\", \"true\");\n\n var hangfireApiService = builder\n .AddProject<Projects.Hangfire_API>(\"hangfire-api\")\n .WithExternalHttpEndpoints()\n .WithReference(hangfireDb)\n .WithReference(identityDb)\n .WithReference(orderDb)\n .WithReference(rabbitmq)\n .WaitFor(postgres)\n .WaitFor(hangfireDb)\n .WaitFor(identityDb)\n .WaitFor(orderDb)\n .WaitFor(rabbitmq)\n .WithEnvironment(\"GRAFANA_URL\", grafana.GetEndpoint(\"http\"))\n .WithEnvironment(\"Observability__ExternalStackEnabled\", \"true\");\n\n var apiGateway = builder\n .AddProject<Projects.ApiGateway>(\"apigateway\")\n .WithExternalHttpEndpoints()\n .WithReference(identityService)\n // .WithReference(identityApiService)\n .WithReference(classroomService)\n .WithReference(resourceApiService)\n .WithReference(notificationApiService)\n .WithReference(emulatorApiService)\n .WaitFor(identityService)\n .WaitFor(resourceApiService)\n .WaitFor(classroomService)\n .WaitFor(notificationApiService)\n .WaitFor(productApiService)\n .WaitFor(orderApiService)\n .WaitFor(cartApiService)\n .WaitFor(emulatorApiService)\n .WaitFor(paymentApiService)\n .WithEnvironment(\"GRAFANA_URL\", grafana.GetEndpoint(\"http\"))\n .WithEnvironment(\"Observability__ExternalStackEnabled\", \"true\");\n\n return builder;\n }\n }\n}\n"}, "fc058e9c2dbd3bbe14e0f6c3a1b7ed68c3847bc2": {"t": 1785748793.230466, "n": 12528, "h": "c5249485491d9e47", "c": "using Microsoft.Extensions.Hosting;\nusing Projects;\nusing System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\n\nnamespace STEMifyBackend.AppHost.Extensions\n{\n public static class ServiceExtensions\n{\n public static IDistributedApplicationBuilder AddApplicationServices(this IDistributedApplicationBuilder builder)\n {\n var postgres = builder.AddPostgres(\"stemify-postgres\");\n\n var identityDb = postgres.AddDatabase(\"stemifyidentity\");\n\n var classroomDb = postgres.AddDatabase(\"stemifyclassroom\");\n\n var resourceDb = postgres.AddDatabase(\"stemifyresource\");\n\n var notificationDb = postgres.AddDatabase(\"stemifynotification\");\n\n var productDb = postgres.AddDatabase(\"stemifyproduct\");\n\n var orderDb = postgres.AddDatabase(\"stemifyorder\");\n var paymentDb = postgres.AddDatabase(\"stemifypayment\");\n\n var cartDb = postgres.AddDatabase(\"stemifycart\");\n\n var hangfireDb = postgres.AddDatabase(\"stemifyhangfire\");\n\n // // Add pgAdmin for database administration (Production)\n // var pgAdmin = builder\n // .AddContainer(\"pgadmin\", \"dpage/pgadmin4\")\n // .WithHttpEndpoint(targetPort: 5050, name: \"http\")\n // .WithExternalHttpEndpoints()\n // .WithEnvironment(\"PGADMIN_DEFAULT_EMAIL\", \"admin@stemify.com\")\n // .WithEnvironment(\"PGADMIN_DEFAULT_PASSWORD\", Environment.GetEnvironmentVariable(\"PGADMIN_PASSWORD\") ?? \"admin\")\n // .WithEnvironment(\"PGADMIN_CONFIG_SERVER_MODE\", \"False\")\n // .WithEnvironment(\"PGADMIN_CONFIG_MASTER_PASSWORD_REQUIRED\", \"False\")\n // .WithEnvironment(\"PGADMIN_LISTEN_PORT\", \"5050\")\n // .WaitFor(postgres);\n\n // Add RabbitMQ for messaging (required for Payment Service Saga pattern)\n var rabbitmq = builder.AddRabbitMQ(\"messaging\")\n .WithManagementPlugin(); \n\n // External observability stack (production)\n var prometheus = builder.AddContainer(\"prometheus\", \"prom/prometheus\", \"v3.5.0\")\n .WithBindMount(\"../../../../config/prometheus\", \"/etc/prometheus\", isReadOnly: true)\n .WithArgs(\n \"--web.enable-otlp-receiver\", \n \"--config.file=/etc/prometheus/prometheus.yml\",\n \"--storage.tsdb.retention.time=15d\",\n \"--storage.tsdb.retention.size=10GB\"\n )\n .WithHttpEndpoint(targetPort: 9090, name: \"http\");\n\n // Add AlertManager for alerting\n var alertmanager = builder.AddContainer(\"alertmanager\", \"prom/alertmanager\", \"v0.27.0\")\n .WithBindMount(\"../../../../config/alertmanager\", \"/etc/alertmanager\", isReadOnly: true)\n .WithArgs(\"--config.file=/etc/alertmanager/alertmanager.yml\", \"--storage.path=/alertmanager\")\n .WithHttpEndpoint(targetPort: 9093, name: \"http\");\n\n var grafana = builder.AddContainer(\"grafana\", \"grafana/grafana\")\n .WithBindMount(\"../../../../config/grafana/config\", \"/etc/grafana\", isReadOnly: true)\n .WithBindMount(\"../../../../config/grafana/dashboards\", \"/var/lib/grafana/dashboards\", isReadOnly: true)\n .WithEnvironment(\"PROMETHEUS_ENDPOINT\", prometheus.GetEndpoint(\"http\"))\n .WithHttpEndpoint(targetPort: 3000, name: \"http\");\n\n // OpenTelemetry Collector configuration based on environment\n var configFile = builder.Environment.IsDevelopment() \n ? \"../../../../config/otelcollector/config.dev.yaml\"\n : \"../../../../config/otelcollector/config.yaml\";\n\n builder.AddOpenTelemetryCollector(\"otelcollector\", configFile)\n .WithEnvironment(\"PROMETHEUS_ENDPOINT\", $\"{prometheus.GetEndpoint(\"http\")}/api/v1/otlp\")\n .WithEnvironment(\"ALERTMANAGER_ENDPOINT\", alertmanager.GetEndpoint(\"http\"));\n\n\n\n var pgAdmin = builder.Environment.IsProduction()\n ? builder\n .AddContainer(\"pgadmin\", \"dpage/pgadmin4:8.10\")\n .WithHttpEndpoint(targetPort: 5050, name: \"pgadmin-http\")\n .WithExternalHttpEndpoints()\n .WithEnvironment(\"PGADMIN_DEFAULT_EMAIL\", \"admin@stemify.com\")\n .WithEnvironment(\n \"PGADMIN_DEFAULT_PASSWORD\",\n Environment.GetEnvironmentVariable(\"PGADMIN_PASSWORD\") ?? \"admin\"\n )\n .WithEnvironment(\"PGADMIN_CONFIG_SERVER_MODE\", \"False\")\n .WithEnvironment(\"PGADMIN_CONFIG_MASTER_PASSWORD_REQUIRED\", \"False\")\n .WithEnvironment(\"PGADMIN_LISTEN_PORT\", \"5050\")\n .WithEnvironment(\"PGADMIN_CONFIG_CHECK_EMAIL_DELIVERABILITY\", \"False\")\n .WithEnvironment(\"GUNICORN_TIMEOUT\", \"120\")\n .WaitFor(postgres)\n : postgres.WithPgAdmin(x =>\n {\n x.WithHttpEndpoint(targetPort: 5050, name: \"pgadmin-dev\");\n x.WithExternalHttpEndpoints();\n x.WithEnvironment(\"PGADMIN_CONFIG_CHECK_EMAIL_DELIVERABILITY\", \"False\");\n x.WithEnvironment(\"GUNICORN_TIMEOUT\", \"120\");\n });\n\n var identityService = builder\n .AddProject<Projects.Identity_Web>(\"identity\")\n .WithExternalHttpEndpoints()\n .WithReference(identityDb)\n .WithReference(rabbitmq)\n .WaitFor(postgres) \n .WaitFor(identityDb)\n .WaitFor(rabbitmq) \n .WithEnvironment(\"GRAFANA_URL\", grafana.GetEndpoint(\"http\"))\n .WithEnvironment(\"Observability__ExternalStackEnabled\", \"true\"); \n\n var identityApiService = builder\n .AddProject<Projects.Identity_API>(\"identity-api\")\n .WithExternalHttpEndpoints()\n .WithReference(identityDb)\n .WithReference(identityService)\n .WithReference(rabbitmq) \n .WaitFor(postgres)\n .WaitFor(identityDb)\n .WaitFor(identityService)\n .WaitFor(rabbitmq)\n .WithEnvironment(\"GRAFANA_URL\", grafana.GetEndpoint(\"http\"))\n .WithEnvironment(\"Observability__ExternalStackEnabled\", \"true\");\n\n builder\n .AddProject<Projects.Saga_Orchestrator>(\"saga-orchestrator\")\n .WithReference(identityService)\n .WithReference(rabbitmq)\n .WaitFor(identityService)\n .WaitFor(rabbitmq)\n .WithEnvironment(\"GRAFANA_URL\", grafana.GetEndpoint(\"http\"))\n .WithEnvironment(\"Observability__ExternalStackEnabled\", \"true\");\n\n var classroomService = builder\n .AddProject<Projects.Classroom_API>(\"classroom-api\")\n .WithExternalHttpEndpoints()\n .WithReference(classroomDb)\n .WithReference(rabbitmq)\n .WaitFor(postgres)\n .WaitFor(classroomDb)\n .WaitFor(rabbitmq)\n .WithEnvironment(\"GRAFANA_URL\", grafana.GetEndpoint(\"http\"))\n .WithEnvironment(\"Observability__ExternalStackEnabled\", \"true\");\n\n var resourceApiService = builder\n .AddProject<Projects.Resource_API>(\"resource-api\")\n .WithExternalHttpEndpoints()\n .WithReference(resourceDb)\n .WithReference(rabbitmq)\n .WaitFor(postgres)\n .WaitFor(resourceDb)\n .WaitFor(rabbitmq)\n .WithEnvironment(\"GRAFANA_URL\", grafana.GetEndpoint(\"http\"))\n .WithEnvironment(\"Observability__ExternalStackEnabled\", \"true\");\n\n var notificationApiService = builder\n .AddProject<Projects.Notification_API>(\"notification-api\")\n .WithExternalHttpEndpoints()\n .WithReference(notificationDb)\n .WithReference(rabbitmq)\n .WaitFor(postgres)\n .WaitFor(notificationDb)\n .WaitFor(rabbitmq)\n .WithEnvironment(\"GRAFANA_URL\", grafana.GetEndpoint(\"http\"))\n .WithEnvironment(\"Observability__ExternalStackEnabled\", \"true\");\n\n var productApiService = builder\n .AddProject<Projects.Product_API>(\"product-api\")\n .WithExternalHttpEndpoints()\n .WithReference(productDb)\n .WithReference(rabbitmq)\n .WaitFor(postgres)\n .WaitFor(productDb)\n .WaitFor(rabbitmq)\n .WithEnvironment(\"GRAFANA_URL\", grafana.GetEndpoint(\"http\"))\n .WithEnvironment(\"Observability__ExternalStackEnabled\", \"true\");\n\n var orderApiService = builder\n .AddProject<Projects.Order_API>(\"order-api\")\n .WithExternalHttpEndpoints()\n .WithReference(orderDb)\n .WithReference(rabbitmq)\n .WaitFor(postgres)\n .WaitFor(orderDb)\n .WaitFor(rabbitmq)\n .WithEnvironment(\"GRAFANA_URL\", grafana.GetEndpoint(\"http\"))\n .WithEnvironment(\"Observability__ExternalStackEnabled\", \"true\");\n\n var paymentApiService = builder\n .AddProject<Projects.Payment_API>(\"payment-api\")\n .WithExternalHttpEndpoints()\n .WithReference(paymentDb)\n .WithReference(rabbitmq)\n .WaitFor(postgres)\n .WaitFor(paymentDb)\n .WaitFor(rabbitmq)\n .WithEnvironment(\"GRAFANA_URL\", grafana.GetEndpoint(\"http\"))\n .WithEnvironment(\"Observability__ExternalStackEnabled\", \"true\");\n\n var cartApiService = builder\n .AddProject<Projects.Cart_API>(\"cart-api\")\n .WithExternalHttpEndpoints()\n .WithReference(cartDb)\n .WithReference(rabbitmq)\n .WaitFor(postgres)\n .WaitFor(cartDb)\n .WaitFor(rabbitmq)\n .WithEnvironment(\"GRAFANA_URL\", grafana.GetEndpoint(\"http\"))\n .WithEnvironment(\"Observability__ExternalStackEnabled\", \"true\");\n\n var emulatorApiService = builder\n .AddProject<Projects.Emulator_API>(\"emulator-api\")\n .WithExternalHttpEndpoints()\n .WithEnvironment(\"GRAFANA_URL\", grafana.GetEndpoint(\"http\"))\n .WithEnvironment(\"Observability__ExternalStackEnabled\", \"true\");\n\n var hangfireApiService = builder\n .AddProject<Projects.Hangfire_API>(\"hangfire-api\")\n .WithExternalHttpEndpoints()\n .WithReference(hangfireDb)\n .WithReference(orderDb)\n .WithReference(rabbitmq)\n .WaitFor(postgres)\n .WaitFor(hangfireDb)\n .WaitFor(orderDb)\n .WaitFor(rabbitmq)\n .WithEnvironment(\"GRAFANA_URL\", grafana.GetEndpoint(\"http\"))\n .WithEnvironment(\"Observability__ExternalStackEnabled\", \"true\");\n\n var apiGateway = builder\n .AddProject<Projects.ApiGateway>(\"apigateway\")\n .WithExternalHttpEndpoints()\n .WithReference(identityService)\n // .WithReference(identityApiService)\n .WithReference(classroomService)\n .WithReference(resourceApiService)\n .WithReference(notificationApiService)\n .WithReference(emulatorApiService)\n .WaitFor(identityService)\n .WaitFor(resourceApiService)\n .WaitFor(classroomService)\n .WaitFor(notificationApiService)\n .WaitFor(productApiService)\n .WaitFor(orderApiService)\n .WaitFor(cartApiService)\n .WaitFor(emulatorApiService)\n .WaitFor(paymentApiService)\n .WithEnvironment(\"GRAFANA_URL\", grafana.GetEndpoint(\"http\"))\n .WithEnvironment(\"Observability__ExternalStackEnabled\", \"true\");\n\n return builder;\n }\n }\n}\n"}, "66f85e00e00e33915442e740b450a6eee82978ca": {"t": 1785748794.5468392, "n": 11876, "h": "b14167184853db25", "c": "using Microsoft.Extensions.Hosting;\nusing Projects;\nusing System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\n\nnamespace STEMifyBackend.AppHost.Extensions\n{\n public static class ServiceExtensions\n{\n public static IDistributedApplicationBuilder AddApplicationServices(this IDistributedApplicationBuilder builder)\n {\n var postgres = builder.AddPostgres(\"stemify-postgres\");\n\n var identityDb = postgres.AddDatabase(\"stemifyidentity\");\n\n var classroomDb = postgres.AddDatabase(\"stemifyclassroom\");\n\n var resourceDb = postgres.AddDatabase(\"stemifyresource\");\n\n var notificationDb = postgres.AddDatabase(\"stemifynotification\");\n\n var productDb = postgres.AddDatabase(\"stemifyproduct\");\n\n var orderDb = postgres.AddDatabase(\"stemifyorder\");\n var paymentDb = postgres.AddDatabase(\"stemifypayment\");\n\n var cartDb = postgres.AddDatabase(\"stemifycart\");\n\n // // Add pgAdmin for database administration (Production)\n // var pgAdmin = builder\n // .AddContainer(\"pgadmin\", \"dpage/pgadmin4\")\n // .WithHttpEndpoint(targetPort: 5050, name: \"http\")\n // .WithExternalHttpEndpoints()\n // .WithEnvironment(\"PGADMIN_DEFAULT_EMAIL\", \"admin@stemify.com\")\n // .WithEnvironment(\"PGADMIN_DEFAULT_PASSWORD\", Environment.GetEnvironmentVariable(\"PGADMIN_PASSWORD\") ?? \"admin\")\n // .WithEnvironment(\"PGADMIN_CONFIG_SERVER_MODE\", \"False\")\n // .WithEnvironment(\"PGADMIN_CONFIG_MASTER_PASSWORD_REQUIRED\", \"False\")\n // .WithEnvironment(\"PGADMIN_LISTEN_PORT\", \"5050\")\n // .WaitFor(postgres);\n\n // Add RabbitMQ for messaging (required for Payment Service Saga pattern)\n var rabbitmq = builder.AddRabbitMQ(\"messaging\")\n .WithManagementPlugin(); \n\n // External observability stack (production)\n var prometheus = builder.AddContainer(\"prometheus\", \"prom/prometheus\", \"v3.5.0\")\n .WithBindMount(\"../../../../config/prometheus\", \"/etc/prometheus\", isReadOnly: true)\n .WithArgs(\n \"--web.enable-otlp-receiver\", \n \"--config.file=/etc/prometheus/prometheus.yml\",\n \"--storage.tsdb.retention.time=15d\",\n \"--storage.tsdb.retention.size=10GB\"\n )\n .WithHttpEndpoint(targetPort: 9090, name: \"http\");\n\n // Add AlertManager for alerting\n var alertmanager = builder.AddContainer(\"alertmanager\", \"prom/alertmanager\", \"v0.27.0\")\n .WithBindMount(\"../../../../config/alertmanager\", \"/etc/alertmanager\", isReadOnly: true)\n .WithArgs(\"--config.file=/etc/alertmanager/alertmanager.yml\", \"--storage.path=/alertmanager\")\n .WithHttpEndpoint(targetPort: 9093, name: \"http\");\n\n var grafana = builder.AddContainer(\"grafana\", \"grafana/grafana\")\n .WithBindMount(\"../../../../config/grafana/config\", \"/etc/grafana\", isReadOnly: true)\n .WithBindMount(\"../../../../config/grafana/dashboards\", \"/var/lib/grafana/dashboards\", isReadOnly: true)\n .WithEnvironment(\"PROMETHEUS_ENDPOINT\", prometheus.GetEndpoint(\"http\"))\n .WithHttpEndpoint(targetPort: 3000, name: \"http\");\n\n // OpenTelemetry Collector configuration based on environment\n var configFile = builder.Environment.IsDevelopment() \n ? \"../../../../config/otelcollector/config.dev.yaml\"\n : \"../../../../config/otelcollector/config.yaml\";\n\n builder.AddOpenTelemetryCollector(\"otelcollector\", configFile)\n .WithEnvironment(\"PROMETHEUS_ENDPOINT\", $\"{prometheus.GetEndpoint(\"http\")}/api/v1/otlp\")\n .WithEnvironment(\"ALERTMANAGER_ENDPOINT\", alertmanager.GetEndpoint(\"http\"));\n\n\n\n var pgAdmin = builder.Environment.IsProduction()\n ? builder\n .AddContainer(\"pgadmin\", \"dpage/pgadmin4:8.10\")\n .WithHttpEndpoint(targetPort: 5050, name: \"pgadmin-http\")\n .WithExternalHttpEndpoints()\n .WithEnvironment(\"PGADMIN_DEFAULT_EMAIL\", \"admin@stemify.com\")\n .WithEnvironment(\n \"PGADMIN_DEFAULT_PASSWORD\",\n Environment.GetEnvironmentVariable(\"PGADMIN_PASSWORD\") ?? \"admin\"\n )\n .WithEnvironment(\"PGADMIN_CONFIG_SERVER_MODE\", \"False\")\n .WithEnvironment(\"PGADMIN_CONFIG_MASTER_PASSWORD_REQUIRED\", \"False\")\n .WithEnvironment(\"PGADMIN_LISTEN_PORT\", \"5050\")\n .WithEnvironment(\"PGADMIN_CONFIG_CHECK_EMAIL_DELIVERABILITY\", \"False\")\n .WithEnvironment(\"GUNICORN_TIMEOUT\", \"120\")\n .WaitFor(postgres)\n : postgres.WithPgAdmin(x =>\n {\n x.WithHttpEndpoint(targetPort: 5050, name: \"pgadmin-dev\");\n x.WithExternalHttpEndpoints();\n x.WithEnvironment(\"PGADMIN_CONFIG_CHECK_EMAIL_DELIVERABILITY\", \"False\");\n x.WithEnvironment(\"GUNICORN_TIMEOUT\", \"120\");\n });\n\n var identityService = builder\n .AddProject<Projects.Identity_Web>(\"identity\")\n .WithExternalHttpEndpoints()\n .WithReference(identityDb)\n .WithReference(rabbitmq)\n .WaitFor(postgres) \n .WaitFor(identityDb)\n .WaitFor(rabbitmq) \n .WithEnvironment(\"GRAFANA_URL\", grafana.GetEndpoint(\"http\"))\n .WithEnvironment(\"Observability__ExternalStackEnabled\", \"true\"); \n\n var identityApiService = builder\n .AddProject<Projects.Identity_API>(\"identity-api\")\n .WithExternalHttpEndpoints()\n .WithReference(identityDb)\n .WithReference(identityService)\n .WithReference(rabbitmq) \n .WaitFor(postgres)\n .WaitFor(identityDb)\n .WaitFor(identityService)\n .WaitFor(rabbitmq)\n .WithEnvironment(\"GRAFANA_URL\", grafana.GetEndpoint(\"http\"))\n .WithEnvironment(\"Observability__ExternalStackEnabled\", \"true\");\n\n builder\n .AddProject<Projects.Saga_Orchestrator>(\"saga-orchestrator\")\n .WithReference(identityService)\n .WithReference(rabbitmq)\n .WaitFor(identityService)\n .WaitFor(rabbitmq)\n .WithEnvironment(\"GRAFANA_URL\", grafana.GetEndpoint(\"http\"))\n .WithEnvironment(\"Observability__ExternalStackEnabled\", \"true\");\n\n var classroomService = builder\n .AddProject<Projects.Classroom_API>(\"classroom-api\")\n .WithExternalHttpEndpoints()\n .WithReference(classroomDb)\n .WithReference(rabbitmq)\n .WaitFor(postgres)\n .WaitFor(classroomDb)\n .WaitFor(rabbitmq)\n .WithEnvironment(\"GRAFANA_URL\", grafana.GetEndpoint(\"http\"))\n .WithEnvironment(\"Observability__ExternalStackEnabled\", \"true\");\n\n var resourceApiService = builder\n .AddProject<Projects.Resource_API>(\"resource-api\")\n .WithExternalHttpEndpoints()\n .WithReference(resourceDb)\n .WithReference(rabbitmq)\n .WaitFor(postgres)\n .WaitFor(resourceDb)\n .WaitFor(rabbitmq)\n .WithEnvironment(\"GRAFANA_URL\", grafana.GetEndpoint(\"http\"))\n .WithEnvironment(\"Observability__ExternalStackEnabled\", \"true\");\n\n var notificationApiService = builder\n .AddProject<Projects.Notification_API>(\"notification-api\")\n .WithExternalHttpEndpoints()\n .WithReference(notificationDb)\n .WithReference(rabbitmq)\n .WaitFor(postgres)\n .WaitFor(notificationDb)\n .WaitFor(rabbitmq)\n .WithEnvironment(\"GRAFANA_URL\", grafana.GetEndpoint(\"http\"))\n .WithEnvironment(\"Observability__ExternalStackEnabled\", \"true\");\n\n var productApiService = builder\n .AddProject<Projects.Product_API>(\"product-api\")\n .WithExternalHttpEndpoints()\n .WithReference(productDb)\n .WithReference(rabbitmq)\n .WaitFor(postgres)\n .WaitFor(productDb)\n .WaitFor(rabbitmq)\n .WithEnvironment(\"GRAFANA_URL\", grafana.GetEndpoint(\"http\"))\n .WithEnvironment(\"Observability__ExternalStackEnabled\", \"true\");\n\n var orderApiService = builder\n .AddProject<Projects.Order_API>(\"order-api\")\n .WithExternalHttpEndpoints()\n .WithReference(orderDb)\n .WithReference(rabbitmq)\n .WaitFor(postgres)\n .WaitFor(orderDb)\n .WaitFor(rabbitmq)\n .WithEnvironment(\"GRAFANA_URL\", grafana.GetEndpoint(\"http\"))\n .WithEnvironment(\"Observability__ExternalStackEnabled\", \"true\");\n\n var paymentApiService = builder\n .AddProject<Projects.Payment_API>(\"payment-api\")\n .WithExternalHttpEndpoints()\n .WithReference(paymentDb)\n .WithReference(rabbitmq)\n .WaitFor(postgres)\n .WaitFor(paymentDb)\n .WaitFor(rabbitmq)\n .WithEnvironment(\"GRAFANA_URL\", grafana.GetEndpoint(\"http\"))\n .WithEnvironment(\"Observability__ExternalStackEnabled\", \"true\");\n\n var cartApiService = builder\n .AddProject<Projects.Cart_API>(\"cart-api\")\n .WithExternalHttpEndpoints()\n .WithReference(cartDb)\n .WithReference(rabbitmq)\n .WaitFor(postgres)\n .WaitFor(cartDb)\n .WaitFor(rabbitmq)\n .WithEnvironment(\"GRAFANA_URL\", grafana.GetEndpoint(\"http\"))\n .WithEnvironment(\"Observability__ExternalStackEnabled\", \"true\");\n\n var emulatorApiService = builder\n .AddProject<Projects.Emulator_API>(\"emulator-api\")\n .WithExternalHttpEndpoints()\n .WithEnvironment(\"GRAFANA_URL\", grafana.GetEndpoint(\"http\"))\n .WithEnvironment(\"Observability__ExternalStackEnabled\", \"true\");\n\n var apiGateway = builder\n .AddProject<Projects.ApiGateway>(\"apigateway\")\n .WithExternalHttpEndpoints()\n .WithReference(identityService)\n // .WithReference(identityApiService)\n .WithReference(classroomService)\n .WithReference(resourceApiService)\n .WithReference(notificationApiService)\n .WithReference(emulatorApiService)\n .WaitFor(identityService)\n .WaitFor(resourceApiService)\n .WaitFor(classroomService)\n .WaitFor(notificationApiService)\n .WaitFor(productApiService)\n .WaitFor(orderApiService)\n .WaitFor(cartApiService)\n .WaitFor(emulatorApiService)\n .WaitFor(paymentApiService)\n .WithEnvironment(\"GRAFANA_URL\", grafana.GetEndpoint(\"http\"))\n .WithEnvironment(\"Observability__ExternalStackEnabled\", \"true\");\n\n return builder;\n }\n }\n}\n"}, "36d9c58525c1222864126229876e24c684aed379": {"t": 1785748795.6414611, "n": 11836, "h": "49744381d1b3078d", "c": "using Microsoft.Extensions.Hosting;\nusing Projects;\nusing System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\n\nnamespace STEMifyBackend.AppHost.Extensions\n{\n public static class ServiceExtensions\n{\n public static IDistributedApplicationBuilder AddApplicationServices(this IDistributedApplicationBuilder builder)\n {\n var postgres = builder.AddPostgres(\"stemify-postgres\");\n\n var identityDb = postgres.AddDatabase(\"stemifyidentity\");\n\n var classroomDb = postgres.AddDatabase(\"stemifyclassroom\");\n\n var resourceDb = postgres.AddDatabase(\"stemifyresource\");\n\n var notificationDb = postgres.AddDatabase(\"stemifynotification\");\n\n var productDb = postgres.AddDatabase(\"stemifyproduct\");\n\n var orderDb = postgres.AddDatabase(\"stemifyorder\");\n var paymentDb = postgres.AddDatabase(\"stemifypayment\");\n\n var cartDb = postgres.AddDatabase(\"stemifycart\");\n\n // // Add pgAdmin for database administration (Production)\n // var pgAdmin = builder\n // .AddContainer(\"pgadmin\", \"dpage/pgadmin4\")\n // .WithHttpEndpoint(targetPort: 5050, name: \"http\")\n // .WithExternalHttpEndpoints()\n // .WithEnvironment(\"PGADMIN_DEFAULT_EMAIL\", \"admin@stemify.com\")\n // .WithEnvironment(\"PGADMIN_DEFAULT_PASSWORD\", Environment.GetEnvironmentVariable(\"PGADMIN_PASSWORD\") ?? \"admin\")\n // .WithEnvironment(\"PGADMIN_CONFIG_SERVER_MODE\", \"False\")\n // .WithEnvironment(\"PGADMIN_CONFIG_MASTER_PASSWORD_REQUIRED\", \"False\")\n // .WithEnvironment(\"PGADMIN_LISTEN_PORT\", \"5050\")\n // .WaitFor(postgres);\n\n // Add RabbitMQ for messaging (required for Payment Service Saga pattern)\n var rabbitmq = builder.AddRabbitMQ(\"messaging\")\n .WithManagementPlugin(); \n\n // External observability stack (production)\n var prometheus = builder.AddContainer(\"prometheus\", \"prom/prometheus\", \"v3.5.0\")\n .WithBindMount(\"../../../../config/prometheus\", \"/etc/prometheus\", isReadOnly: true)\n .WithArgs(\n \"--web.enable-otlp-receiver\", \n \"--config.file=/etc/prometheus/prometheus.yml\",\n \"--storage.tsdb.retention.time=15d\",\n \"--storage.tsdb.retention.size=10GB\"\n )\n .WithHttpEndpoint(targetPort: 9090, name: \"http\");\n\n // Add AlertManager for alerting\n var alertmanager = builder.AddContainer(\"alertmanager\", \"prom/alertmanager\", \"v0.27.0\")\n .WithBindMount(\"../../../../config/alertmanager\", \"/etc/alertmanager\", isReadOnly: true)\n .WithArgs(\"--config.file=/etc/alertmanager/alertmanager.yml\", \"--storage.path=/alertmanager\")\n .WithHttpEndpoint(targetPort: 9093, name: \"http\");\n\n var grafana = builder.AddContainer(\"grafana\", \"grafana/grafana\")\n .WithBindMount(\"../../../../config/grafana/config\", \"/etc/grafana\", isReadOnly: true)\n .WithBindMount(\"../../../../config/grafana/dashboards\", \"/var/lib/grafana/dashboards\", isReadOnly: true)\n .WithEnvironment(\"PROMETHEUS_ENDPOINT\", prometheus.GetEndpoint(\"http\"))\n .WithHttpEndpoint(targetPort: 3000, name: \"http\");\n\n // OpenTelemetry Collector configuration based on environment\n var configFile = builder.Environment.IsDevelopment() \n ? \"../../../../config/otelcollector/config.dev.yaml\"\n : \"../../../../config/otelcollector/config.yaml\";\n\n builder.AddOpenTelemetryCollector(\"otelcollector\", configFile)\n .WithEnvironment(\"PROMETHEUS_ENDPOINT\", $\"{prometheus.GetEndpoint(\"http\")}/api/v1/otlp\")\n .WithEnvironment(\"ALERTMANAGER_ENDPOINT\", alertmanager.GetEndpoint(\"http\"));\n\n\n\n var pgAdmin = builder.Environment.IsProduction()\n ? builder\n .AddContainer(\"pgadmin\", \"dpage/pgadmin4:8.10\")\n .WithHttpEndpoint(targetPort: 5050, name: \"pgadmin-http\")\n .WithExternalHttpEndpoints()\n .WithEnvironment(\"PGADMIN_DEFAULT_EMAIL\", \"admin@stemify.com\")\n .WithEnvironment(\n \"PGADMIN_DEFAULT_PASSWORD\",\n Environment.GetEnvironmentVariable(\"PGADMIN_PASSWORD\") ?? \"admin\"\n )\n .WithEnvironment(\"PGADMIN_CONFIG_SERVER_MODE\", \"False\")\n .WithEnvironment(\"PGADMIN_CONFIG_MASTER_PASSWORD_REQUIRED\", \"False\")\n .WithEnvironment(\"PGADMIN_LISTEN_PORT\", \"5050\")\n .WithEnvironment(\"PGADMIN_CONFIG_CHECK_EMAIL_DELIVERABILITY\", \"False\")\n .WithEnvironment(\"GUNICORN_TIMEOUT\", \"120\")\n .WaitFor(postgres)\n : postgres.WithPgAdmin(x =>\n {\n x.WithHttpEndpoint(targetPort: 5050, name: \"pgadmin-dev\");\n x.WithExternalHttpEndpoints();\n x.WithEnvironment(\"PGADMIN_CONFIG_CHECK_EMAIL_DELIVERABILITY\", \"False\");\n x.WithEnvironment(\"GUNICORN_TIMEOUT\", \"120\");\n });\n\n var identityService = builder\n .AddProject<Projects.Identity_Web>(\"identity\")\n .WithExternalHttpEndpoints()\n .WithReference(identityDb)\n .WithReference(rabbitmq)\n .WaitFor(postgres) // Wait for PostgreSQL container first\n .WaitFor(identityDb)\n .WithEnvironment(\"GRAFANA_URL\", grafana.GetEndpoint(\"http\"))\n .WithEnvironment(\"Observability__ExternalStackEnabled\", \"true\"); \n\n var identityApiService = builder\n .AddProject<Projects.Identity_API>(\"identity-api\")\n .WithExternalHttpEndpoints()\n .WithReference(identityDb)\n .WithReference(identityService)\n .WaitFor(postgres)\n .WaitFor(identityDb)\n .WaitFor(identityService)\n .WaitFor(rabbitmq)\n .WithEnvironment(\"GRAFANA_URL\", grafana.GetEndpoint(\"http\"))\n .WithEnvironment(\"Observability__ExternalStackEnabled\", \"true\");\n\n builder\n .AddProject<Projects.Saga_Orchestrator>(\"saga-orchestrator\")\n .WithReference(identityService)\n .WithReference(rabbitmq)\n .WaitFor(identityService)\n .WaitFor(rabbitmq)\n .WithEnvironment(\"GRAFANA_URL\", grafana.GetEndpoint(\"http\"))\n .WithEnvironment(\"Observability__ExternalStackEnabled\", \"true\");\n\n var classroomService = builder\n .AddProject<Projects.Classroom_API>(\"classroom-api\")\n .WithExternalHttpEndpoints()\n .WithReference(classroomDb)\n .WithReference(rabbitmq)\n .WaitFor(postgres)\n .WaitFor(classroomDb)\n .WaitFor(rabbitmq)\n .WithEnvironment(\"GRAFANA_URL\", grafana.GetEndpoint(\"http\"))\n .WithEnvironment(\"Observability__ExternalStackEnabled\", \"true\");\n\n var resourceApiService = builder\n .AddProject<Projects.Resource_API>(\"resource-api\")\n .WithExternalHttpEndpoints()\n .WithReference(resourceDb)\n .WithReference(rabbitmq)\n .WaitFor(postgres)\n .WaitFor(resourceDb)\n .WaitFor(rabbitmq)\n .WithEnvironment(\"GRAFANA_URL\", grafana.GetEndpoint(\"http\"))\n .WithEnvironment(\"Observability__ExternalStackEnabled\", \"true\");\n\n var notificationApiService = builder\n .AddProject<Projects.Notification_API>(\"notification-api\")\n .WithExternalHttpEndpoints()\n .WithReference(notificationDb)\n .WithReference(rabbitmq)\n .WaitFor(postgres)\n .WaitFor(notificationDb)\n .WaitFor(rabbitmq)\n .WithEnvironment(\"GRAFANA_URL\", grafana.GetEndpoint(\"http\"))\n .WithEnvironment(\"Observability__ExternalStackEnabled\", \"true\");\n\n var productApiService = builder\n .AddProject<Projects.Product_API>(\"product-api\")\n .WithExternalHttpEndpoints()\n .WithReference(productDb)\n .WithReference(rabbitmq)\n .WaitFor(postgres)\n .WaitFor(productDb)\n .WaitFor(rabbitmq)\n .WithEnvironment(\"GRAFANA_URL\", grafana.GetEndpoint(\"http\"))\n .WithEnvironment(\"Observability__ExternalStackEnabled\", \"true\");\n\n var orderApiService = builder\n .AddProject<Projects.Order_API>(\"order-api\")\n .WithExternalHttpEndpoints()\n .WithReference(orderDb)\n .WithReference(rabbitmq)\n .WaitFor(postgres)\n .WaitFor(orderDb)\n .WaitFor(rabbitmq)\n .WithEnvironment(\"GRAFANA_URL\", grafana.GetEndpoint(\"http\"))\n .WithEnvironment(\"Observability__ExternalStackEnabled\", \"true\");\n\n var paymentApiService = builder\n .AddProject<Projects.Payment_API>(\"payment-api\")\n .WithExternalHttpEndpoints()\n .WithReference(paymentDb)\n .WithReference(rabbitmq)\n .WaitFor(postgres)\n .WaitFor(paymentDb)\n .WaitFor(rabbitmq)\n .WithEnvironment(\"GRAFANA_URL\", grafana.GetEndpoint(\"http\"))\n .WithEnvironment(\"Observability__ExternalStackEnabled\", \"true\");\n\n var cartApiService = builder\n .AddProject<Projects.Cart_API>(\"cart-api\")\n .WithExternalHttpEndpoints()\n .WithReference(cartDb)\n .WithReference(rabbitmq)\n .WaitFor(postgres)\n .WaitFor(cartDb)\n .WaitFor(rabbitmq)\n .WithEnvironment(\"GRAFANA_URL\", grafana.GetEndpoint(\"http\"))\n .WithEnvironment(\"Observability__ExternalStackEnabled\", \"true\");\n\n var emulatorApiService = builder\n .AddProject<Projects.Emulator_API>(\"emulator-api\")\n .WithExternalHttpEndpoints()\n .WithEnvironment(\"GRAFANA_URL\", grafana.GetEndpoint(\"http\"))\n .WithEnvironment(\"Observability__ExternalStackEnabled\", \"true\");\n\n var apiGateway = builder\n .AddProject<Projects.ApiGateway>(\"apigateway\")\n .WithExternalHttpEndpoints()\n .WithReference(identityService)\n // .WithReference(identityApiService)\n .WithReference(classroomService)\n .WithReference(resourceApiService)\n .WithReference(notificationApiService)\n .WithReference(emulatorApiService)\n .WaitFor(identityService)\n .WaitFor(resourceApiService)\n .WaitFor(classroomService)\n .WaitFor(notificationApiService)\n .WaitFor(productApiService)\n .WaitFor(orderApiService)\n .WaitFor(cartApiService)\n .WaitFor(emulatorApiService)\n .WaitFor(paymentApiService)\n .WithEnvironment(\"GRAFANA_URL\", grafana.GetEndpoint(\"http\"))\n .WithEnvironment(\"Observability__ExternalStackEnabled\", \"true\");\n\n return builder;\n }\n }\n}\n"}, "69720b16426c5a2f29ba535f986af4249f165ad8": {"t": 1785748796.766582, "n": 11734, "h": "d8e2eb638b394197", "c": "using Microsoft.Extensions.Hosting;\n\nnamespace STEMifyBackend.AppHost.Extensions\n{\n public static class ServiceExtensions\n{\n public static IDistributedApplicationBuilder AddApplicationServices(this IDistributedApplicationBuilder builder)\n {\n var postgres = builder.AddPostgres(\"stemify-postgres\");\n\n var identityDb = postgres.AddDatabase(\"stemifyidentity\");\n\n var classroomDb = postgres.AddDatabase(\"stemifyclassroom\");\n\n var resourceDb = postgres.AddDatabase(\"stemifyresource\");\n\n var notificationDb = postgres.AddDatabase(\"stemifynotification\");\n\n var productDb = postgres.AddDatabase(\"stemifyproduct\");\n\n var orderDb = postgres.AddDatabase(\"stemifyorder\");\n var paymentDb = postgres.AddDatabase(\"stemifypayment\");\n\n var cartDb = postgres.AddDatabase(\"stemifycart\");\n\n // // Add pgAdmin for database administration (Production)\n // var pgAdmin = builder\n // .AddContainer(\"pgadmin\", \"dpage/pgadmin4\")\n // .WithHttpEndpoint(targetPort: 5050, name: \"http\")\n // .WithExternalHttpEndpoints()\n // .WithEnvironment(\"PGADMIN_DEFAULT_EMAIL\", \"admin@stemify.com\")\n // .WithEnvironment(\"PGADMIN_DEFAULT_PASSWORD\", Environment.GetEnvironmentVariable(\"PGADMIN_PASSWORD\") ?? \"admin\")\n // .WithEnvironment(\"PGADMIN_CONFIG_SERVER_MODE\", \"False\")\n // .WithEnvironment(\"PGADMIN_CONFIG_MASTER_PASSWORD_REQUIRED\", \"False\")\n // .WithEnvironment(\"PGADMIN_LISTEN_PORT\", \"5050\")\n // .WaitFor(postgres);\n\n // Add RabbitMQ for messaging (required for Payment Service Saga pattern)\n var rabbitmq = builder.AddRabbitMQ(\"messaging\")\n .WithManagementPlugin(); // Adds management UI at http://localhost:15672 (guest/guest)\n\n // External observability stack (production)\n var prometheus = builder.AddContainer(\"prometheus\", \"prom/prometheus\", \"v3.5.0\")\n .WithBindMount(\"../../../../config/prometheus\", \"/etc/prometheus\", isReadOnly: true)\n .WithArgs(\n \"--web.enable-otlp-receiver\", \n \"--config.file=/etc/prometheus/prometheus.yml\",\n \"--storage.tsdb.retention.time=15d\",\n \"--storage.tsdb.retention.size=10GB\"\n )\n .WithHttpEndpoint(targetPort: 9090, name: \"http\");\n\n // Add AlertManager for alerting\n var alertmanager = builder.AddContainer(\"alertmanager\", \"prom/alertmanager\", \"v0.27.0\")\n .WithBindMount(\"../../../../config/alertmanager\", \"/etc/alertmanager\", isReadOnly: true)\n .WithArgs(\"--config.file=/etc/alertmanager/alertmanager.yml\", \"--storage.path=/alertmanager\")\n .WithHttpEndpoint(targetPort: 9093, name: \"http\");\n\n var grafana = builder.AddContainer(\"grafana\", \"grafana/grafana\")\n .WithBindMount(\"../../../../config/grafana/config\", \"/etc/grafana\", isReadOnly: true)\n .WithBindMount(\"../../../../config/grafana/dashboards\", \"/var/lib/grafana/dashboards\", isReadOnly: true)\n .WithEnvironment(\"PROMETHEUS_ENDPOINT\", prometheus.GetEndpoint(\"http\"))\n .WithHttpEndpoint(targetPort: 3000, name: \"http\");\n\n // OpenTelemetry Collector configuration based on environment\n var configFile = builder.Environment.IsDevelopment() \n ? \"../../../../config/otelcollector/config.dev.yaml\"\n : \"../../../../config/otelcollector/config.yaml\";\n\n builder.AddOpenTelemetryCollector(\"otelcollector\", configFile)\n .WithEnvironment(\"PROMETHEUS_ENDPOINT\", $\"{prometheus.GetEndpoint(\"http\")}/api/v1/otlp\")\n .WithEnvironment(\"ALERTMANAGER_ENDPOINT\", alertmanager.GetEndpoint(\"http\"));\n\n\n\n var pgAdmin = builder.Environment.IsProduction()\n ? builder\n .AddContainer(\"pgadmin\", \"dpage/pgadmin4:8.10\")\n .WithHttpEndpoint(targetPort: 5050, name: \"pgadmin-http\")\n .WithExternalHttpEndpoints()\n .WithEnvironment(\"PGADMIN_DEFAULT_EMAIL\", \"admin@stemify.com\")\n .WithEnvironment(\n \"PGADMIN_DEFAULT_PASSWORD\",\n Environment.GetEnvironmentVariable(\"PGADMIN_PASSWORD\") ?? \"admin\"\n )\n .WithEnvironment(\"PGADMIN_CONFIG_SERVER_MODE\", \"False\")\n .WithEnvironment(\"PGADMIN_CONFIG_MASTER_PASSWORD_REQUIRED\", \"False\")\n .WithEnvironment(\"PGADMIN_LISTEN_PORT\", \"5050\")\n .WithEnvironment(\"PGADMIN_CONFIG_CHECK_EMAIL_DELIVERABILITY\", \"False\")\n .WithEnvironment(\"GUNICORN_TIMEOUT\", \"120\")\n .WaitFor(postgres)\n : postgres.WithPgAdmin(x =>\n {\n x.WithHttpEndpoint(targetPort: 5050, name: \"pgadmin-dev\");\n x.WithExternalHttpEndpoints();\n x.WithEnvironment(\"PGADMIN_CONFIG_CHECK_EMAIL_DELIVERABILITY\", \"False\");\n x.WithEnvironment(\"GUNICORN_TIMEOUT\", \"120\");\n });\n\n var identityService = builder\n .AddProject<Projects.Identity_Web>(\"identity\")\n .WithExternalHttpEndpoints()\n .WithReference(identityDb)\n .WaitFor(postgres) // Wait for PostgreSQL container first\n .WaitFor(identityDb)\n .WithEnvironment(\"GRAFANA_URL\", grafana.GetEndpoint(\"http\"))\n .WithEnvironment(\"Observability__ExternalStackEnabled\", \"true\"); \n\n var identityApiService = builder\n .AddProject<Projects.Identity_API>(\"identity-api\")\n .WithExternalHttpEndpoints()\n .WithReference(identityDb)\n .WithReference(identityService)\n .WaitFor(postgres)\n .WaitFor(identityDb)\n .WaitFor(identityService)\n .WithEnvironment(\"GRAFANA_URL\", grafana.GetEndpoint(\"http\"))\n .WithEnvironment(\"Observability__ExternalStackEnabled\", \"true\");\n\n builder\n .AddProject<Projects.Saga_Orchestrator>(\"saga-orchestrator\")\n .WithReference(identityService)\n .WithReference(rabbitmq)\n .WaitFor(identityService)\n .WaitFor(rabbitmq)\n .WithEnvironment(\"GRAFANA_URL\", grafana.GetEndpoint(\"http\"))\n .WithEnvironment(\"Observability__ExternalStackEnabled\", \"true\");\n\n var classroomService = builder\n .AddProject<Projects.Classroom_API>(\"classroom-api\")\n .WithExternalHttpEndpoints()\n .WithReference(classroomDb)\n .WithReference(rabbitmq)\n .WaitFor(postgres)\n .WaitFor(classroomDb)\n .WaitFor(rabbitmq)\n .WithEnvironment(\"GRAFANA_URL\", grafana.GetEndpoint(\"http\"))\n .WithEnvironment(\"Observability__ExternalStackEnabled\", \"true\");\n\n var resourceApiService = builder\n .AddProject<Projects.Resource_API>(\"resource-api\")\n .WithExternalHttpEndpoints()\n .WithReference(resourceDb)\n .WithReference(rabbitmq)\n .WaitFor(postgres)\n .WaitFor(resourceDb)\n .WaitFor(rabbitmq)\n .WithEnvironment(\"GRAFANA_URL\", grafana.GetEndpoint(\"http\"))\n .WithEnvironment(\"Observability__ExternalStackEnabled\", \"true\");\n\n var notificationApiService = builder\n .AddProject<Projects.Notification_API>(\"notification-api\")\n .WithExternalHttpEndpoints()\n .WithReference(notificationDb)\n .WithReference(rabbitmq)\n .WaitFor(postgres)\n .WaitFor(notificationDb)\n .WaitFor(rabbitmq)\n .WithEnvironment(\"GRAFANA_URL\", grafana.GetEndpoint(\"http\"))\n .WithEnvironment(\"Observability__ExternalStackEnabled\", \"true\");\n\n var productApiService = builder\n .AddProject<Projects.Product_API>(\"product-api\")\n .WithExternalHttpEndpoints()\n .WithReference(productDb)\n .WithReference(rabbitmq)\n .WaitFor(postgres)\n .WaitFor(productDb)\n .WaitFor(rabbitmq)\n .WithEnvironment(\"GRAFANA_URL\", grafana.GetEndpoint(\"http\"))\n .WithEnvironment(\"Observability__ExternalStackEnabled\", \"true\");\n\n var orderApiService = builder\n .AddProject<Projects.Order_API>(\"order-api\")\n .WithExternalHttpEndpoints()\n .WithReference(orderDb)\n .WithReference(rabbitmq)\n .WaitFor(postgres)\n .WaitFor(orderDb)\n .WaitFor(rabbitmq)\n .WithEnvironment(\"GRAFANA_URL\", grafana.GetEndpoint(\"http\"))\n .WithEnvironment(\"Observability__ExternalStackEnabled\", \"true\");\n\n var paymentApiService = builder\n .AddProject<Projects.Payment_API>(\"payment-api\")\n .WithExternalHttpEndpoints()\n .WithReference(paymentDb)\n .WithReference(rabbitmq)\n .WaitFor(postgres)\n .WaitFor(paymentDb)\n .WaitFor(rabbitmq)\n .WithEnvironment(\"GRAFANA_URL\", grafana.GetEndpoint(\"http\"))\n .WithEnvironment(\"Observability__ExternalStackEnabled\", \"true\");\n\n var cartApiService = builder\n .AddProject<Projects.Cart_API>(\"cart-api\")\n .WithExternalHttpEndpoints()\n .WithReference(cartDb)\n .WithReference(rabbitmq)\n .WaitFor(postgres)\n .WaitFor(cartDb)\n .WaitFor(rabbitmq)\n .WithEnvironment(\"GRAFANA_URL\", grafana.GetEndpoint(\"http\"))\n .WithEnvironment(\"Observability__ExternalStackEnabled\", \"true\");\n\n var emulatorApiService = builder\n .AddProject<Projects.Emulator_API>(\"emulator-api\")\n .WithExternalHttpEndpoints()\n .WithEnvironment(\"GRAFANA_URL\", grafana.GetEndpoint(\"http\"))\n .WithEnvironment(\"Observability__ExternalStackEnabled\", \"true\");\n\n var apiGateway = builder\n .AddProject<Projects.ApiGateway>(\"apigateway\")\n .WithExternalHttpEndpoints()\n .WithReference(identityService)\n // .WithReference(identityApiService)\n .WithReference(classroomService)\n .WithReference(resourceApiService)\n .WithReference(notificationApiService)\n .WithReference(emulatorApiService)\n .WaitFor(identityService)\n .WaitFor(identityApiService)\n .WaitFor(resourceApiService)\n .WaitFor(classroomService)\n .WaitFor(notificationApiService)\n .WaitFor(productApiService)\n .WaitFor(orderApiService)\n .WaitFor(cartApiService)\n .WaitFor(emulatorApiService)\n .WaitFor(paymentApiService)\n .WithEnvironment(\"GRAFANA_URL\", grafana.GetEndpoint(\"http\"))\n .WithEnvironment(\"Observability__ExternalStackEnabled\", \"true\");\n\n return builder;\n }\n }\n}\n"}, "af0e8c7eb3b562ed5cb397f88a0368a14c7e33e9": {"t": 1785748797.8559232, "n": 10573, "h": "1623fc80c95cebea", "c": "using Microsoft.Extensions.Hosting;\nusing System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\n\nnamespace STEMifyBackend.AppHost.Extensions\n{\n public static class ServiceExtensions\n{\n public static IDistributedApplicationBuilder AddApplicationServices(this IDistributedApplicationBuilder builder)\n {\n var postgres = builder.AddPostgres(\"stemify-postgres\");\n\n var identityDb = postgres.AddDatabase(\"stemifyidentity\");\n\n var classroomDb = postgres.AddDatabase(\"stemifyclassroom\");\n\n var resourceDb = postgres.AddDatabase(\"stemifyresource\");\n\n var notificationDb = postgres.AddDatabase(\"stemifynotification\");\n\n var productDb = postgres.AddDatabase(\"stemifyproduct\");\n\n var orderDb = postgres.AddDatabase(\"stemifyorder\");\n var paymentDb = postgres.AddDatabase(\"stemifypayment\");\n\n var cartDb = postgres.AddDatabase(\"stemifycart\");\n\n // // Add pgAdmin for database administration (Production)\n // var pgAdmin = builder\n // .AddContainer(\"pgadmin\", \"dpage/pgadmin4\")\n // .WithHttpEndpoint(targetPort: 5050, name: \"http\")\n // .WithExternalHttpEndpoints()\n // .WithEnvironment(\"PGADMIN_DEFAULT_EMAIL\", \"admin@stemify.com\")\n // .WithEnvironment(\"PGADMIN_DEFAULT_PASSWORD\", Environment.GetEnvironmentVariable(\"PGADMIN_PASSWORD\") ?? \"admin\")\n // .WithEnvironment(\"PGADMIN_CONFIG_SERVER_MODE\", \"False\")\n // .WithEnvironment(\"PGADMIN_CONFIG_MASTER_PASSWORD_REQUIRED\", \"False\")\n // .WithEnvironment(\"PGADMIN_LISTEN_PORT\", \"5050\")\n // .WaitFor(postgres);\n\n // Add RabbitMQ for messaging (required for Payment Service Saga pattern)\n var rabbitmq = builder.AddRabbitMQ(\"messaging\")\n .WithManagementPlugin(); // Adds management UI at http://localhost:15672 (guest/guest)\n\n // External observability stack (production)\n var prometheus = builder.AddContainer(\"prometheus\", \"prom/prometheus\", \"v3.5.0\")\n .WithBindMount(\"../../../../config/prometheus\", \"/etc/prometheus\", isReadOnly: true)\n .WithArgs(\n \"--web.enable-otlp-receiver\", \n \"--config.file=/etc/prometheus/prometheus.yml\",\n \"--storage.tsdb.retention.time=15d\",\n \"--storage.tsdb.retention.size=10GB\"\n )\n .WithHttpEndpoint(targetPort: 9090, name: \"http\");\n\n // Add AlertManager for alerting\n var alertmanager = builder.AddContainer(\"alertmanager\", \"prom/alertmanager\", \"v0.27.0\")\n .WithBindMount(\"../../../../config/alertmanager\", \"/etc/alertmanager\", isReadOnly: true)\n .WithArgs(\"--config.file=/etc/alertmanager/alertmanager.yml\", \"--storage.path=/alertmanager\")\n .WithHttpEndpoint(targetPort: 9093, name: \"http\");\n\n var grafana = builder.AddContainer(\"grafana\", \"grafana/grafana\")\n .WithBindMount(\"../../../../config/grafana/config\", \"/etc/grafana\", isReadOnly: true)\n .WithBindMount(\"../../../../config/grafana/dashboards\", \"/var/lib/grafana/dashboards\", isReadOnly: true)\n .WithEnvironment(\"PROMETHEUS_ENDPOINT\", prometheus.GetEndpoint(\"http\"))\n .WithHttpEndpoint(targetPort: 3000, name: \"http\");\n\n builder.AddOpenTelemetryCollector(\"otelcollector\", \"../../../../config/otelcollector/config.yaml\")\n .WithEnvironment(\"PROMETHEUS_ENDPOINT\", $\"{prometheus.GetEndpoint(\"http\")}/api/v1/otlp\")\n .WithEnvironment(\"ALERTMANAGER_ENDPOINT\", alertmanager.GetEndpoint(\"http\"));\n\n\n\n var pgAdmin = builder.Environment.IsProduction()\n ? builder\n .AddContainer(\"pgadmin\", \"dpage/pgadmin4:8.10\")\n .WithHttpEndpoint(targetPort: 5050, name: \"pgadmin-http\")\n .WithExternalHttpEndpoints()\n .WithEnvironment(\"PGADMIN_DEFAULT_EMAIL\", \"admin@stemify.com\")\n .WithEnvironment(\n \"PGADMIN_DEFAULT_PASSWORD\",\n Environment.GetEnvironmentVariable(\"PGADMIN_PASSWORD\") ?? \"admin\"\n )\n .WithEnvironment(\"PGADMIN_CONFIG_SERVER_MODE\", \"False\")\n .WithEnvironment(\"PGADMIN_CONFIG_MASTER_PASSWORD_REQUIRED\", \"False\")\n .WithEnvironment(\"PGADMIN_LISTEN_PORT\", \"5050\")\n .WithEnvironment(\"PGADMIN_CONFIG_CHECK_EMAIL_DELIVERABILITY\", \"False\")\n .WithEnvironment(\"GUNICORN_TIMEOUT\", \"120\")\n .WaitFor(postgres)\n : postgres.WithPgAdmin(x =>\n {\n x.WithHttpEndpoint(targetPort: 5050, name: \"pgadmin-dev\");\n x.WithExternalHttpEndpoints();\n x.WithEnvironment(\"PGADMIN_CONFIG_CHECK_EMAIL_DELIVERABILITY\", \"False\");\n x.WithEnvironment(\"GUNICORN_TIMEOUT\", \"120\");\n });\n\n var identityService = builder\n .AddProject<Projects.Identity_Web>(\"identity\")\n .WithExternalHttpEndpoints()\n .WithReference(identityDb)\n .WaitFor(postgres) // Wait for PostgreSQL container first\n .WaitFor(identityDb)\n .WithEnvironment(\"GRAFANA_URL\", grafana.GetEndpoint(\"http\")); \n\n var identityApiService = builder\n .AddProject<Projects.Identity_API>(\"identity-api\")\n .WithExternalHttpEndpoints()\n .WithReference(identityDb)\n .WithReference(identityService)\n .WaitFor(postgres)\n .WaitFor(identityDb)\n .WaitFor(identityService)\n .WithEnvironment(\"GRAFANA_URL\", grafana.GetEndpoint(\"http\"));\n\n builder\n .AddProject<Projects.Saga_Orchestrator>(\"saga-orchestrator\")\n .WithReference(identityService)\n .WithReference(rabbitmq)\n .WaitFor(identityService)\n .WaitFor(rabbitmq)\n .WithEnvironment(\"GRAFANA_URL\", grafana.GetEndpoint(\"http\"));\n\n var classroomService = builder\n .AddProject<Projects.Classroom_API>(\"classroom-api\")\n .WithExternalHttpEndpoints()\n .WithReference(classroomDb)\n .WithReference(rabbitmq)\n .WaitFor(postgres)\n .WaitFor(classroomDb)\n .WaitFor(rabbitmq)\n .WithEnvironment(\"GRAFANA_URL\", grafana.GetEndpoint(\"http\"));\n\n var resourceApiService = builder\n .AddProject<Projects.Resource_API>(\"resource-api\")\n .WithExternalHttpEndpoints()\n .WithReference(resourceDb)\n .WithReference(rabbitmq)\n .WaitFor(postgres)\n .WaitFor(resourceDb)\n .WaitFor(rabbitmq)\n .WithEnvironment(\"GRAFANA_URL\", grafana.GetEndpoint(\"http\"));\n\n var notificationApiService = builder\n .AddProject<Projects.Notification_API>(\"notification-api\")\n .WithExternalHttpEndpoints()\n .WithReference(notificationDb)\n .WithReference(rabbitmq)\n .WaitFor(postgres)\n .WaitFor(notificationDb)\n .WaitFor(rabbitmq)\n .WithEnvironment(\"GRAFANA_URL\", grafana.GetEndpoint(\"http\"));\n\n var productApiService = builder\n .AddProject<Projects.Product_API>(\"product-api\")\n .WithExternalHttpEndpoints()\n .WithReference(productDb)\n .WithReference(rabbitmq)\n .WaitFor(postgres)\n .WaitFor(productDb)\n .WaitFor(rabbitmq)\n .WithEnvironment(\"GRAFANA_URL\", grafana.GetEndpoint(\"http\"));\n\n var orderApiService = builder\n .AddProject<Projects.Order_API>(\"order-api\")\n .WithExternalHttpEndpoints()\n .WithReference(orderDb)\n .WithReference(rabbitmq)\n .WaitFor(postgres)\n .WaitFor(orderDb)\n .WaitFor(rabbitmq)\n .WithEnvironment(\"GRAFANA_URL\", grafana.GetEndpoint(\"http\"));\n\n var paymentApiService = builder\n .AddProject<Projects.Payment_API>(\"payment-api\")\n .WithExternalHttpEndpoints()\n .WithReference(paymentDb)\n .WithReference(rabbitmq)\n .WaitFor(postgres)\n .WaitFor(paymentDb)\n .WaitFor(rabbitmq)\n .WithEnvironment(\"GRAFANA_URL\", grafana.GetEndpoint(\"http\"));\n\n var cartApiService = builder\n .AddProject<Projects.Cart_API>(\"cart-api\")\n .WithExternalHttpEndpoints()\n .WithReference(cartDb)\n .WithReference(rabbitmq)\n .WaitFor(postgres)\n .WaitFor(cartDb)\n .WaitFor(rabbitmq)\n .WithEnvironment(\"GRAFANA_URL\", grafana.GetEndpoint(\"http\"));\n\n var emulatorApiService = builder\n .AddProject<Projects.Emulator_API>(\"emulator-api\")\n .WithExternalHttpEndpoints()\n .WithEnvironment(\"GRAFANA_URL\", grafana.GetEndpoint(\"http\"));\n\n var apiGateway = builder\n .AddProject<Projects.ApiGateway>(\"apigateway\")\n .WithExternalHttpEndpoints()\n .WithReference(identityService)\n // .WithReference(identityApiService)\n .WithReference(classroomService)\n .WithReference(resourceApiService)\n .WithReference(notificationApiService)\n .WithReference(emulatorApiService)\n .WaitFor(identityService)\n .WaitFor(identityApiService)\n .WaitFor(resourceApiService)\n .WaitFor(classroomService)\n .WaitFor(notificationApiService)\n .WaitFor(productApiService)\n .WaitFor(orderApiService)\n .WaitFor(cartApiService)\n .WaitFor(emulatorApiService)\n .WaitFor(paymentApiService);\n\n return builder;\n }\n }\n}\n"}}, "docker-compose.yml": {"aeba9aa66118a8bea1f397d76877a378f0c4ccd0": {"t": 1785746992.8422978, "n": 23914, "h": "6344108eac82d8ca", "c": "services:\n postgres:\n image: postgres:17\n container_name: stemify-postgres\n environment:\n - POSTGRES_PASSWORD=${DB_PASSWORD}\n ports:\n - '5432:5432'\n volumes:\n - postgres_data:/var/lib/postgresql/data\n restart: unless-stopped\n rabbitmq:\n image: rabbitmq:3-management-alpine\n ports:\n - 5672:5672\n - 15672:15672\n environment:\n - RABBITMQ_DEFAULT_USER=guest\n - RABBITMQ_DEFAULT_PASS=guest\n healthcheck:\n test: ['CMD', 'rabbitmq-diagnostics', 'ping']\n interval: 30s\n timeout: 10s\n retries: 5\n start_period: 30s\n restart: unless-stopped\n mongo:\n image: mongo\n environment:\n - MONGO_INITDB_ROOT_USERNAME=root\n - MONGO_INITDB_ROOT_PASSWORD=mongopw\n ports:\n - 27018:27017\n volumes:\n - mongodata:/var/lib/mongodb/data\n redis:\n image: redis:latest\n ports:\n - 6379:6379\n command: ['redis-server', '--appendonly', 'yes']\n volumes:\n - redis-data:/data\n redis-commander:\n image: rediscommander/redis-commander:latest\n environment:\n - REDIS_HOSTS=local:redis:6379\n - HTTP_USER=root\n - HTTP_PASSWORD=secret\n ports:\n - 8082:8082\n depends_on:\n - redis\n classroom-service:\n image: rosie91/stemify:classroom-service\n build:\n context: .\n dockerfile: src/Services/ClassroomService/Classroom.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5001\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcResourceUrl=http://resource-service:5003\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcOrderUrl=http://order-service:5006\n - ConnectionStrings__stemifyclassroom=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_classroom\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=classroom-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=classroom-service,service.version=1.0.0\n # Cloudinary Configuration\n - Cloudinary__CloudName=${CLOUDINARY_CLOUD_NAME}\n - Cloudinary__ApiKey=${CLOUDINARY_API_KEY}\n - Cloudinary__ApiSecret=${CLOUDINARY_API_SECRET}\n - Pdflayer__ApiKey=e4a39484d4c94acacd0da61ba5970b09\n ports:\n - 7001:80\n - 5001:5001\n depends_on:\n - postgres\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n resource-service:\n image: rosie91/stemify:resource-service\n build:\n context: .\n dockerfile: src/Services/Resource/Resource.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5003\n - ConnectionStrings__stemifyresource=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_resource\n - Cloudinary__CloudName=${CLOUDINARY_CLOUD_NAME}\n - Cloudinary__ApiKey=${CLOUDINARY_API_KEY}\n - Cloudinary__ApiSecret=${CLOUDINARY_API_SECRET}\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcEmulatorUrl=http://emulator-service:5009\n - ClientApp=https://localhost:3000\n - Gemini__BaseUrl=https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent\n - Gemini__ApiKey=${GEMINI_API_KEY}\n - Gemini__Model=gemini-2.5-flash\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=resource-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=resource-service,service.version=1.0.0\n ports:\n - 7003:80\n - 5003:5003\n depends_on:\n - postgres\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n product-service:\n image: rosie91/stemify:product-service\n build:\n context: .\n dockerfile: src/Services/ProductService/Product.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5005\n - ConnectionStrings__stemifyproduct=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_product\n - Cloudinary__CloudName=${CLOUDINARY_CLOUD_NAME}\n - Cloudinary__ApiKey=${CLOUDINARY_API_KEY}\n - Cloudinary__ApiSecret=${CLOUDINARY_API_SECRET}\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcResourceUrl=http://resource-service:5003\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=product-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=product-service,service.version=1.0.0\n ports:\n - 7005:80\n - 5005:5005\n depends_on:\n - postgres\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n order-service:\n image: rosie91/stemify:order-service\n build:\n context: .\n dockerfile: src/Services/OrderService/Order.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5006\n - ConnectionStrings__stemifyorder=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_order\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcResourceUrl=http://resource-service:5003\n - GrpcProductUrl=http://product-service:5005\n - GrpcClassroomUrl=http://classroom-service:5001\n - Cloudinary__CloudName=${CLOUDINARY_CLOUD_NAME}\n - Cloudinary__ApiKey=${CLOUDINARY_API_KEY}\n - Cloudinary__ApiSecret=${CLOUDINARY_API_SECRET}\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=order-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=order-service,service.version=1.0.0\n\n ports:\n - 7006:80\n - 5006:5006\n depends_on:\n - postgres\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n notification-service:\n image: rosie91/stemify:notification-service\n build:\n context: .\n dockerfile: src/Services/Notification/Notification.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5004\n - ConnectionStrings__stemifynotification=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_notification\n # SendGrid configuration\n - SendGridSettings__ApiKey=${SENDGRID_API_KEY}\n - SendGridSettings__FromEmail=stemify30062025@gmail.com\n - SendGridSettings__FromName=STEMify\n # Client App configuration\n - ClientApp__BaseUrl=https://localhost:3000\n - ClientApp=https://localhost:3000\n - Issuer=http://localhost:5000/\n - IdentityServiceUrl=http://identity-service\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=notification-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=notification-service,service.version=1.0.0\n ports:\n - 7004:80\n - 5004:5004\n depends_on:\n - postgres\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n gateway-service:\n image: rosie91/stemify:gateway-service\n build:\n context: .\n dockerfile: src/ApiGateways/ApiGateway/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Docker\n - ASPNETCORE_URLS=http://+:80\n - IdentityServiceUrl=http://identity-service\n - Issuer=http://localhost:5000/\n - ClientApp__0=http://localhost:3232\n - ClientApp__1=https://localhost:3000\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=gateway-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=gateway-service,service.version=1.0.0\n ports:\n - 6001:80\n depends_on:\n - otelcollector\n restart: unless-stopped\n identity-api-service:\n image: rosie91/stemify:identity-api-service\n build:\n context: .\n dockerfile: src/Services/Identity/Identity.Api/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5002\n - ConnectionStrings__stemifyidentity=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_identity\n # RabbitMQ Configuration (for MassTransit)\n - RabbitMq__Url=${RABBITMQ_URL}\n # OpenIddict Configuration\n - OpenIddict__Authority=http://identity-service:80\n # Client App Configuration\n - ClientApp=https://localhost:3000\n # Email Settings\n - EmailSettings__From=${IDENTITY_EMAIL_FROM}\n - EmailSettings__DisplayName=${IDENTITY_EMAIL_DISPLAY_NAME}\n - EmailSettings__SMTPServer=${IDENTITY_EMAIL_SMTP_SERVER}\n - EmailSettings__Port=${IDENTITY_EMAIL_PORT}\n - EmailSettings__Username=${IDENTITY_EMAIL_USERNAME}\n - EmailSettings__Password=${IDENTITY_EMAIL_PASSWORD}\n - EmailSettings__UseSSL=${IDENTITY_EMAIL_USE_SSL}\n - EmailSettings__ApplicationName=${IDENTITY_EMAIL_APP_NAME}\n - EmailSettings__SupportEmail=${IDENTITY_EMAIL_SUPPORT}\n # Google Authentication\n - Authentication__Google__ClientId=${GOOGLE_CLIENT_ID}\n - Authentication__Google__ClientSecret=${GOOGLE_CLIENT_SECRET}\n - Authentication__Google__RedirectUri=${GOOGLE_REDIRECT_URI}\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=identity-api-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=identity-api-service,service.version=1.0.0\n - GrpcServices__OrderService__Address=http://order-service:5006\n # Google Authentication\n - Authentication__Google__ClientId=${GOOGLE_CLIENT_ID}\n - Authentication__Google__ClientSecret=${GOOGLE_CLIENT_SECRET}\n - Authentication__Google__RedirectUri=${GOOGLE_REDIRECT_URI}\n - BackendUrl=${IDENTITY_BACKEND_URL:-http://localhost:6001}\n ports:\n - 7002:80\n - 5002:5002\n depends_on:\n - postgres\n - otelcollector\n restart: unless-stopped\n identity-service:\n image: rosie91/stemify:identity-service\n build:\n context: .\n dockerfile: src/Services/Identity/Identity.Web/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - ASPNETCORE_URLS=http://+:80\n - ConnectionStrings__stemifyidentity=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_identity\n # OpenIddict configuration for Docker\n - OpenIddict__Issuer=http://localhost:5000\n - OpenIddict__Authority=http://localhost:5000\n - OpenIddict__SigningKey=${OPENIDDICT_SIGNING_KEY}\n - OpenIddict__EncryptionKey=${OPENIDDICT_ENCRYPTION_KEY}\n - OpenIddict__ApiClientSecret=${OPENIDDICT_API_CLIENT_SECRET}\n - OpenIddict__ForceRecreateClients=${OPENIDDICT_FORCE_RECREATE_CLIENTS:-true}\n - OpenIddict__DisableHttpsRequirement=${OPENIDDICT_DISABLE_HTTPS:-true}\n # CORS for Docker environment\n - Cors__Origins=${IDENTITY_CORS_ORIGINS:-https://localhost:3000}\n - ClientApp=${IDENTITY_CLIENT_APP:-https://localhost:3000}\n - BackendUrl=${IDENTITY_BACKEND_URL:-http://localhost:5000}\n # Google Authentication\n - Authentication__Google__ClientId=${GOOGLE_CLIENT_ID}\n - Authentication__Google__ClientSecret=${GOOGLE_CLIENT_SECRET}\n - Authentication__Google__RedirectUri=${GOOGLE_REDIRECT_URI}\n # Email Settings\n - EmailSettings__SMTPServer=${IDENTITY_EMAIL_SMTP_SERVER}\n - EmailSettings__Port=${IDENTITY_EMAIL_PORT}\n - EmailSettings__UseSSL=${IDENTITY_EMAIL_USE_SSL}\n - EmailSettings__Username=${IDENTITY_EMAIL_USERNAME}\n - EmailSettings__Password=${IDENTITY_EMAIL_PASSWORD}\n - EmailSettings__From=${IDENTITY_EMAIL_FROM}\n - EmailSettings__DisplayName=${IDENTITY_EMAIL_DISPLAY_NAME}\n - EmailSettings__ApplicationName=${IDENTITY_EMAIL_APP_NAME}\n - EmailSettings__SupportEmail=${IDENTITY_EMAIL_SUPPORT}\n - EmailSettings__Enabled=${IDENTITY_EMAIL_ENABLED}\n - EmailSettings__EnableVerification=${IDENTITY_EMAIL_ENABLE_VERIFICATION}\n - EmailSettings__TimeoutMs=${IDENTITY_EMAIL_TIMEOUT_MS}\n - EmailSettings__MaxRetryAttempts=${IDENTITY_EMAIL_MAX_RETRY}\n - EmailSettings__LogEmailContent=${IDENTITY_EMAIL_LOG_CONTENT}\n # gRPC Services\n - GrpcServices__OrderService__Address=http://order-service:5006\n # Database Options\n - DatabaseOptions__MaxRetryCount=5\n - DatabaseOptions__MaxRetryDelay=00:00:10\n - DatabaseOptions__CommandTimeout=30\n - DatabaseOptions__EnableRetryOnFailure=true\n - DatabaseOptions__EnableSensitiveDataLogging=${DB_ENABLE_SENSITIVE_LOGGING}\n - DatabaseOptions__EnableDetailedErrors=${DB_ENABLE_DETAILED_ERRORS}\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=identity-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=identity-service,service.version=1.0.0\n - RabbitMq__Url=${RABBITMQ_URL}\n ports:\n - 5000:80\n depends_on:\n - postgres\n - otelcollector\n restart: unless-stopped\n # cart-service:\n # image: rosie91/stemify:cart-service\n # build:\n # context: .\n # dockerfile: src/Services/CartService/Cart.API/Dockerfile\n # environment:\n # - ASPNETCORE_ENVIRONMENT=Development\n # - RabbitMq__Url=${RABBITMQ_URL}\n # - Kestrel__Endpoints__WebApi__Protocols=Http1\n # - Kestrel__Endpoints__WebApi__Url=http://+:80\n # - Kestrel__Endpoints__Grpc__Protocols=Http2\n # - Kestrel__Endpoints__Grpc__Url=http://+:5007\n # - ConnectionStrings__stemifycart=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_cart\n # - RedisConfig__Host=redis\n # - RedisConfig__Port=6379\n # - RedisConfig__Password=secret\n # - RedisConfig__Ssl=false\n # - GrpcIdentityUrl=http://identity-api-service:5002\n # - GrpcProductUrl=http://product-service:5005\n # # OpenTelemetry Configuration\n # - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n # - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n # - OTEL_SERVICE_NAME=cart-service\n # - OTEL_RESOURCE_ATTRIBUTES=service.name=cart-service,service.version=1.0.0\n # ports:\n # - 7007:80\n # - 5007:5007\n # depends_on:\n # - postgres\n # - redis\n # - otelcollector\n # #- rabbitmq\n # restart: unless-stopped\n # payment-service:\n # image: rosie91/stemify:payment-service\n # build:\n # context: .\n # dockerfile: src/Services/PaymentService/Payment.API/Dockerfile\n # environment:\n # - ASPNETCORE_ENVIRONMENT=Development\n # - Logging__LogLevel__Microsoft.EntityFrameworkCore.Database.Command=Warning\n # - RabbitMq__Url=${RABBITMQ_URL}\n # - Kestrel__Endpoints__WebApi__Protocols=Http1\n # - Kestrel__Endpoints__WebApi__Url=http://+:80\n # - Kestrel__Endpoints__Grpc__Protocols=Http2\n # - Kestrel__Endpoints__Grpc__Url=http://+:5008\n # - ConnectionStrings__stemifypayment=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_payment\n # - RedisConfig__Host=redis\n # - RedisConfig__Port=6379\n # - RedisConfig__Password=secret\n # - RedisConfig__Ssl=false\n # - GrpcIdentityUrl=http://identity-api-service:5002\n # # PayOS configuration\n # - PaymentGateways__PayOS__ClientId=${PAYOS_CLIENT_ID}\n # - PaymentGateways__PayOS__ApiKey=${PAYOS_API_KEY}\n # - PaymentGateways__PayOS__ChecksumKey=${PAYOS_CHECKSUM_KEY}\n # - PaymentGateways__PayOS__BaseUrl=https://api-merchant.payos.vn\n # # Stripe configuration\n # - PaymentGateways__Stripe__SecretKey=${STRIPE_SECRET_KEY}\n # - PaymentGateways__Stripe__PublishableKey=${STRIPE_PUBLISH_KEY}\n # - PaymentGateways__Stripe__WebhookSecret=${STRIPE_WEBHOOK_KEY}\n # # OpenTelemetry Configuration\n # - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n # - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n # - OTEL_SERVICE_NAME=payment-service\n # - OTEL_RESOURCE_ATTRIBUTES=service.name=payment-service,service.version=1.0.0\n # ports:\n # - 7008:80\n # - 5008:5008\n # depends_on:\n # - postgres\n # - redis\n # - otelcollector\n # #- rabbitmq\n # restart: unless-stopped\n emulator-service:\n image: rosie91/stemify:emulator-service\n build:\n context: .\n dockerfile: src/Services/EmulatorService/Emulator.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5009\n # MongoDB configuration\n - ConnectionStrings__emulator-mongodb=mongodb://root:mongopw@mongo\n - MongoDb__DatabaseName=stemify_emulator\n # Redis configuration\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n # gRPC endpoints\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcResourceUrl=http://resource-service:5003\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=emulator-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=emulator-service,service.version=1.0.0\n # Cloudinary Configuration\n - Cloudinary__CloudName=${CLOUDINARY_CLOUD_NAME}\n - Cloudinary__ApiKey=${CLOUDINARY_API_KEY}\n - Cloudinary__ApiSecret=${CLOUDINARY_API_SECRET}\n ports:\n - 7009:80\n - 5009:5009\n depends_on:\n - redis\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n\n # ========================================\n # MONITORING STACK\n # ========================================\n\n # Prometheus - Metrics Collection\n prometheus:\n image: prom/prometheus:v3.5.0\n container_name: stemify-prometheus\n ports:\n - '9090:9090'\n volumes:\n - ./config/prometheus:/etc/prometheus:ro\n - prometheus_data:/prometheus\n command:\n - '--config.file=/etc/prometheus/prometheus.yml'\n - '--storage.tsdb.path=/prometheus'\n - '--storage.tsdb.retention.time=15d'\n - '--storage.tsdb.retention.size=10GB'\n - '--web.enable-lifecycle'\n - '--web.enable-otlp-receiver'\n restart: unless-stopped\n\n # AlertManager - Alert Management\n alertmanager:\n image: prom/alertmanager:v0.27.0\n container_name: stemify-alertmanager\n ports:\n - '9093:9093'\n volumes:\n - ./config/alertmanager:/etc/alertmanager:ro\n - alertmanager_data:/alertmanager\n command:\n - '--config.file=/etc/alertmanager/alertmanager.yml'\n - '--storage.path=/alertmanager'\n - '--web.external-url=http://localhost:9093'\n restart: unless-stopped\n\n # Grafana - Visualization\n grafana:\n image: grafana/grafana:latest\n container_name: stemify-grafana\n ports:\n - '3001:3001'\n volumes:\n - grafana_data:/var/lib/grafana\n - ./config/grafana/config:/etc/grafana:ro\n - ./config/grafana/dashboards:/var/lib/grafana/dashboards:ro\n environment:\n - GF_SECURITY_ADMIN_USER=${GRAFANA_ADMIN_USERNAME}\n - GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_ADMIN_PASSWORD:-admin123}\n - GF_USERS_ALLOW_SIGN_UP=false\n - GF_INSTALL_PLUGINS=grafana-clock-panel,grafana-simple-json-datasource\n - PROMETHEUS_ENDPOINT=http://prometheus:9090\n restart: unless-stopped\n depends_on:\n - prometheus\n\n # OpenTelemetry Collector\n otelcollector:\n image: otel/opentelemetry-collector-contrib:latest\n container_name: stemify-otelcollector\n ports:\n - '8888:8888' # Prometheus metrics\n - '8889:8889' # Prometheus exporter\n - '4317:4317' # OTLP gRPC receiver\n - '4318:4318' # OTLP HTTP receiver\n volumes:\n - ./config/otelcollector:/etc/otelcol-contrib:ro\n environment:\n - PROMETHEUS_ENDPOINT=http://prometheus:9090/api/v1/write\n command: ['--config=/etc/otelcol-contrib/config.yaml']\n restart: unless-stopped\n depends_on:\n - prometheus\n\n # ========================================\n # AI SERVICE & VECTOR DATABASE\n # ========================================\n\n # Qdrant - Vector Database for RAG\n qdrant:\n image: qdrant/qdrant:latest\n container_name: stemify-qdrant\n ports:\n - '6333:6333' # HTTP API\n - '6334:6334' # gRPC\n volumes:\n - qdrant_data:/qdrant/storage\n environment:\n - QDRANT__SERVICE__GRPC_PORT=6334\n restart: unless-stopped\n\n neo4j:\n image: neo4j:5.20\n container_name: stemify-neo4j\n ports:\n - '7474:7474' # HTTP\n - '7687:7687' # Bolt\n environment:\n - NEO4J_AUTH=neo4j/password\n - NEO4J_PLUGINS=[\"apoc\"]\n - NEO4J_dbms_memory_heap_max__size=2G\n - NEO4J_dbms_memory_pagecache_size=1G\n volumes:\n - neo4j_data:/data\n - neo4j_logs:/logs\n restart: unless-stopped\n healthcheck:\n test: ['CMD', 'cypher-shell', '-u', 'neo4j', '-p', 'password', 'RETURN 1']\n interval: 30s\n timeout: 10s\n retries: 3\n start_period: 40s\n ai-service:\n image: rosie91/stemify:ai-service\n build:\n context: ./src/Services/AIService\n dockerfile: Dockerfile\n environment:\n - PORT=80\n - OPENAI_API_KEY=${OPENAI_API_KEY}\n - OPENAI_BASE_URL=${OPENAI_BASE_URL:-https://api.openai.com/v1}\n - DEEPSEEK_API_KEY=${DEEPSEEK_API_KEY}\n - RESOURCE_GRPC_CERT_PATH=${RESOURCE_GRPC_CERT_PATH}\n ports:\n - '7010:80'\n - '5010:5010' # gRPC\n # depends_on:\n # - qdrant\n # - neo4j\n restart: unless-stopped\n healthcheck:\n test: ['CMD', 'curl', '-f', 'http://localhost:80/health']\n interval: 30s\n timeout: 10s\n retries: 3\n start_period: 40s\n\nvolumes:\n redis-data:\n postgres_data:\n mongodata:\n prometheus_data:\n alertmanager_data:\n grafana_data:\n qdrant_data:\n neo4j_data:\n neo4j_logs:\n"}, "31d71b96390237d5d923f6b8e5ee4894470cb571": {"t": 1785748760.0754573, "n": 23914, "h": "6344108eac82d8ca", "c": "services:\n postgres:\n image: postgres:17\n container_name: stemify-postgres\n environment:\n - POSTGRES_PASSWORD=${DB_PASSWORD}\n ports:\n - '5432:5432'\n volumes:\n - postgres_data:/var/lib/postgresql/data\n restart: unless-stopped\n rabbitmq:\n image: rabbitmq:3-management-alpine\n ports:\n - 5672:5672\n - 15672:15672\n environment:\n - RABBITMQ_DEFAULT_USER=guest\n - RABBITMQ_DEFAULT_PASS=guest\n healthcheck:\n test: ['CMD', 'rabbitmq-diagnostics', 'ping']\n interval: 30s\n timeout: 10s\n retries: 5\n start_period: 30s\n restart: unless-stopped\n mongo:\n image: mongo\n environment:\n - MONGO_INITDB_ROOT_USERNAME=root\n - MONGO_INITDB_ROOT_PASSWORD=mongopw\n ports:\n - 27018:27017\n volumes:\n - mongodata:/var/lib/mongodb/data\n redis:\n image: redis:latest\n ports:\n - 6379:6379\n command: ['redis-server', '--appendonly', 'yes']\n volumes:\n - redis-data:/data\n redis-commander:\n image: rediscommander/redis-commander:latest\n environment:\n - REDIS_HOSTS=local:redis:6379\n - HTTP_USER=root\n - HTTP_PASSWORD=secret\n ports:\n - 8082:8082\n depends_on:\n - redis\n classroom-service:\n image: rosie91/stemify:classroom-service\n build:\n context: .\n dockerfile: src/Services/ClassroomService/Classroom.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5001\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcResourceUrl=http://resource-service:5003\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcOrderUrl=http://order-service:5006\n - ConnectionStrings__stemifyclassroom=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_classroom\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=classroom-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=classroom-service,service.version=1.0.0\n # Cloudinary Configuration\n - Cloudinary__CloudName=${CLOUDINARY_CLOUD_NAME}\n - Cloudinary__ApiKey=${CLOUDINARY_API_KEY}\n - Cloudinary__ApiSecret=${CLOUDINARY_API_SECRET}\n - Pdflayer__ApiKey=e4a39484d4c94acacd0da61ba5970b09\n ports:\n - 7001:80\n - 5001:5001\n depends_on:\n - postgres\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n resource-service:\n image: rosie91/stemify:resource-service\n build:\n context: .\n dockerfile: src/Services/Resource/Resource.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5003\n - ConnectionStrings__stemifyresource=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_resource\n - Cloudinary__CloudName=${CLOUDINARY_CLOUD_NAME}\n - Cloudinary__ApiKey=${CLOUDINARY_API_KEY}\n - Cloudinary__ApiSecret=${CLOUDINARY_API_SECRET}\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcEmulatorUrl=http://emulator-service:5009\n - ClientApp=https://localhost:3000\n - Gemini__BaseUrl=https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent\n - Gemini__ApiKey=${GEMINI_API_KEY}\n - Gemini__Model=gemini-2.5-flash\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=resource-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=resource-service,service.version=1.0.0\n ports:\n - 7003:80\n - 5003:5003\n depends_on:\n - postgres\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n product-service:\n image: rosie91/stemify:product-service\n build:\n context: .\n dockerfile: src/Services/ProductService/Product.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5005\n - ConnectionStrings__stemifyproduct=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_product\n - Cloudinary__CloudName=${CLOUDINARY_CLOUD_NAME}\n - Cloudinary__ApiKey=${CLOUDINARY_API_KEY}\n - Cloudinary__ApiSecret=${CLOUDINARY_API_SECRET}\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcResourceUrl=http://resource-service:5003\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=product-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=product-service,service.version=1.0.0\n ports:\n - 7005:80\n - 5005:5005\n depends_on:\n - postgres\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n order-service:\n image: rosie91/stemify:order-service\n build:\n context: .\n dockerfile: src/Services/OrderService/Order.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5006\n - ConnectionStrings__stemifyorder=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_order\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcResourceUrl=http://resource-service:5003\n - GrpcProductUrl=http://product-service:5005\n - GrpcClassroomUrl=http://classroom-service:5001\n - Cloudinary__CloudName=${CLOUDINARY_CLOUD_NAME}\n - Cloudinary__ApiKey=${CLOUDINARY_API_KEY}\n - Cloudinary__ApiSecret=${CLOUDINARY_API_SECRET}\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=order-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=order-service,service.version=1.0.0\n\n ports:\n - 7006:80\n - 5006:5006\n depends_on:\n - postgres\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n notification-service:\n image: rosie91/stemify:notification-service\n build:\n context: .\n dockerfile: src/Services/Notification/Notification.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5004\n - ConnectionStrings__stemifynotification=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_notification\n # SendGrid configuration\n - SendGridSettings__ApiKey=${SENDGRID_API_KEY}\n - SendGridSettings__FromEmail=stemify30062025@gmail.com\n - SendGridSettings__FromName=STEMify\n # Client App configuration\n - ClientApp__BaseUrl=https://localhost:3000\n - ClientApp=https://localhost:3000\n - Issuer=http://localhost:5000/\n - IdentityServiceUrl=http://identity-service\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=notification-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=notification-service,service.version=1.0.0\n ports:\n - 7004:80\n - 5004:5004\n depends_on:\n - postgres\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n gateway-service:\n image: rosie91/stemify:gateway-service\n build:\n context: .\n dockerfile: src/ApiGateways/ApiGateway/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Docker\n - ASPNETCORE_URLS=http://+:80\n - IdentityServiceUrl=http://identity-service\n - Issuer=http://localhost:5000/\n - ClientApp__0=http://localhost:3232\n - ClientApp__1=https://localhost:3000\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=gateway-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=gateway-service,service.version=1.0.0\n ports:\n - 6001:80\n depends_on:\n - otelcollector\n restart: unless-stopped\n identity-api-service:\n image: rosie91/stemify:identity-api-service\n build:\n context: .\n dockerfile: src/Services/Identity/Identity.Api/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5002\n - ConnectionStrings__stemifyidentity=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_identity\n # RabbitMQ Configuration (for MassTransit)\n - RabbitMq__Url=${RABBITMQ_URL}\n # OpenIddict Configuration\n - OpenIddict__Authority=http://identity-service:80\n # Client App Configuration\n - ClientApp=https://localhost:3000\n # Email Settings\n - EmailSettings__From=${IDENTITY_EMAIL_FROM}\n - EmailSettings__DisplayName=${IDENTITY_EMAIL_DISPLAY_NAME}\n - EmailSettings__SMTPServer=${IDENTITY_EMAIL_SMTP_SERVER}\n - EmailSettings__Port=${IDENTITY_EMAIL_PORT}\n - EmailSettings__Username=${IDENTITY_EMAIL_USERNAME}\n - EmailSettings__Password=${IDENTITY_EMAIL_PASSWORD}\n - EmailSettings__UseSSL=${IDENTITY_EMAIL_USE_SSL}\n - EmailSettings__ApplicationName=${IDENTITY_EMAIL_APP_NAME}\n - EmailSettings__SupportEmail=${IDENTITY_EMAIL_SUPPORT}\n # Google Authentication\n - Authentication__Google__ClientId=${GOOGLE_CLIENT_ID}\n - Authentication__Google__ClientSecret=${GOOGLE_CLIENT_SECRET}\n - Authentication__Google__RedirectUri=${GOOGLE_REDIRECT_URI}\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=identity-api-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=identity-api-service,service.version=1.0.0\n - GrpcServices__OrderService__Address=http://order-service:5006\n # Google Authentication\n - Authentication__Google__ClientId=${GOOGLE_CLIENT_ID}\n - Authentication__Google__ClientSecret=${GOOGLE_CLIENT_SECRET}\n - Authentication__Google__RedirectUri=${GOOGLE_REDIRECT_URI}\n - BackendUrl=${IDENTITY_BACKEND_URL:-http://localhost:6001}\n ports:\n - 7002:80\n - 5002:5002\n depends_on:\n - postgres\n - otelcollector\n restart: unless-stopped\n identity-service:\n image: rosie91/stemify:identity-service\n build:\n context: .\n dockerfile: src/Services/Identity/Identity.Web/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - ASPNETCORE_URLS=http://+:80\n - ConnectionStrings__stemifyidentity=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_identity\n # OpenIddict configuration for Docker\n - OpenIddict__Issuer=http://localhost:5000\n - OpenIddict__Authority=http://localhost:5000\n - OpenIddict__SigningKey=${OPENIDDICT_SIGNING_KEY}\n - OpenIddict__EncryptionKey=${OPENIDDICT_ENCRYPTION_KEY}\n - OpenIddict__ApiClientSecret=${OPENIDDICT_API_CLIENT_SECRET}\n - OpenIddict__ForceRecreateClients=${OPENIDDICT_FORCE_RECREATE_CLIENTS:-true}\n - OpenIddict__DisableHttpsRequirement=${OPENIDDICT_DISABLE_HTTPS:-true}\n # CORS for Docker environment\n - Cors__Origins=${IDENTITY_CORS_ORIGINS:-https://localhost:3000}\n - ClientApp=${IDENTITY_CLIENT_APP:-https://localhost:3000}\n - BackendUrl=${IDENTITY_BACKEND_URL:-http://localhost:5000}\n # Google Authentication\n - Authentication__Google__ClientId=${GOOGLE_CLIENT_ID}\n - Authentication__Google__ClientSecret=${GOOGLE_CLIENT_SECRET}\n - Authentication__Google__RedirectUri=${GOOGLE_REDIRECT_URI}\n # Email Settings\n - EmailSettings__SMTPServer=${IDENTITY_EMAIL_SMTP_SERVER}\n - EmailSettings__Port=${IDENTITY_EMAIL_PORT}\n - EmailSettings__UseSSL=${IDENTITY_EMAIL_USE_SSL}\n - EmailSettings__Username=${IDENTITY_EMAIL_USERNAME}\n - EmailSettings__Password=${IDENTITY_EMAIL_PASSWORD}\n - EmailSettings__From=${IDENTITY_EMAIL_FROM}\n - EmailSettings__DisplayName=${IDENTITY_EMAIL_DISPLAY_NAME}\n - EmailSettings__ApplicationName=${IDENTITY_EMAIL_APP_NAME}\n - EmailSettings__SupportEmail=${IDENTITY_EMAIL_SUPPORT}\n - EmailSettings__Enabled=${IDENTITY_EMAIL_ENABLED}\n - EmailSettings__EnableVerification=${IDENTITY_EMAIL_ENABLE_VERIFICATION}\n - EmailSettings__TimeoutMs=${IDENTITY_EMAIL_TIMEOUT_MS}\n - EmailSettings__MaxRetryAttempts=${IDENTITY_EMAIL_MAX_RETRY}\n - EmailSettings__LogEmailContent=${IDENTITY_EMAIL_LOG_CONTENT}\n # gRPC Services\n - GrpcServices__OrderService__Address=http://order-service:5006\n # Database Options\n - DatabaseOptions__MaxRetryCount=5\n - DatabaseOptions__MaxRetryDelay=00:00:10\n - DatabaseOptions__CommandTimeout=30\n - DatabaseOptions__EnableRetryOnFailure=true\n - DatabaseOptions__EnableSensitiveDataLogging=${DB_ENABLE_SENSITIVE_LOGGING}\n - DatabaseOptions__EnableDetailedErrors=${DB_ENABLE_DETAILED_ERRORS}\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=identity-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=identity-service,service.version=1.0.0\n - RabbitMq__Url=${RABBITMQ_URL}\n ports:\n - 5000:80\n depends_on:\n - postgres\n - otelcollector\n restart: unless-stopped\n # cart-service:\n # image: rosie91/stemify:cart-service\n # build:\n # context: .\n # dockerfile: src/Services/CartService/Cart.API/Dockerfile\n # environment:\n # - ASPNETCORE_ENVIRONMENT=Development\n # - RabbitMq__Url=${RABBITMQ_URL}\n # - Kestrel__Endpoints__WebApi__Protocols=Http1\n # - Kestrel__Endpoints__WebApi__Url=http://+:80\n # - Kestrel__Endpoints__Grpc__Protocols=Http2\n # - Kestrel__Endpoints__Grpc__Url=http://+:5007\n # - ConnectionStrings__stemifycart=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_cart\n # - RedisConfig__Host=redis\n # - RedisConfig__Port=6379\n # - RedisConfig__Password=secret\n # - RedisConfig__Ssl=false\n # - GrpcIdentityUrl=http://identity-api-service:5002\n # - GrpcProductUrl=http://product-service:5005\n # # OpenTelemetry Configuration\n # - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n # - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n # - OTEL_SERVICE_NAME=cart-service\n # - OTEL_RESOURCE_ATTRIBUTES=service.name=cart-service,service.version=1.0.0\n # ports:\n # - 7007:80\n # - 5007:5007\n # depends_on:\n # - postgres\n # - redis\n # - otelcollector\n # #- rabbitmq\n # restart: unless-stopped\n # payment-service:\n # image: rosie91/stemify:payment-service\n # build:\n # context: .\n # dockerfile: src/Services/PaymentService/Payment.API/Dockerfile\n # environment:\n # - ASPNETCORE_ENVIRONMENT=Development\n # - Logging__LogLevel__Microsoft.EntityFrameworkCore.Database.Command=Warning\n # - RabbitMq__Url=${RABBITMQ_URL}\n # - Kestrel__Endpoints__WebApi__Protocols=Http1\n # - Kestrel__Endpoints__WebApi__Url=http://+:80\n # - Kestrel__Endpoints__Grpc__Protocols=Http2\n # - Kestrel__Endpoints__Grpc__Url=http://+:5008\n # - ConnectionStrings__stemifypayment=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_payment\n # - RedisConfig__Host=redis\n # - RedisConfig__Port=6379\n # - RedisConfig__Password=secret\n # - RedisConfig__Ssl=false\n # - GrpcIdentityUrl=http://identity-api-service:5002\n # # PayOS configuration\n # - PaymentGateways__PayOS__ClientId=${PAYOS_CLIENT_ID}\n # - PaymentGateways__PayOS__ApiKey=${PAYOS_API_KEY}\n # - PaymentGateways__PayOS__ChecksumKey=${PAYOS_CHECKSUM_KEY}\n # - PaymentGateways__PayOS__BaseUrl=https://api-merchant.payos.vn\n # # Stripe configuration\n # - PaymentGateways__Stripe__SecretKey=${STRIPE_SECRET_KEY}\n # - PaymentGateways__Stripe__PublishableKey=${STRIPE_PUBLISH_KEY}\n # - PaymentGateways__Stripe__WebhookSecret=${STRIPE_WEBHOOK_KEY}\n # # OpenTelemetry Configuration\n # - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n # - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n # - OTEL_SERVICE_NAME=payment-service\n # - OTEL_RESOURCE_ATTRIBUTES=service.name=payment-service,service.version=1.0.0\n # ports:\n # - 7008:80\n # - 5008:5008\n # depends_on:\n # - postgres\n # - redis\n # - otelcollector\n # #- rabbitmq\n # restart: unless-stopped\n emulator-service:\n image: rosie91/stemify:emulator-service\n build:\n context: .\n dockerfile: src/Services/EmulatorService/Emulator.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5009\n # MongoDB configuration\n - ConnectionStrings__emulator-mongodb=mongodb://root:mongopw@mongo\n - MongoDb__DatabaseName=stemify_emulator\n # Redis configuration\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n # gRPC endpoints\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcResourceUrl=http://resource-service:5003\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=emulator-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=emulator-service,service.version=1.0.0\n # Cloudinary Configuration\n - Cloudinary__CloudName=${CLOUDINARY_CLOUD_NAME}\n - Cloudinary__ApiKey=${CLOUDINARY_API_KEY}\n - Cloudinary__ApiSecret=${CLOUDINARY_API_SECRET}\n ports:\n - 7009:80\n - 5009:5009\n depends_on:\n - redis\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n\n # ========================================\n # MONITORING STACK\n # ========================================\n\n # Prometheus - Metrics Collection\n prometheus:\n image: prom/prometheus:v3.5.0\n container_name: stemify-prometheus\n ports:\n - '9090:9090'\n volumes:\n - ./config/prometheus:/etc/prometheus:ro\n - prometheus_data:/prometheus\n command:\n - '--config.file=/etc/prometheus/prometheus.yml'\n - '--storage.tsdb.path=/prometheus'\n - '--storage.tsdb.retention.time=15d'\n - '--storage.tsdb.retention.size=10GB'\n - '--web.enable-lifecycle'\n - '--web.enable-otlp-receiver'\n restart: unless-stopped\n\n # AlertManager - Alert Management\n alertmanager:\n image: prom/alertmanager:v0.27.0\n container_name: stemify-alertmanager\n ports:\n - '9093:9093'\n volumes:\n - ./config/alertmanager:/etc/alertmanager:ro\n - alertmanager_data:/alertmanager\n command:\n - '--config.file=/etc/alertmanager/alertmanager.yml'\n - '--storage.path=/alertmanager'\n - '--web.external-url=http://localhost:9093'\n restart: unless-stopped\n\n # Grafana - Visualization\n grafana:\n image: grafana/grafana:latest\n container_name: stemify-grafana\n ports:\n - '3001:3001'\n volumes:\n - grafana_data:/var/lib/grafana\n - ./config/grafana/config:/etc/grafana:ro\n - ./config/grafana/dashboards:/var/lib/grafana/dashboards:ro\n environment:\n - GF_SECURITY_ADMIN_USER=${GRAFANA_ADMIN_USERNAME}\n - GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_ADMIN_PASSWORD:-admin123}\n - GF_USERS_ALLOW_SIGN_UP=false\n - GF_INSTALL_PLUGINS=grafana-clock-panel,grafana-simple-json-datasource\n - PROMETHEUS_ENDPOINT=http://prometheus:9090\n restart: unless-stopped\n depends_on:\n - prometheus\n\n # OpenTelemetry Collector\n otelcollector:\n image: otel/opentelemetry-collector-contrib:latest\n container_name: stemify-otelcollector\n ports:\n - '8888:8888' # Prometheus metrics\n - '8889:8889' # Prometheus exporter\n - '4317:4317' # OTLP gRPC receiver\n - '4318:4318' # OTLP HTTP receiver\n volumes:\n - ./config/otelcollector:/etc/otelcol-contrib:ro\n environment:\n - PROMETHEUS_ENDPOINT=http://prometheus:9090/api/v1/write\n command: ['--config=/etc/otelcol-contrib/config.yaml']\n restart: unless-stopped\n depends_on:\n - prometheus\n\n # ========================================\n # AI SERVICE & VECTOR DATABASE\n # ========================================\n\n # Qdrant - Vector Database for RAG\n qdrant:\n image: qdrant/qdrant:latest\n container_name: stemify-qdrant\n ports:\n - '6333:6333' # HTTP API\n - '6334:6334' # gRPC\n volumes:\n - qdrant_data:/qdrant/storage\n environment:\n - QDRANT__SERVICE__GRPC_PORT=6334\n restart: unless-stopped\n\n neo4j:\n image: neo4j:5.20\n container_name: stemify-neo4j\n ports:\n - '7474:7474' # HTTP\n - '7687:7687' # Bolt\n environment:\n - NEO4J_AUTH=neo4j/password\n - NEO4J_PLUGINS=[\"apoc\"]\n - NEO4J_dbms_memory_heap_max__size=2G\n - NEO4J_dbms_memory_pagecache_size=1G\n volumes:\n - neo4j_data:/data\n - neo4j_logs:/logs\n restart: unless-stopped\n healthcheck:\n test: ['CMD', 'cypher-shell', '-u', 'neo4j', '-p', 'password', 'RETURN 1']\n interval: 30s\n timeout: 10s\n retries: 3\n start_period: 40s\n ai-service:\n image: rosie91/stemify:ai-service\n build:\n context: ./src/Services/AIService\n dockerfile: Dockerfile\n environment:\n - PORT=80\n - OPENAI_API_KEY=${OPENAI_API_KEY}\n - OPENAI_BASE_URL=${OPENAI_BASE_URL:-https://api.openai.com/v1}\n - DEEPSEEK_API_KEY=${DEEPSEEK_API_KEY}\n - RESOURCE_GRPC_CERT_PATH=${RESOURCE_GRPC_CERT_PATH}\n ports:\n - '7010:80'\n - '5010:5010' # gRPC\n # depends_on:\n # - qdrant\n # - neo4j\n restart: unless-stopped\n healthcheck:\n test: ['CMD', 'curl', '-f', 'http://localhost:80/health']\n interval: 30s\n timeout: 10s\n retries: 3\n start_period: 40s\n\nvolumes:\n redis-data:\n postgres_data:\n mongodata:\n prometheus_data:\n alertmanager_data:\n grafana_data:\n qdrant_data:\n neo4j_data:\n neo4j_logs:\n"}, "64c9bb4c0da719bc2df26d628744a1924c923937": {"t": 1785748761.3529472, "n": 23857, "h": "901615390c28a67a", "c": "services:\n postgres:\n image: postgres:17\n container_name: stemify-postgres\n environment:\n - POSTGRES_PASSWORD=${DB_PASSWORD}\n ports:\n - '5432:5432'\n volumes:\n - postgres_data:/var/lib/postgresql/data\n restart: unless-stopped\n rabbitmq:\n image: rabbitmq:3-management-alpine\n ports:\n - 5672:5672\n - 15672:15672\n environment:\n - RABBITMQ_DEFAULT_USER=guest\n - RABBITMQ_DEFAULT_PASS=guest\n healthcheck:\n test: ['CMD', 'rabbitmq-diagnostics', 'ping']\n interval: 30s\n timeout: 10s\n retries: 5\n start_period: 30s\n restart: unless-stopped\n mongo:\n image: mongo\n environment:\n - MONGO_INITDB_ROOT_USERNAME=root\n - MONGO_INITDB_ROOT_PASSWORD=mongopw\n ports:\n - 27018:27017\n volumes:\n - mongodata:/var/lib/mongodb/data\n redis:\n image: redis:latest\n ports:\n - 6379:6379\n command: ['redis-server', '--appendonly', 'yes']\n volumes:\n - redis-data:/data\n redis-commander:\n image: rediscommander/redis-commander:latest\n environment:\n - REDIS_HOSTS=local:redis:6379\n - HTTP_USER=root\n - HTTP_PASSWORD=secret\n ports:\n - 8082:8082\n depends_on:\n - redis\n classroom-service:\n image: rosie91/stemify:classroom-service\n build:\n context: .\n dockerfile: src/Services/ClassroomService/Classroom.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5001\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcResourceUrl=http://resource-service:5003\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcOrderUrl=http://order-service:5006\n - ConnectionStrings__stemifyclassroom=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_classroom\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=classroom-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=classroom-service,service.version=1.0.0\n # Cloudinary Configuration\n - Cloudinary__CloudName=${CLOUDINARY_CLOUD_NAME}\n - Cloudinary__ApiKey=${CLOUDINARY_API_KEY}\n - Cloudinary__ApiSecret=${CLOUDINARY_API_SECRET}\n ports:\n - 7001:80\n - 5001:5001\n depends_on:\n - postgres\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n resource-service:\n image: rosie91/stemify:resource-service\n build:\n context: .\n dockerfile: src/Services/Resource/Resource.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5003\n - ConnectionStrings__stemifyresource=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_resource\n - Cloudinary__CloudName=${CLOUDINARY_CLOUD_NAME}\n - Cloudinary__ApiKey=${CLOUDINARY_API_KEY}\n - Cloudinary__ApiSecret=${CLOUDINARY_API_SECRET}\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcEmulatorUrl=http://emulator-service:5009\n - ClientApp=https://localhost:3000\n - Gemini__BaseUrl=https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent\n - Gemini__ApiKey=${GEMINI_API_KEY}\n - Gemini__Model=gemini-2.5-flash\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=resource-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=resource-service,service.version=1.0.0\n ports:\n - 7003:80\n - 5003:5003\n depends_on:\n - postgres\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n product-service:\n image: rosie91/stemify:product-service\n build:\n context: .\n dockerfile: src/Services/ProductService/Product.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5005\n - ConnectionStrings__stemifyproduct=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_product\n - Cloudinary__CloudName=${CLOUDINARY_CLOUD_NAME}\n - Cloudinary__ApiKey=${CLOUDINARY_API_KEY}\n - Cloudinary__ApiSecret=${CLOUDINARY_API_SECRET}\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcResourceUrl=http://resource-service:5003\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=product-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=product-service,service.version=1.0.0\n ports:\n - 7005:80\n - 5005:5005\n depends_on:\n - postgres\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n order-service:\n image: rosie91/stemify:order-service\n build:\n context: .\n dockerfile: src/Services/OrderService/Order.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5006\n - ConnectionStrings__stemifyorder=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_order\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcResourceUrl=http://resource-service:5003\n - GrpcProductUrl=http://product-service:5005\n - GrpcClassroomUrl=http://classroom-service:5001\n - Cloudinary__CloudName=${CLOUDINARY_CLOUD_NAME}\n - Cloudinary__ApiKey=${CLOUDINARY_API_KEY}\n - Cloudinary__ApiSecret=${CLOUDINARY_API_SECRET}\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=order-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=order-service,service.version=1.0.0\n\n ports:\n - 7006:80\n - 5006:5006\n depends_on:\n - postgres\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n notification-service:\n image: rosie91/stemify:notification-service\n build:\n context: .\n dockerfile: src/Services/Notification/Notification.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5004\n - ConnectionStrings__stemifynotification=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_notification\n # SendGrid configuration\n - SendGridSettings__ApiKey=${SENDGRID_API_KEY}\n - SendGridSettings__FromEmail=stemify30062025@gmail.com\n - SendGridSettings__FromName=STEMify\n # Client App configuration\n - ClientApp__BaseUrl=https://localhost:3000\n - ClientApp=https://localhost:3000\n - Issuer=http://localhost:5000/\n - IdentityServiceUrl=http://identity-service\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=notification-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=notification-service,service.version=1.0.0\n ports:\n - 7004:80\n - 5004:5004\n depends_on:\n - postgres\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n gateway-service:\n image: rosie91/stemify:gateway-service\n build:\n context: .\n dockerfile: src/ApiGateways/ApiGateway/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Docker\n - ASPNETCORE_URLS=http://+:80\n - IdentityServiceUrl=http://identity-service\n - Issuer=http://localhost:5000/\n - ClientApp__0=http://localhost:3232\n - ClientApp__1=https://localhost:3000\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=gateway-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=gateway-service,service.version=1.0.0\n ports:\n - 6001:80\n depends_on:\n - otelcollector\n restart: unless-stopped\n identity-api-service:\n image: rosie91/stemify:identity-api-service\n build:\n context: .\n dockerfile: src/Services/Identity/Identity.Api/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5002\n - ConnectionStrings__stemifyidentity=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_identity\n # RabbitMQ Configuration (for MassTransit)\n - RabbitMq__Url=${RABBITMQ_URL}\n # OpenIddict Configuration\n - OpenIddict__Authority=http://identity-service:80\n # Client App Configuration\n - ClientApp=https://localhost:3000\n # Email Settings\n - EmailSettings__From=${IDENTITY_EMAIL_FROM}\n - EmailSettings__DisplayName=${IDENTITY_EMAIL_DISPLAY_NAME}\n - EmailSettings__SMTPServer=${IDENTITY_EMAIL_SMTP_SERVER}\n - EmailSettings__Port=${IDENTITY_EMAIL_PORT}\n - EmailSettings__Username=${IDENTITY_EMAIL_USERNAME}\n - EmailSettings__Password=${IDENTITY_EMAIL_PASSWORD}\n - EmailSettings__UseSSL=${IDENTITY_EMAIL_USE_SSL}\n - EmailSettings__ApplicationName=${IDENTITY_EMAIL_APP_NAME}\n - EmailSettings__SupportEmail=${IDENTITY_EMAIL_SUPPORT}\n # Google Authentication\n - Authentication__Google__ClientId=${GOOGLE_CLIENT_ID}\n - Authentication__Google__ClientSecret=${GOOGLE_CLIENT_SECRET}\n - Authentication__Google__RedirectUri=${GOOGLE_REDIRECT_URI}\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=identity-api-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=identity-api-service,service.version=1.0.0\n - GrpcServices__OrderService__Address=http://order-service:5006\n # Google Authentication\n - Authentication__Google__ClientId=${GOOGLE_CLIENT_ID}\n - Authentication__Google__ClientSecret=${GOOGLE_CLIENT_SECRET}\n - Authentication__Google__RedirectUri=${GOOGLE_REDIRECT_URI}\n - BackendUrl=${IDENTITY_BACKEND_URL:-http://localhost:6001}\n ports:\n - 7002:80\n - 5002:5002\n depends_on:\n - postgres\n - otelcollector\n restart: unless-stopped\n identity-service:\n image: rosie91/stemify:identity-service\n build:\n context: .\n dockerfile: src/Services/Identity/Identity.Web/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - ASPNETCORE_URLS=http://+:80\n - ConnectionStrings__stemifyidentity=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_identity\n # OpenIddict configuration for Docker\n - OpenIddict__Issuer=http://localhost:5000\n - OpenIddict__Authority=http://localhost:5000\n - OpenIddict__SigningKey=${OPENIDDICT_SIGNING_KEY}\n - OpenIddict__EncryptionKey=${OPENIDDICT_ENCRYPTION_KEY}\n - OpenIddict__ApiClientSecret=${OPENIDDICT_API_CLIENT_SECRET}\n - OpenIddict__ForceRecreateClients=${OPENIDDICT_FORCE_RECREATE_CLIENTS:-true}\n - OpenIddict__DisableHttpsRequirement=${OPENIDDICT_DISABLE_HTTPS:-true}\n # CORS for Docker environment\n - Cors__Origins=${IDENTITY_CORS_ORIGINS:-https://localhost:3000}\n - ClientApp=${IDENTITY_CLIENT_APP:-https://localhost:3000}\n - BackendUrl=${IDENTITY_BACKEND_URL:-http://localhost:5000}\n # Google Authentication\n - Authentication__Google__ClientId=${GOOGLE_CLIENT_ID}\n - Authentication__Google__ClientSecret=${GOOGLE_CLIENT_SECRET}\n - Authentication__Google__RedirectUri=${GOOGLE_REDIRECT_URI}\n # Email Settings\n - EmailSettings__SMTPServer=${IDENTITY_EMAIL_SMTP_SERVER}\n - EmailSettings__Port=${IDENTITY_EMAIL_PORT}\n - EmailSettings__UseSSL=${IDENTITY_EMAIL_USE_SSL}\n - EmailSettings__Username=${IDENTITY_EMAIL_USERNAME}\n - EmailSettings__Password=${IDENTITY_EMAIL_PASSWORD}\n - EmailSettings__From=${IDENTITY_EMAIL_FROM}\n - EmailSettings__DisplayName=${IDENTITY_EMAIL_DISPLAY_NAME}\n - EmailSettings__ApplicationName=${IDENTITY_EMAIL_APP_NAME}\n - EmailSettings__SupportEmail=${IDENTITY_EMAIL_SUPPORT}\n - EmailSettings__Enabled=${IDENTITY_EMAIL_ENABLED}\n - EmailSettings__EnableVerification=${IDENTITY_EMAIL_ENABLE_VERIFICATION}\n - EmailSettings__TimeoutMs=${IDENTITY_EMAIL_TIMEOUT_MS}\n - EmailSettings__MaxRetryAttempts=${IDENTITY_EMAIL_MAX_RETRY}\n - EmailSettings__LogEmailContent=${IDENTITY_EMAIL_LOG_CONTENT}\n # gRPC Services\n - GrpcServices__OrderService__Address=http://order-service:5006\n # Database Options\n - DatabaseOptions__MaxRetryCount=5\n - DatabaseOptions__MaxRetryDelay=00:00:10\n - DatabaseOptions__CommandTimeout=30\n - DatabaseOptions__EnableRetryOnFailure=true\n - DatabaseOptions__EnableSensitiveDataLogging=${DB_ENABLE_SENSITIVE_LOGGING}\n - DatabaseOptions__EnableDetailedErrors=${DB_ENABLE_DETAILED_ERRORS}\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=identity-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=identity-service,service.version=1.0.0\n - RabbitMq__Url=${RABBITMQ_URL}\n ports:\n - 5000:80\n depends_on:\n - postgres\n - otelcollector\n restart: unless-stopped\n # cart-service:\n # image: rosie91/stemify:cart-service\n # build:\n # context: .\n # dockerfile: src/Services/CartService/Cart.API/Dockerfile\n # environment:\n # - ASPNETCORE_ENVIRONMENT=Development\n # - RabbitMq__Url=${RABBITMQ_URL}\n # - Kestrel__Endpoints__WebApi__Protocols=Http1\n # - Kestrel__Endpoints__WebApi__Url=http://+:80\n # - Kestrel__Endpoints__Grpc__Protocols=Http2\n # - Kestrel__Endpoints__Grpc__Url=http://+:5007\n # - ConnectionStrings__stemifycart=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_cart\n # - RedisConfig__Host=redis\n # - RedisConfig__Port=6379\n # - RedisConfig__Password=secret\n # - RedisConfig__Ssl=false\n # - GrpcIdentityUrl=http://identity-api-service:5002\n # - GrpcProductUrl=http://product-service:5005\n # # OpenTelemetry Configuration\n # - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n # - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n # - OTEL_SERVICE_NAME=cart-service\n # - OTEL_RESOURCE_ATTRIBUTES=service.name=cart-service,service.version=1.0.0\n # ports:\n # - 7007:80\n # - 5007:5007\n # depends_on:\n # - postgres\n # - redis\n # - otelcollector\n # #- rabbitmq\n # restart: unless-stopped\n # payment-service:\n # image: rosie91/stemify:payment-service\n # build:\n # context: .\n # dockerfile: src/Services/PaymentService/Payment.API/Dockerfile\n # environment:\n # - ASPNETCORE_ENVIRONMENT=Development\n # - Logging__LogLevel__Microsoft.EntityFrameworkCore.Database.Command=Warning\n # - RabbitMq__Url=${RABBITMQ_URL}\n # - Kestrel__Endpoints__WebApi__Protocols=Http1\n # - Kestrel__Endpoints__WebApi__Url=http://+:80\n # - Kestrel__Endpoints__Grpc__Protocols=Http2\n # - Kestrel__Endpoints__Grpc__Url=http://+:5008\n # - ConnectionStrings__stemifypayment=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_payment\n # - RedisConfig__Host=redis\n # - RedisConfig__Port=6379\n # - RedisConfig__Password=secret\n # - RedisConfig__Ssl=false\n # - GrpcIdentityUrl=http://identity-api-service:5002\n # # PayOS configuration\n # - PaymentGateways__PayOS__ClientId=${PAYOS_CLIENT_ID}\n # - PaymentGateways__PayOS__ApiKey=${PAYOS_API_KEY}\n # - PaymentGateways__PayOS__ChecksumKey=${PAYOS_CHECKSUM_KEY}\n # - PaymentGateways__PayOS__BaseUrl=https://api-merchant.payos.vn\n # # Stripe configuration\n # - PaymentGateways__Stripe__SecretKey=${STRIPE_SECRET_KEY}\n # - PaymentGateways__Stripe__PublishableKey=${STRIPE_PUBLISH_KEY}\n # - PaymentGateways__Stripe__WebhookSecret=${STRIPE_WEBHOOK_KEY}\n # # OpenTelemetry Configuration\n # - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n # - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n # - OTEL_SERVICE_NAME=payment-service\n # - OTEL_RESOURCE_ATTRIBUTES=service.name=payment-service,service.version=1.0.0\n # ports:\n # - 7008:80\n # - 5008:5008\n # depends_on:\n # - postgres\n # - redis\n # - otelcollector\n # #- rabbitmq\n # restart: unless-stopped\n emulator-service:\n image: rosie91/stemify:emulator-service\n build:\n context: .\n dockerfile: src/Services/EmulatorService/Emulator.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5009\n # MongoDB configuration\n - ConnectionStrings__emulator-mongodb=mongodb://root:mongopw@mongo\n - MongoDb__DatabaseName=stemify_emulator\n # Redis configuration\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n # gRPC endpoints\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcResourceUrl=http://resource-service:5003\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=emulator-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=emulator-service,service.version=1.0.0\n # Cloudinary Configuration\n - Cloudinary__CloudName=${CLOUDINARY_CLOUD_NAME}\n - Cloudinary__ApiKey=${CLOUDINARY_API_KEY}\n - Cloudinary__ApiSecret=${CLOUDINARY_API_SECRET}\n ports:\n - 7009:80\n - 5009:5009\n depends_on:\n - redis\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n\n # ========================================\n # MONITORING STACK\n # ========================================\n\n # Prometheus - Metrics Collection\n prometheus:\n image: prom/prometheus:v3.5.0\n container_name: stemify-prometheus\n ports:\n - '9090:9090'\n volumes:\n - ./config/prometheus:/etc/prometheus:ro\n - prometheus_data:/prometheus\n command:\n - '--config.file=/etc/prometheus/prometheus.yml'\n - '--storage.tsdb.path=/prometheus'\n - '--storage.tsdb.retention.time=15d'\n - '--storage.tsdb.retention.size=10GB'\n - '--web.enable-lifecycle'\n - '--web.enable-otlp-receiver'\n restart: unless-stopped\n\n # AlertManager - Alert Management\n alertmanager:\n image: prom/alertmanager:v0.27.0\n container_name: stemify-alertmanager\n ports:\n - '9093:9093'\n volumes:\n - ./config/alertmanager:/etc/alertmanager:ro\n - alertmanager_data:/alertmanager\n command:\n - '--config.file=/etc/alertmanager/alertmanager.yml'\n - '--storage.path=/alertmanager'\n - '--web.external-url=http://localhost:9093'\n restart: unless-stopped\n\n # Grafana - Visualization\n grafana:\n image: grafana/grafana:latest\n container_name: stemify-grafana\n ports:\n - '3001:3001'\n volumes:\n - grafana_data:/var/lib/grafana\n - ./config/grafana/config:/etc/grafana:ro\n - ./config/grafana/dashboards:/var/lib/grafana/dashboards:ro\n environment:\n - GF_SECURITY_ADMIN_USER=${GRAFANA_ADMIN_USERNAME}\n - GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_ADMIN_PASSWORD:-admin123}\n - GF_USERS_ALLOW_SIGN_UP=false\n - GF_INSTALL_PLUGINS=grafana-clock-panel,grafana-simple-json-datasource\n - PROMETHEUS_ENDPOINT=http://prometheus:9090\n restart: unless-stopped\n depends_on:\n - prometheus\n\n # OpenTelemetry Collector\n otelcollector:\n image: otel/opentelemetry-collector-contrib:latest\n container_name: stemify-otelcollector\n ports:\n - '8888:8888' # Prometheus metrics\n - '8889:8889' # Prometheus exporter\n - '4317:4317' # OTLP gRPC receiver\n - '4318:4318' # OTLP HTTP receiver\n volumes:\n - ./config/otelcollector:/etc/otelcol-contrib:ro\n environment:\n - PROMETHEUS_ENDPOINT=http://prometheus:9090/api/v1/write\n command: ['--config=/etc/otelcol-contrib/config.yaml']\n restart: unless-stopped\n depends_on:\n - prometheus\n\n # ========================================\n # AI SERVICE & VECTOR DATABASE\n # ========================================\n\n # Qdrant - Vector Database for RAG\n qdrant:\n image: qdrant/qdrant:latest\n container_name: stemify-qdrant\n ports:\n - '6333:6333' # HTTP API\n - '6334:6334' # gRPC\n volumes:\n - qdrant_data:/qdrant/storage\n environment:\n - QDRANT__SERVICE__GRPC_PORT=6334\n restart: unless-stopped\n\n neo4j:\n image: neo4j:5.20\n container_name: stemify-neo4j\n ports:\n - '7474:7474' # HTTP\n - '7687:7687' # Bolt\n environment:\n - NEO4J_AUTH=neo4j/password\n - NEO4J_PLUGINS=[\"apoc\"]\n - NEO4J_dbms_memory_heap_max__size=2G\n - NEO4J_dbms_memory_pagecache_size=1G\n volumes:\n - neo4j_data:/data\n - neo4j_logs:/logs\n restart: unless-stopped\n healthcheck:\n test: ['CMD', 'cypher-shell', '-u', 'neo4j', '-p', 'password', 'RETURN 1']\n interval: 30s\n timeout: 10s\n retries: 3\n start_period: 40s\n ai-service:\n image: rosie91/stemify:ai-service\n build:\n context: ./src/Services/AIService\n dockerfile: Dockerfile\n environment:\n - PORT=80\n - OPENAI_API_KEY=${OPENAI_API_KEY}\n - OPENAI_BASE_URL=${OPENAI_BASE_URL:-https://api.openai.com/v1}\n - DEEPSEEK_API_KEY=${DEEPSEEK_API_KEY}\n - RESOURCE_GRPC_CERT_PATH=${RESOURCE_GRPC_CERT_PATH}\n ports:\n - '7010:80' \n - '5010:5010' # gRPC\n # depends_on:\n # - qdrant\n # - neo4j\n restart: unless-stopped\n healthcheck:\n test: ['CMD', 'curl', '-f', 'http://localhost:80/health']\n interval: 30s\n timeout: 10s\n retries: 3\n start_period: 40s\n\nvolumes:\n redis-data:\n postgres_data:\n mongodata:\n prometheus_data:\n alertmanager_data:\n grafana_data:\n qdrant_data:\n neo4j_data:\n neo4j_logs:\n"}, "1fb35aff95d911399ff032c83749dd6e74fc699f": {"t": 1785748762.5996063, "n": 23707, "h": "c8a6f732cd32c867", "c": "services:\n postgres:\n image: postgres:17\n container_name: stemify-postgres\n environment:\n - POSTGRES_PASSWORD=${DB_PASSWORD}\n ports:\n - '5432:5432'\n volumes:\n - postgres_data:/var/lib/postgresql/data\n restart: unless-stopped\n rabbitmq:\n image: rabbitmq:3-management-alpine\n ports:\n - 5672:5672\n - 15672:15672\n environment:\n - RABBITMQ_DEFAULT_USER=guest\n - RABBITMQ_DEFAULT_PASS=guest\n healthcheck:\n test: ['CMD', 'rabbitmq-diagnostics', 'ping']\n interval: 30s\n timeout: 10s\n retries: 5\n start_period: 30s\n restart: unless-stopped\n mongo:\n image: mongo\n environment:\n - MONGO_INITDB_ROOT_USERNAME=root\n - MONGO_INITDB_ROOT_PASSWORD=mongopw\n ports:\n - 27018:27017\n volumes:\n - mongodata:/var/lib/mongodb/data\n redis:\n image: redis:latest\n ports:\n - 6379:6379\n command: ['redis-server', '--appendonly', 'yes']\n volumes:\n - redis-data:/data\n redis-commander:\n image: rediscommander/redis-commander:latest\n environment:\n - REDIS_HOSTS=local:redis:6379\n - HTTP_USER=root\n - HTTP_PASSWORD=secret\n ports:\n - 8082:8082\n depends_on:\n - redis\n classroom-service:\n image: rosie91/stemify:classroom-service\n build:\n context: .\n dockerfile: src/Services/ClassroomService/Classroom.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5001\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcResourceUrl=http://resource-service:5003\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcOrderUrl=http://order-service:5006\n - ConnectionStrings__stemifyclassroom=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_classroom\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=classroom-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=classroom-service,service.version=1.0.0\n # Cloudinary Configuration\n - Cloudinary__CloudName=${CLOUDINARY_CLOUD_NAME}\n - Cloudinary__ApiKey=${CLOUDINARY_API_KEY}\n - Cloudinary__ApiSecret=${CLOUDINARY_API_SECRET}\n ports:\n - 7001:80\n - 5001:5001\n depends_on:\n - postgres\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n resource-service:\n image: rosie91/stemify:resource-service\n build:\n context: .\n dockerfile: src/Services/Resource/Resource.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5003\n - ConnectionStrings__stemifyresource=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_resource\n - Cloudinary__CloudName=${CLOUDINARY_CLOUD_NAME}\n - Cloudinary__ApiKey=${CLOUDINARY_API_KEY}\n - Cloudinary__ApiSecret=${CLOUDINARY_API_SECRET}\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcEmulatorUrl=http://emulator-service:5009\n - ClientApp=https://localhost:3000\n - Gemini__BaseUrl=https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent\n - Gemini__ApiKey=${GEMINI_API_KEY}\n - Gemini__Model=gemini-2.5-flash\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=resource-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=resource-service,service.version=1.0.0\n ports:\n - 7003:80\n - 5003:5003\n depends_on:\n - postgres\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n product-service:\n image: rosie91/stemify:product-service\n build:\n context: .\n dockerfile: src/Services/ProductService/Product.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5005\n - ConnectionStrings__stemifyproduct=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_product\n - Cloudinary__CloudName=${CLOUDINARY_CLOUD_NAME}\n - Cloudinary__ApiKey=${CLOUDINARY_API_KEY}\n - Cloudinary__ApiSecret=${CLOUDINARY_API_SECRET}\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcResourceUrl=http://resource-service:5003\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=product-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=product-service,service.version=1.0.0\n ports:\n - 7005:80\n - 5005:5005\n depends_on:\n - postgres\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n order-service:\n image: rosie91/stemify:order-service\n build:\n context: .\n dockerfile: src/Services/OrderService/Order.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5006\n - ConnectionStrings__stemifyorder=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_order\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcResourceUrl=http://resource-service:5003\n - GrpcProductUrl=http://product-service:5005\n - GrpcClassroomUrl=http://classroom-service:5001\n - Cloudinary__CloudName=${CLOUDINARY_CLOUD_NAME}\n - Cloudinary__ApiKey=${CLOUDINARY_API_KEY}\n - Cloudinary__ApiSecret=${CLOUDINARY_API_SECRET}\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=order-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=order-service,service.version=1.0.0\n\n ports:\n - 7006:80\n - 5006:5006\n depends_on:\n - postgres\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n notification-service:\n image: rosie91/stemify:notification-service\n build:\n context: .\n dockerfile: src/Services/Notification/Notification.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5004\n - ConnectionStrings__stemifynotification=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_notification\n # SendGrid configuration\n - SendGridSettings__ApiKey=${SENDGRID_API_KEY}\n - SendGridSettings__FromEmail=stemify30062025@gmail.com\n - SendGridSettings__FromName=STEMify\n # Client App configuration\n - ClientApp__BaseUrl=https://localhost:3000\n - ClientApp=https://localhost:3000\n - Issuer=http://localhost:5000/\n - IdentityServiceUrl=http://identity-service\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=notification-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=notification-service,service.version=1.0.0\n ports:\n - 7004:80\n - 5004:5004\n depends_on:\n - postgres\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n gateway-service:\n image: rosie91/stemify:gateway-service\n build:\n context: .\n dockerfile: src/ApiGateways/ApiGateway/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Docker\n - ASPNETCORE_URLS=http://+:80\n - IdentityServiceUrl=http://identity-service\n - Issuer=http://localhost:5000/\n - ClientApp__0=http://localhost:3232\n - ClientApp__1=https://localhost:3000\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=gateway-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=gateway-service,service.version=1.0.0\n ports:\n - 6001:80\n depends_on:\n - otelcollector\n restart: unless-stopped\n identity-api-service:\n image: rosie91/stemify:identity-api-service\n build:\n context: .\n dockerfile: src/Services/Identity/Identity.Api/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5002\n - ConnectionStrings__stemifyidentity=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_identity\n # RabbitMQ Configuration (for MassTransit)\n - RabbitMq__Url=${RABBITMQ_URL}\n # OpenIddict Configuration\n - OpenIddict__Authority=http://identity-service:80\n # Client App Configuration\n - ClientApp=https://localhost:3000\n # Email Settings\n - EmailSettings__From=${IDENTITY_EMAIL_FROM}\n - EmailSettings__DisplayName=${IDENTITY_EMAIL_DISPLAY_NAME}\n - EmailSettings__SMTPServer=${IDENTITY_EMAIL_SMTP_SERVER}\n - EmailSettings__Port=${IDENTITY_EMAIL_PORT}\n - EmailSettings__Username=${IDENTITY_EMAIL_USERNAME}\n - EmailSettings__Password=${IDENTITY_EMAIL_PASSWORD}\n - EmailSettings__UseSSL=${IDENTITY_EMAIL_USE_SSL}\n - EmailSettings__ApplicationName=${IDENTITY_EMAIL_APP_NAME}\n - EmailSettings__SupportEmail=${IDENTITY_EMAIL_SUPPORT}\n # Google Authentication\n - Authentication__Google__ClientId=${GOOGLE_CLIENT_ID}\n - Authentication__Google__ClientSecret=${GOOGLE_CLIENT_SECRET}\n - Authentication__Google__RedirectUri=${GOOGLE_REDIRECT_URI}\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=identity-api-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=identity-api-service,service.version=1.0.0\n - GrpcServices__OrderService__Address=http://order-service:5006\n # Google Authentication\n - Authentication__Google__ClientId=${GOOGLE_CLIENT_ID}\n - Authentication__Google__ClientSecret=${GOOGLE_CLIENT_SECRET}\n - Authentication__Google__RedirectUri=${GOOGLE_REDIRECT_URI}\n - BackendUrl=${IDENTITY_BACKEND_URL:-http://localhost:6001}\n ports:\n - 7002:80\n - 5002:5002\n depends_on:\n - postgres\n - otelcollector\n restart: unless-stopped\n identity-service:\n image: rosie91/stemify:identity-service\n build:\n context: .\n dockerfile: src/Services/Identity/Identity.Web/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - ASPNETCORE_URLS=http://+:80\n - ConnectionStrings__stemifyidentity=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_identity\n # OpenIddict configuration for Docker\n - OpenIddict__Issuer=http://localhost:5000\n - OpenIddict__Authority=http://localhost:5000\n - OpenIddict__SigningKey=${OPENIDDICT_SIGNING_KEY}\n - OpenIddict__EncryptionKey=${OPENIDDICT_ENCRYPTION_KEY}\n - OpenIddict__ApiClientSecret=${OPENIDDICT_API_CLIENT_SECRET}\n - OpenIddict__ForceRecreateClients=${OPENIDDICT_FORCE_RECREATE_CLIENTS:-true}\n - OpenIddict__DisableHttpsRequirement=${OPENIDDICT_DISABLE_HTTPS:-true}\n # CORS for Docker environment\n - Cors__Origins=${IDENTITY_CORS_ORIGINS:-https://localhost:3000}\n - ClientApp=${IDENTITY_CLIENT_APP:-https://localhost:3000}\n - BackendUrl=${IDENTITY_BACKEND_URL:-http://localhost:5000}\n # Google Authentication\n - Authentication__Google__ClientId=${GOOGLE_CLIENT_ID}\n - Authentication__Google__ClientSecret=${GOOGLE_CLIENT_SECRET}\n - Authentication__Google__RedirectUri=${GOOGLE_REDIRECT_URI}\n # Email Settings\n - EmailSettings__SMTPServer=${IDENTITY_EMAIL_SMTP_SERVER}\n - EmailSettings__Port=${IDENTITY_EMAIL_PORT}\n - EmailSettings__UseSSL=${IDENTITY_EMAIL_USE_SSL}\n - EmailSettings__Username=${IDENTITY_EMAIL_USERNAME}\n - EmailSettings__Password=${IDENTITY_EMAIL_PASSWORD}\n - EmailSettings__From=${IDENTITY_EMAIL_FROM}\n - EmailSettings__DisplayName=${IDENTITY_EMAIL_DISPLAY_NAME}\n - EmailSettings__ApplicationName=${IDENTITY_EMAIL_APP_NAME}\n - EmailSettings__SupportEmail=${IDENTITY_EMAIL_SUPPORT}\n - EmailSettings__Enabled=${IDENTITY_EMAIL_ENABLED}\n - EmailSettings__EnableVerification=${IDENTITY_EMAIL_ENABLE_VERIFICATION}\n - EmailSettings__TimeoutMs=${IDENTITY_EMAIL_TIMEOUT_MS}\n - EmailSettings__MaxRetryAttempts=${IDENTITY_EMAIL_MAX_RETRY}\n - EmailSettings__LogEmailContent=${IDENTITY_EMAIL_LOG_CONTENT}\n # gRPC Services\n - GrpcServices__OrderService__Address=http://order-service:5006\n # Database Options\n - DatabaseOptions__MaxRetryCount=5\n - DatabaseOptions__MaxRetryDelay=00:00:10\n - DatabaseOptions__CommandTimeout=30\n - DatabaseOptions__EnableRetryOnFailure=true\n - DatabaseOptions__EnableSensitiveDataLogging=${DB_ENABLE_SENSITIVE_LOGGING}\n - DatabaseOptions__EnableDetailedErrors=${DB_ENABLE_DETAILED_ERRORS}\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=identity-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=identity-service,service.version=1.0.0\n - RabbitMq__Url=${RABBITMQ_URL}\n ports:\n - 5000:80\n depends_on:\n - postgres\n - otelcollector\n restart: unless-stopped\n cart-service:\n image: rosie91/stemify:cart-service\n build:\n context: .\n dockerfile: src/Services/CartService/Cart.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5007\n - ConnectionStrings__stemifycart=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_cart\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcProductUrl=http://product-service:5005\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=cart-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=cart-service,service.version=1.0.0\n ports:\n - 7007:80\n - 5007:5007\n depends_on:\n - postgres\n - redis\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n payment-service:\n image: rosie91/stemify:payment-service\n build:\n context: .\n dockerfile: src/Services/PaymentService/Payment.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - Logging__LogLevel__Microsoft.EntityFrameworkCore.Database.Command=Warning\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5008\n - ConnectionStrings__stemifypayment=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_payment\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n # PayOS configuration\n - PaymentGateways__PayOS__ClientId=${PAYOS_CLIENT_ID}\n - PaymentGateways__PayOS__ApiKey=${PAYOS_API_KEY}\n - PaymentGateways__PayOS__ChecksumKey=${PAYOS_CHECKSUM_KEY}\n - PaymentGateways__PayOS__BaseUrl=https://api-merchant.payos.vn\n # Stripe configuration\n - PaymentGateways__Stripe__SecretKey=${STRIPE_SECRET_KEY}\n - PaymentGateways__Stripe__PublishableKey=${STRIPE_PUBLISH_KEY}\n - PaymentGateways__Stripe__WebhookSecret=${STRIPE_WEBHOOK_KEY}\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=payment-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=payment-service,service.version=1.0.0\n ports:\n - 7008:80\n - 5008:5008\n depends_on:\n - postgres\n - redis\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n emulator-service:\n image: rosie91/stemify:emulator-service\n build:\n context: .\n dockerfile: src/Services/EmulatorService/Emulator.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5009\n # MongoDB configuration\n - ConnectionStrings__emulator-mongodb=mongodb://root:mongopw@mongo\n - MongoDb__DatabaseName=stemify_emulator\n # Redis configuration\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n # gRPC endpoints\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcResourceUrl=http://resource-service:5003\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=emulator-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=emulator-service,service.version=1.0.0\n # Cloudinary Configuration\n - Cloudinary__CloudName=${CLOUDINARY_CLOUD_NAME}\n - Cloudinary__ApiKey=${CLOUDINARY_API_KEY}\n - Cloudinary__ApiSecret=${CLOUDINARY_API_SECRET}\n ports:\n - 7009:80\n - 5009:5009\n depends_on:\n - redis\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n\n # ========================================\n # MONITORING STACK\n # ========================================\n\n # Prometheus - Metrics Collection\n prometheus:\n image: prom/prometheus:v3.5.0\n container_name: stemify-prometheus\n ports:\n - '9090:9090'\n volumes:\n - ./config/prometheus:/etc/prometheus:ro\n - prometheus_data:/prometheus\n command:\n - '--config.file=/etc/prometheus/prometheus.yml'\n - '--storage.tsdb.path=/prometheus'\n - '--storage.tsdb.retention.time=15d'\n - '--storage.tsdb.retention.size=10GB'\n - '--web.enable-lifecycle'\n - '--web.enable-otlp-receiver'\n restart: unless-stopped\n\n # AlertManager - Alert Management\n alertmanager:\n image: prom/alertmanager:v0.27.0\n container_name: stemify-alertmanager\n ports:\n - '9093:9093'\n volumes:\n - ./config/alertmanager:/etc/alertmanager:ro\n - alertmanager_data:/alertmanager\n command:\n - '--config.file=/etc/alertmanager/alertmanager.yml'\n - '--storage.path=/alertmanager'\n - '--web.external-url=http://localhost:9093'\n restart: unless-stopped\n\n # Grafana - Visualization\n grafana:\n image: grafana/grafana:latest\n container_name: stemify-grafana\n ports:\n - '3001:3001'\n volumes:\n - grafana_data:/var/lib/grafana\n - ./config/grafana/config:/etc/grafana:ro\n - ./config/grafana/dashboards:/var/lib/grafana/dashboards:ro\n environment:\n - GF_SECURITY_ADMIN_USER=${GRAFANA_ADMIN_USERNAME}\n - GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_ADMIN_PASSWORD:-admin123}\n - GF_USERS_ALLOW_SIGN_UP=false\n - GF_INSTALL_PLUGINS=grafana-clock-panel,grafana-simple-json-datasource\n - PROMETHEUS_ENDPOINT=http://prometheus:9090\n restart: unless-stopped\n depends_on:\n - prometheus\n\n # OpenTelemetry Collector\n otelcollector:\n image: otel/opentelemetry-collector-contrib:latest\n container_name: stemify-otelcollector\n ports:\n - '8888:8888' # Prometheus metrics\n - '8889:8889' # Prometheus exporter\n - '4317:4317' # OTLP gRPC receiver\n - '4318:4318' # OTLP HTTP receiver\n volumes:\n - ./config/otelcollector:/etc/otelcol-contrib:ro\n environment:\n - PROMETHEUS_ENDPOINT=http://prometheus:9090/api/v1/write\n command: ['--config=/etc/otelcol-contrib/config.yaml']\n restart: unless-stopped\n depends_on:\n - prometheus\n\n # ========================================\n # AI SERVICE & VECTOR DATABASE\n # ========================================\n\n # Qdrant - Vector Database for RAG\n qdrant:\n image: qdrant/qdrant:latest\n container_name: stemify-qdrant\n ports:\n - '6333:6333' # HTTP API\n - '6334:6334' # gRPC\n volumes:\n - qdrant_data:/qdrant/storage\n environment:\n - QDRANT__SERVICE__GRPC_PORT=6334\n restart: unless-stopped\n\n neo4j:\n image: neo4j:5.20\n container_name: stemify-neo4j\n ports:\n - '7474:7474' # HTTP\n - '7687:7687' # Bolt\n environment:\n - NEO4J_AUTH=neo4j/password\n - NEO4J_PLUGINS=[\"apoc\"]\n - NEO4J_dbms_memory_heap_max__size=2G\n - NEO4J_dbms_memory_pagecache_size=1G\n volumes:\n - neo4j_data:/data\n - neo4j_logs:/logs\n restart: unless-stopped\n healthcheck:\n test: ['CMD', 'cypher-shell', '-u', 'neo4j', '-p', 'password', 'RETURN 1']\n interval: 30s\n timeout: 10s\n retries: 3\n start_period: 40s\n ai-service:\n image: rosie91/stemify:ai-service\n build:\n context: ./src/Services/AIService\n dockerfile: Dockerfile\n environment:\n - PORT=80\n - OPENAI_API_KEY=${OPENAI_API_KEY}\n - OPENAI_BASE_URL=${OPENAI_BASE_URL:-https://api.openai.com/v1}\n - DEEPSEEK_API_KEY=${DEEPSEEK_API_KEY}\n - RESOURCE_GRPC_CERT_PATH=${RESOURCE_GRPC_CERT_PATH}\n ports:\n - '7010:80' \n - '5010:5010' # gRPC\n # depends_on:\n # - qdrant\n # - neo4j\n restart: unless-stopped\n healthcheck:\n test: ['CMD', 'curl', '-f', 'http://localhost:80/health']\n interval: 30s\n timeout: 10s\n retries: 3\n start_period: 40s\n\nvolumes:\n redis-data:\n postgres_data:\n mongodata:\n prometheus_data:\n alertmanager_data:\n grafana_data:\n qdrant_data:\n neo4j_data:\n neo4j_logs:\n"}, "a7c67fa5a5fa15955f6b3690485f5e0f49080484": {"t": 1785748763.8718991, "n": 23715, "h": "3e6888b8952ce67d", "c": "services:\n postgres:\n image: postgres:17\n container_name: stemify-postgres\n environment:\n - POSTGRES_PASSWORD=${DB_PASSWORD}\n ports:\n - '5432:5432'\n volumes:\n - postgres_data:/var/lib/postgresql/data\n restart: unless-stopped\n rabbitmq:\n image: rabbitmq:3-management-alpine\n ports:\n - 5672:5672\n - 15672:15672\n environment:\n - RABBITMQ_DEFAULT_USER=guest\n - RABBITMQ_DEFAULT_PASS=guest\n healthcheck:\n test: ['CMD', 'rabbitmq-diagnostics', 'ping']\n interval: 30s\n timeout: 10s\n retries: 5\n start_period: 30s\n restart: unless-stopped\n mongo:\n image: mongo\n environment:\n - MONGO_INITDB_ROOT_USERNAME=root\n - MONGO_INITDB_ROOT_PASSWORD=mongopw\n ports:\n - 27018:27017\n volumes:\n - mongodata:/var/lib/mongodb/data\n redis:\n image: redis:latest\n ports:\n - 6379:6379\n command: ['redis-server', '--appendonly', 'yes']\n volumes:\n - redis-data:/data\n redis-commander:\n image: rediscommander/redis-commander:latest\n environment:\n - REDIS_HOSTS=local:redis:6379\n - HTTP_USER=root\n - HTTP_PASSWORD=secret\n ports:\n - 8082:8082\n depends_on:\n - redis\n classroom-service:\n image: rosie91/stemify:classroom-service\n build:\n context: .\n dockerfile: src/Services/ClassroomService/Classroom.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5001\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcResourceUrl=http://resource-service:5003\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcOrderUrl=http://order-service:5006\n - ConnectionStrings__stemifyclassroom=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_classroom\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=classroom-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=classroom-service,service.version=1.0.0\n # Cloudinary Configuration\n - Cloudinary__CloudName=${CLOUDINARY_CLOUD_NAME}\n - Cloudinary__ApiKey=${CLOUDINARY_API_KEY}\n - Cloudinary__ApiSecret=${CLOUDINARY_API_SECRET}\n ports:\n - 7001:80\n - 5001:5001\n depends_on:\n - postgres\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n resource-service:\n image: rosie91/stemify:resource-service\n build:\n context: .\n dockerfile: src/Services/Resource/Resource.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5003\n - ConnectionStrings__stemifyresource=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_resource\n - Cloudinary__CloudName=${CLOUDINARY_CLOUD_NAME}\n - Cloudinary__ApiKey=${CLOUDINARY_API_KEY}\n - Cloudinary__ApiSecret=${CLOUDINARY_API_SECRET}\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcEmulatorUrl=http://emulator-service:5009\n - ClientApp=https://localhost:3000\n - Gemini__BaseUrl=https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent\n - Gemini__ApiKey=${GEMINI_API_KEY}\n - Gemini__Model=gemini-2.5-flash\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=resource-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=resource-service,service.version=1.0.0\n ports:\n - 7003:80\n - 5003:5003\n depends_on:\n - postgres\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n product-service:\n image: rosie91/stemify:product-service\n build:\n context: .\n dockerfile: src/Services/ProductService/Product.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5005\n - ConnectionStrings__stemifyproduct=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_product\n - Cloudinary__CloudName=${CLOUDINARY_CLOUD_NAME}\n - Cloudinary__ApiKey=${CLOUDINARY_API_KEY}\n - Cloudinary__ApiSecret=${CLOUDINARY_API_SECRET}\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcResourceUrl=http://resource-service:5003\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=product-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=product-service,service.version=1.0.0\n ports:\n - 7005:80\n - 5005:5005\n depends_on:\n - postgres\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n order-service:\n image: rosie91/stemify:order-service\n build:\n context: .\n dockerfile: src/Services/OrderService/Order.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5006\n - ConnectionStrings__stemifyorder=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_order\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcResourceUrl=http://resource-service:5003\n - GrpcProductUrl=http://product-service:5005\n - GrpcClassroomUrl=http://classroom-service:5001\n - Cloudinary__CloudName=${CLOUDINARY_CLOUD_NAME}\n - Cloudinary__ApiKey=${CLOUDINARY_API_KEY}\n - Cloudinary__ApiSecret=${CLOUDINARY_API_SECRET}\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=order-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=order-service,service.version=1.0.0\n\n ports:\n - 7006:80\n - 5006:5006\n depends_on:\n - postgres\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n notification-service:\n image: rosie91/stemify:notification-service\n build:\n context: .\n dockerfile: src/Services/Notification/Notification.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5004\n - ConnectionStrings__stemifynotification=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_notification\n # SendGrid configuration\n - SendGridSettings__ApiKey=${SENDGRID_API_KEY}\n - SendGridSettings__FromEmail=stemify30062025@gmail.com\n - SendGridSettings__FromName=STEMify\n # Client App configuration\n - ClientApp__BaseUrl=https://localhost:3000\n - ClientApp=https://localhost:3000\n - Issuer=http://localhost:5000/\n - IdentityServiceUrl=http://identity-service\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=notification-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=notification-service,service.version=1.0.0\n ports:\n - 7004:80\n - 5004:5004\n depends_on:\n - postgres\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n gateway-service:\n image: rosie91/stemify:gateway-service\n build:\n context: .\n dockerfile: src/ApiGateways/ApiGateway/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Docker\n - ASPNETCORE_URLS=http://+:80\n - IdentityServiceUrl=http://identity-service\n - Issuer=http://localhost:5000/\n - ClientApp__0=http://localhost:3232\n - ClientApp__1=https://localhost:3000\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=gateway-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=gateway-service,service.version=1.0.0\n ports:\n - 6001:80\n depends_on:\n - otelcollector\n restart: unless-stopped\n identity-api-service:\n image: rosie91/stemify:identity-api-service\n build:\n context: .\n dockerfile: src/Services/Identity/Identity.Api/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5002\n - ConnectionStrings__stemifyidentity=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_identity\n # RabbitMQ Configuration (for MassTransit)\n - RabbitMq__Url=${RABBITMQ_URL}\n # OpenIddict Configuration\n - OpenIddict__Authority=http://identity-service:80\n # Client App Configuration\n - ClientApp=https://localhost:3000\n # Email Settings\n - EmailSettings__From=${IDENTITY_EMAIL_FROM}\n - EmailSettings__DisplayName=${IDENTITY_EMAIL_DISPLAY_NAME}\n - EmailSettings__SMTPServer=${IDENTITY_EMAIL_SMTP_SERVER}\n - EmailSettings__Port=${IDENTITY_EMAIL_PORT}\n - EmailSettings__Username=${IDENTITY_EMAIL_USERNAME}\n - EmailSettings__Password=${IDENTITY_EMAIL_PASSWORD}\n - EmailSettings__UseSSL=${IDENTITY_EMAIL_USE_SSL}\n - EmailSettings__ApplicationName=${IDENTITY_EMAIL_APP_NAME}\n - EmailSettings__SupportEmail=${IDENTITY_EMAIL_SUPPORT}\n # Google Authentication\n - Authentication__Google__ClientId=${GOOGLE_CLIENT_ID}\n - Authentication__Google__ClientSecret=${GOOGLE_CLIENT_SECRET}\n - Authentication__Google__RedirectUri=${GOOGLE_REDIRECT_URI}\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=identity-api-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=identity-api-service,service.version=1.0.0\n - GrpcServices__OrderService__Address=http://order-service:5006\n # Google Authentication\n - Authentication__Google__ClientId=${GOOGLE_CLIENT_ID}\n - Authentication__Google__ClientSecret=${GOOGLE_CLIENT_SECRET}\n - Authentication__Google__RedirectUri=${GOOGLE_REDIRECT_URI}\n - BackendUrl=${IDENTITY_BACKEND_URL:-http://localhost:6001}\n ports:\n - 7002:80\n - 5002:5002\n depends_on:\n - postgres\n - otelcollector\n restart: unless-stopped\n identity-service:\n image: rosie91/stemify:identity-service\n build:\n context: .\n dockerfile: src/Services/Identity/Identity.Web/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - ASPNETCORE_URLS=http://+:80\n - ConnectionStrings__stemifyidentity=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_identity\n # OpenIddict configuration for Docker\n - OpenIddict__Issuer=http://localhost:5000\n - OpenIddict__Authority=http://localhost:5000\n - OpenIddict__SigningKey=${OPENIDDICT_SIGNING_KEY}\n - OpenIddict__EncryptionKey=${OPENIDDICT_ENCRYPTION_KEY}\n - OpenIddict__ApiClientSecret=${OPENIDDICT_API_CLIENT_SECRET}\n - OpenIddict__ForceRecreateClients=${OPENIDDICT_FORCE_RECREATE_CLIENTS:-true}\n - OpenIddict__DisableHttpsRequirement=${OPENIDDICT_DISABLE_HTTPS:-true}\n # CORS for Docker environment\n - Cors__Origins=${IDENTITY_CORS_ORIGINS:-https://localhost:3000}\n - ClientApp=${IDENTITY_CLIENT_APP:-https://localhost:3000}\n - BackendUrl=${IDENTITY_BACKEND_URL:-http://localhost:5000}\n # Google Authentication\n - Authentication__Google__ClientId=${GOOGLE_CLIENT_ID}\n - Authentication__Google__ClientSecret=${GOOGLE_CLIENT_SECRET}\n - Authentication__Google__RedirectUri=${GOOGLE_REDIRECT_URI}\n # Email Settings\n - EmailSettings__SMTPServer=${IDENTITY_EMAIL_SMTP_SERVER}\n - EmailSettings__Port=${IDENTITY_EMAIL_PORT}\n - EmailSettings__UseSSL=${IDENTITY_EMAIL_USE_SSL}\n - EmailSettings__Username=${IDENTITY_EMAIL_USERNAME}\n - EmailSettings__Password=${IDENTITY_EMAIL_PASSWORD}\n - EmailSettings__From=${IDENTITY_EMAIL_FROM}\n - EmailSettings__DisplayName=${IDENTITY_EMAIL_DISPLAY_NAME}\n - EmailSettings__ApplicationName=${IDENTITY_EMAIL_APP_NAME}\n - EmailSettings__SupportEmail=${IDENTITY_EMAIL_SUPPORT}\n - EmailSettings__Enabled=${IDENTITY_EMAIL_ENABLED}\n - EmailSettings__EnableVerification=${IDENTITY_EMAIL_ENABLE_VERIFICATION}\n - EmailSettings__TimeoutMs=${IDENTITY_EMAIL_TIMEOUT_MS}\n - EmailSettings__MaxRetryAttempts=${IDENTITY_EMAIL_MAX_RETRY}\n - EmailSettings__LogEmailContent=${IDENTITY_EMAIL_LOG_CONTENT}\n # gRPC Services\n - GrpcServices__OrderService__Address=http://order-service:5006\n # Database Options\n - DatabaseOptions__MaxRetryCount=5\n - DatabaseOptions__MaxRetryDelay=00:00:10\n - DatabaseOptions__CommandTimeout=30\n - DatabaseOptions__EnableRetryOnFailure=true\n - DatabaseOptions__EnableSensitiveDataLogging=${DB_ENABLE_SENSITIVE_LOGGING}\n - DatabaseOptions__EnableDetailedErrors=${DB_ENABLE_DETAILED_ERRORS}\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=identity-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=identity-service,service.version=1.0.0\n - RabbitMq__Url=${RABBITMQ_URL}\n ports:\n - 5000:80\n depends_on:\n - postgres\n - otelcollector\n restart: unless-stopped\n cart-service:\n image: rosie91/stemify:cart-service\n build:\n context: .\n dockerfile: src/Services/CartService/Cart.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5007\n - ConnectionStrings__stemifycart=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_cart\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcProductUrl=http://product-service:5005\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=cart-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=cart-service,service.version=1.0.0\n ports:\n - 7007:80\n - 5007:5007\n depends_on:\n - postgres\n - redis\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n payment-service:\n image: rosie91/stemify:payment-service\n build:\n context: .\n dockerfile: src/Services/PaymentService/Payment.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - Logging__LogLevel__Microsoft.EntityFrameworkCore.Database.Command=Warning\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5008\n - ConnectionStrings__stemifypayment=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_payment\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n # PayOS configuration\n - PaymentGateways__PayOS__ClientId=${PAYOS_CLIENT_ID}\n - PaymentGateways__PayOS__ApiKey=${PAYOS_API_KEY}\n - PaymentGateways__PayOS__ChecksumKey=${PAYOS_CHECKSUM_KEY}\n - PaymentGateways__PayOS__BaseUrl=https://api-merchant.payos.vn\n # Stripe configuration\n - PaymentGateways__Stripe__SecretKey=${STRIPE_SECRET_KEY}\n - PaymentGateways__Stripe__PublishableKey=${STRIPE_PUBLISH_KEY}\n - PaymentGateways__Stripe__WebhookSecret=${STRIPE_WEBHOOK_KEY}\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=payment-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=payment-service,service.version=1.0.0\n ports:\n - 7008:80\n - 5008:5008\n depends_on:\n - postgres\n - redis\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n emulator-service:\n image: rosie91/stemify:emulator-service\n build:\n context: .\n dockerfile: src/Services/EmulatorService/Emulator.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5009\n # MongoDB configuration\n - ConnectionStrings__emulator-mongodb=mongodb://root:mongopw@mongo\n - MongoDb__DatabaseName=stemify_emulator\n # Redis configuration\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n # gRPC endpoints\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcResourceUrl=http://resource-service:5003\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=emulator-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=emulator-service,service.version=1.0.0\n # Cloudinary Configuration\n - Cloudinary__CloudName=${CLOUDINARY_CLOUD_NAME}\n - Cloudinary__ApiKey=${CLOUDINARY_API_KEY}\n - Cloudinary__ApiSecret=${CLOUDINARY_API_SECRET}\n ports:\n - 7009:80\n - 5009:5009\n depends_on:\n - redis\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n\n # ========================================\n # MONITORING STACK\n # ========================================\n\n # Prometheus - Metrics Collection\n prometheus:\n image: prom/prometheus:v3.5.0\n container_name: stemify-prometheus\n ports:\n - '9090:9090'\n volumes:\n - ./config/prometheus:/etc/prometheus:ro\n - prometheus_data:/prometheus\n command:\n - '--config.file=/etc/prometheus/prometheus.yml'\n - '--storage.tsdb.path=/prometheus'\n - '--storage.tsdb.retention.time=15d'\n - '--storage.tsdb.retention.size=10GB'\n - '--web.enable-lifecycle'\n - '--web.enable-otlp-receiver'\n restart: unless-stopped\n\n # AlertManager - Alert Management\n alertmanager:\n image: prom/alertmanager:v0.27.0\n container_name: stemify-alertmanager\n ports:\n - '9093:9093'\n volumes:\n - ./config/alertmanager:/etc/alertmanager:ro\n - alertmanager_data:/alertmanager\n command:\n - '--config.file=/etc/alertmanager/alertmanager.yml'\n - '--storage.path=/alertmanager'\n - '--web.external-url=http://localhost:9093'\n restart: unless-stopped\n\n # Grafana - Visualization\n grafana:\n image: grafana/grafana:latest\n container_name: stemify-grafana\n ports:\n - '3001:3001'\n volumes:\n - grafana_data:/var/lib/grafana\n - ./config/grafana/config:/etc/grafana:ro\n - ./config/grafana/dashboards:/var/lib/grafana/dashboards:ro\n environment:\n - GF_SECURITY_ADMIN_USER=${GRAFANA_ADMIN_USERNAME}\n - GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_ADMIN_PASSWORD:-admin123}\n - GF_USERS_ALLOW_SIGN_UP=false\n - GF_INSTALL_PLUGINS=grafana-clock-panel,grafana-simple-json-datasource\n - PROMETHEUS_ENDPOINT=http://prometheus:9090\n restart: unless-stopped\n depends_on:\n - prometheus\n\n # OpenTelemetry Collector\n otelcollector:\n image: otel/opentelemetry-collector-contrib:latest\n container_name: stemify-otelcollector\n ports:\n - '8888:8888' # Prometheus metrics\n - '8889:8889' # Prometheus exporter\n - '4317:4317' # OTLP gRPC receiver\n - '4318:4318' # OTLP HTTP receiver\n volumes:\n - ./config/otelcollector:/etc/otelcol-contrib:ro\n environment:\n - PROMETHEUS_ENDPOINT=http://prometheus:9090/api/v1/write\n command: ['--config=/etc/otelcol-contrib/config.yaml']\n restart: unless-stopped\n depends_on:\n - prometheus\n\n # ========================================\n # AI SERVICE & VECTOR DATABASE\n # ========================================\n\n # Qdrant - Vector Database for RAG\n qdrant:\n image: qdrant/qdrant:latest\n container_name: stemify-qdrant\n ports:\n - '6333:6333' # HTTP API\n - '6334:6334' # gRPC\n volumes:\n - qdrant_data:/qdrant/storage\n environment:\n - QDRANT__SERVICE__GRPC_PORT=6334\n restart: unless-stopped\n\n neo4j:\n image: neo4j:5.20\n container_name: stemify-neo4j\n ports:\n - '7474:7474' # HTTP\n - '7687:7687' # Bolt\n environment:\n - NEO4J_AUTH=neo4j/password\n - NEO4J_PLUGINS=[\"apoc\"]\n - NEO4J_dbms_memory_heap_max__size=2G\n - NEO4J_dbms_memory_pagecache_size=1G\n volumes:\n - neo4j_data:/data\n - neo4j_logs:/logs\n restart: unless-stopped\n healthcheck:\n test: ['CMD', 'cypher-shell', '-u', 'neo4j', '-p', 'password', 'RETURN 1']\n interval: 30s\n timeout: 10s\n retries: 3\n start_period: 40s\n ai-service:\n image: rosie91/stemify:ai-service\n build:\n context: ./src/Services/AIService\n dockerfile: Dockerfile\n environment:\n - OPENAI_API_KEY=${OPENAI_API_KEY}\n - OPENAI_BASE_URL=${OPENAI_BASE_URL:-https://api.openai.com/v1}\n - DEEPSEEK_API_KEY=${DEEPSEEK_API_KEY}\n - RESOURCE_GRPC_CERT_PATH=${RESOURCE_GRPC_CERT_PATH}\n ports:\n - '7010:8080' # HTTP API (FastAPI)\n - '5010:5010' # gRPC\n # depends_on:\n # - qdrant\n # - neo4j\n restart: unless-stopped\n healthcheck:\n test: ['CMD', 'curl', '-f', 'http://localhost:8080/health']\n interval: 30s\n timeout: 10s\n retries: 3\n start_period: 40s\n\nvolumes:\n redis-data:\n postgres_data:\n mongodata:\n prometheus_data:\n alertmanager_data:\n grafana_data:\n qdrant_data:\n neo4j_data:\n neo4j_logs:\n"}, "d0a0ef062bec8fcb30822557eea5db4293da1d61": {"t": 1785748765.091452, "n": 23662, "h": "cda27cfa314f1d0e", "c": "services:\n postgres:\n image: postgres:17\n container_name: stemify-postgres\n environment:\n - POSTGRES_PASSWORD=${DB_PASSWORD}\n ports:\n - '5432:5432'\n volumes:\n - postgres_data:/var/lib/postgresql/data\n restart: unless-stopped\n rabbitmq:\n image: rabbitmq:3-management-alpine\n ports:\n - 5672:5672\n - 15672:15672\n environment:\n - RABBITMQ_DEFAULT_USER=guest\n - RABBITMQ_DEFAULT_PASS=guest\n healthcheck:\n test: ['CMD', 'rabbitmq-diagnostics', 'ping']\n interval: 30s\n timeout: 10s\n retries: 5\n start_period: 30s\n restart: unless-stopped\n mongo:\n image: mongo\n environment:\n - MONGO_INITDB_ROOT_USERNAME=root\n - MONGO_INITDB_ROOT_PASSWORD=mongopw\n ports:\n - 27018:27017\n volumes:\n - mongodata:/var/lib/mongodb/data\n redis:\n image: redis:latest\n ports:\n - 6379:6379\n command: ['redis-server', '--appendonly', 'yes']\n volumes:\n - redis-data:/data\n redis-commander:\n image: rediscommander/redis-commander:latest\n environment:\n - REDIS_HOSTS=local:redis:6379\n - HTTP_USER=root\n - HTTP_PASSWORD=secret\n ports:\n - 8082:8082\n depends_on:\n - redis\n classroom-service:\n image: rosie91/stemify:classroom-service\n build:\n context: .\n dockerfile: src/Services/ClassroomService/Classroom.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5001\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcResourceUrl=http://resource-service:5003\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcOrderUrl=http://order-service:5006\n - ConnectionStrings__stemifyclassroom=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_classroom\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=classroom-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=classroom-service,service.version=1.0.0\n # Cloudinary Configuration\n - Cloudinary__CloudName=${CLOUDINARY_CLOUD_NAME}\n - Cloudinary__ApiKey=${CLOUDINARY_API_KEY}\n - Cloudinary__ApiSecret=${CLOUDINARY_API_SECRET}\n ports:\n - 7001:80\n - 5001:5001\n depends_on:\n - postgres\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n resource-service:\n image: rosie91/stemify:resource-service\n build:\n context: .\n dockerfile: src/Services/Resource/Resource.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5003\n - ConnectionStrings__stemifyresource=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_resource\n - Cloudinary__CloudName=${CLOUDINARY_CLOUD_NAME}\n - Cloudinary__ApiKey=${CLOUDINARY_API_KEY}\n - Cloudinary__ApiSecret=${CLOUDINARY_API_SECRET}\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n - ClientApp=https://localhost:3000\n - Gemini__BaseUrl=https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent\n - Gemini__ApiKey=${GEMINI_API_KEY}\n - Gemini__Model=gemini-2.5-flash\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=resource-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=resource-service,service.version=1.0.0\n ports:\n - 7003:80\n - 5003:5003\n depends_on:\n - postgres\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n product-service:\n image: rosie91/stemify:product-service\n build:\n context: .\n dockerfile: src/Services/ProductService/Product.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5005\n - ConnectionStrings__stemifyproduct=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_product\n - Cloudinary__CloudName=${CLOUDINARY_CLOUD_NAME}\n - Cloudinary__ApiKey=${CLOUDINARY_API_KEY}\n - Cloudinary__ApiSecret=${CLOUDINARY_API_SECRET}\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcResourceUrl=http://resource-service:5003\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=product-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=product-service,service.version=1.0.0\n ports:\n - 7005:80\n - 5005:5005\n depends_on:\n - postgres\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n order-service:\n image: rosie91/stemify:order-service\n build:\n context: .\n dockerfile: src/Services/OrderService/Order.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5006\n - ConnectionStrings__stemifyorder=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_order\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcResourceUrl=http://resource-service:5003\n - GrpcProductUrl=http://product-service:5005\n - GrpcClassroomUrl=http://classroom-service:5001\n - Cloudinary__CloudName=${CLOUDINARY_CLOUD_NAME}\n - Cloudinary__ApiKey=${CLOUDINARY_API_KEY}\n - Cloudinary__ApiSecret=${CLOUDINARY_API_SECRET}\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=order-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=order-service,service.version=1.0.0\n\n ports:\n - 7006:80\n - 5006:5006\n depends_on:\n - postgres\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n notification-service:\n image: rosie91/stemify:notification-service\n build:\n context: .\n dockerfile: src/Services/Notification/Notification.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5004\n - ConnectionStrings__stemifynotification=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_notification\n # SendGrid configuration\n - SendGridSettings__ApiKey=${SENDGRID_API_KEY}\n - SendGridSettings__FromEmail=stemify30062025@gmail.com\n - SendGridSettings__FromName=STEMify\n # Client App configuration\n - ClientApp__BaseUrl=https://localhost:3000\n - ClientApp=https://localhost:3000\n - Issuer=http://localhost:5000/\n - IdentityServiceUrl=http://identity-service\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=notification-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=notification-service,service.version=1.0.0\n ports:\n - 7004:80\n - 5004:5004\n depends_on:\n - postgres\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n gateway-service:\n image: rosie91/stemify:gateway-service\n build:\n context: .\n dockerfile: src/ApiGateways/ApiGateway/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Docker\n - ASPNETCORE_URLS=http://+:80\n - IdentityServiceUrl=http://identity-service\n - Issuer=http://localhost:5000/\n - ClientApp__0=http://localhost:3232\n - ClientApp__1=https://localhost:3000\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=gateway-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=gateway-service,service.version=1.0.0\n ports:\n - 6001:80\n depends_on:\n - otelcollector\n restart: unless-stopped\n identity-api-service:\n image: rosie91/stemify:identity-api-service\n build:\n context: .\n dockerfile: src/Services/Identity/Identity.Api/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5002\n - ConnectionStrings__stemifyidentity=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_identity\n # RabbitMQ Configuration (for MassTransit)\n - RabbitMq__Url=${RABBITMQ_URL}\n # OpenIddict Configuration\n - OpenIddict__Authority=http://identity-service:80\n # Client App Configuration\n - ClientApp=https://localhost:3000\n # Email Settings\n - EmailSettings__From=${IDENTITY_EMAIL_FROM}\n - EmailSettings__DisplayName=${IDENTITY_EMAIL_DISPLAY_NAME}\n - EmailSettings__SMTPServer=${IDENTITY_EMAIL_SMTP_SERVER}\n - EmailSettings__Port=${IDENTITY_EMAIL_PORT}\n - EmailSettings__Username=${IDENTITY_EMAIL_USERNAME}\n - EmailSettings__Password=${IDENTITY_EMAIL_PASSWORD}\n - EmailSettings__UseSSL=${IDENTITY_EMAIL_USE_SSL}\n - EmailSettings__ApplicationName=${IDENTITY_EMAIL_APP_NAME}\n - EmailSettings__SupportEmail=${IDENTITY_EMAIL_SUPPORT}\n # Google Authentication\n - Authentication__Google__ClientId=${GOOGLE_CLIENT_ID}\n - Authentication__Google__ClientSecret=${GOOGLE_CLIENT_SECRET}\n - Authentication__Google__RedirectUri=${GOOGLE_REDIRECT_URI}\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=identity-api-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=identity-api-service,service.version=1.0.0\n - GrpcServices__OrderService__Address=http://order-service:5006\n # Google Authentication\n - Authentication__Google__ClientId=${GOOGLE_CLIENT_ID}\n - Authentication__Google__ClientSecret=${GOOGLE_CLIENT_SECRET}\n - Authentication__Google__RedirectUri=${GOOGLE_REDIRECT_URI}\n - BackendUrl=${IDENTITY_BACKEND_URL:-http://localhost:6001}\n ports:\n - 7002:80\n - 5002:5002\n depends_on:\n - postgres\n - otelcollector\n restart: unless-stopped\n identity-service:\n image: rosie91/stemify:identity-service\n build:\n context: .\n dockerfile: src/Services/Identity/Identity.Web/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - ASPNETCORE_URLS=http://+:80\n - ConnectionStrings__stemifyidentity=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_identity\n # OpenIddict configuration for Docker\n - OpenIddict__Issuer=http://localhost:5000\n - OpenIddict__Authority=http://localhost:5000\n - OpenIddict__SigningKey=${OPENIDDICT_SIGNING_KEY}\n - OpenIddict__EncryptionKey=${OPENIDDICT_ENCRYPTION_KEY}\n - OpenIddict__ApiClientSecret=${OPENIDDICT_API_CLIENT_SECRET}\n - OpenIddict__ForceRecreateClients=${OPENIDDICT_FORCE_RECREATE_CLIENTS:-true}\n - OpenIddict__DisableHttpsRequirement=${OPENIDDICT_DISABLE_HTTPS:-true}\n # CORS for Docker environment\n - Cors__Origins=${IDENTITY_CORS_ORIGINS:-https://localhost:3000}\n - ClientApp=${IDENTITY_CLIENT_APP:-https://localhost:3000}\n - BackendUrl=${IDENTITY_BACKEND_URL:-http://localhost:5000}\n # Google Authentication\n - Authentication__Google__ClientId=${GOOGLE_CLIENT_ID}\n - Authentication__Google__ClientSecret=${GOOGLE_CLIENT_SECRET}\n - Authentication__Google__RedirectUri=${GOOGLE_REDIRECT_URI}\n # Email Settings\n - EmailSettings__SMTPServer=${IDENTITY_EMAIL_SMTP_SERVER}\n - EmailSettings__Port=${IDENTITY_EMAIL_PORT}\n - EmailSettings__UseSSL=${IDENTITY_EMAIL_USE_SSL}\n - EmailSettings__Username=${IDENTITY_EMAIL_USERNAME}\n - EmailSettings__Password=${IDENTITY_EMAIL_PASSWORD}\n - EmailSettings__From=${IDENTITY_EMAIL_FROM}\n - EmailSettings__DisplayName=${IDENTITY_EMAIL_DISPLAY_NAME}\n - EmailSettings__ApplicationName=${IDENTITY_EMAIL_APP_NAME}\n - EmailSettings__SupportEmail=${IDENTITY_EMAIL_SUPPORT}\n - EmailSettings__Enabled=${IDENTITY_EMAIL_ENABLED}\n - EmailSettings__EnableVerification=${IDENTITY_EMAIL_ENABLE_VERIFICATION}\n - EmailSettings__TimeoutMs=${IDENTITY_EMAIL_TIMEOUT_MS}\n - EmailSettings__MaxRetryAttempts=${IDENTITY_EMAIL_MAX_RETRY}\n - EmailSettings__LogEmailContent=${IDENTITY_EMAIL_LOG_CONTENT}\n # gRPC Services\n - GrpcServices__OrderService__Address=http://order-service:5006\n # Database Options\n - DatabaseOptions__MaxRetryCount=5\n - DatabaseOptions__MaxRetryDelay=00:00:10\n - DatabaseOptions__CommandTimeout=30\n - DatabaseOptions__EnableRetryOnFailure=true\n - DatabaseOptions__EnableSensitiveDataLogging=${DB_ENABLE_SENSITIVE_LOGGING}\n - DatabaseOptions__EnableDetailedErrors=${DB_ENABLE_DETAILED_ERRORS}\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=identity-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=identity-service,service.version=1.0.0\n - RabbitMq__Url=${RABBITMQ_URL}\n ports:\n - 5000:80\n depends_on:\n - postgres\n - otelcollector\n restart: unless-stopped\n cart-service:\n image: rosie91/stemify:cart-service\n build:\n context: .\n dockerfile: src/Services/CartService/Cart.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5007\n - ConnectionStrings__stemifycart=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_cart\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcProductUrl=http://product-service:5005\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=cart-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=cart-service,service.version=1.0.0\n ports:\n - 7007:80\n - 5007:5007\n depends_on:\n - postgres\n - redis\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n payment-service:\n image: rosie91/stemify:payment-service\n build:\n context: .\n dockerfile: src/Services/PaymentService/Payment.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - Logging__LogLevel__Microsoft.EntityFrameworkCore.Database.Command=Warning\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5008\n - ConnectionStrings__stemifypayment=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_payment\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n # PayOS configuration\n - PaymentGateways__PayOS__ClientId=${PAYOS_CLIENT_ID}\n - PaymentGateways__PayOS__ApiKey=${PAYOS_API_KEY}\n - PaymentGateways__PayOS__ChecksumKey=${PAYOS_CHECKSUM_KEY}\n - PaymentGateways__PayOS__BaseUrl=https://api-merchant.payos.vn\n # Stripe configuration\n - PaymentGateways__Stripe__SecretKey=${STRIPE_SECRET_KEY}\n - PaymentGateways__Stripe__PublishableKey=${STRIPE_PUBLISH_KEY}\n - PaymentGateways__Stripe__WebhookSecret=${STRIPE_WEBHOOK_KEY}\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=payment-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=payment-service,service.version=1.0.0\n ports:\n - 7008:80\n - 5008:5008\n depends_on:\n - postgres\n - redis\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n emulator-service:\n image: rosie91/stemify:emulator-service\n build:\n context: .\n dockerfile: src/Services/EmulatorService/Emulator.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5009\n # MongoDB configuration\n - ConnectionStrings__emulator-mongodb=mongodb://root:mongopw@mongo\n - MongoDb__DatabaseName=stemify_emulator\n # Redis configuration\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n # gRPC endpoints\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcResourceUrl=http://resource-service:5003\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=emulator-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=emulator-service,service.version=1.0.0\n # Cloudinary Configuration\n - Cloudinary__CloudName=${CLOUDINARY_CLOUD_NAME}\n - Cloudinary__ApiKey=${CLOUDINARY_API_KEY}\n - Cloudinary__ApiSecret=${CLOUDINARY_API_SECRET}\n ports:\n - 7009:80\n - 5009:5009\n depends_on:\n - redis\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n\n # ========================================\n # MONITORING STACK\n # ========================================\n\n # Prometheus - Metrics Collection\n prometheus:\n image: prom/prometheus:v3.5.0\n container_name: stemify-prometheus\n ports:\n - '9090:9090'\n volumes:\n - ./config/prometheus:/etc/prometheus:ro\n - prometheus_data:/prometheus\n command:\n - '--config.file=/etc/prometheus/prometheus.yml'\n - '--storage.tsdb.path=/prometheus'\n - '--storage.tsdb.retention.time=15d'\n - '--storage.tsdb.retention.size=10GB'\n - '--web.enable-lifecycle'\n - '--web.enable-otlp-receiver'\n restart: unless-stopped\n\n # AlertManager - Alert Management\n alertmanager:\n image: prom/alertmanager:v0.27.0\n container_name: stemify-alertmanager\n ports:\n - '9093:9093'\n volumes:\n - ./config/alertmanager:/etc/alertmanager:ro\n - alertmanager_data:/alertmanager\n command:\n - '--config.file=/etc/alertmanager/alertmanager.yml'\n - '--storage.path=/alertmanager'\n - '--web.external-url=http://localhost:9093'\n restart: unless-stopped\n\n # Grafana - Visualization\n grafana:\n image: grafana/grafana:latest\n container_name: stemify-grafana\n ports:\n - '3001:3001'\n volumes:\n - grafana_data:/var/lib/grafana\n - ./config/grafana/config:/etc/grafana:ro\n - ./config/grafana/dashboards:/var/lib/grafana/dashboards:ro\n environment:\n - GF_SECURITY_ADMIN_USER=${GRAFANA_ADMIN_USERNAME}\n - GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_ADMIN_PASSWORD:-admin123}\n - GF_USERS_ALLOW_SIGN_UP=false\n - GF_INSTALL_PLUGINS=grafana-clock-panel,grafana-simple-json-datasource\n - PROMETHEUS_ENDPOINT=http://prometheus:9090\n restart: unless-stopped\n depends_on:\n - prometheus\n\n # OpenTelemetry Collector\n otelcollector:\n image: otel/opentelemetry-collector-contrib:latest\n container_name: stemify-otelcollector\n ports:\n - '8888:8888' # Prometheus metrics\n - '8889:8889' # Prometheus exporter\n - '4317:4317' # OTLP gRPC receiver\n - '4318:4318' # OTLP HTTP receiver\n volumes:\n - ./config/otelcollector:/etc/otelcol-contrib:ro\n environment:\n - PROMETHEUS_ENDPOINT=http://prometheus:9090/api/v1/write\n command: ['--config=/etc/otelcol-contrib/config.yaml']\n restart: unless-stopped\n depends_on:\n - prometheus\n\n # ========================================\n # AI SERVICE & VECTOR DATABASE\n # ========================================\n\n # Qdrant - Vector Database for RAG\n qdrant:\n image: qdrant/qdrant:latest\n container_name: stemify-qdrant\n ports:\n - '6333:6333' # HTTP API\n - '6334:6334' # gRPC\n volumes:\n - qdrant_data:/qdrant/storage\n environment:\n - QDRANT__SERVICE__GRPC_PORT=6334\n restart: unless-stopped\n\n neo4j:\n image: neo4j:5.20\n container_name: stemify-neo4j\n ports:\n - '7474:7474' # HTTP\n - '7687:7687' # Bolt\n environment:\n - NEO4J_AUTH=neo4j/password\n - NEO4J_PLUGINS=[\"apoc\"]\n - NEO4J_dbms_memory_heap_max__size=2G\n - NEO4J_dbms_memory_pagecache_size=1G\n volumes:\n - neo4j_data:/data\n - neo4j_logs:/logs\n restart: unless-stopped\n healthcheck:\n test: ['CMD', 'cypher-shell', '-u', 'neo4j', '-p', 'password', 'RETURN 1']\n interval: 30s\n timeout: 10s\n retries: 3\n start_period: 40s\n ai-service:\n image: rosie91/stemify:ai-service\n build:\n context: ./src/Services/AIService\n dockerfile: Dockerfile\n environment:\n - OPENAI_API_KEY=${OPENAI_API_KEY}\n - OPENAI_BASE_URL=${OPENAI_BASE_URL:-https://api.openai.com/v1}\n - DEEPSEEK_API_KEY=${DEEPSEEK_API_KEY}\n - RESOURCE_GRPC_CERT_PATH=${RESOURCE_GRPC_CERT_PATH}\n ports:\n - '7010:8080' # HTTP API (FastAPI)\n - '5010:5010' # gRPC\n # depends_on:\n # - qdrant\n # - neo4j\n restart: unless-stopped\n healthcheck:\n test: ['CMD', 'curl', '-f', 'http://localhost:8080/health']\n interval: 30s\n timeout: 10s\n retries: 3\n start_period: 40s\n\nvolumes:\n redis-data:\n postgres_data:\n mongodata:\n prometheus_data:\n alertmanager_data:\n grafana_data:\n qdrant_data:\n neo4j_data:\n neo4j_logs:\n"}, "4cb8548d1e87a7df688f0b6059bc2ec37661af95": {"t": 1785748766.3355274, "n": 23662, "h": "55e0fcb134b1bcc1", "c": "services:\n postgres:\n image: postgres:17\n container_name: stemify-postgres\n environment:\n - POSTGRES_PASSWORD=${DB_PASSWORD}\n ports:\n - '5432:5432'\n volumes:\n - postgres_data:/var/lib/postgresql/data\n restart: unless-stopped\n rabbitmq:\n image: rabbitmq:3-management-alpine\n ports:\n - 5672:5672\n - 15672:15672\n environment:\n - RABBITMQ_DEFAULT_USER=guest\n - RABBITMQ_DEFAULT_PASS=guest\n healthcheck:\n test: ['CMD', 'rabbitmq-diagnostics', 'ping']\n interval: 30s\n timeout: 10s\n retries: 5\n start_period: 30s\n restart: unless-stopped\n mongo:\n image: mongo\n environment:\n - MONGO_INITDB_ROOT_USERNAME=root\n - MONGO_INITDB_ROOT_PASSWORD=mongopw\n ports:\n - 27017:27017\n volumes:\n - mongodata:/var/lib/mongodb/data\n redis:\n image: redis:latest\n ports:\n - 6379:6379\n command: ['redis-server', '--appendonly', 'yes']\n volumes:\n - redis-data:/data\n redis-commander:\n image: rediscommander/redis-commander:latest\n environment:\n - REDIS_HOSTS=local:redis:6379\n - HTTP_USER=root\n - HTTP_PASSWORD=secret\n ports:\n - 8082:8082\n depends_on:\n - redis\n classroom-service:\n image: rosie91/stemify:classroom-service\n build:\n context: .\n dockerfile: src/Services/ClassroomService/Classroom.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5001\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcResourceUrl=http://resource-service:5003\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcOrderUrl=http://order-service:5006\n - ConnectionStrings__stemifyclassroom=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_classroom\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=classroom-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=classroom-service,service.version=1.0.0\n # Cloudinary Configuration\n - Cloudinary__CloudName=${CLOUDINARY_CLOUD_NAME}\n - Cloudinary__ApiKey=${CLOUDINARY_API_KEY}\n - Cloudinary__ApiSecret=${CLOUDINARY_API_SECRET}\n ports:\n - 7001:80\n - 5001:5001\n depends_on:\n - postgres\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n resource-service:\n image: rosie91/stemify:resource-service\n build:\n context: .\n dockerfile: src/Services/Resource/Resource.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5003\n - ConnectionStrings__stemifyresource=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_resource\n - Cloudinary__CloudName=${CLOUDINARY_CLOUD_NAME}\n - Cloudinary__ApiKey=${CLOUDINARY_API_KEY}\n - Cloudinary__ApiSecret=${CLOUDINARY_API_SECRET}\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n - ClientApp=https://localhost:3000\n - Gemini__BaseUrl=https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent\n - Gemini__ApiKey=${GEMINI_API_KEY}\n - Gemini__Model=gemini-2.5-flash\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=resource-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=resource-service,service.version=1.0.0\n ports:\n - 7003:80\n - 5003:5003\n depends_on:\n - postgres\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n product-service:\n image: rosie91/stemify:product-service\n build:\n context: .\n dockerfile: src/Services/ProductService/Product.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5005\n - ConnectionStrings__stemifyproduct=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_product\n - Cloudinary__CloudName=${CLOUDINARY_CLOUD_NAME}\n - Cloudinary__ApiKey=${CLOUDINARY_API_KEY}\n - Cloudinary__ApiSecret=${CLOUDINARY_API_SECRET}\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcResourceUrl=http://resource-service:5003\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=product-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=product-service,service.version=1.0.0\n ports:\n - 7005:80\n - 5005:5005\n depends_on:\n - postgres\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n order-service:\n image: rosie91/stemify:order-service\n build:\n context: .\n dockerfile: src/Services/OrderService/Order.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5006\n - ConnectionStrings__stemifyorder=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_order\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcResourceUrl=http://resource-service:5003\n - GrpcProductUrl=http://product-service:5005\n - GrpcClassroomUrl=http://classroom-service:5001\n - Cloudinary__CloudName=${CLOUDINARY_CLOUD_NAME}\n - Cloudinary__ApiKey=${CLOUDINARY_API_KEY}\n - Cloudinary__ApiSecret=${CLOUDINARY_API_SECRET}\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=order-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=order-service,service.version=1.0.0\n\n ports:\n - 7006:80\n - 5006:5006\n depends_on:\n - postgres\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n notification-service:\n image: rosie91/stemify:notification-service\n build:\n context: .\n dockerfile: src/Services/Notification/Notification.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5004\n - ConnectionStrings__stemifynotification=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_notification\n # SendGrid configuration\n - SendGridSettings__ApiKey=${SENDGRID_API_KEY}\n - SendGridSettings__FromEmail=stemify30062025@gmail.com\n - SendGridSettings__FromName=STEMify\n # Client App configuration\n - ClientApp__BaseUrl=https://localhost:3000\n - ClientApp=https://localhost:3000\n - Issuer=http://localhost:5000/\n - IdentityServiceUrl=http://identity-service\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=notification-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=notification-service,service.version=1.0.0\n ports:\n - 7004:80\n - 5004:5004\n depends_on:\n - postgres\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n gateway-service:\n image: rosie91/stemify:gateway-service\n build:\n context: .\n dockerfile: src/ApiGateways/ApiGateway/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Docker\n - ASPNETCORE_URLS=http://+:80\n - IdentityServiceUrl=http://identity-service\n - Issuer=http://localhost:5000/\n - ClientApp__0=http://localhost:3232\n - ClientApp__1=https://localhost:3000\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=gateway-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=gateway-service,service.version=1.0.0\n ports:\n - 6001:80\n depends_on:\n - otelcollector\n restart: unless-stopped\n identity-api-service:\n image: rosie91/stemify:identity-api-service\n build:\n context: .\n dockerfile: src/Services/Identity/Identity.Api/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5002\n - ConnectionStrings__stemifyidentity=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_identity\n # RabbitMQ Configuration (for MassTransit)\n - RabbitMq__Url=${RABBITMQ_URL}\n # OpenIddict Configuration\n - OpenIddict__Authority=http://identity-service:80\n # Client App Configuration\n - ClientApp=https://localhost:3000\n # Email Settings\n - EmailSettings__From=${IDENTITY_EMAIL_FROM}\n - EmailSettings__DisplayName=${IDENTITY_EMAIL_DISPLAY_NAME}\n - EmailSettings__SMTPServer=${IDENTITY_EMAIL_SMTP_SERVER}\n - EmailSettings__Port=${IDENTITY_EMAIL_PORT}\n - EmailSettings__Username=${IDENTITY_EMAIL_USERNAME}\n - EmailSettings__Password=${IDENTITY_EMAIL_PASSWORD}\n - EmailSettings__UseSSL=${IDENTITY_EMAIL_USE_SSL}\n - EmailSettings__ApplicationName=${IDENTITY_EMAIL_APP_NAME}\n - EmailSettings__SupportEmail=${IDENTITY_EMAIL_SUPPORT}\n # Google Authentication\n - Authentication__Google__ClientId=${GOOGLE_CLIENT_ID}\n - Authentication__Google__ClientSecret=${GOOGLE_CLIENT_SECRET}\n - Authentication__Google__RedirectUri=${GOOGLE_REDIRECT_URI}\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=identity-api-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=identity-api-service,service.version=1.0.0\n - GrpcServices__OrderService__Address=http://order-service:5006\n # Google Authentication\n - Authentication__Google__ClientId=${GOOGLE_CLIENT_ID}\n - Authentication__Google__ClientSecret=${GOOGLE_CLIENT_SECRET}\n - Authentication__Google__RedirectUri=${GOOGLE_REDIRECT_URI}\n - BackendUrl=${IDENTITY_BACKEND_URL:-http://localhost:6001}\n ports:\n - 7002:80\n - 5002:5002\n depends_on:\n - postgres\n - otelcollector\n restart: unless-stopped\n identity-service:\n image: rosie91/stemify:identity-service\n build:\n context: .\n dockerfile: src/Services/Identity/Identity.Web/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - ASPNETCORE_URLS=http://+:80\n - ConnectionStrings__stemifyidentity=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_identity\n # OpenIddict configuration for Docker\n - OpenIddict__Issuer=http://localhost:5000\n - OpenIddict__Authority=http://localhost:5000\n - OpenIddict__SigningKey=${OPENIDDICT_SIGNING_KEY}\n - OpenIddict__EncryptionKey=${OPENIDDICT_ENCRYPTION_KEY}\n - OpenIddict__ApiClientSecret=${OPENIDDICT_API_CLIENT_SECRET}\n - OpenIddict__ForceRecreateClients=${OPENIDDICT_FORCE_RECREATE_CLIENTS:-true}\n - OpenIddict__DisableHttpsRequirement=${OPENIDDICT_DISABLE_HTTPS:-true}\n # CORS for Docker environment\n - Cors__Origins=${IDENTITY_CORS_ORIGINS:-https://localhost:3000}\n - ClientApp=${IDENTITY_CLIENT_APP:-https://localhost:3000}\n - BackendUrl=${IDENTITY_BACKEND_URL:-http://localhost:5000}\n # Google Authentication\n - Authentication__Google__ClientId=${GOOGLE_CLIENT_ID}\n - Authentication__Google__ClientSecret=${GOOGLE_CLIENT_SECRET}\n - Authentication__Google__RedirectUri=${GOOGLE_REDIRECT_URI}\n # Email Settings\n - EmailSettings__SMTPServer=${IDENTITY_EMAIL_SMTP_SERVER}\n - EmailSettings__Port=${IDENTITY_EMAIL_PORT}\n - EmailSettings__UseSSL=${IDENTITY_EMAIL_USE_SSL}\n - EmailSettings__Username=${IDENTITY_EMAIL_USERNAME}\n - EmailSettings__Password=${IDENTITY_EMAIL_PASSWORD}\n - EmailSettings__From=${IDENTITY_EMAIL_FROM}\n - EmailSettings__DisplayName=${IDENTITY_EMAIL_DISPLAY_NAME}\n - EmailSettings__ApplicationName=${IDENTITY_EMAIL_APP_NAME}\n - EmailSettings__SupportEmail=${IDENTITY_EMAIL_SUPPORT}\n - EmailSettings__Enabled=${IDENTITY_EMAIL_ENABLED}\n - EmailSettings__EnableVerification=${IDENTITY_EMAIL_ENABLE_VERIFICATION}\n - EmailSettings__TimeoutMs=${IDENTITY_EMAIL_TIMEOUT_MS}\n - EmailSettings__MaxRetryAttempts=${IDENTITY_EMAIL_MAX_RETRY}\n - EmailSettings__LogEmailContent=${IDENTITY_EMAIL_LOG_CONTENT}\n # gRPC Services\n - GrpcServices__OrderService__Address=http://order-service:5006\n # Database Options\n - DatabaseOptions__MaxRetryCount=5\n - DatabaseOptions__MaxRetryDelay=00:00:10\n - DatabaseOptions__CommandTimeout=30\n - DatabaseOptions__EnableRetryOnFailure=true\n - DatabaseOptions__EnableSensitiveDataLogging=${DB_ENABLE_SENSITIVE_LOGGING}\n - DatabaseOptions__EnableDetailedErrors=${DB_ENABLE_DETAILED_ERRORS}\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=identity-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=identity-service,service.version=1.0.0\n - RabbitMq__Url=${RABBITMQ_URL}\n ports:\n - 5000:80\n depends_on:\n - postgres\n - otelcollector\n restart: unless-stopped\n cart-service:\n image: rosie91/stemify:cart-service\n build:\n context: .\n dockerfile: src/Services/CartService/Cart.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5007\n - ConnectionStrings__stemifycart=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_cart\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcProductUrl=http://product-service:5005\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=cart-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=cart-service,service.version=1.0.0\n ports:\n - 7007:80\n - 5007:5007\n depends_on:\n - postgres\n - redis\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n payment-service:\n image: rosie91/stemify:payment-service\n build:\n context: .\n dockerfile: src/Services/PaymentService/Payment.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - Logging__LogLevel__Microsoft.EntityFrameworkCore.Database.Command=Warning\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5008\n - ConnectionStrings__stemifypayment=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_payment\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n # PayOS configuration\n - PaymentGateways__PayOS__ClientId=${PAYOS_CLIENT_ID}\n - PaymentGateways__PayOS__ApiKey=${PAYOS_API_KEY}\n - PaymentGateways__PayOS__ChecksumKey=${PAYOS_CHECKSUM_KEY}\n - PaymentGateways__PayOS__BaseUrl=https://api-merchant.payos.vn\n # Stripe configuration\n - PaymentGateways__Stripe__SecretKey=${STRIPE_SECRET_KEY}\n - PaymentGateways__Stripe__PublishableKey=${STRIPE_PUBLISH_KEY}\n - PaymentGateways__Stripe__WebhookSecret=${STRIPE_WEBHOOK_KEY}\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=payment-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=payment-service,service.version=1.0.0\n ports:\n - 7008:80\n - 5008:5008\n depends_on:\n - postgres\n - redis\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n emulator-service:\n image: rosie91/stemify:emulator-service\n build:\n context: .\n dockerfile: src/Services/EmulatorService/Emulator.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5009\n # MongoDB configuration\n - ConnectionStrings__emulator-mongodb=mongodb://root:mongopw@mongo\n - MongoDb__DatabaseName=stemify_emulator\n # Redis configuration\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n # gRPC endpoints\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcResourceUrl=http://resource-service:5003\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=emulator-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=emulator-service,service.version=1.0.0\n # Cloudinary Configuration\n - Cloudinary__CloudName=${CLOUDINARY_CLOUD_NAME}\n - Cloudinary__ApiKey=${CLOUDINARY_API_KEY}\n - Cloudinary__ApiSecret=${CLOUDINARY_API_SECRET}\n ports:\n - 7009:80\n - 5009:5009\n depends_on:\n - redis\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n\n # ========================================\n # MONITORING STACK\n # ========================================\n\n # Prometheus - Metrics Collection\n prometheus:\n image: prom/prometheus:v3.5.0\n container_name: stemify-prometheus\n ports:\n - '9090:9090'\n volumes:\n - ./config/prometheus:/etc/prometheus:ro\n - prometheus_data:/prometheus\n command:\n - '--config.file=/etc/prometheus/prometheus.yml'\n - '--storage.tsdb.path=/prometheus'\n - '--storage.tsdb.retention.time=15d'\n - '--storage.tsdb.retention.size=10GB'\n - '--web.enable-lifecycle'\n - '--web.enable-otlp-receiver'\n restart: unless-stopped\n\n # AlertManager - Alert Management\n alertmanager:\n image: prom/alertmanager:v0.27.0\n container_name: stemify-alertmanager\n ports:\n - '9093:9093'\n volumes:\n - ./config/alertmanager:/etc/alertmanager:ro\n - alertmanager_data:/alertmanager\n command:\n - '--config.file=/etc/alertmanager/alertmanager.yml'\n - '--storage.path=/alertmanager'\n - '--web.external-url=http://localhost:9093'\n restart: unless-stopped\n\n # Grafana - Visualization\n grafana:\n image: grafana/grafana:latest\n container_name: stemify-grafana\n ports:\n - '3001:3001'\n volumes:\n - grafana_data:/var/lib/grafana\n - ./config/grafana/config:/etc/grafana:ro\n - ./config/grafana/dashboards:/var/lib/grafana/dashboards:ro\n environment:\n - GF_SECURITY_ADMIN_USER=${GRAFANA_ADMIN_USERNAME}\n - GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_ADMIN_PASSWORD:-admin123}\n - GF_USERS_ALLOW_SIGN_UP=false\n - GF_INSTALL_PLUGINS=grafana-clock-panel,grafana-simple-json-datasource\n - PROMETHEUS_ENDPOINT=http://prometheus:9090\n restart: unless-stopped\n depends_on:\n - prometheus\n\n # OpenTelemetry Collector\n otelcollector:\n image: otel/opentelemetry-collector-contrib:latest\n container_name: stemify-otelcollector\n ports:\n - '8888:8888' # Prometheus metrics\n - '8889:8889' # Prometheus exporter\n - '4317:4317' # OTLP gRPC receiver\n - '4318:4318' # OTLP HTTP receiver\n volumes:\n - ./config/otelcollector:/etc/otelcol-contrib:ro\n environment:\n - PROMETHEUS_ENDPOINT=http://prometheus:9090/api/v1/write\n command: ['--config=/etc/otelcol-contrib/config.yaml']\n restart: unless-stopped\n depends_on:\n - prometheus\n\n # ========================================\n # AI SERVICE & VECTOR DATABASE\n # ========================================\n\n # Qdrant - Vector Database for RAG\n qdrant:\n image: qdrant/qdrant:latest\n container_name: stemify-qdrant\n ports:\n - '6333:6333' # HTTP API\n - '6334:6334' # gRPC\n volumes:\n - qdrant_data:/qdrant/storage\n environment:\n - QDRANT__SERVICE__GRPC_PORT=6334\n restart: unless-stopped\n\n neo4j:\n image: neo4j:5.20\n container_name: stemify-neo4j\n ports:\n - '7474:7474' # HTTP\n - '7687:7687' # Bolt\n environment:\n - NEO4J_AUTH=neo4j/password\n - NEO4J_PLUGINS=[\"apoc\"]\n - NEO4J_dbms_memory_heap_max__size=2G\n - NEO4J_dbms_memory_pagecache_size=1G\n volumes:\n - neo4j_data:/data\n - neo4j_logs:/logs\n restart: unless-stopped\n healthcheck:\n test: ['CMD', 'cypher-shell', '-u', 'neo4j', '-p', 'password', 'RETURN 1']\n interval: 30s\n timeout: 10s\n retries: 3\n start_period: 40s\n ai-service:\n image: rosie91/stemify:ai-service\n build:\n context: ./src/Services/AIService\n dockerfile: Dockerfile\n environment:\n - OPENAI_API_KEY=${OPENAI_API_KEY}\n - OPENAI_BASE_URL=${OPENAI_BASE_URL:-https://api.openai.com/v1}\n - DEEPSEEK_API_KEY=${DEEPSEEK_API_KEY}\n - RESOURCE_GRPC_CERT_PATH=${RESOURCE_GRPC_CERT_PATH}\n ports:\n - '7010:8080' # HTTP API (FastAPI)\n - '5010:5010' # gRPC\n # depends_on:\n # - qdrant\n # - neo4j\n restart: unless-stopped\n healthcheck:\n test: ['CMD', 'curl', '-f', 'http://localhost:8080/health']\n interval: 30s\n timeout: 10s\n retries: 3\n start_period: 40s\n\nvolumes:\n redis-data:\n postgres_data:\n mongodata:\n prometheus_data:\n alertmanager_data:\n grafana_data:\n qdrant_data:\n neo4j_data:\n neo4j_logs:\n"}, "2e32d769c947604f60a73ae2f4e6e3d4341ab6e2": {"t": 1785748767.569034, "n": 23649, "h": "77374d53a9ff5b98", "c": "services:\n postgres:\n image: postgres:17\n container_name: stemify-postgres\n environment:\n - POSTGRES_PASSWORD=${DB_PASSWORD}\n ports:\n - '5432:5432'\n volumes:\n - postgres_data:/var/lib/postgresql/data\n restart: unless-stopped\n rabbitmq:\n image: rabbitmq:3-management-alpine\n ports:\n - 5672:5672\n - 15672:15672\n environment:\n - RABBITMQ_DEFAULT_USER=guest\n - RABBITMQ_DEFAULT_PASS=guest\n healthcheck:\n test: [\"CMD\", \"rabbitmq-diagnostics\", \"ping\"]\n interval: 30s\n timeout: 10s\n retries: 5\n start_period: 30s\n restart: unless-stopped\n mongo:\n image: mongo\n environment:\n - MONGO_INITDB_ROOT_USERNAME=root\n - MONGO_INITDB_ROOT_PASSWORD=mongopw\n ports:\n - 27017:27017\n volumes:\n - mongodata:/var/lib/mongodb/data\n redis:\n image: redis:latest\n ports:\n - 6379:6379\n command: [ 'redis-server', '--appendonly', 'yes' ]\n volumes:\n - redis-data:/data\n redis-commander:\n image: rediscommander/redis-commander:latest\n environment:\n - REDIS_HOSTS=local:redis:6379\n - HTTP_USER=root\n - HTTP_PASSWORD=secret\n ports:\n - 8082:8082\n depends_on:\n - redis\n classroom-service:\n image: rosie91/stemify:classroom-service\n build:\n context: .\n dockerfile: src/Services/ClassroomService/Classroom.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5001\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcResourceUrl=http://resource-service:5003\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcOrderUrl=http://order-service:5006\n - ConnectionStrings__stemifyclassroom=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_classroom\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=classroom-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=classroom-service,service.version=1.0.0\n # Cloudinary Configuration\n - Cloudinary__CloudName=${CLOUDINARY_CLOUD_NAME}\n - Cloudinary__ApiKey=${CLOUDINARY_API_KEY}\n - Cloudinary__ApiSecret=${CLOUDINARY_API_SECRET}\n ports:\n - 7001:80\n - 5001:5001\n depends_on:\n - postgres\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n resource-service:\n image: rosie91/stemify:resource-service\n build:\n context: .\n dockerfile: src/Services/Resource/Resource.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5003\n - ConnectionStrings__stemifyresource=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_resource\n - Cloudinary__CloudName=${CLOUDINARY_CLOUD_NAME}\n - Cloudinary__ApiKey=${CLOUDINARY_API_KEY}\n - Cloudinary__ApiSecret=${CLOUDINARY_API_SECRET}\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n - ClientApp=https://localhost:3000\n - Gemini__BaseUrl=https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent\n - Gemini__ApiKey=${GEMINI_API_KEY}\n - Gemini__Model=gemini-2.5-flash\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=resource-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=resource-service,service.version=1.0.0\n ports:\n - 7003:80\n - 5003:5003\n depends_on:\n - postgres\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n product-service:\n image: rosie91/stemify:product-service\n build:\n context: .\n dockerfile: src/Services/ProductService/Product.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5005\n - ConnectionStrings__stemifyproduct=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_product\n - Cloudinary__CloudName=${CLOUDINARY_CLOUD_NAME}\n - Cloudinary__ApiKey=${CLOUDINARY_API_KEY}\n - Cloudinary__ApiSecret=${CLOUDINARY_API_SECRET}\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcResourceUrl=http://resource-service:5003\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=product-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=product-service,service.version=1.0.0\n ports:\n - 7005:80\n - 5005:5005\n depends_on:\n - postgres\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n order-service:\n image: rosie91/stemify:order-service\n build:\n context: .\n dockerfile: src/Services/OrderService/Order.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5006\n - ConnectionStrings__stemifyorder=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_order\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcResourceUrl=http://resource-service:5003\n - GrpcProductUrl=http://product-service:5005\n - GrpcClassroomUrl=http://classroom-service:5001\n - Cloudinary__CloudName=${CLOUDINARY_CLOUD_NAME}\n - Cloudinary__ApiKey=${CLOUDINARY_API_KEY}\n - Cloudinary__ApiSecret=${CLOUDINARY_API_SECRET}\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=order-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=order-service,service.version=1.0.0\n\n ports:\n - 7006:80\n - 5006:5006\n depends_on:\n - postgres\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n notification-service:\n image: rosie91/stemify:notification-service\n build:\n context: .\n dockerfile: src/Services/Notification/Notification.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5004\n - ConnectionStrings__stemifynotification=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_notification\n # SendGrid configuration\n - SendGridSettings__ApiKey=${SENDGRID_API_KEY}\n - SendGridSettings__FromEmail=stemify30062025@gmail.com\n - SendGridSettings__FromName=STEMify\n # Client App configuration\n - ClientApp__BaseUrl=https://localhost:3000\n - ClientApp=https://localhost:3000\n - Issuer=http://localhost:5000/\n - IdentityServiceUrl=http://identity-service\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=notification-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=notification-service,service.version=1.0.0\n ports:\n - 7004:80\n - 5004:5004\n depends_on:\n - postgres\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n gateway-service:\n image: rosie91/stemify:gateway-service\n build:\n context: .\n dockerfile: src/ApiGateways/ApiGateway/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Docker\n - ASPNETCORE_URLS=http://+:80\n - IdentityServiceUrl=http://identity-service\n - Issuer=http://localhost:5000/\n - ClientApp=https://localhost:3000\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=gateway-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=gateway-service,service.version=1.0.0\n ports:\n - 6001:80\n depends_on:\n - otelcollector\n restart: unless-stopped\n identity-api-service:\n image: rosie91/stemify:identity-api-service\n build:\n context: .\n dockerfile: src/Services/Identity/Identity.Api/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5002\n - ConnectionStrings__stemifyidentity=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_identity\n # RabbitMQ Configuration (for MassTransit)\n - RabbitMq__Url=${RABBITMQ_URL}\n # OpenIddict Configuration\n - OpenIddict__Authority=http://identity-service:80\n # Client App Configuration\n - ClientApp=https://localhost:3000\n # Email Settings\n - EmailSettings__From=${IDENTITY_EMAIL_FROM}\n - EmailSettings__DisplayName=${IDENTITY_EMAIL_DISPLAY_NAME}\n - EmailSettings__SMTPServer=${IDENTITY_EMAIL_SMTP_SERVER}\n - EmailSettings__Port=${IDENTITY_EMAIL_PORT}\n - EmailSettings__Username=${IDENTITY_EMAIL_USERNAME}\n - EmailSettings__Password=${IDENTITY_EMAIL_PASSWORD}\n - EmailSettings__UseSSL=${IDENTITY_EMAIL_USE_SSL}\n - EmailSettings__ApplicationName=${IDENTITY_EMAIL_APP_NAME}\n - EmailSettings__SupportEmail=${IDENTITY_EMAIL_SUPPORT}\n # Google Authentication\n - Authentication__Google__ClientId=${GOOGLE_CLIENT_ID}\n - Authentication__Google__ClientSecret=${GOOGLE_CLIENT_SECRET}\n - Authentication__Google__RedirectUri=${GOOGLE_REDIRECT_URI}\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=identity-api-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=identity-api-service,service.version=1.0.0\n - GrpcServices__OrderService__Address=http://order-service:5006\n # Google Authentication\n - Authentication__Google__ClientId=${GOOGLE_CLIENT_ID}\n - Authentication__Google__ClientSecret=${GOOGLE_CLIENT_SECRET}\n - Authentication__Google__RedirectUri=${GOOGLE_REDIRECT_URI}\n - BackendUrl=${IDENTITY_BACKEND_URL:-http://localhost:6001}\n ports:\n - 7002:80\n - 5002:5002\n depends_on:\n - postgres\n - otelcollector\n restart: unless-stopped\n identity-service:\n image: rosie91/stemify:identity-service\n build:\n context: .\n dockerfile: src/Services/Identity/Identity.Web/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - ASPNETCORE_URLS=http://+:80\n - ConnectionStrings__stemifyidentity=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_identity\n # OpenIddict configuration for Docker\n - OpenIddict__Issuer=http://localhost:5000\n - OpenIddict__Authority=http://localhost:5000\n - OpenIddict__SigningKey=${OPENIDDICT_SIGNING_KEY}\n - OpenIddict__EncryptionKey=${OPENIDDICT_ENCRYPTION_KEY}\n - OpenIddict__ApiClientSecret=${OPENIDDICT_API_CLIENT_SECRET}\n - OpenIddict__ForceRecreateClients=${OPENIDDICT_FORCE_RECREATE_CLIENTS:-true}\n - OpenIddict__DisableHttpsRequirement=${OPENIDDICT_DISABLE_HTTPS:-true}\n # CORS for Docker environment\n - Cors__Origins=${IDENTITY_CORS_ORIGINS:-https://localhost:3000}\n - ClientApp=${IDENTITY_CLIENT_APP:-https://localhost:3000}\n - BackendUrl=${IDENTITY_BACKEND_URL:-http://localhost:5000}\n # Google Authentication\n - Authentication__Google__ClientId=${GOOGLE_CLIENT_ID}\n - Authentication__Google__ClientSecret=${GOOGLE_CLIENT_SECRET}\n - Authentication__Google__RedirectUri=${GOOGLE_REDIRECT_URI}\n # Email Settings\n - EmailSettings__SMTPServer=${IDENTITY_EMAIL_SMTP_SERVER}\n - EmailSettings__Port=${IDENTITY_EMAIL_PORT}\n - EmailSettings__UseSSL=${IDENTITY_EMAIL_USE_SSL}\n - EmailSettings__Username=${IDENTITY_EMAIL_USERNAME}\n - EmailSettings__Password=${IDENTITY_EMAIL_PASSWORD}\n - EmailSettings__From=${IDENTITY_EMAIL_FROM}\n - EmailSettings__DisplayName=${IDENTITY_EMAIL_DISPLAY_NAME}\n - EmailSettings__ApplicationName=${IDENTITY_EMAIL_APP_NAME}\n - EmailSettings__SupportEmail=${IDENTITY_EMAIL_SUPPORT}\n - EmailSettings__Enabled=${IDENTITY_EMAIL_ENABLED}\n - EmailSettings__EnableVerification=${IDENTITY_EMAIL_ENABLE_VERIFICATION}\n - EmailSettings__TimeoutMs=${IDENTITY_EMAIL_TIMEOUT_MS}\n - EmailSettings__MaxRetryAttempts=${IDENTITY_EMAIL_MAX_RETRY}\n - EmailSettings__LogEmailContent=${IDENTITY_EMAIL_LOG_CONTENT}\n # gRPC Services\n - GrpcServices__OrderService__Address=http://order-service:5006\n # Database Options\n - DatabaseOptions__MaxRetryCount=5\n - DatabaseOptions__MaxRetryDelay=00:00:10\n - DatabaseOptions__CommandTimeout=30\n - DatabaseOptions__EnableRetryOnFailure=true\n - DatabaseOptions__EnableSensitiveDataLogging=${DB_ENABLE_SENSITIVE_LOGGING}\n - DatabaseOptions__EnableDetailedErrors=${DB_ENABLE_DETAILED_ERRORS}\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=identity-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=identity-service,service.version=1.0.0\n - RabbitMq__Url=${RABBITMQ_URL}\n ports:\n - 5000:80\n depends_on:\n - postgres\n - otelcollector\n restart: unless-stopped\n cart-service:\n image: rosie91/stemify:cart-service\n build:\n context: .\n dockerfile: src/Services/CartService/Cart.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5007\n - ConnectionStrings__stemifycart=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_cart\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcProductUrl=http://product-service:5005\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=cart-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=cart-service,service.version=1.0.0\n ports:\n - 7007:80\n - 5007:5007\n depends_on:\n - postgres\n - redis\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n payment-service:\n image: rosie91/stemify:payment-service\n build:\n context: .\n dockerfile: src/Services/PaymentService/Payment.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - Logging__LogLevel__Microsoft.EntityFrameworkCore.Database.Command=Warning\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5008\n - ConnectionStrings__stemifypayment=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_payment\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n # PayOS configuration\n - PaymentGateways__PayOS__ClientId=${PAYOS_CLIENT_ID}\n - PaymentGateways__PayOS__ApiKey=${PAYOS_API_KEY}\n - PaymentGateways__PayOS__ChecksumKey=${PAYOS_CHECKSUM_KEY}\n - PaymentGateways__PayOS__BaseUrl=https://api-merchant.payos.vn\n # Stripe configuration\n - PaymentGateways__Stripe__SecretKey=${STRIPE_SECRET_KEY}\n - PaymentGateways__Stripe__PublishableKey=${STRIPE_PUBLISH_KEY}\n - PaymentGateways__Stripe__WebhookSecret=${STRIPE_WEBHOOK_KEY}\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=payment-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=payment-service,service.version=1.0.0\n ports:\n - 7008:80\n - 5008:5008\n depends_on:\n - postgres\n - redis\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n emulator-service:\n image: rosie91/stemify:emulator-service\n build:\n context: .\n dockerfile: src/Services/EmulatorService/Emulator.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5009\n # MongoDB configuration\n - MongoDb__ConnectionString=mongodb://root:mongopw@mongo\n - MongoDb__DatabaseName=stemify_emulator\n # Redis configuration\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n # gRPC endpoints\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcResourceUrl=http://resource-service:5003\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=emulator-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=emulator-service,service.version=1.0.0\n # Cloudinary Configuration\n - Cloudinary__CloudName=${CLOUDINARY_CLOUD_NAME}\n - Cloudinary__ApiKey=${CLOUDINARY_API_KEY}\n - Cloudinary__ApiSecret=${CLOUDINARY_API_SECRET}\n ports:\n - 7009:80\n - 5009:5009\n depends_on:\n - redis\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n\n # ========================================\n # MONITORING STACK\n # ========================================\n\n # Prometheus - Metrics Collection\n prometheus:\n image: prom/prometheus:v3.5.0\n container_name: stemify-prometheus\n ports:\n - '9090:9090'\n volumes:\n - ./config/prometheus:/etc/prometheus:ro\n - prometheus_data:/prometheus\n command:\n - '--config.file=/etc/prometheus/prometheus.yml'\n - '--storage.tsdb.path=/prometheus'\n - '--storage.tsdb.retention.time=15d'\n - '--storage.tsdb.retention.size=10GB'\n - '--web.enable-lifecycle'\n - '--web.enable-otlp-receiver'\n restart: unless-stopped\n\n # AlertManager - Alert Management\n alertmanager:\n image: prom/alertmanager:v0.27.0\n container_name: stemify-alertmanager\n ports:\n - '9093:9093'\n volumes:\n - ./config/alertmanager:/etc/alertmanager:ro\n - alertmanager_data:/alertmanager\n command:\n - '--config.file=/etc/alertmanager/alertmanager.yml'\n - '--storage.path=/alertmanager'\n - '--web.external-url=http://localhost:9093'\n restart: unless-stopped\n\n # Grafana - Visualization\n grafana:\n image: grafana/grafana:latest\n container_name: stemify-grafana\n ports:\n - '3001:3001'\n volumes:\n - grafana_data:/var/lib/grafana\n - ./config/grafana/config:/etc/grafana:ro\n - ./config/grafana/dashboards:/var/lib/grafana/dashboards:ro\n environment:\n - GF_SECURITY_ADMIN_USER=${GRAFANA_ADMIN_USERNAME}\n - GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_ADMIN_PASSWORD:-admin123}\n - GF_USERS_ALLOW_SIGN_UP=false\n - GF_INSTALL_PLUGINS=grafana-clock-panel,grafana-simple-json-datasource\n - PROMETHEUS_ENDPOINT=http://prometheus:9090\n restart: unless-stopped\n depends_on:\n - prometheus\n\n # OpenTelemetry Collector\n otelcollector:\n image: otel/opentelemetry-collector-contrib:latest\n container_name: stemify-otelcollector\n ports:\n - '8888:8888' # Prometheus metrics\n - '8889:8889' # Prometheus exporter\n - '4317:4317' # OTLP gRPC receiver\n - '4318:4318' # OTLP HTTP receiver\n volumes:\n - ./config/otelcollector:/etc/otelcol-contrib:ro\n environment:\n - PROMETHEUS_ENDPOINT=http://prometheus:9090/api/v1/write\n command: [ '--config=/etc/otelcol-contrib/config.yaml' ]\n restart: unless-stopped\n depends_on:\n - prometheus\n\n # ========================================\n # AI SERVICE & VECTOR DATABASE\n # ========================================\n\n # Qdrant - Vector Database for RAG\n qdrant:\n image: qdrant/qdrant:latest\n container_name: stemify-qdrant\n ports:\n - '6333:6333' # HTTP API\n - '6334:6334' # gRPC\n volumes:\n - qdrant_data:/qdrant/storage\n environment:\n - QDRANT__SERVICE__GRPC_PORT=6334\n restart: unless-stopped\n\n neo4j:\n image: neo4j:5.20\n container_name: stemify-neo4j\n ports:\n - \"7474:7474\" # HTTP\n - \"7687:7687\" # Bolt\n environment:\n - NEO4J_AUTH=neo4j/password\n - NEO4J_PLUGINS=[\"apoc\"]\n - NEO4J_dbms_memory_heap_max__size=2G\n - NEO4J_dbms_memory_pagecache_size=1G\n volumes:\n - neo4j_data:/data\n - neo4j_logs:/logs\n restart: unless-stopped\n healthcheck:\n test: [ \"CMD\", \"cypher-shell\", \"-u\", \"neo4j\", \"-p\", \"password\", \"RETURN 1\" ]\n interval: 30s\n timeout: 10s\n retries: 3\n start_period: 40s\n ai-service:\n image: rosie91/stemify:ai-service\n build:\n context: ./src/Services/AIService\n dockerfile: Dockerfile\n container_name: stemify-ai-service\n environment:\n - OPENAI_API_KEY=${OPENAI_API_KEY}\n - OPENAI_BASE_URL=${OPENAI_BASE_URL:-https://api.openai.com/v1}\n - DEEPSEEK_API_KEY=${DEEPSEEK_API_KEY}\n - RESOURCE_GRPC_CERT_PATH=${RESOURCE_GRPC_CERT_PATH}\n ports:\n - '7010:80' # HTTP API (FastAPI)\n - '5010:5010' # gRPC\n # depends_on:\n # - qdrant\n # - neo4j\n restart: unless-stopped\n healthcheck:\n test: [ \"CMD\", \"curl\", \"-f\", \"http://localhost:80/health\" ]\n interval: 30s\n timeout: 10s\n retries: 3\n start_period: 40s\n\nvolumes:\n redis-data:\n postgres_data:\n mongodata:\n prometheus_data:\n alertmanager_data:\n grafana_data:\n qdrant_data:\n neo4j_data:\n neo4j_logs:\n"}, "cabd5c33a8c847d099cad62fd83779af6ef38a1f": {"t": 1785748768.7989643, "n": 23730, "h": "f56fb5db9f376ae9", "c": "services:\n postgres:\n image: postgres:17\n container_name: stemify-postgres\n environment:\n - POSTGRES_PASSWORD=${DB_PASSWORD}\n ports:\n - '5432:5432'\n volumes:\n - postgres_data:/var/lib/postgresql/data\n restart: unless-stopped\n # RabbitMQ is now using CloudAMQP online service\n # rabbitmq:\n # image: rabbitmq:3-management-alpine\n # ports:\n # - 5672:5672\n # - 15672:15672\n # environment:\n # - RABBITMQ_DEFAULT_USER=guest\n # - RABBITMQ_DEFAULT_PASS=guest\n # healthcheck:\n # test: [\"CMD\", \"rabbitmq-diagnostics\", \"ping\"]\n # interval: 30s\n # timeout: 10s\n # retries: 5\n # start_period: 30s\n # restart: unless-stopped\n mongo:\n image: mongo\n environment:\n - MONGO_INITDB_ROOT_USERNAME=root\n - MONGO_INITDB_ROOT_PASSWORD=mongopw\n ports:\n - 27017:27017\n volumes:\n - mongodata:/var/lib/mongodb/data\n redis:\n image: redis:latest\n ports:\n - 6379:6379\n command: [ 'redis-server', '--appendonly', 'yes' ]\n volumes:\n - redis-data:/data\n redis-commander:\n image: rediscommander/redis-commander:latest\n environment:\n - REDIS_HOSTS=local:redis:6379\n - HTTP_USER=root\n - HTTP_PASSWORD=secret\n ports:\n - 8082:8082\n depends_on:\n - redis\n classroom-service:\n image: rosie91/stemify:classroom-service\n build:\n context: .\n dockerfile: src/Services/ClassroomService/Classroom.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5001\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcResourceUrl=http://resource-service:5003\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcOrderUrl=http://order-service:5006\n - ConnectionStrings__stemifyclassroom=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_classroom\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=classroom-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=classroom-service,service.version=1.0.0\n # Cloudinary Configuration\n - Cloudinary__CloudName=${CLOUDINARY_CLOUD_NAME}\n - Cloudinary__ApiKey=${CLOUDINARY_API_KEY}\n - Cloudinary__ApiSecret=${CLOUDINARY_API_SECRET}\n ports:\n - 7001:80\n - 5001:5001\n depends_on:\n - postgres\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n resource-service:\n image: rosie91/stemify:resource-service\n build:\n context: .\n dockerfile: src/Services/Resource/Resource.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5003\n - ConnectionStrings__stemifyresource=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_resource\n - Cloudinary__CloudName=${CLOUDINARY_CLOUD_NAME}\n - Cloudinary__ApiKey=${CLOUDINARY_API_KEY}\n - Cloudinary__ApiSecret=${CLOUDINARY_API_SECRET}\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n - ClientApp=https://localhost:3000\n - Gemini__BaseUrl=https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent\n - Gemini__ApiKey=${GEMINI_API_KEY}\n - Gemini__Model=gemini-2.5-flash\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=resource-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=resource-service,service.version=1.0.0\n ports:\n - 7003:80\n - 5003:5003\n depends_on:\n - postgres\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n product-service:\n image: rosie91/stemify:product-service\n build:\n context: .\n dockerfile: src/Services/ProductService/Product.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5005\n - ConnectionStrings__stemifyproduct=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_product\n - Cloudinary__CloudName=${CLOUDINARY_CLOUD_NAME}\n - Cloudinary__ApiKey=${CLOUDINARY_API_KEY}\n - Cloudinary__ApiSecret=${CLOUDINARY_API_SECRET}\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcResourceUrl=http://resource-service:5003\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=product-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=product-service,service.version=1.0.0\n ports:\n - 7005:80\n - 5005:5005\n depends_on:\n - postgres\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n order-service:\n image: rosie91/stemify:order-service\n build:\n context: .\n dockerfile: src/Services/OrderService/Order.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5006\n - ConnectionStrings__stemifyorder=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_order\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcResourceUrl=http://resource-service:5003\n - GrpcProductUrl=http://product-service:5005\n - GrpcClassroomUrl=http://classroom-service:5001\n - Cloudinary__CloudName=${CLOUDINARY_CLOUD_NAME}\n - Cloudinary__ApiKey=${CLOUDINARY_API_KEY}\n - Cloudinary__ApiSecret=${CLOUDINARY_API_SECRET}\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=order-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=order-service,service.version=1.0.0\n\n ports:\n - 7006:80\n - 5006:5006\n depends_on:\n - postgres\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n notification-service:\n image: rosie91/stemify:notification-service\n build:\n context: .\n dockerfile: src/Services/Notification/Notification.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5004\n - ConnectionStrings__stemifynotification=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_notification\n # SendGrid configuration\n - SendGridSettings__ApiKey=${SENDGRID_API_KEY}\n - SendGridSettings__FromEmail=stemify30062025@gmail.com\n - SendGridSettings__FromName=STEMify\n # Client App configuration\n - ClientApp__BaseUrl=https://localhost:3000\n - ClientApp=https://localhost:3000\n - Issuer=http://localhost:5000/\n - IdentityServiceUrl=http://identity-service\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=notification-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=notification-service,service.version=1.0.0\n ports:\n - 7004:80\n - 5004:5004\n depends_on:\n - postgres\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n gateway-service:\n image: rosie91/stemify:gateway-service\n build:\n context: .\n dockerfile: src/ApiGateways/ApiGateway/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Docker\n - ASPNETCORE_URLS=http://+:80\n - IdentityServiceUrl=http://identity-service\n - Issuer=http://localhost:5000/\n - ClientApp=https://localhost:3000\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=gateway-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=gateway-service,service.version=1.0.0\n ports:\n - 6001:80\n depends_on:\n - otelcollector\n restart: unless-stopped\n identity-api-service:\n image: rosie91/stemify:identity-api-service\n build:\n context: .\n dockerfile: src/Services/Identity/Identity.Api/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5002\n - ConnectionStrings__stemifyidentity=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_identity\n # RabbitMQ Configuration (for MassTransit)\n - RabbitMq__Url=${RABBITMQ_URL}\n # OpenIddict Configuration\n - OpenIddict__Authority=http://identity-service:80\n # Client App Configuration\n - ClientApp=https://localhost:3000\n # Email Settings\n - EmailSettings__From=${IDENTITY_EMAIL_FROM}\n - EmailSettings__DisplayName=${IDENTITY_EMAIL_DISPLAY_NAME}\n - EmailSettings__SMTPServer=${IDENTITY_EMAIL_SMTP_SERVER}\n - EmailSettings__Port=${IDENTITY_EMAIL_PORT}\n - EmailSettings__Username=${IDENTITY_EMAIL_USERNAME}\n - EmailSettings__Password=${IDENTITY_EMAIL_PASSWORD}\n - EmailSettings__UseSSL=${IDENTITY_EMAIL_USE_SSL}\n - EmailSettings__ApplicationName=${IDENTITY_EMAIL_APP_NAME}\n - EmailSettings__SupportEmail=${IDENTITY_EMAIL_SUPPORT}\n # Google Authentication\n - Authentication__Google__ClientId=${GOOGLE_CLIENT_ID}\n - Authentication__Google__ClientSecret=${GOOGLE_CLIENT_SECRET}\n - Authentication__Google__RedirectUri=${GOOGLE_REDIRECT_URI}\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=identity-api-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=identity-api-service,service.version=1.0.0\n - GrpcServices__OrderService__Address=http://order-service:5006\n # Google Authentication\n - Authentication__Google__ClientId=${GOOGLE_CLIENT_ID}\n - Authentication__Google__ClientSecret=${GOOGLE_CLIENT_SECRET}\n - Authentication__Google__RedirectUri=${GOOGLE_REDIRECT_URI}\n - BackendUrl=${IDENTITY_BACKEND_URL:-http://localhost:6001}\n ports:\n - 7002:80\n - 5002:5002\n depends_on:\n - postgres\n - otelcollector\n restart: unless-stopped\n identity-service:\n image: rosie91/stemify:identity-service\n build:\n context: .\n dockerfile: src/Services/Identity/Identity.Web/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - ASPNETCORE_URLS=http://+:80\n - ConnectionStrings__stemifyidentity=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_identity\n # OpenIddict configuration for Docker\n - OpenIddict__Issuer=http://localhost:5000\n - OpenIddict__Authority=http://localhost:5000\n - OpenIddict__SigningKey=${OPENIDDICT_SIGNING_KEY}\n - OpenIddict__EncryptionKey=${OPENIDDICT_ENCRYPTION_KEY}\n - OpenIddict__ApiClientSecret=${OPENIDDICT_API_CLIENT_SECRET}\n - OpenIddict__ForceRecreateClients=${OPENIDDICT_FORCE_RECREATE_CLIENTS:-true}\n - OpenIddict__DisableHttpsRequirement=${OPENIDDICT_DISABLE_HTTPS:-true}\n # CORS for Docker environment\n - Cors__Origins=${IDENTITY_CORS_ORIGINS:-https://localhost:3000}\n - ClientApp=${IDENTITY_CLIENT_APP:-https://localhost:3000}\n - BackendUrl=${IDENTITY_BACKEND_URL:-http://localhost:5000}\n # Google Authentication\n - Authentication__Google__ClientId=${GOOGLE_CLIENT_ID}\n - Authentication__Google__ClientSecret=${GOOGLE_CLIENT_SECRET}\n - Authentication__Google__RedirectUri=${GOOGLE_REDIRECT_URI}\n # Email Settings\n - EmailSettings__SMTPServer=${IDENTITY_EMAIL_SMTP_SERVER}\n - EmailSettings__Port=${IDENTITY_EMAIL_PORT}\n - EmailSettings__UseSSL=${IDENTITY_EMAIL_USE_SSL}\n - EmailSettings__Username=${IDENTITY_EMAIL_USERNAME}\n - EmailSettings__Password=${IDENTITY_EMAIL_PASSWORD}\n - EmailSettings__From=${IDENTITY_EMAIL_FROM}\n - EmailSettings__DisplayName=${IDENTITY_EMAIL_DISPLAY_NAME}\n - EmailSettings__ApplicationName=${IDENTITY_EMAIL_APP_NAME}\n - EmailSettings__SupportEmail=${IDENTITY_EMAIL_SUPPORT}\n - EmailSettings__Enabled=${IDENTITY_EMAIL_ENABLED}\n - EmailSettings__EnableVerification=${IDENTITY_EMAIL_ENABLE_VERIFICATION}\n - EmailSettings__TimeoutMs=${IDENTITY_EMAIL_TIMEOUT_MS}\n - EmailSettings__MaxRetryAttempts=${IDENTITY_EMAIL_MAX_RETRY}\n - EmailSettings__LogEmailContent=${IDENTITY_EMAIL_LOG_CONTENT}\n # gRPC Services\n - GrpcServices__OrderService__Address=http://order-service:5006\n # Database Options\n - DatabaseOptions__MaxRetryCount=5\n - DatabaseOptions__MaxRetryDelay=00:00:10\n - DatabaseOptions__CommandTimeout=30\n - DatabaseOptions__EnableRetryOnFailure=true\n - DatabaseOptions__EnableSensitiveDataLogging=${DB_ENABLE_SENSITIVE_LOGGING}\n - DatabaseOptions__EnableDetailedErrors=${DB_ENABLE_DETAILED_ERRORS}\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=identity-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=identity-service,service.version=1.0.0\n - RabbitMq__Url=${RABBITMQ_URL}\n ports:\n - 5000:80\n depends_on:\n - postgres\n - otelcollector\n restart: unless-stopped\n cart-service:\n image: rosie91/stemify:cart-service\n build:\n context: .\n dockerfile: src/Services/CartService/Cart.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5007\n - ConnectionStrings__stemifycart=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_cart\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcProductUrl=http://product-service:5005\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=cart-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=cart-service,service.version=1.0.0\n ports:\n - 7007:80\n - 5007:5007\n depends_on:\n - postgres\n - redis\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n payment-service:\n image: rosie91/stemify:payment-service\n build:\n context: .\n dockerfile: src/Services/PaymentService/Payment.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - Logging__LogLevel__Microsoft.EntityFrameworkCore.Database.Command=Warning\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5008\n - ConnectionStrings__stemifypayment=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_payment\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n # PayOS configuration\n - PaymentGateways__PayOS__ClientId=${PAYOS_CLIENT_ID}\n - PaymentGateways__PayOS__ApiKey=${PAYOS_API_KEY}\n - PaymentGateways__PayOS__ChecksumKey=${PAYOS_CHECKSUM_KEY}\n - PaymentGateways__PayOS__BaseUrl=https://api-merchant.payos.vn\n # Stripe configuration\n - PaymentGateways__Stripe__SecretKey=${STRIPE_SECRET_KEY}\n - PaymentGateways__Stripe__PublishableKey=${STRIPE_PUBLISH_KEY}\n - PaymentGateways__Stripe__WebhookSecret=${STRIPE_WEBHOOK_KEY}\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=payment-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=payment-service,service.version=1.0.0\n ports:\n - 7008:80\n - 5008:5008\n depends_on:\n - postgres\n - redis\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n emulator-service:\n image: rosie91/stemify:emulator-service\n build:\n context: .\n dockerfile: src/Services/EmulatorService/Emulator.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5009\n # MongoDB configuration\n - MongoDb__ConnectionString=mongodb://root:mongopw@mongo\n - MongoDb__DatabaseName=stemify_emulator\n # Redis configuration\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n # gRPC endpoints\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcResourceUrl=http://resource-service:5003\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=emulator-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=emulator-service,service.version=1.0.0\n # Cloudinary Configuration\n - Cloudinary__CloudName=${CLOUDINARY_CLOUD_NAME}\n - Cloudinary__ApiKey=${CLOUDINARY_API_KEY}\n - Cloudinary__ApiSecret=${CLOUDINARY_API_SECRET}\n ports:\n - 7009:80\n - 5009:5009\n depends_on:\n - redis\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n\n # ========================================\n # MONITORING STACK\n # ========================================\n\n # Prometheus - Metrics Collection\n prometheus:\n image: prom/prometheus:v3.5.0\n container_name: stemify-prometheus\n ports:\n - '9090:9090'\n volumes:\n - ./config/prometheus:/etc/prometheus:ro\n - prometheus_data:/prometheus\n command:\n - '--config.file=/etc/prometheus/prometheus.yml'\n - '--storage.tsdb.path=/prometheus'\n - '--storage.tsdb.retention.time=15d'\n - '--storage.tsdb.retention.size=10GB'\n - '--web.enable-lifecycle'\n - '--web.enable-otlp-receiver'\n restart: unless-stopped\n\n # AlertManager - Alert Management\n alertmanager:\n image: prom/alertmanager:v0.27.0\n container_name: stemify-alertmanager\n ports:\n - '9093:9093'\n volumes:\n - ./config/alertmanager:/etc/alertmanager:ro\n - alertmanager_data:/alertmanager\n command:\n - '--config.file=/etc/alertmanager/alertmanager.yml'\n - '--storage.path=/alertmanager'\n - '--web.external-url=http://localhost:9093'\n restart: unless-stopped\n\n # Grafana - Visualization\n grafana:\n image: grafana/grafana:latest\n container_name: stemify-grafana\n ports:\n - '3001:3001'\n volumes:\n - grafana_data:/var/lib/grafana\n - ./config/grafana/config:/etc/grafana:ro\n - ./config/grafana/dashboards:/var/lib/grafana/dashboards:ro\n environment:\n - GF_SECURITY_ADMIN_USER=${GRAFANA_ADMIN_USERNAME}\n - GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_ADMIN_PASSWORD:-admin123}\n - GF_USERS_ALLOW_SIGN_UP=false\n - GF_INSTALL_PLUGINS=grafana-clock-panel,grafana-simple-json-datasource\n - PROMETHEUS_ENDPOINT=http://prometheus:9090\n restart: unless-stopped\n depends_on:\n - prometheus\n\n # OpenTelemetry Collector\n otelcollector:\n image: otel/opentelemetry-collector-contrib:latest\n container_name: stemify-otelcollector\n ports:\n - '8888:8888' # Prometheus metrics\n - '8889:8889' # Prometheus exporter\n - '4317:4317' # OTLP gRPC receiver\n - '4318:4318' # OTLP HTTP receiver\n volumes:\n - ./config/otelcollector:/etc/otelcol-contrib:ro\n environment:\n - PROMETHEUS_ENDPOINT=http://prometheus:9090/api/v1/write\n command: [ '--config=/etc/otelcol-contrib/config.yaml' ]\n restart: unless-stopped\n depends_on:\n - prometheus\n\n # ========================================\n # AI SERVICE & VECTOR DATABASE\n # ========================================\n\n # Qdrant - Vector Database for RAG\n qdrant:\n image: qdrant/qdrant:latest\n container_name: stemify-qdrant\n ports:\n - '6333:6333' # HTTP API\n - '6334:6334' # gRPC\n volumes:\n - qdrant_data:/qdrant/storage\n environment:\n - QDRANT__SERVICE__GRPC_PORT=6334\n restart: unless-stopped\n\n neo4j:\n image: neo4j:5.20\n container_name: stemify-neo4j\n ports:\n - \"7474:7474\" # HTTP\n - \"7687:7687\" # Bolt\n environment:\n - NEO4J_AUTH=neo4j/password\n - NEO4J_PLUGINS=[\"apoc\"]\n - NEO4J_dbms_memory_heap_max__size=2G\n - NEO4J_dbms_memory_pagecache_size=1G\n volumes:\n - neo4j_data:/data\n - neo4j_logs:/logs\n restart: unless-stopped\n healthcheck:\n test: [ \"CMD\", \"cypher-shell\", \"-u\", \"neo4j\", \"-p\", \"password\", \"RETURN 1\" ]\n interval: 30s\n timeout: 10s\n retries: 3\n start_period: 40s\n ai-service:\n image: rosie91/stemify:ai-service\n build:\n context: ./src/Services/AIService\n dockerfile: Dockerfile\n container_name: stemify-ai-service\n environment:\n - OPENAI_API_KEY=${OPENAI_API_KEY}\n - OPENAI_BASE_URL=${OPENAI_BASE_URL:-https://api.openai.com/v1}\n - DEEPSEEK_API_KEY=${DEEPSEEK_API_KEY}\n - RESOURCE_GRPC_CERT_PATH=${RESOURCE_GRPC_CERT_PATH}\n ports:\n - '7010:80' # HTTP API (FastAPI)\n - '5010:5010' # gRPC\n # depends_on:\n # - qdrant\n # - neo4j\n restart: unless-stopped\n healthcheck:\n test: [ \"CMD\", \"curl\", \"-f\", \"http://localhost:80/health\" ]\n interval: 30s\n timeout: 10s\n retries: 3\n start_period: 40s\n\nvolumes:\n redis-data:\n postgres_data:\n mongodata:\n prometheus_data:\n alertmanager_data:\n grafana_data:\n qdrant_data:\n neo4j_data:\n neo4j_logs:\n"}, "63c9140fcd9e2d198e1b930d1f7a7120ffc22f42": {"t": 1785748770.0140655, "n": 23728, "h": "3913eb569f2105c7", "c": "services:\n postgres:\n image: postgres:17\n container_name: stemify-postgres\n environment:\n - POSTGRES_PASSWORD=${DB_PASSWORD}\n ports:\n - '5432:5432'\n volumes:\n - postgres_data:/var/lib/postgresql/data\n restart: unless-stopped\n # RabbitMQ is now using CloudAMQP online service\n # rabbitmq:\n # image: rabbitmq:3-management-alpine\n # ports:\n # - 5672:5672\n # - 15672:15672\n # environment:\n # - RABBITMQ_DEFAULT_USER=guest\n # - RABBITMQ_DEFAULT_PASS=guest\n # healthcheck:\n # test: [\"CMD\", \"rabbitmq-diagnostics\", \"ping\"]\n # interval: 30s\n # timeout: 10s\n # retries: 5\n # start_period: 30s\n # restart: unless-stopped\n mongo:\n image: mongo\n environment:\n - MONGO_INITDB_ROOT_USERNAME=root\n - MONGO_INITDB_ROOT_PASSWORD=mongopw\n ports:\n - 27017:27017\n volumes:\n - mongodata:/var/lib/mongodb/data\n redis:\n image: redis:latest\n ports:\n - 6379:6379\n command: [ 'redis-server', '--appendonly', 'yes' ]\n volumes:\n - redis-data:/data\n redis-commander:\n image: rediscommander/redis-commander:latest\n environment:\n - REDIS_HOSTS=local:redis:6379\n - HTTP_USER=root\n - HTTP_PASSWORD=secret\n ports:\n - 8082:8082\n depends_on:\n - redis\n classroom-service:\n image: rosie91/stemify:classroom-service\n build:\n context: .\n dockerfile: src/Services/ClassroomService/Classroom.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5001\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcResourceUrl=http://resource-service:5003\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcOrderUrl=http://order-service:5006\n - ConnectionStrings__stemifyclassroom=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_classroom\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=classroom-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=classroom-service,service.version=1.0.0\n # Cloudinary Configuration\n - Cloudinary__CloudName=${CLOUDINARY_CLOUD_NAME}\n - Cloudinary__ApiKey=${CLOUDINARY_API_KEY}\n - Cloudinary__ApiSecret=${CLOUDINARY_API_SECRET}\n ports:\n - 7001:80\n - 5001:5001\n depends_on:\n - postgres\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n resource-service:\n image: rosie91/stemify:resource-service\n build:\n context: .\n dockerfile: src/Services/Resource/Resource.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5003\n - ConnectionStrings__stemifyresource=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_resource\n - Cloudinary__CloudName=${CLOUDINARY_CLOUD_NAME}\n - Cloudinary__ApiKey=${CLOUDINARY_API_KEY}\n - Cloudinary__ApiSecret=${CLOUDINARY_API_SECRET}\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n - ClientApp=https://localhost:3000\n - Gemini__BaseUrl=https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent\n - Gemini__ApiKey=${GEMINI_API_KEY}\n - Gemini__Model=gemini-2.5-flash\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=resource-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=resource-service,service.version=1.0.0\n ports:\n - 7003:80\n - 5003:5003\n depends_on:\n - postgres\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n product-service:\n image: rosie91/stemify:product-service\n build:\n context: .\n dockerfile: src/Services/ProductService/Product.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5005\n - ConnectionStrings__stemifyproduct=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_product\n - Cloudinary__CloudName=${CLOUDINARY_CLOUD_NAME}\n - Cloudinary__ApiKey=${CLOUDINARY_API_KEY}\n - Cloudinary__ApiSecret=${CLOUDINARY_API_SECRET}\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcResourceUrl=http://resource-service:5003\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=product-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=product-service,service.version=1.0.0\n ports:\n - 7005:80\n - 5005:5005\n depends_on:\n - postgres\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n order-service:\n image: rosie91/stemify:order-service\n build:\n context: .\n dockerfile: src/Services/OrderService/Order.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5006\n - ConnectionStrings__stemifyorder=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_order\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcResourceUrl=http://resource-service:5003\n - GrpcProductUrl=http://product-service:5005\n - GrpcClassroomUrl=http://classroom-service:5001\n - Cloudinary__CloudName=${CLOUDINARY_CLOUD_NAME}\n - Cloudinary__ApiKey=${CLOUDINARY_API_KEY}\n - Cloudinary__ApiSecret=${CLOUDINARY_API_SECRET}\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=order-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=order-service,service.version=1.0.0\n\n ports:\n - 7006:80\n - 5006:5006\n depends_on:\n - postgres\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n notification-service:\n image: rosie91/stemify:notification-service\n build:\n context: .\n dockerfile: src/Services/Notification/Notification.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5004\n - ConnectionStrings__stemifynotification=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_notification\n # SendGrid configuration\n - SendGridSettings__ApiKey=${SENDGRID_API_KEY}\n - SendGridSettings__FromEmail=stemify30062025@gmail.com\n - SendGridSettings__FromName=STEMify\n # Client App configuration\n - ClientApp__BaseUrl=https://localhost:3000\n - ClientApp=https://localhost:3000\n - Issuer=http://localhost:5000/\n - IdentityServiceUrl=http://identity-service\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=notification-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=notification-service,service.version=1.0.0\n ports:\n - 7004:80\n - 5004:5004\n depends_on:\n - postgres\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n gateway-service:\n image: rosie91/stemify:gateway-service\n build:\n context: .\n dockerfile: src/ApiGateways/ApiGateway/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Docker\n - ASPNETCORE_URLS=http://+:80\n - IdentityServiceUrl=http://identity-service\n - Issuer=http://localhost:5000/\n - ClientApp=https://localhost:3000\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=gateway-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=gateway-service,service.version=1.0.0\n ports:\n - 6001:80\n depends_on:\n - otelcollector\n restart: unless-stopped\n identity-api-service:\n image: rosie91/stemify:identity-api-service\n build:\n context: .\n dockerfile: src/Services/Identity/Identity.Api/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5002\n - ConnectionStrings__stemifyidentity=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_identity\n # RabbitMQ Configuration (for MassTransit)\n - RabbitMq__Url=${RABBITMQ_URL}\n # OpenIddict Configuration\n - OpenIddict__Authority=http://identity-service:80\n # Client App Configuration\n - ClientApp=https://localhost:3000\n # Email Settings\n - EmailSettings__From=${IDENTITY_EMAIL_FROM}\n - EmailSettings__DisplayName=${IDENTITY_EMAIL_DISPLAY_NAME}\n - EmailSettings__SMTPServer=${IDENTITY_EMAIL_SMTP_SERVER}\n - EmailSettings__Port=${IDENTITY_EMAIL_PORT}\n - EmailSettings__Username=${IDENTITY_EMAIL_USERNAME}\n - EmailSettings__Password=${IDENTITY_EMAIL_PASSWORD}\n - EmailSettings__UseSSL=${IDENTITY_EMAIL_USE_SSL}\n - EmailSettings__ApplicationName=${IDENTITY_EMAIL_APP_NAME}\n - EmailSettings__SupportEmail=${IDENTITY_EMAIL_SUPPORT}\n # Google Authentication\n - Authentication__Google__ClientId=${GOOGLE_CLIENT_ID}\n - Authentication__Google__ClientSecret=${GOOGLE_CLIENT_SECRET}\n - Authentication__Google__RedirectUri=${GOOGLE_REDIRECT_URI}\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=identity-api-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=identity-api-service,service.version=1.0.0\n - GrpcServices__OrderService__Address=http://order-service:5006\n # Google Authentication\n - Authentication__Google__ClientId=${GOOGLE_CLIENT_ID}\n - Authentication__Google__ClientSecret=${GOOGLE_CLIENT_SECRET}\n - Authentication__Google__RedirectUri=${GOOGLE_REDIRECT_URI}\n - BackendUrl=${IDENTITY_BACKEND_URL:-http://localhost:6001}\n ports:\n - 7002:80\n - 5002:5002\n depends_on:\n - postgres\n - otelcollector\n restart: unless-stopped\n identity-service:\n image: rosie91/stemify:identity-service\n build:\n context: .\n dockerfile: src/Services/Identity/Identity.Web/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - ASPNETCORE_URLS=http://+:80\n - ConnectionStrings__stemifyidentity=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_identity\n # OpenIddict configuration for Docker\n - OpenIddict__Issuer=http://localhost:5000\n - OpenIddict__Authority=http://localhost:5000\n - OpenIddict__SigningKey=${OPENIDDICT_SIGNING_KEY}\n - OpenIddict__EncryptionKey=${OPENIDDICT_ENCRYPTION_KEY}\n - OpenIddict__ApiClientSecret=${OPENIDDICT_API_CLIENT_SECRET}\n - OpenIddict__ForceRecreateClients=${OPENIDDICT_FORCE_RECREATE_CLIENTS:-true}\n - OpenIddict__DisableHttpsRequirement=${OPENIDDICT_DISABLE_HTTPS:-true}\n # CORS for Docker environment\n - Cors__Origins=${IDENTITY_CORS_ORIGINS:-https://localhost:3000}\n - ClientApp=${IDENTITY_CLIENT_APP:-https://localhost:3000}\n - BackendUrl=${IDENTITY_BACKEND_URL:-http://localhost:5000}\n # Google Authentication\n - Authentication__Google__ClientId=${GOOGLE_CLIENT_ID}\n - Authentication__Google__ClientSecret=${GOOGLE_CLIENT_SECRET}\n - Authentication__Google__RedirectUri=${GOOGLE_REDIRECT_URI}\n # Email Settings\n - EmailSettings__SMTPServer=${IDENTITY_EMAIL_SMTP_SERVER}\n - EmailSettings__Port=${IDENTITY_EMAIL_PORT}\n - EmailSettings__UseSSL=${IDENTITY_EMAIL_USE_SSL}\n - EmailSettings__Username=${IDENTITY_EMAIL_USERNAME}\n - EmailSettings__Password=${IDENTITY_EMAIL_PASSWORD}\n - EmailSettings__From=${IDENTITY_EMAIL_FROM}\n - EmailSettings__DisplayName=${IDENTITY_EMAIL_DISPLAY_NAME}\n - EmailSettings__ApplicationName=${IDENTITY_EMAIL_APP_NAME}\n - EmailSettings__SupportEmail=${IDENTITY_EMAIL_SUPPORT}\n - EmailSettings__Enabled=${IDENTITY_EMAIL_ENABLED}\n - EmailSettings__EnableVerification=${IDENTITY_EMAIL_ENABLE_VERIFICATION}\n - EmailSettings__TimeoutMs=${IDENTITY_EMAIL_TIMEOUT_MS}\n - EmailSettings__MaxRetryAttempts=${IDENTITY_EMAIL_MAX_RETRY}\n - EmailSettings__LogEmailContent=${IDENTITY_EMAIL_LOG_CONTENT}\n # gRPC Services\n - GrpcServices__OrderService__Address=http://order-service:5006\n # Database Options\n - DatabaseOptions__MaxRetryCount=5\n - DatabaseOptions__MaxRetryDelay=00:00:10\n - DatabaseOptions__CommandTimeout=30\n - DatabaseOptions__EnableRetryOnFailure=true\n - DatabaseOptions__EnableSensitiveDataLogging=${DB_ENABLE_SENSITIVE_LOGGING}\n - DatabaseOptions__EnableDetailedErrors=${DB_ENABLE_DETAILED_ERRORS}\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=identity-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=identity-service,service.version=1.0.0\n - RabbitMq__Url=${RABBITMQ_URL}\n ports:\n - 5000:80\n depends_on:\n - postgres\n - otelcollector\n restart: unless-stopped\n cart-service:\n image: rosie91/stemify:cart-service\n build:\n context: .\n dockerfile: src/Services/CartService/Cart.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5007\n - ConnectionStrings__stemifycart=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_cart\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcProductUrl=http://product-service:5005\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=cart-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=cart-service,service.version=1.0.0\n ports:\n - 7007:80\n - 5007:5007\n depends_on:\n - postgres\n - redis\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n payment-service:\n image: rosie91/stemify:payment-service\n build:\n context: .\n dockerfile: src/Services/PaymentService/Payment.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - Logging__LogLevel__Microsoft.EntityFrameworkCore.Database.Command=Warning\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5008\n - ConnectionStrings__stemifypayment=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_payment\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n # PayOS configuration\n - PaymentGateways__PayOS__ClientId=${PAYOS_CLIENT_ID}\n - PaymentGateways__PayOS__ApiKey=${PAYOS_API_KEY}\n - PaymentGateways__PayOS__ChecksumKey=${PAYOS_CHECKSUM_KEY}\n - PaymentGateways__PayOS__BaseUrl=https://api-merchant.payos.vn\n # Stripe configuration\n - PaymentGateways__Stripe__SecretKey=${STRIPE_SECRET_KEY}\n - PaymentGateways__Stripe__PublishableKey=${STRIPE_PUBLISH_KEY}\n - PaymentGateways__Stripe__WebhookSecret=${STRIPE_WEBHOOK_KEY}\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=payment-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=payment-service,service.version=1.0.0\n ports:\n - 7008:80\n - 5008:5008\n depends_on:\n - postgres\n - redis\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n emulator-service:\n image: rosie91/stemify:emulator-service\n build:\n context: .\n dockerfile: src/Services/EmulatorService/Emulator.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5009\n # MongoDB configuration\n - MongoDb__ConnectionString=mongodb://root:mongopw@mongo\n - MongoDb__DatabaseName=stemify_emulator\n # Redis configuration\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n # gRPC endpoints\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcResourceUrl=http://resource-service:5003\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=emulator-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=emulator-service,service.version=1.0.0\n # Cloudinary Configuration\n - Cloudinary__CloudName=${CLOUDINARY_CLOUD_NAME}\n - Cloudinary__ApiKey=${CLOUDINARY_API_KEY}\n - Cloudinary__ApiSecret=${CLOUDINARY_API_SECRET}\n ports:\n - 7009:80\n - 5009:5009\n depends_on:\n - redis\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n\n # ========================================\n # MONITORING STACK\n # ========================================\n\n # Prometheus - Metrics Collection\n prometheus:\n image: prom/prometheus:v3.5.0\n container_name: stemify-prometheus\n ports:\n - '9090:9090'\n volumes:\n - ./config/prometheus:/etc/prometheus:ro\n - prometheus_data:/prometheus\n command:\n - '--config.file=/etc/prometheus/prometheus.yml'\n - '--storage.tsdb.path=/prometheus'\n - '--storage.tsdb.retention.time=15d'\n - '--storage.tsdb.retention.size=10GB'\n - '--web.enable-lifecycle'\n - '--web.enable-otlp-receiver'\n restart: unless-stopped\n\n # AlertManager - Alert Management\n alertmanager:\n image: prom/alertmanager:v0.27.0\n container_name: stemify-alertmanager\n ports:\n - '9093:9093'\n volumes:\n - ./config/alertmanager:/etc/alertmanager:ro\n - alertmanager_data:/alertmanager\n command:\n - '--config.file=/etc/alertmanager/alertmanager.yml'\n - '--storage.path=/alertmanager'\n - '--web.external-url=http://localhost:9093'\n restart: unless-stopped\n\n # Grafana - Visualization\n grafana:\n image: grafana/grafana:latest\n container_name: stemify-grafana\n ports:\n - '3001:3001'\n volumes:\n - grafana_data:/var/lib/grafana\n - ./config/grafana/config:/etc/grafana:ro\n - ./config/grafana/dashboards:/var/lib/grafana/dashboards:ro\n environment:\n - GF_SECURITY_ADMIN_USER=${GRAFANA_ADMIN_USERNAME}\n - GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_ADMIN_PASSWORD:-admin123}\n - GF_USERS_ALLOW_SIGN_UP=false\n - GF_INSTALL_PLUGINS=grafana-clock-panel,grafana-simple-json-datasource\n - PROMETHEUS_ENDPOINT=http://prometheus:9090\n restart: unless-stopped\n depends_on:\n - prometheus\n\n # OpenTelemetry Collector\n otelcollector:\n image: otel/opentelemetry-collector-contrib:latest\n container_name: stemify-otelcollector\n ports:\n - '8888:8888' # Prometheus metrics\n - '8889:8889' # Prometheus exporter\n - '4317:4317' # OTLP gRPC receiver\n - '4318:4318' # OTLP HTTP receiver\n volumes:\n - ./config/otelcollector:/etc/otelcol-contrib:ro\n environment:\n - PROMETHEUS_ENDPOINT=http://prometheus:9090/api/v1/write\n command: [ '--config=/etc/otelcol-contrib/config.yaml' ]\n restart: unless-stopped\n depends_on:\n - prometheus\n\n # ========================================\n # AI SERVICE & VECTOR DATABASE\n # ========================================\n\n # Qdrant - Vector Database for RAG\n qdrant:\n image: qdrant/qdrant:latest\n container_name: stemify-qdrant\n ports:\n - '6333:6333' # HTTP API\n - '6334:6334' # gRPC\n volumes:\n - qdrant_data:/qdrant/storage\n environment:\n - QDRANT__SERVICE__GRPC_PORT=6334\n restart: unless-stopped\n\n neo4j:\n image: neo4j:5.20\n container_name: stemify-neo4j\n ports:\n - \"7474:7474\" # HTTP\n - \"7687:7687\" # Bolt\n environment:\n - NEO4J_AUTH=neo4j/password\n - NEO4J_PLUGINS=[\"apoc\"]\n - NEO4J_dbms_memory_heap_max__size=2G\n - NEO4J_dbms_memory_pagecache_size=1G\n volumes:\n - neo4j_data:/data\n - neo4j_logs:/logs\n restart: unless-stopped\n healthcheck:\n test: [ \"CMD\", \"cypher-shell\", \"-u\", \"neo4j\", \"-p\", \"password\", \"RETURN 1\" ]\n interval: 30s\n timeout: 10s\n retries: 3\n start_period: 40s\n ai-service:\n image: rosie/stemify:ai-service\n build:\n context: ./src/Services/AIService\n dockerfile: Dockerfile\n container_name: stemify-ai-service\n environment:\n - OPENAI_API_KEY=${OPENAI_API_KEY}\n - OPENAI_BASE_URL=${OPENAI_BASE_URL:-https://api.openai.com/v1}\n - DEEPSEEK_API_KEY=${DEEPSEEK_API_KEY}\n - RESOURCE_GRPC_CERT_PATH=${RESOURCE_GRPC_CERT_PATH}\n ports:\n - '7010:80' # HTTP API (FastAPI)\n - '5010:5010' # gRPC\n # depends_on:\n # - qdrant\n # - neo4j\n restart: unless-stopped\n healthcheck:\n test: [ \"CMD\", \"curl\", \"-f\", \"http://localhost:80/health\" ]\n interval: 30s\n timeout: 10s\n retries: 3\n start_period: 40s\n\nvolumes:\n redis-data:\n postgres_data:\n mongodata:\n prometheus_data:\n alertmanager_data:\n grafana_data:\n qdrant_data:\n neo4j_data:\n neo4j_logs:\n"}, "d6557d3b2f273e3d4657092c276d45e807491020": {"t": 1785748771.4783487, "n": 23692, "h": "7e7a0066a5108a09", "c": "services:\n postgres:\n image: postgres:17\n container_name: stemify-postgres\n environment:\n - POSTGRES_PASSWORD=${DB_PASSWORD}\n ports:\n - '5432:5432'\n volumes:\n - postgres_data:/var/lib/postgresql/data\n restart: unless-stopped\n # RabbitMQ is now using CloudAMQP online service\n # rabbitmq:\n # image: rabbitmq:3-management-alpine\n # ports:\n # - 5672:5672\n # - 15672:15672\n # environment:\n # - RABBITMQ_DEFAULT_USER=guest\n # - RABBITMQ_DEFAULT_PASS=guest\n # healthcheck:\n # test: [\"CMD\", \"rabbitmq-diagnostics\", \"ping\"]\n # interval: 30s\n # timeout: 10s\n # retries: 5\n # start_period: 30s\n # restart: unless-stopped\n mongo:\n image: mongo\n environment:\n - MONGO_INITDB_ROOT_USERNAME=root\n - MONGO_INITDB_ROOT_PASSWORD=mongopw\n ports:\n - 27017:27017\n volumes:\n - mongodata:/var/lib/mongodb/data\n redis:\n image: redis:latest\n ports:\n - 6379:6379\n command: [ 'redis-server', '--appendonly', 'yes' ]\n volumes:\n - redis-data:/data\n redis-commander:\n image: rediscommander/redis-commander:latest\n environment:\n - REDIS_HOSTS=local:redis:6379\n - HTTP_USER=root\n - HTTP_PASSWORD=secret\n ports:\n - 8082:8082\n depends_on:\n - redis\n classroom-service:\n image: rosie91/stemify:classroom-service\n build:\n context: .\n dockerfile: src/Services/ClassroomService/Classroom.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5001\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcResourceUrl=http://resource-service:5003\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcOrderUrl=http://order-service:5006\n - ConnectionStrings__stemifyclassroom=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_classroom\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=classroom-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=classroom-service,service.version=1.0.0\n # Cloudinary Configuration\n - Cloudinary__CloudName=${CLOUDINARY_CLOUD_NAME}\n - Cloudinary__ApiKey=${CLOUDINARY_API_KEY}\n - Cloudinary__ApiSecret=${CLOUDINARY_API_SECRET}\n ports:\n - 7001:80\n - 5001:5001\n depends_on:\n - postgres\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n resource-service:\n image: rosie91/stemify:resource-service\n build:\n context: .\n dockerfile: src/Services/Resource/Resource.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5003\n - ConnectionStrings__stemifyresource=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_resource\n - Cloudinary__CloudName=${CLOUDINARY_CLOUD_NAME}\n - Cloudinary__ApiKey=${CLOUDINARY_API_KEY}\n - Cloudinary__ApiSecret=${CLOUDINARY_API_SECRET}\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n - ClientApp=https://localhost:3000\n - Gemini__BaseUrl=https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent\n - Gemini__ApiKey=${GEMINI_API_KEY}\n - Gemini__Model=gemini-2.5-flash\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=resource-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=resource-service,service.version=1.0.0\n ports:\n - 7003:80\n - 5003:5003\n depends_on:\n - postgres\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n product-service:\n image: rosie91/stemify:product-service\n build:\n context: .\n dockerfile: src/Services/ProductService/Product.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5005\n - ConnectionStrings__stemifyproduct=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_product\n - Cloudinary__CloudName=${CLOUDINARY_CLOUD_NAME}\n - Cloudinary__ApiKey=${CLOUDINARY_API_KEY}\n - Cloudinary__ApiSecret=${CLOUDINARY_API_SECRET}\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcResourceUrl=http://resource-service:5003\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=product-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=product-service,service.version=1.0.0\n ports:\n - 7005:80\n - 5005:5005\n depends_on:\n - postgres\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n order-service:\n image: rosie91/stemify:order-service\n build:\n context: .\n dockerfile: src/Services/OrderService/Order.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5006\n - ConnectionStrings__stemifyorder=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_order\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcResourceUrl=http://resource-service:5003\n - GrpcProductUrl=http://product-service:5005\n - GrpcClassroomUrl=http://classroom-service:5001\n - Cloudinary__CloudName=${CLOUDINARY_CLOUD_NAME}\n - Cloudinary__ApiKey=${CLOUDINARY_API_KEY}\n - Cloudinary__ApiSecret=${CLOUDINARY_API_SECRET}\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=order-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=order-service,service.version=1.0.0\n\n ports:\n - 7006:80\n - 5006:5006\n depends_on:\n - postgres\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n notification-service:\n image: rosie91/stemify:notification-service\n build:\n context: .\n dockerfile: src/Services/Notification/Notification.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5004\n - ConnectionStrings__stemifynotification=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_notification\n # SendGrid configuration\n - SendGridSettings__ApiKey=${SENDGRID_API_KEY}\n - SendGridSettings__FromEmail=stemify30062025@gmail.com\n - SendGridSettings__FromName=STEMify\n # Client App configuration\n - ClientApp__BaseUrl=https://localhost:3000\n - ClientApp=https://localhost:3000\n - Issuer=http://localhost:5000/\n - IdentityServiceUrl=http://identity-service\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=notification-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=notification-service,service.version=1.0.0\n ports:\n - 7004:80\n - 5004:5004\n depends_on:\n - postgres\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n gateway-service:\n image: rosie91/stemify:gateway-service\n build:\n context: .\n dockerfile: src/ApiGateways/ApiGateway/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Docker\n - ASPNETCORE_URLS=http://+:80\n - IdentityServiceUrl=http://identity-service\n - Issuer=http://localhost:5000/\n - ClientApp=https://localhost:3000\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=gateway-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=gateway-service,service.version=1.0.0\n ports:\n - 6001:80\n depends_on:\n - otelcollector\n restart: unless-stopped\n identity-api-service:\n image: rosie91/stemify:identity-api-service\n build:\n context: .\n dockerfile: src/Services/Identity/Identity.Api/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5002\n - ConnectionStrings__stemifyidentity=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_identity\n # RabbitMQ Configuration (for MassTransit)\n - RabbitMq__Url=${RABBITMQ_URL}\n # OpenIddict Configuration\n - OpenIddict__Authority=http://identity-service:80\n # Client App Configuration\n - ClientApp=https://localhost:3000\n # Email Settings\n - EmailSettings__From=${IDENTITY_EMAIL_FROM}\n - EmailSettings__DisplayName=${IDENTITY_EMAIL_DISPLAY_NAME}\n - EmailSettings__SMTPServer=${IDENTITY_EMAIL_SMTP_SERVER}\n - EmailSettings__Port=${IDENTITY_EMAIL_PORT}\n - EmailSettings__Username=${IDENTITY_EMAIL_USERNAME}\n - EmailSettings__Password=${IDENTITY_EMAIL_PASSWORD}\n - EmailSettings__UseSSL=${IDENTITY_EMAIL_USE_SSL}\n - EmailSettings__ApplicationName=${IDENTITY_EMAIL_APP_NAME}\n - EmailSettings__SupportEmail=${IDENTITY_EMAIL_SUPPORT}\n # Google Authentication\n - Authentication__Google__ClientId=${GOOGLE_CLIENT_ID}\n - Authentication__Google__ClientSecret=${GOOGLE_CLIENT_SECRET}\n - Authentication__Google__RedirectUri=${GOOGLE_REDIRECT_URI}\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=identity-api-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=identity-api-service,service.version=1.0.0\n - GrpcServices__OrderService__Address=http://order-service:5006\n # Google Authentication\n - Authentication__Google__ClientId=${GOOGLE_CLIENT_ID}\n - Authentication__Google__ClientSecret=${GOOGLE_CLIENT_SECRET}\n - Authentication__Google__RedirectUri=${GOOGLE_REDIRECT_URI}\n - BackendUrl=${IDENTITY_BACKEND_URL:-http://localhost:6001}\n ports:\n - 7002:80\n - 5002:5002\n depends_on:\n - postgres\n - otelcollector\n restart: unless-stopped\n identity-service:\n image: rosie91/stemify:identity-service\n build:\n context: .\n dockerfile: src/Services/Identity/Identity.Web/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - ASPNETCORE_URLS=http://+:80\n - ConnectionStrings__stemifyidentity=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_identity\n # OpenIddict configuration for Docker\n - OpenIddict__Issuer=http://localhost:5000\n - OpenIddict__Authority=http://localhost:5000\n - OpenIddict__SigningKey=${OPENIDDICT_SIGNING_KEY}\n - OpenIddict__EncryptionKey=${OPENIDDICT_ENCRYPTION_KEY}\n - OpenIddict__ApiClientSecret=${OPENIDDICT_API_CLIENT_SECRET}\n - OpenIddict__ForceRecreateClients=${OPENIDDICT_FORCE_RECREATE_CLIENTS:-true}\n - OpenIddict__DisableHttpsRequirement=${OPENIDDICT_DISABLE_HTTPS:-true}\n # CORS for Docker environment\n - Cors__Origins=${IDENTITY_CORS_ORIGINS:-https://localhost:3000}\n - ClientApp=${IDENTITY_CLIENT_APP:-https://localhost:3000}\n - BackendUrl=${IDENTITY_BACKEND_URL:-http://localhost:5000}\n # Google Authentication\n - Authentication__Google__ClientId=${GOOGLE_CLIENT_ID}\n - Authentication__Google__ClientSecret=${GOOGLE_CLIENT_SECRET}\n - Authentication__Google__RedirectUri=${GOOGLE_REDIRECT_URI}\n # Email Settings\n - EmailSettings__SMTPServer=${IDENTITY_EMAIL_SMTP_SERVER}\n - EmailSettings__Port=${IDENTITY_EMAIL_PORT}\n - EmailSettings__UseSSL=${IDENTITY_EMAIL_USE_SSL}\n - EmailSettings__Username=${IDENTITY_EMAIL_USERNAME}\n - EmailSettings__Password=${IDENTITY_EMAIL_PASSWORD}\n - EmailSettings__From=${IDENTITY_EMAIL_FROM}\n - EmailSettings__DisplayName=${IDENTITY_EMAIL_DISPLAY_NAME}\n - EmailSettings__ApplicationName=${IDENTITY_EMAIL_APP_NAME}\n - EmailSettings__SupportEmail=${IDENTITY_EMAIL_SUPPORT}\n - EmailSettings__Enabled=${IDENTITY_EMAIL_ENABLED}\n - EmailSettings__EnableVerification=${IDENTITY_EMAIL_ENABLE_VERIFICATION}\n - EmailSettings__TimeoutMs=${IDENTITY_EMAIL_TIMEOUT_MS}\n - EmailSettings__MaxRetryAttempts=${IDENTITY_EMAIL_MAX_RETRY}\n - EmailSettings__LogEmailContent=${IDENTITY_EMAIL_LOG_CONTENT}\n # gRPC Services\n - GrpcServices__OrderService__Address=http://order-service:5006\n # Database Options\n - DatabaseOptions__MaxRetryCount=5\n - DatabaseOptions__MaxRetryDelay=00:00:10\n - DatabaseOptions__CommandTimeout=30\n - DatabaseOptions__EnableRetryOnFailure=true\n - DatabaseOptions__EnableSensitiveDataLogging=${DB_ENABLE_SENSITIVE_LOGGING}\n - DatabaseOptions__EnableDetailedErrors=${DB_ENABLE_DETAILED_ERRORS}\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=identity-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=identity-service,service.version=1.0.0\n - RabbitMq__Url=${RABBITMQ_URL}\n ports:\n - 5000:80\n depends_on:\n - postgres\n - otelcollector\n restart: unless-stopped\n cart-service:\n image: rosie91/stemify:cart-service\n build:\n context: .\n dockerfile: src/Services/CartService/Cart.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5007\n - ConnectionStrings__stemifycart=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_cart\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcProductUrl=http://product-service:5005\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=cart-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=cart-service,service.version=1.0.0\n ports:\n - 7007:80\n - 5007:5007\n depends_on:\n - postgres\n - redis\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n payment-service:\n image: rosie91/stemify:payment-service\n build:\n context: .\n dockerfile: src/Services/PaymentService/Payment.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - Logging__LogLevel__Microsoft.EntityFrameworkCore.Database.Command=Warning\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5008\n - ConnectionStrings__stemifypayment=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_payment\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n # PayOS configuration\n - PaymentGateways__PayOS__ClientId=${PAYOS_CLIENT_ID}\n - PaymentGateways__PayOS__ApiKey=${PAYOS_API_KEY}\n - PaymentGateways__PayOS__ChecksumKey=${PAYOS_CHECKSUM_KEY}\n - PaymentGateways__PayOS__BaseUrl=https://api-merchant.payos.vn\n # Stripe configuration\n - PaymentGateways__Stripe__SecretKey=${STRIPE_SECRET_KEY}\n - PaymentGateways__Stripe__PublishableKey=${STRIPE_PUBLISH_KEY}\n - PaymentGateways__Stripe__WebhookSecret=${STRIPE_WEBHOOK_KEY}\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=payment-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=payment-service,service.version=1.0.0\n ports:\n - 7008:80\n - 5008:5008\n depends_on:\n - postgres\n - redis\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n emulator-service:\n image: rosie91/stemify:emulator-service\n build:\n context: .\n dockerfile: src/Services/EmulatorService/Emulator.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5009\n # MongoDB configuration\n - MongoDb__ConnectionString=mongodb://root:mongopw@mongo\n - MongoDb__DatabaseName=stemify_emulator\n # Redis configuration\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n # gRPC endpoints\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcResourceUrl=http://resource-service:5003\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=emulator-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=emulator-service,service.version=1.0.0\n # Cloudinary Configuration\n - Cloudinary__CloudName=${CLOUDINARY_CLOUD_NAME}\n - Cloudinary__ApiKey=${CLOUDINARY_API_KEY}\n - Cloudinary__ApiSecret=${CLOUDINARY_API_SECRET}\n ports:\n - 7009:80\n - 5009:5009\n depends_on:\n - redis\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n\n # ========================================\n # MONITORING STACK\n # ========================================\n\n # Prometheus - Metrics Collection\n prometheus:\n image: prom/prometheus:v3.5.0\n container_name: stemify-prometheus\n ports:\n - '9090:9090'\n volumes:\n - ./config/prometheus:/etc/prometheus:ro\n - prometheus_data:/prometheus\n command:\n - '--config.file=/etc/prometheus/prometheus.yml'\n - '--storage.tsdb.path=/prometheus'\n - '--storage.tsdb.retention.time=15d'\n - '--storage.tsdb.retention.size=10GB'\n - '--web.enable-lifecycle'\n - '--web.enable-otlp-receiver'\n restart: unless-stopped\n\n # AlertManager - Alert Management\n alertmanager:\n image: prom/alertmanager:v0.27.0\n container_name: stemify-alertmanager\n ports:\n - '9093:9093'\n volumes:\n - ./config/alertmanager:/etc/alertmanager:ro\n - alertmanager_data:/alertmanager\n command:\n - '--config.file=/etc/alertmanager/alertmanager.yml'\n - '--storage.path=/alertmanager'\n - '--web.external-url=http://localhost:9093'\n restart: unless-stopped\n\n # Grafana - Visualization\n grafana:\n image: grafana/grafana:latest\n container_name: stemify-grafana\n ports:\n - '3001:3001'\n volumes:\n - grafana_data:/var/lib/grafana\n - ./config/grafana/config:/etc/grafana:ro\n - ./config/grafana/dashboards:/var/lib/grafana/dashboards:ro\n environment:\n - GF_SECURITY_ADMIN_USER=${GRAFANA_ADMIN_USERNAME}\n - GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_ADMIN_PASSWORD:-admin123}\n - GF_USERS_ALLOW_SIGN_UP=false\n - GF_INSTALL_PLUGINS=grafana-clock-panel,grafana-simple-json-datasource\n - PROMETHEUS_ENDPOINT=http://prometheus:9090\n restart: unless-stopped\n depends_on:\n - prometheus\n\n # OpenTelemetry Collector\n otelcollector:\n image: otel/opentelemetry-collector-contrib:latest\n container_name: stemify-otelcollector\n ports:\n - '8888:8888' # Prometheus metrics\n - '8889:8889' # Prometheus exporter\n - '4317:4317' # OTLP gRPC receiver\n - '4318:4318' # OTLP HTTP receiver\n volumes:\n - ./config/otelcollector:/etc/otelcol-contrib:ro\n environment:\n - PROMETHEUS_ENDPOINT=http://prometheus:9090/api/v1/write\n command: [ '--config=/etc/otelcol-contrib/config.yaml' ]\n restart: unless-stopped\n depends_on:\n - prometheus\n\n # ========================================\n # AI SERVICE & VECTOR DATABASE\n # ========================================\n\n # Qdrant - Vector Database for RAG\n qdrant:\n image: qdrant/qdrant:latest\n container_name: stemify-qdrant\n ports:\n - '6333:6333' # HTTP API\n - '6334:6334' # gRPC\n volumes:\n - qdrant_data:/qdrant/storage\n environment:\n - QDRANT__SERVICE__GRPC_PORT=6334\n restart: unless-stopped\n\n neo4j:\n image: neo4j:5.20\n container_name: stemify-neo4j\n ports:\n - \"7474:7474\" # HTTP\n - \"7687:7687\" # Bolt\n environment:\n - NEO4J_AUTH=neo4j/password\n - NEO4J_PLUGINS=[\"apoc\"]\n - NEO4J_dbms_memory_heap_max__size=2G\n - NEO4J_dbms_memory_pagecache_size=1G\n volumes:\n - neo4j_data:/data\n - neo4j_logs:/logs\n restart: unless-stopped\n healthcheck:\n test: [ \"CMD\", \"cypher-shell\", \"-u\", \"neo4j\", \"-p\", \"password\", \"RETURN 1\" ]\n interval: 30s\n timeout: 10s\n retries: 3\n start_period: 40s\n ai-service:\n build:\n context: ./src/Services/AIService\n dockerfile: Dockerfile\n container_name: stemify-ai-service\n environment:\n - OPENAI_API_KEY=${OPENAI_API_KEY}\n - OPENAI_BASE_URL=${OPENAI_BASE_URL:-https://api.openai.com/v1}\n - DEEPSEEK_API_KEY=${DEEPSEEK_API_KEY}\n - RESOURCE_GRPC_CERT_PATH=${RESOURCE_GRPC_CERT_PATH}\n ports:\n - '7010:80' # HTTP API (FastAPI)\n - '5010:5010' # gRPC\n # depends_on:\n # - qdrant\n # - neo4j\n restart: unless-stopped\n healthcheck:\n test: [\"CMD\", \"curl\", \"-f\", \"http://localhost:80/health\"]\n interval: 30s\n timeout: 10s\n retries: 3\n start_period: 40s\n\nvolumes:\n redis-data:\n postgres_data:\n mongodata:\n prometheus_data:\n alertmanager_data:\n grafana_data:\n qdrant_data:\n neo4j_data:\n neo4j_logs:\n"}, "69c583887e7a56a61c2edf5a864b75c34a95f9bc": {"t": 1785748772.742413, "n": 24163, "h": "57350085ee79c0cb", "c": "services:\n postgres:\n image: postgres:17\n container_name: stemify-postgres\n environment:\n - POSTGRES_PASSWORD=${DB_PASSWORD}\n ports:\n - '5432:5432'\n volumes:\n - postgres_data:/var/lib/postgresql/data\n restart: unless-stopped\n # RabbitMQ is now using CloudAMQP online service\n # rabbitmq:\n # image: rabbitmq:3-management-alpine\n # ports:\n # - 5672:5672\n # - 15672:15672\n # environment:\n # - RABBITMQ_DEFAULT_USER=guest\n # - RABBITMQ_DEFAULT_PASS=guest\n # healthcheck:\n # test: [\"CMD\", \"rabbitmq-diagnostics\", \"ping\"]\n # interval: 30s\n # timeout: 10s\n # retries: 5\n # start_period: 30s\n # restart: unless-stopped\n mongo:\n image: mongo\n environment:\n - MONGO_INITDB_ROOT_USERNAME=root\n - MONGO_INITDB_ROOT_PASSWORD=mongopw\n ports:\n - 27017:27017\n volumes:\n - mongodata:/var/lib/mongodb/data\n redis:\n image: redis:latest\n ports:\n - 6379:6379\n command: [ 'redis-server', '--appendonly', 'yes' ]\n volumes:\n - redis-data:/data\n redis-commander:\n image: rediscommander/redis-commander:latest\n environment:\n - REDIS_HOSTS=local:redis:6379\n - HTTP_USER=root\n - HTTP_PASSWORD=secret\n ports:\n - 8082:8082\n depends_on:\n - redis\n classroom-service:\n image: rosie91/stemify:classroom-service\n build:\n context: .\n dockerfile: src/Services/ClassroomService/Classroom.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5001\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcResourceUrl=http://resource-service:5003\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcOrderUrl=http://order-service:5006\n - ConnectionStrings__stemifyclassroom=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_classroom\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=classroom-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=classroom-service,service.version=1.0.0\n # Cloudinary Configuration\n - Cloudinary__CloudName=${CLOUDINARY_CLOUD_NAME}\n - Cloudinary__ApiKey=${CLOUDINARY_API_KEY}\n - Cloudinary__ApiSecret=${CLOUDINARY_API_SECRET}\n ports:\n - 7001:80\n - 5001:5001\n depends_on:\n - postgres\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n resource-service:\n image: rosie91/stemify:resource-service\n build:\n context: .\n dockerfile: src/Services/Resource/Resource.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5003\n - ConnectionStrings__stemifyresource=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_resource\n - Cloudinary__CloudName=${CLOUDINARY_CLOUD_NAME}\n - Cloudinary__ApiKey=${CLOUDINARY_API_KEY}\n - Cloudinary__ApiSecret=${CLOUDINARY_API_SECRET}\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n - ClientApp=https://localhost:3000\n - Gemini__BaseUrl=https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent\n - Gemini__ApiKey=${GEMINI_API_KEY}\n - Gemini__Model=gemini-2.5-flash\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=resource-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=resource-service,service.version=1.0.0\n ports:\n - 7003:80\n - 5003:5003\n depends_on:\n - postgres\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n product-service:\n image: rosie91/stemify:product-service\n build:\n context: .\n dockerfile: src/Services/ProductService/Product.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5005\n - ConnectionStrings__stemifyproduct=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_product\n - Cloudinary__CloudName=${CLOUDINARY_CLOUD_NAME}\n - Cloudinary__ApiKey=${CLOUDINARY_API_KEY}\n - Cloudinary__ApiSecret=${CLOUDINARY_API_SECRET}\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcResourceUrl=http://resource-service:5003\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=product-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=product-service,service.version=1.0.0\n ports:\n - 7005:80\n - 5005:5005\n depends_on:\n - postgres\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n order-service:\n image: rosie91/stemify:order-service\n build:\n context: .\n dockerfile: src/Services/OrderService/Order.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5006\n - ConnectionStrings__stemifyorder=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_order\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcResourceUrl=http://resource-service:5003\n - GrpcProductUrl=http://product-service:5005\n - GrpcClassroomUrl=http://classroom-service:5001\n - Cloudinary__CloudName=${CLOUDINARY_CLOUD_NAME}\n - Cloudinary__ApiKey=${CLOUDINARY_API_KEY}\n - Cloudinary__ApiSecret=${CLOUDINARY_API_SECRET}\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=order-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=order-service,service.version=1.0.0\n\n ports:\n - 7006:80\n - 5006:5006\n depends_on:\n - postgres\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n notification-service:\n image: rosie91/stemify:notification-service\n build:\n context: .\n dockerfile: src/Services/Notification/Notification.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5004\n - ConnectionStrings__stemifynotification=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_notification\n # SendGrid configuration\n - SendGridSettings__ApiKey=${SENDGRID_API_KEY}\n - SendGridSettings__FromEmail=stemify30062025@gmail.com\n - SendGridSettings__FromName=STEMify\n # Client App configuration\n - ClientApp__BaseUrl=https://localhost:3000\n - ClientApp=https://localhost:3000\n - Issuer=http://localhost:5000/\n - IdentityServiceUrl=http://identity-service\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=notification-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=notification-service,service.version=1.0.0\n ports:\n - 7004:80\n - 5004:5004\n depends_on:\n - postgres\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n gateway-service:\n image: rosie91/stemify:gateway-service\n build:\n context: .\n dockerfile: src/ApiGateways/ApiGateway/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Docker\n - ASPNETCORE_URLS=http://+:80\n - IdentityServiceUrl=http://identity-service\n - Issuer=http://localhost:5000/\n - ClientApp=https://localhost:3000\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=gateway-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=gateway-service,service.version=1.0.0\n ports:\n - 6001:80\n depends_on:\n - otelcollector\n restart: unless-stopped\n identity-api-service:\n image: rosie91/stemify:identity-api-service\n build:\n context: .\n dockerfile: src/Services/Identity/Identity.Api/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5002\n - ConnectionStrings__stemifyidentity=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_identity\n # RabbitMQ Configuration (for MassTransit)\n - RabbitMq__Url=${RABBITMQ_URL}\n # OpenIddict Configuration\n - OpenIddict__Authority=http://identity-service:80\n # Client App Configuration\n - ClientApp=https://localhost:3000\n # Email Settings\n - EmailSettings__From=${IDENTITY_EMAIL_FROM}\n - EmailSettings__DisplayName=${IDENTITY_EMAIL_DISPLAY_NAME}\n - EmailSettings__SMTPServer=${IDENTITY_EMAIL_SMTP_SERVER}\n - EmailSettings__Port=${IDENTITY_EMAIL_PORT}\n - EmailSettings__Username=${IDENTITY_EMAIL_USERNAME}\n - EmailSettings__Password=${IDENTITY_EMAIL_PASSWORD}\n - EmailSettings__UseSSL=${IDENTITY_EMAIL_USE_SSL}\n - EmailSettings__ApplicationName=${IDENTITY_EMAIL_APP_NAME}\n - EmailSettings__SupportEmail=${IDENTITY_EMAIL_SUPPORT}\n # Google Authentication\n - Authentication__Google__ClientId=${GOOGLE_CLIENT_ID}\n - Authentication__Google__ClientSecret=${GOOGLE_CLIENT_SECRET}\n - Authentication__Google__RedirectUri=${GOOGLE_REDIRECT_URI}\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=identity-api-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=identity-api-service,service.version=1.0.0\n - GrpcServices__OrderService__Address=http://order-service:5006\n # Google Authentication\n - Authentication__Google__ClientId=${GOOGLE_CLIENT_ID}\n - Authentication__Google__ClientSecret=${GOOGLE_CLIENT_SECRET}\n - Authentication__Google__RedirectUri=${GOOGLE_REDIRECT_URI}\n - BackendUrl=${IDENTITY_BACKEND_URL:-http://localhost:6001}\n ports:\n - 7002:80\n - 5002:5002\n depends_on:\n - postgres\n - otelcollector\n restart: unless-stopped\n identity-service:\n image: rosie91/stemify:identity-service\n build:\n context: .\n dockerfile: src/Services/Identity/Identity.Web/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - ASPNETCORE_URLS=http://+:80\n - ConnectionStrings__stemifyidentity=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_identity\n # OpenIddict configuration for Docker\n - OpenIddict__Issuer=http://localhost:5000\n - OpenIddict__Authority=http://localhost:5000\n - OpenIddict__SigningKey=${OPENIDDICT_SIGNING_KEY}\n - OpenIddict__EncryptionKey=${OPENIDDICT_ENCRYPTION_KEY}\n - OpenIddict__ApiClientSecret=${OPENIDDICT_API_CLIENT_SECRET}\n - OpenIddict__ForceRecreateClients=${OPENIDDICT_FORCE_RECREATE_CLIENTS:-true}\n - OpenIddict__DisableHttpsRequirement=${OPENIDDICT_DISABLE_HTTPS:-true}\n # CORS for Docker environment\n - Cors__Origins=${IDENTITY_CORS_ORIGINS:-https://localhost:3000}\n - ClientApp=${IDENTITY_CLIENT_APP:-https://localhost:3000}\n - BackendUrl=${IDENTITY_BACKEND_URL:-http://localhost:5000}\n # Google Authentication\n - Authentication__Google__ClientId=${GOOGLE_CLIENT_ID}\n - Authentication__Google__ClientSecret=${GOOGLE_CLIENT_SECRET}\n - Authentication__Google__RedirectUri=${GOOGLE_REDIRECT_URI}\n # Email Settings\n - EmailSettings__SMTPServer=${IDENTITY_EMAIL_SMTP_SERVER}\n - EmailSettings__Port=${IDENTITY_EMAIL_PORT}\n - EmailSettings__UseSSL=${IDENTITY_EMAIL_USE_SSL}\n - EmailSettings__Username=${IDENTITY_EMAIL_USERNAME}\n - EmailSettings__Password=${IDENTITY_EMAIL_PASSWORD}\n - EmailSettings__From=${IDENTITY_EMAIL_FROM}\n - EmailSettings__DisplayName=${IDENTITY_EMAIL_DISPLAY_NAME}\n - EmailSettings__ApplicationName=${IDENTITY_EMAIL_APP_NAME}\n - EmailSettings__SupportEmail=${IDENTITY_EMAIL_SUPPORT}\n - EmailSettings__Enabled=${IDENTITY_EMAIL_ENABLED}\n - EmailSettings__EnableVerification=${IDENTITY_EMAIL_ENABLE_VERIFICATION}\n - EmailSettings__TimeoutMs=${IDENTITY_EMAIL_TIMEOUT_MS}\n - EmailSettings__MaxRetryAttempts=${IDENTITY_EMAIL_MAX_RETRY}\n - EmailSettings__LogEmailContent=${IDENTITY_EMAIL_LOG_CONTENT}\n # gRPC Services\n - GrpcServices__OrderService__Address=http://order-service:5006\n # Database Options\n - DatabaseOptions__MaxRetryCount=5\n - DatabaseOptions__MaxRetryDelay=00:00:10\n - DatabaseOptions__CommandTimeout=30\n - DatabaseOptions__EnableRetryOnFailure=true\n - DatabaseOptions__EnableSensitiveDataLogging=${DB_ENABLE_SENSITIVE_LOGGING}\n - DatabaseOptions__EnableDetailedErrors=${DB_ENABLE_DETAILED_ERRORS}\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=identity-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=identity-service,service.version=1.0.0\n - RabbitMq__Url=${RABBITMQ_URL}\n ports:\n - 5000:80\n depends_on:\n - postgres\n - otelcollector\n restart: unless-stopped\n cart-service:\n image: rosie91/stemify:cart-service\n build:\n context: .\n dockerfile: src/Services/CartService/Cart.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5007\n - ConnectionStrings__stemifycart=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_cart\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcProductUrl=http://product-service:5005\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=cart-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=cart-service,service.version=1.0.0\n ports:\n - 7007:80\n - 5007:5007\n depends_on:\n - postgres\n - redis\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n payment-service:\n image: rosie91/stemify:payment-service\n build:\n context: .\n dockerfile: src/Services/PaymentService/Payment.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - Logging__LogLevel__Microsoft.EntityFrameworkCore.Database.Command=Warning\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5008\n - ConnectionStrings__stemifypayment=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_payment\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n # PayOS configuration\n - PaymentGateways__PayOS__ClientId=${PAYOS_CLIENT_ID}\n - PaymentGateways__PayOS__ApiKey=${PAYOS_API_KEY}\n - PaymentGateways__PayOS__ChecksumKey=${PAYOS_CHECKSUM_KEY}\n - PaymentGateways__PayOS__BaseUrl=https://api-merchant.payos.vn\n # Stripe configuration\n - PaymentGateways__Stripe__SecretKey=${STRIPE_SECRET_KEY}\n - PaymentGateways__Stripe__PublishableKey=${STRIPE_PUBLISH_KEY}\n - PaymentGateways__Stripe__WebhookSecret=${STRIPE_WEBHOOK_KEY}\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=payment-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=payment-service,service.version=1.0.0\n ports:\n - 7008:80\n - 5008:5008\n depends_on:\n - postgres\n - redis\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n emulator-service:\n image: rosie91/stemify:emulator-service\n build:\n context: .\n dockerfile: src/Services/EmulatorService/Emulator.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5009\n # MongoDB configuration\n - MongoDb__ConnectionString=mongodb://root:mongopw@mongo\n - MongoDb__DatabaseName=stemify_emulator\n # Redis configuration\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n # gRPC endpoints\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcResourceUrl=http://resource-service:5003\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=emulator-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=emulator-service,service.version=1.0.0\n # Cloudinary Configuration\n - Cloudinary__CloudName=${CLOUDINARY_CLOUD_NAME}\n - Cloudinary__ApiKey=${CLOUDINARY_API_KEY}\n - Cloudinary__ApiSecret=${CLOUDINARY_API_SECRET}\n ports:\n - 7009:80\n - 5009:5009\n depends_on:\n - redis\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n\n # ========================================\n # MONITORING STACK\n # ========================================\n\n # Prometheus - Metrics Collection\n prometheus:\n image: prom/prometheus:v3.5.0\n container_name: stemify-prometheus\n ports:\n - '9090:9090'\n volumes:\n - ./config/prometheus:/etc/prometheus:ro\n - prometheus_data:/prometheus\n command:\n - '--config.file=/etc/prometheus/prometheus.yml'\n - '--storage.tsdb.path=/prometheus'\n - '--storage.tsdb.retention.time=15d'\n - '--storage.tsdb.retention.size=10GB'\n - '--web.enable-lifecycle'\n - '--web.enable-otlp-receiver'\n restart: unless-stopped\n\n # AlertManager - Alert Management\n alertmanager:\n image: prom/alertmanager:v0.27.0\n container_name: stemify-alertmanager\n ports:\n - '9093:9093'\n volumes:\n - ./config/alertmanager:/etc/alertmanager:ro\n - alertmanager_data:/alertmanager\n command:\n - '--config.file=/etc/alertmanager/alertmanager.yml'\n - '--storage.path=/alertmanager'\n - '--web.external-url=http://localhost:9093'\n restart: unless-stopped\n\n # Grafana - Visualization\n grafana:\n image: grafana/grafana:latest\n container_name: stemify-grafana\n ports:\n - '3001:3001'\n volumes:\n - grafana_data:/var/lib/grafana\n - ./config/grafana/config:/etc/grafana:ro\n - ./config/grafana/dashboards:/var/lib/grafana/dashboards:ro\n environment:\n - GF_SECURITY_ADMIN_USER=${GRAFANA_ADMIN_USERNAME}\n - GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_ADMIN_PASSWORD:-admin123}\n - GF_USERS_ALLOW_SIGN_UP=false\n - GF_INSTALL_PLUGINS=grafana-clock-panel,grafana-simple-json-datasource\n - PROMETHEUS_ENDPOINT=http://prometheus:9090\n restart: unless-stopped\n depends_on:\n - prometheus\n\n # OpenTelemetry Collector\n otelcollector:\n image: otel/opentelemetry-collector-contrib:latest\n container_name: stemify-otelcollector\n ports:\n - '8888:8888' # Prometheus metrics\n - '8889:8889' # Prometheus exporter\n - '4317:4317' # OTLP gRPC receiver\n - '4318:4318' # OTLP HTTP receiver\n volumes:\n - ./config/otelcollector:/etc/otelcol-contrib:ro\n environment:\n - PROMETHEUS_ENDPOINT=http://prometheus:9090/api/v1/write\n command: [ '--config=/etc/otelcol-contrib/config.yaml' ]\n restart: unless-stopped\n depends_on:\n - prometheus\n\n # ========================================\n # AI SERVICE & VECTOR DATABASE\n # ========================================\n\n # Qdrant - Vector Database for RAG\n qdrant:\n image: qdrant/qdrant:latest\n container_name: stemify-qdrant\n ports:\n - '6333:6333' # HTTP API\n - '6334:6334' # gRPC\n volumes:\n - qdrant_data:/qdrant/storage\n environment:\n - QDRANT__SERVICE__GRPC_PORT=6334\n restart: unless-stopped\n\n neo4j:\n image: neo4j:5.20\n container_name: stemify-neo4j\n ports:\n - \"7474:7474\" # HTTP\n - \"7687:7687\" # Bolt\n environment:\n - NEO4J_AUTH=neo4j/password\n - NEO4J_PLUGINS=[\"apoc\"]\n - NEO4J_dbms_memory_heap_max__size=2G\n - NEO4J_dbms_memory_pagecache_size=1G\n volumes:\n - neo4j_data:/data\n - neo4j_logs:/logs\n restart: unless-stopped\n healthcheck:\n test: [ \"CMD\", \"cypher-shell\", \"-u\", \"neo4j\", \"-p\", \"password\", \"RETURN 1\" ]\n interval: 30s\n timeout: 10s\n retries: 3\n start_period: 40s\n # AI Service - Python FastAPI with gRPC\n # ai-service:\n # build:\n # context: .\n # dockerfile: src/Services/AIService/Dockerfile\n # container_name: stemify-ai-service\n # environment:\n # - PYTHONUNBUFFERED=1\n # - ENVIRONMENT=Development\n # - QDRANT_URL=http://qdrant:6333\n # - QDRANT_GRPC_URL=http://qdrant:6334\n # - QDRANT_COLLECTION=classroom_insights\n # - NEO4J_URI=bolt://neo4j:7687\n # - NEO4J_USER=neo4j\n # - NEO4J_PASSWORD=password\n # - OPENAI_API_KEY=${OPENAI_API_KEY}\n # - OPENAI_BASE_URL=${OPENAI_BASE_URL:-https://api.openai.com/v1}\n # - EMBEDDING_MODEL=paraphrase-multilingual-MiniLM-L12-v2\n # - LLM_MODEL=gpt-4o-mini\n # - LOG_LEVEL=INFO\n # # gRPC Configuration\n # - GRPC_PORT=5010\n # - GRPC_HOST=0.0.0.0\n # ports:\n # - '7010:80' # HTTP API (FastAPI)\n # - '5010:5010' # gRPC\n # depends_on:\n # - qdrant\n # - neo4j\n # restart: unless-stopped\n # healthcheck:\n # test: [\"CMD\", \"curl\", \"-f\", \"http://localhost:80/health\"]\n # interval: 30s\n # timeout: 10s\n # retries: 3\n # start_period: 40s\n\nvolumes:\n redis-data:\n postgres_data:\n mongodata:\n prometheus_data:\n alertmanager_data:\n grafana_data:\n qdrant_data:\n neo4j_data:\n neo4j_logs:\n"}, "a2b22b093848a6c5baea9e3956766a061dca15ef": {"t": 1785748775.0118227, "n": 23936, "h": "e41316b52bad619f", "c": "services:\n postgres:\n image: postgres:17\n container_name: stemify-postgres\n environment:\n - POSTGRES_PASSWORD=${DB_PASSWORD}\n ports:\n - '5432:5432'\n volumes:\n - postgres_data:/var/lib/postgresql/data\n restart: unless-stopped\n # RabbitMQ is now using CloudAMQP online service\n # rabbitmq:\n # image: rabbitmq:3-management-alpine\n # ports:\n # - 5672:5672\n # - 15672:15672\n # environment:\n # - RABBITMQ_DEFAULT_USER=guest\n # - RABBITMQ_DEFAULT_PASS=guest\n # healthcheck:\n # test: [\"CMD\", \"rabbitmq-diagnostics\", \"ping\"]\n # interval: 30s\n # timeout: 10s\n # retries: 5\n # start_period: 30s\n # restart: unless-stopped\n mongo:\n image: mongo\n environment:\n - MONGO_INITDB_ROOT_USERNAME=root\n - MONGO_INITDB_ROOT_PASSWORD=mongopw\n ports:\n - 27017:27017\n volumes:\n - mongodata:/var/lib/mongodb/data\n redis:\n image: redis:latest\n ports:\n - 6379:6379\n command: [ 'redis-server', '--appendonly', 'yes' ]\n volumes:\n - redis-data:/data\n redis-commander:\n image: rediscommander/redis-commander:latest\n environment:\n - REDIS_HOSTS=local:redis:6379\n - HTTP_USER=root\n - HTTP_PASSWORD=secret\n ports:\n - 8082:8082\n depends_on:\n - redis\n classroom-service:\n image: rosie91/stemify:classroom-service\n build:\n context: .\n dockerfile: src/Services/ClassroomService/Classroom.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5001\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcResourceUrl=http://resource-service:5003\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcOrderUrl=http://order-service:5006\n - ConnectionStrings__stemifyclassroom=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_classroom\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=classroom-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=classroom-service,service.version=1.0.0\n # Cloudinary Configuration\n - Cloudinary__CloudName=${CLOUDINARY_CLOUD_NAME}\n - Cloudinary__ApiKey=${CLOUDINARY_API_KEY}\n - Cloudinary__ApiSecret=${CLOUDINARY_API_SECRET}\n ports:\n - 7001:80\n - 5001:5001\n depends_on:\n - postgres\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n resource-service:\n image: rosie91/stemify:resource-service\n build:\n context: .\n dockerfile: src/Services/Resource/Resource.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5003\n - ConnectionStrings__stemifyresource=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_resource\n - Cloudinary__CloudName=${CLOUDINARY_CLOUD_NAME}\n - Cloudinary__ApiKey=${CLOUDINARY_API_KEY}\n - Cloudinary__ApiSecret=${CLOUDINARY_API_SECRET}\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n - ClientApp=https://localhost:3000\n - Gemini__BaseUrl=https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent\n - Gemini__ApiKey=${GEMINI_API_KEY}\n - Gemini__Model=gemini-2.5-flash\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=resource-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=resource-service,service.version=1.0.0\n ports:\n - 7003:80\n - 5003:5003\n depends_on:\n - postgres\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n product-service:\n image: rosie91/stemify:product-service\n build:\n context: .\n dockerfile: src/Services/ProductService/Product.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5005\n - ConnectionStrings__stemifyproduct=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_product\n - Cloudinary__CloudName=${CLOUDINARY_CLOUD_NAME}\n - Cloudinary__ApiKey=${CLOUDINARY_API_KEY}\n - Cloudinary__ApiSecret=${CLOUDINARY_API_SECRET}\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcResourceUrl=http://resource-service:5003\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=product-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=product-service,service.version=1.0.0\n ports:\n - 7005:80\n - 5005:5005\n depends_on:\n - postgres\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n order-service:\n image: rosie91/stemify:order-service\n build:\n context: .\n dockerfile: src/Services/OrderService/Order.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5006\n - ConnectionStrings__stemifyorder=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_order\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcResourceUrl=http://resource-service:5003\n - GrpcProductUrl=http://product-service:5005\n - GrpcClassroomUrl=http://classroom-service:5001\n - Cloudinary__CloudName=${CLOUDINARY_CLOUD_NAME}\n - Cloudinary__ApiKey=${CLOUDINARY_API_KEY}\n - Cloudinary__ApiSecret=${CLOUDINARY_API_SECRET}\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=order-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=order-service,service.version=1.0.0\n\n ports:\n - 7006:80\n - 5006:5006\n depends_on:\n - postgres\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n notification-service:\n image: rosie91/stemify:notification-service\n build:\n context: .\n dockerfile: src/Services/Notification/Notification.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5004\n - ConnectionStrings__stemifynotification=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_notification\n # SendGrid configuration\n - SendGridSettings__ApiKey=${SENDGRID_API_KEY}\n - SendGridSettings__FromEmail=stemify30062025@gmail.com\n - SendGridSettings__FromName=STEMify\n # Client App configuration\n - ClientApp__BaseUrl=https://localhost:3000\n - ClientApp=https://localhost:3000\n - Issuer=http://localhost:5000/\n - IdentityServiceUrl=http://identity-service\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=notification-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=notification-service,service.version=1.0.0\n ports:\n - 7004:80\n - 5004:5004\n depends_on:\n - postgres\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n gateway-service:\n image: rosie91/stemify:gateway-service\n build:\n context: .\n dockerfile: src/ApiGateways/ApiGateway/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Docker\n - ASPNETCORE_URLS=http://+:80\n - IdentityServiceUrl=http://identity-service\n - Issuer=http://localhost:5000/\n - ClientApp=https://localhost:3000\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=gateway-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=gateway-service,service.version=1.0.0\n ports:\n - 6001:80\n depends_on:\n - otelcollector\n restart: unless-stopped\n identity-api-service:\n image: rosie91/stemify:identity-api-service\n build:\n context: .\n dockerfile: src/Services/Identity/Identity.Api/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5002\n - ConnectionStrings__stemifyidentity=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_identity\n # RabbitMQ Configuration (for MassTransit)\n - RabbitMq__Url=${RABBITMQ_URL}\n # OpenIddict Configuration\n - OpenIddict__Authority=http://identity-service:80\n # Client App Configuration\n - ClientApp=https://localhost:3000\n # Email Settings\n - EmailSettings__From=${IDENTITY_EMAIL_FROM}\n - EmailSettings__DisplayName=${IDENTITY_EMAIL_DISPLAY_NAME}\n - EmailSettings__SMTPServer=${IDENTITY_EMAIL_SMTP_SERVER}\n - EmailSettings__Port=${IDENTITY_EMAIL_PORT}\n - EmailSettings__Username=${IDENTITY_EMAIL_USERNAME}\n - EmailSettings__Password=${IDENTITY_EMAIL_PASSWORD}\n - EmailSettings__UseSSL=${IDENTITY_EMAIL_USE_SSL}\n - EmailSettings__ApplicationName=${IDENTITY_EMAIL_APP_NAME}\n - EmailSettings__SupportEmail=${IDENTITY_EMAIL_SUPPORT}\n # Google Authentication\n - Authentication__Google__ClientId=${GOOGLE_CLIENT_ID}\n - Authentication__Google__ClientSecret=${GOOGLE_CLIENT_SECRET}\n - Authentication__Google__RedirectUri=${GOOGLE_REDIRECT_URI}\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=identity-api-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=identity-api-service,service.version=1.0.0\n - GrpcServices__OrderService__Address=http://order-service:5006\n - BackendUrl=${IDENTITY_BACKEND_URL:-http://localhost:5000}\n ports:\n - 7002:80\n - 5002:5002\n depends_on:\n - postgres\n - otelcollector\n restart: unless-stopped\n identity-service:\n image: rosie91/stemify:identity-service\n build:\n context: .\n dockerfile: src/Services/Identity/Identity.Web/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - ASPNETCORE_URLS=http://+:80\n - ConnectionStrings__stemifyidentity=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_identity\n # OpenIddict configuration for Docker\n - OpenIddict__Issuer=http://localhost:5000\n - OpenIddict__Authority=http://localhost:5000\n - OpenIddict__SigningKey=${OPENIDDICT_SIGNING_KEY}\n - OpenIddict__EncryptionKey=${OPENIDDICT_ENCRYPTION_KEY}\n - OpenIddict__ApiClientSecret=${OPENIDDICT_API_CLIENT_SECRET}\n - OpenIddict__ForceRecreateClients=${OPENIDDICT_FORCE_RECREATE_CLIENTS:-true}\n - OpenIddict__DisableHttpsRequirement=${OPENIDDICT_DISABLE_HTTPS:-true}\n # CORS for Docker environment\n - Cors__Origins=${IDENTITY_CORS_ORIGINS:-https://localhost:3000}\n - ClientApp=${IDENTITY_CLIENT_APP:-https://localhost:3000}\n - BackendUrl=${IDENTITY_BACKEND_URL:-http://localhost:5000}\n # Google Authentication\n - Authentication__Google__ClientId=${GOOGLE_CLIENT_ID}\n - Authentication__Google__ClientSecret=${GOOGLE_CLIENT_SECRET}\n - Authentication__Google__RedirectUri=${GOOGLE_REDIRECT_URI}\n # Email Settings\n - EmailSettings__SMTPServer=${IDENTITY_EMAIL_SMTP_SERVER}\n - EmailSettings__Port=${IDENTITY_EMAIL_PORT}\n - EmailSettings__UseSSL=${IDENTITY_EMAIL_USE_SSL}\n - EmailSettings__Username=${IDENTITY_EMAIL_USERNAME}\n - EmailSettings__Password=${IDENTITY_EMAIL_PASSWORD}\n - EmailSettings__From=${IDENTITY_EMAIL_FROM}\n - EmailSettings__DisplayName=${IDENTITY_EMAIL_DISPLAY_NAME}\n - EmailSettings__ApplicationName=${IDENTITY_EMAIL_APP_NAME}\n - EmailSettings__SupportEmail=${IDENTITY_EMAIL_SUPPORT}\n - EmailSettings__Enabled=${IDENTITY_EMAIL_ENABLED}\n - EmailSettings__EnableVerification=${IDENTITY_EMAIL_ENABLE_VERIFICATION}\n - EmailSettings__TimeoutMs=${IDENTITY_EMAIL_TIMEOUT_MS}\n - EmailSettings__MaxRetryAttempts=${IDENTITY_EMAIL_MAX_RETRY}\n - EmailSettings__LogEmailContent=${IDENTITY_EMAIL_LOG_CONTENT}\n # gRPC Services\n - GrpcServices__OrderService__Address=http://order-service:5006\n # Database Options\n - DatabaseOptions__MaxRetryCount=5\n - DatabaseOptions__MaxRetryDelay=00:00:10\n - DatabaseOptions__CommandTimeout=30\n - DatabaseOptions__EnableRetryOnFailure=true\n - DatabaseOptions__EnableSensitiveDataLogging=${DB_ENABLE_SENSITIVE_LOGGING}\n - DatabaseOptions__EnableDetailedErrors=${DB_ENABLE_DETAILED_ERRORS}\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=identity-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=identity-service,service.version=1.0.0\n - RabbitMq__Url=${RABBITMQ_URL}\n ports:\n - 5000:80\n depends_on:\n - postgres\n - otelcollector\n restart: unless-stopped\n cart-service:\n image: rosie91/stemify:cart-service\n build:\n context: .\n dockerfile: src/Services/CartService/Cart.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5007\n - ConnectionStrings__stemifycart=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_cart\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcProductUrl=http://product-service:5005\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=cart-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=cart-service,service.version=1.0.0\n ports:\n - 7007:80\n - 5007:5007\n depends_on:\n - postgres\n - redis\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n payment-service:\n image: rosie91/stemify:payment-service\n build:\n context: .\n dockerfile: src/Services/PaymentService/Payment.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - Logging__LogLevel__Microsoft.EntityFrameworkCore.Database.Command=Warning\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5008\n - ConnectionStrings__stemifypayment=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_payment\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n # PayOS configuration\n - PaymentGateways__PayOS__ClientId=${PAYOS_CLIENT_ID}\n - PaymentGateways__PayOS__ApiKey=${PAYOS_API_KEY}\n - PaymentGateways__PayOS__ChecksumKey=${PAYOS_CHECKSUM_KEY}\n - PaymentGateways__PayOS__BaseUrl=https://api-merchant.payos.vn\n # Stripe configuration\n - PaymentGateways__Stripe__SecretKey=${STRIPE_SECRET_KEY}\n - PaymentGateways__Stripe__PublishableKey=${STRIPE_PUBLISH_KEY}\n - PaymentGateways__Stripe__WebhookSecret=${STRIPE_WEBHOOK_KEY}\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=payment-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=payment-service,service.version=1.0.0\n ports:\n - 7008:80\n - 5008:5008\n depends_on:\n - postgres\n - redis\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n emulator-service:\n image: rosie91/stemify:emulator-service\n build:\n context: .\n dockerfile: src/Services/EmulatorService/Emulator.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5009\n # MongoDB configuration\n - MongoDb__ConnectionString=mongodb://root:mongopw@mongo\n - MongoDb__DatabaseName=stemify_emulator\n # Redis configuration\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n # gRPC endpoints\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcResourceUrl=http://resource-service:5003\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=emulator-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=emulator-service,service.version=1.0.0\n # Cloudinary Configuration\n - Cloudinary__CloudName=${CLOUDINARY_CLOUD_NAME}\n - Cloudinary__ApiKey=${CLOUDINARY_API_KEY}\n - Cloudinary__ApiSecret=${CLOUDINARY_API_SECRET}\n ports:\n - 7009:80\n - 5009:5009\n depends_on:\n - redis\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n\n # ========================================\n # MONITORING STACK\n # ========================================\n\n # Prometheus - Metrics Collection\n prometheus:\n image: prom/prometheus:v3.5.0\n container_name: stemify-prometheus\n ports:\n - '9090:9090'\n volumes:\n - ./config/prometheus:/etc/prometheus:ro\n - prometheus_data:/prometheus\n command:\n - '--config.file=/etc/prometheus/prometheus.yml'\n - '--storage.tsdb.path=/prometheus'\n - '--storage.tsdb.retention.time=15d'\n - '--storage.tsdb.retention.size=10GB'\n - '--web.enable-lifecycle'\n - '--web.enable-otlp-receiver'\n restart: unless-stopped\n\n # AlertManager - Alert Management\n alertmanager:\n image: prom/alertmanager:v0.27.0\n container_name: stemify-alertmanager\n ports:\n - '9093:9093'\n volumes:\n - ./config/alertmanager:/etc/alertmanager:ro\n - alertmanager_data:/alertmanager\n command:\n - '--config.file=/etc/alertmanager/alertmanager.yml'\n - '--storage.path=/alertmanager'\n - '--web.external-url=http://localhost:9093'\n restart: unless-stopped\n\n # Grafana - Visualization\n grafana:\n image: grafana/grafana:latest\n container_name: stemify-grafana\n ports:\n - '3001:3001'\n volumes:\n - grafana_data:/var/lib/grafana\n - ./config/grafana/config:/etc/grafana:ro\n - ./config/grafana/dashboards:/var/lib/grafana/dashboards:ro\n environment:\n - GF_SECURITY_ADMIN_USER=${GRAFANA_ADMIN_USERNAME}\n - GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_ADMIN_PASSWORD:-admin123}\n - GF_USERS_ALLOW_SIGN_UP=false\n - GF_INSTALL_PLUGINS=grafana-clock-panel,grafana-simple-json-datasource\n - PROMETHEUS_ENDPOINT=http://prometheus:9090\n restart: unless-stopped\n depends_on:\n - prometheus\n\n # OpenTelemetry Collector\n otelcollector:\n image: otel/opentelemetry-collector-contrib:latest\n container_name: stemify-otelcollector\n ports:\n - '8888:8888' # Prometheus metrics\n - '8889:8889' # Prometheus exporter\n - '4317:4317' # OTLP gRPC receiver\n - '4318:4318' # OTLP HTTP receiver\n volumes:\n - ./config/otelcollector:/etc/otelcol-contrib:ro\n environment:\n - PROMETHEUS_ENDPOINT=http://prometheus:9090/api/v1/write\n command: [ '--config=/etc/otelcol-contrib/config.yaml' ]\n restart: unless-stopped\n depends_on:\n - prometheus\n\n # ========================================\n # AI SERVICE & VECTOR DATABASE\n # ========================================\n\n # Qdrant - Vector Database for RAG\n qdrant:\n image: qdrant/qdrant:latest\n container_name: stemify-qdrant\n ports:\n - '6333:6333' # HTTP API\n - '6334:6334' # gRPC\n volumes:\n - qdrant_data:/qdrant/storage\n environment:\n - QDRANT__SERVICE__GRPC_PORT=6334\n restart: unless-stopped\n\n neo4j:\n image: neo4j:5.20\n container_name: stemify-neo4j\n ports:\n - \"7474:7474\" # HTTP\n - \"7687:7687\" # Bolt\n environment:\n - NEO4J_AUTH=neo4j/password\n - NEO4J_PLUGINS=[\"apoc\"]\n - NEO4J_dbms_memory_heap_max__size=2G\n - NEO4J_dbms_memory_pagecache_size=1G\n volumes:\n - neo4j_data:/data\n - neo4j_logs:/logs\n restart: unless-stopped\n healthcheck:\n test: [ \"CMD\", \"cypher-shell\", \"-u\", \"neo4j\", \"-p\", \"password\", \"RETURN 1\" ]\n interval: 30s\n timeout: 10s\n retries: 3\n start_period: 40s\n # AI Service - Python FastAPI with gRPC\n # ai-service:\n # build:\n # context: .\n # dockerfile: src/Services/AIService/Dockerfile\n # container_name: stemify-ai-service\n # environment:\n # - PYTHONUNBUFFERED=1\n # - ENVIRONMENT=Development\n # - QDRANT_URL=http://qdrant:6333\n # - QDRANT_GRPC_URL=http://qdrant:6334\n # - QDRANT_COLLECTION=classroom_insights\n # - NEO4J_URI=bolt://neo4j:7687\n # - NEO4J_USER=neo4j\n # - NEO4J_PASSWORD=password\n # - OPENAI_API_KEY=${OPENAI_API_KEY}\n # - OPENAI_BASE_URL=${OPENAI_BASE_URL:-https://api.openai.com/v1}\n # - EMBEDDING_MODEL=paraphrase-multilingual-MiniLM-L12-v2\n # - LLM_MODEL=gpt-4o-mini\n # - LOG_LEVEL=INFO\n # # gRPC Configuration\n # - GRPC_PORT=5010\n # - GRPC_HOST=0.0.0.0\n # ports:\n # - '7010:80' # HTTP API (FastAPI)\n # - '5010:5010' # gRPC\n # depends_on:\n # - qdrant\n # - neo4j\n # restart: unless-stopped\n # healthcheck:\n # test: [\"CMD\", \"curl\", \"-f\", \"http://localhost:80/health\"]\n # interval: 30s\n # timeout: 10s\n # retries: 3\n # start_period: 40s\n\nvolumes:\n redis-data:\n postgres_data:\n mongodata:\n prometheus_data:\n alertmanager_data:\n grafana_data:\n qdrant_data:\n neo4j_data:\n neo4j_logs:\n"}, "28c7eb4ef50eb4baf00a47ddb285306ee0791cd6": {"t": 1785748776.2495956, "n": 24091, "h": "46b7836590e343af", "c": "services:\n postgres:\n image: postgres:17\n container_name: stemify-postgres\n environment:\n - POSTGRES_PASSWORD=${DB_PASSWORD}\n ports:\n - '5432:5432'\n volumes:\n - postgres_data:/var/lib/postgresql/data\n restart: unless-stopped\n # RabbitMQ is now using CloudAMQP online service\n # rabbitmq:\n # image: rabbitmq:3-management-alpine\n # ports:\n # - 5672:5672\n # - 15672:15672\n # environment:\n # - RABBITMQ_DEFAULT_USER=guest\n # - RABBITMQ_DEFAULT_PASS=guest\n # healthcheck:\n # test: [\"CMD\", \"rabbitmq-diagnostics\", \"ping\"]\n # interval: 30s\n # timeout: 10s\n # retries: 5\n # start_period: 30s\n # restart: unless-stopped\n mongo:\n image: mongo\n environment:\n - MONGO_INITDB_ROOT_USERNAME=root\n - MONGO_INITDB_ROOT_PASSWORD=mongopw\n ports:\n - 27017:27017\n volumes:\n - mongodata:/var/lib/mongodb/data\n redis:\n image: redis:latest\n ports:\n - 6379:6379\n command: ['redis-server', '--appendonly', 'yes']\n volumes:\n - redis-data:/data\n redis-commander:\n image: rediscommander/redis-commander:latest\n environment:\n - REDIS_HOSTS=local:redis:6379\n - HTTP_USER=root\n - HTTP_PASSWORD=secret\n ports:\n - 8082:8082\n depends_on:\n - redis\n classroom-service:\n image: rosie91/stemify:classroom-service\n build:\n context: .\n dockerfile: src/Services/ClassroomService/Classroom.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5001\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcResourceUrl=http://resource-service:5003\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcOrderUrl=http://order-service:5006\n - ConnectionStrings__stemifyclassroom=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_classroom\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=classroom-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=classroom-service,service.version=1.0.0\n # Cloudinary Configuration\n - Cloudinary__CloudName=${CLOUDINARY_CLOUD_NAME}\n - Cloudinary__ApiKey=${CLOUDINARY_API_KEY}\n - Cloudinary__ApiSecret=${CLOUDINARY_API_SECRET}\n ports:\n - 7001:80\n - 5001:5001\n depends_on:\n - postgres\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n resource-service:\n image: rosie91/stemify:resource-service\n build:\n context: .\n dockerfile: src/Services/Resource/Resource.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5003\n - ConnectionStrings__stemifyresource=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_resource\n - Cloudinary__CloudName=${CLOUDINARY_CLOUD_NAME}\n - Cloudinary__ApiKey=${CLOUDINARY_API_KEY}\n - Cloudinary__ApiSecret=${CLOUDINARY_API_SECRET}\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n - ClientApp=https://localhost:3000\n - Gemini__BaseUrl=https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent\n - Gemini__ApiKey=${GEMINI_API_KEY}\n - Gemini__Model=gemini-2.5-flash\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=resource-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=resource-service,service.version=1.0.0\n ports:\n - 7003:80\n - 5003:5003\n depends_on:\n - postgres\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n product-service:\n image: rosie91/stemify:product-service\n build:\n context: .\n dockerfile: src/Services/ProductService/Product.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5005\n - ConnectionStrings__stemifyproduct=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_product\n - Cloudinary__CloudName=${CLOUDINARY_CLOUD_NAME}\n - Cloudinary__ApiKey=${CLOUDINARY_API_KEY}\n - Cloudinary__ApiSecret=${CLOUDINARY_API_SECRET}\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcResourceUrl=http://resource-service:5003\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=product-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=product-service,service.version=1.0.0\n ports:\n - 7005:80\n - 5005:5005\n depends_on:\n - postgres\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n order-service:\n image: rosie91/stemify:order-service\n build:\n context: .\n dockerfile: src/Services/OrderService/Order.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5006\n - ConnectionStrings__stemifyorder=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_order\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcResourceUrl=http://resource-service:5003\n - GrpcProductUrl=http://product-service:5005\n - GrpcClassroomUrl=http://classroom-service:5001\n - Cloudinary__CloudName=${CLOUDINARY_CLOUD_NAME}\n - Cloudinary__ApiKey=${CLOUDINARY_API_KEY}\n - Cloudinary__ApiSecret=${CLOUDINARY_API_SECRET}\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=order-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=order-service,service.version=1.0.0\n\n ports:\n - 7006:80\n - 5006:5006\n depends_on:\n - postgres\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n notification-service:\n image: rosie91/stemify:notification-service\n build:\n context: .\n dockerfile: src/Services/Notification/Notification.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5004\n - ConnectionStrings__stemifynotification=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_notification\n # SendGrid configuration\n - SendGridSettings__ApiKey=${SENDGRID_API_KEY}\n - SendGridSettings__FromEmail=stemify30062025@gmail.com\n - SendGridSettings__FromName=STEMify\n # Client App configuration\n - ClientApp__BaseUrl=https://localhost:3000\n - ClientApp=https://localhost:3000\n - Issuer=http://localhost:5000/\n - IdentityServiceUrl=http://identity-service\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=notification-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=notification-service,service.version=1.0.0\n ports:\n - 7004:80\n - 5004:5004\n depends_on:\n - postgres\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n gateway-service:\n image: rosie91/stemify:gateway-service\n build:\n context: .\n dockerfile: src/ApiGateways/ApiGateway/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Docker\n - ASPNETCORE_URLS=http://+:80\n - IdentityServiceUrl=http://identity-service\n - Issuer=http://localhost:5000/\n - ClientApp=https://localhost:3000\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=gateway-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=gateway-service,service.version=1.0.0\n ports:\n - 6001:80\n depends_on:\n - otelcollector\n restart: unless-stopped\n identity-api-service:\n image: rosie91/stemify:identity-api-service\n build:\n context: .\n dockerfile: src/Services/Identity/Identity.Api/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5002\n - ConnectionStrings__stemifyidentity=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_identity\n # RabbitMQ Configuration (for MassTransit)\n - RabbitMq__Url=${RABBITMQ_URL}\n # OpenIddict Configuration\n - OpenIddict__Authority=http://identity-service:80\n # Client App Configuration\n - ClientApp=https://localhost:3000\n # Email Settings\n - EmailSettings__From=${IDENTITY_EMAIL_FROM}\n - EmailSettings__DisplayName=${IDENTITY_EMAIL_DISPLAY_NAME}\n - EmailSettings__SMTPServer=${IDENTITY_EMAIL_SMTP_SERVER}\n - EmailSettings__Port=${IDENTITY_EMAIL_PORT}\n - EmailSettings__Username=${IDENTITY_EMAIL_USERNAME}\n - EmailSettings__Password=${IDENTITY_EMAIL_PASSWORD}\n - EmailSettings__UseSSL=${IDENTITY_EMAIL_USE_SSL}\n - EmailSettings__ApplicationName=${IDENTITY_EMAIL_APP_NAME}\n - EmailSettings__SupportEmail=${IDENTITY_EMAIL_SUPPORT}\n # Google Authentication\n - Authentication__Google__ClientId=${GOOGLE_CLIENT_ID}\n - Authentication__Google__ClientSecret=${GOOGLE_CLIENT_SECRET}\n - Authentication__Google__RedirectUri=${GOOGLE_REDIRECT_URI}\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=identity-api-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=identity-api-service,service.version=1.0.0\n - GrpcServices__OrderService__Address=http://order-service:5006\n # Google Authentication\n - Authentication__Google__ClientId=${GOOGLE_CLIENT_ID}\n - Authentication__Google__ClientSecret=${GOOGLE_CLIENT_SECRET}\n - Authentication__Google__RedirectUri=${GOOGLE_REDIRECT_URI}\n - BackendUrl=${IDENTITY_BACKEND_URL:-http://localhost:6001}\n ports:\n - 7002:80\n - 5002:5002\n depends_on:\n - postgres\n - otelcollector\n restart: unless-stopped\n identity-service:\n image: rosie91/stemify:identity-service\n build:\n context: .\n dockerfile: src/Services/Identity/Identity.Web/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - ASPNETCORE_URLS=http://+:80\n - ConnectionStrings__stemifyidentity=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_identity\n # OpenIddict configuration for Docker\n - OpenIddict__Issuer=http://localhost:5000\n - OpenIddict__Authority=http://localhost:5000\n - OpenIddict__SigningKey=${OPENIDDICT_SIGNING_KEY}\n - OpenIddict__EncryptionKey=${OPENIDDICT_ENCRYPTION_KEY}\n - OpenIddict__ApiClientSecret=${OPENIDDICT_API_CLIENT_SECRET}\n - OpenIddict__ForceRecreateClients=${OPENIDDICT_FORCE_RECREATE_CLIENTS:-true}\n - OpenIddict__DisableHttpsRequirement=${OPENIDDICT_DISABLE_HTTPS:-true}\n # CORS for Docker environment\n - Cors__Origins=${IDENTITY_CORS_ORIGINS:-https://localhost:3000}\n - ClientApp=${IDENTITY_CLIENT_APP:-https://localhost:3000}\n - BackendUrl=${IDENTITY_BACKEND_URL:-http://localhost:5000}\n # Google Authentication\n - Authentication__Google__ClientId=${GOOGLE_CLIENT_ID}\n - Authentication__Google__ClientSecret=${GOOGLE_CLIENT_SECRET}\n - Authentication__Google__RedirectUri=${GOOGLE_REDIRECT_URI}\n # Email Settings\n - EmailSettings__SMTPServer=${IDENTITY_EMAIL_SMTP_SERVER}\n - EmailSettings__Port=${IDENTITY_EMAIL_PORT}\n - EmailSettings__UseSSL=${IDENTITY_EMAIL_USE_SSL}\n - EmailSettings__Username=${IDENTITY_EMAIL_USERNAME}\n - EmailSettings__Password=${IDENTITY_EMAIL_PASSWORD}\n - EmailSettings__From=${IDENTITY_EMAIL_FROM}\n - EmailSettings__DisplayName=${IDENTITY_EMAIL_DISPLAY_NAME}\n - EmailSettings__ApplicationName=${IDENTITY_EMAIL_APP_NAME}\n - EmailSettings__SupportEmail=${IDENTITY_EMAIL_SUPPORT}\n - EmailSettings__Enabled=${IDENTITY_EMAIL_ENABLED}\n - EmailSettings__EnableVerification=${IDENTITY_EMAIL_ENABLE_VERIFICATION}\n - EmailSettings__TimeoutMs=${IDENTITY_EMAIL_TIMEOUT_MS}\n - EmailSettings__MaxRetryAttempts=${IDENTITY_EMAIL_MAX_RETRY}\n - EmailSettings__LogEmailContent=${IDENTITY_EMAIL_LOG_CONTENT}\n # gRPC Services\n - GrpcServices__OrderService__Address=http://order-service:5006\n # Database Options\n - DatabaseOptions__MaxRetryCount=5\n - DatabaseOptions__MaxRetryDelay=00:00:10\n - DatabaseOptions__CommandTimeout=30\n - DatabaseOptions__EnableRetryOnFailure=true\n - DatabaseOptions__EnableSensitiveDataLogging=${DB_ENABLE_SENSITIVE_LOGGING}\n - DatabaseOptions__EnableDetailedErrors=${DB_ENABLE_DETAILED_ERRORS}\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=identity-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=identity-service,service.version=1.0.0\n - RabbitMq__Url=${RABBITMQ_URL}\n ports:\n - 5000:80\n depends_on:\n - postgres\n - otelcollector\n restart: unless-stopped\n cart-service:\n image: rosie91/stemify:cart-service\n build:\n context: .\n dockerfile: src/Services/CartService/Cart.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5007\n - ConnectionStrings__stemifycart=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_cart\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcProductUrl=http://product-service:5005\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=cart-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=cart-service,service.version=1.0.0\n ports:\n - 7007:80\n - 5007:5007\n depends_on:\n - postgres\n - redis\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n payment-service:\n image: rosie91/stemify:payment-service\n build:\n context: .\n dockerfile: src/Services/PaymentService/Payment.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - Logging__LogLevel__Microsoft.EntityFrameworkCore.Database.Command=Warning\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5008\n - ConnectionStrings__stemifypayment=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_payment\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n # PayOS configuration\n - PaymentGateways__PayOS__ClientId=${PAYOS_CLIENT_ID}\n - PaymentGateways__PayOS__ApiKey=${PAYOS_API_KEY}\n - PaymentGateways__PayOS__ChecksumKey=${PAYOS_CHECKSUM_KEY}\n - PaymentGateways__PayOS__BaseUrl=https://api-merchant.payos.vn\n # Stripe configuration\n - PaymentGateways__Stripe__SecretKey=${STRIPE_SECRET_KEY}\n - PaymentGateways__Stripe__PublishableKey=${STRIPE_PUBLISH_KEY}\n - PaymentGateways__Stripe__WebhookSecret=${STRIPE_WEBHOOK_KEY}\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=payment-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=payment-service,service.version=1.0.0\n ports:\n - 7008:80\n - 5008:5008\n depends_on:\n - postgres\n - redis\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n emulator-service:\n image: rosie91/stemify:emulator-service\n build:\n context: .\n dockerfile: src/Services/EmulatorService/Emulator.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5009\n # MongoDB configuration\n - MongoDb__ConnectionString=mongodb://root:mongopw@mongo\n - MongoDb__DatabaseName=stemify_emulator\n # Redis configuration\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n # gRPC endpoints\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcResourceUrl=http://resource-service:5003\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=emulator-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=emulator-service,service.version=1.0.0\n # Cloudinary Configuration\n - Cloudinary__CloudName=${CLOUDINARY_CLOUD_NAME}\n - Cloudinary__ApiKey=${CLOUDINARY_API_KEY}\n - Cloudinary__ApiSecret=${CLOUDINARY_API_SECRET}\n ports:\n - 7009:80\n - 5009:5009\n depends_on:\n - redis\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n\n # ========================================\n # MONITORING STACK\n # ========================================\n\n # Prometheus - Metrics Collection\n prometheus:\n image: prom/prometheus:v3.5.0\n container_name: stemify-prometheus\n ports:\n - '9090:9090'\n volumes:\n - ./config/prometheus:/etc/prometheus:ro\n - prometheus_data:/prometheus\n command:\n - '--config.file=/etc/prometheus/prometheus.yml'\n - '--storage.tsdb.path=/prometheus'\n - '--storage.tsdb.retention.time=15d'\n - '--storage.tsdb.retention.size=10GB'\n - '--web.enable-lifecycle'\n - '--web.enable-otlp-receiver'\n restart: unless-stopped\n\n # AlertManager - Alert Management\n alertmanager:\n image: prom/alertmanager:v0.27.0\n container_name: stemify-alertmanager\n ports:\n - '9093:9093'\n volumes:\n - ./config/alertmanager:/etc/alertmanager:ro\n - alertmanager_data:/alertmanager\n command:\n - '--config.file=/etc/alertmanager/alertmanager.yml'\n - '--storage.path=/alertmanager'\n - '--web.external-url=http://localhost:9093'\n restart: unless-stopped\n\n # Grafana - Visualization\n grafana:\n image: grafana/grafana:latest\n container_name: stemify-grafana\n ports:\n - '3001:3001'\n volumes:\n - grafana_data:/var/lib/grafana\n - ./config/grafana/config:/etc/grafana:ro\n - ./config/grafana/dashboards:/var/lib/grafana/dashboards:ro\n environment:\n - GF_SECURITY_ADMIN_USER=${GRAFANA_ADMIN_USERNAME}\n - GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_ADMIN_PASSWORD:-admin123}\n - GF_USERS_ALLOW_SIGN_UP=false\n - GF_INSTALL_PLUGINS=grafana-clock-panel,grafana-simple-json-datasource\n - PROMETHEUS_ENDPOINT=http://prometheus:9090\n restart: unless-stopped\n depends_on:\n - prometheus\n\n # OpenTelemetry Collector\n otelcollector:\n image: otel/opentelemetry-collector-contrib:latest\n container_name: stemify-otelcollector\n ports:\n - '8888:8888' # Prometheus metrics\n - '8889:8889' # Prometheus exporter\n - '4317:4317' # OTLP gRPC receiver\n - '4318:4318' # OTLP HTTP receiver\n volumes:\n - ./config/otelcollector:/etc/otelcol-contrib:ro\n environment:\n - PROMETHEUS_ENDPOINT=http://prometheus:9090/api/v1/write\n command: ['--config=/etc/otelcol-contrib/config.yaml']\n restart: unless-stopped\n depends_on:\n - prometheus\n\n # ========================================\n # AI SERVICE & VECTOR DATABASE\n # ========================================\n\n # Qdrant - Vector Database for RAG\n qdrant:\n image: qdrant/qdrant:latest\n container_name: stemify-qdrant\n ports:\n - '6333:6333' # HTTP API\n - '6334:6334' # gRPC\n volumes:\n - qdrant_data:/qdrant/storage\n environment:\n - QDRANT__SERVICE__GRPC_PORT=6334\n restart: unless-stopped\n\n neo4j:\n image: neo4j:5.20\n container_name: stemify-neo4j\n ports:\n - \"7474:7474\" # HTTP\n - \"7687:7687\" # Bolt\n environment:\n - NEO4J_AUTH=neo4j/password\n - NEO4J_PLUGINS=[\"apoc\"]\n - NEO4J_dbms_memory_heap_max__size=2G\n - NEO4J_dbms_memory_pagecache_size=1G\n volumes:\n - neo4j_data:/data\n - neo4j_logs:/logs\n restart: unless-stopped\n healthcheck:\n test: [\"CMD\", \"cypher-shell\", \"-u\", \"neo4j\", \"-p\", \"password\", \"RETURN 1\"]\n interval: 30s\n timeout: 10s\n retries: 3\n start_period: 40s\n\n # AI Service - Python FastAPI with gRPC\n ai-service:\n build:\n context: .\n dockerfile: src/Services/AIService/Dockerfile\n container_name: stemify-ai-service\n environment:\n - PYTHONUNBUFFERED=1\n - ENVIRONMENT=Development\n - QDRANT_URL=http://qdrant:6333\n - QDRANT_GRPC_URL=http://qdrant:6334\n - QDRANT_COLLECTION=classroom_insights\n - NEO4J_URI=bolt://neo4j:7687\n - NEO4J_USER=neo4j\n - NEO4J_PASSWORD=password\n - OPENAI_API_KEY=${OPENAI_API_KEY}\n - OPENAI_BASE_URL=${OPENAI_BASE_URL:-https://api.openai.com/v1}\n - EMBEDDING_MODEL=paraphrase-multilingual-MiniLM-L12-v2\n - LLM_MODEL=gpt-4o-mini\n - LOG_LEVEL=INFO\n # gRPC Configuration\n - GRPC_PORT=5010\n - GRPC_HOST=0.0.0.0\n ports:\n - '7010:80' # HTTP API (FastAPI)\n - '5010:5010' # gRPC\n depends_on:\n - qdrant\n - neo4j\n restart: unless-stopped\n healthcheck:\n test: [\"CMD\", \"curl\", \"-f\", \"http://localhost:80/health\"]\n interval: 30s\n timeout: 10s\n retries: 3\n start_period: 40s\n\nvolumes:\n redis-data:\n postgres_data:\n mongodata:\n prometheus_data:\n alertmanager_data:\n grafana_data:\n qdrant_data:\n neo4j_data:\n neo4j_logs:\n"}, "4256fbf76f10c3e6abde3c3c51c865e21d66d7e8": {"t": 1785748777.6755087, "n": 23864, "h": "3205cd28d2ab39a3", "c": "services:\n postgres:\n image: postgres:17\n container_name: stemify-postgres\n environment:\n - POSTGRES_PASSWORD=${DB_PASSWORD}\n ports:\n - '5432:5432'\n volumes:\n - postgres_data:/var/lib/postgresql/data\n restart: unless-stopped\n # RabbitMQ is now using CloudAMQP online service\n # rabbitmq:\n # image: rabbitmq:3-management-alpine\n # ports:\n # - 5672:5672\n # - 15672:15672\n # environment:\n # - RABBITMQ_DEFAULT_USER=guest\n # - RABBITMQ_DEFAULT_PASS=guest\n # healthcheck:\n # test: [\"CMD\", \"rabbitmq-diagnostics\", \"ping\"]\n # interval: 30s\n # timeout: 10s\n # retries: 5\n # start_period: 30s\n # restart: unless-stopped\n mongo:\n image: mongo\n environment:\n - MONGO_INITDB_ROOT_USERNAME=root\n - MONGO_INITDB_ROOT_PASSWORD=mongopw\n ports:\n - 27017:27017\n volumes:\n - mongodata:/var/lib/mongodb/data\n redis:\n image: redis:latest\n ports:\n - 6379:6379\n command: ['redis-server', '--appendonly', 'yes']\n volumes:\n - redis-data:/data\n redis-commander:\n image: rediscommander/redis-commander:latest\n environment:\n - REDIS_HOSTS=local:redis:6379\n - HTTP_USER=root\n - HTTP_PASSWORD=secret\n ports:\n - 8082:8082\n depends_on:\n - redis\n classroom-service:\n image: rosie91/stemify:classroom-service\n build:\n context: .\n dockerfile: src/Services/ClassroomService/Classroom.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5001\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcResourceUrl=http://resource-service:5003\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcOrderUrl=http://order-service:5006\n - ConnectionStrings__stemifyclassroom=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_classroom\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=classroom-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=classroom-service,service.version=1.0.0\n # Cloudinary Configuration\n - Cloudinary__CloudName=${CLOUDINARY_CLOUD_NAME}\n - Cloudinary__ApiKey=${CLOUDINARY_API_KEY}\n - Cloudinary__ApiSecret=${CLOUDINARY_API_SECRET}\n ports:\n - 7001:80\n - 5001:5001\n depends_on:\n - postgres\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n resource-service:\n image: rosie91/stemify:resource-service\n build:\n context: .\n dockerfile: src/Services/Resource/Resource.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5003\n - ConnectionStrings__stemifyresource=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_resource\n - Cloudinary__CloudName=${CLOUDINARY_CLOUD_NAME}\n - Cloudinary__ApiKey=${CLOUDINARY_API_KEY}\n - Cloudinary__ApiSecret=${CLOUDINARY_API_SECRET}\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n - ClientApp=https://localhost:3000\n - Gemini__BaseUrl=https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent\n - Gemini__ApiKey=${GEMINI_API_KEY}\n - Gemini__Model=gemini-2.5-flash\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=resource-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=resource-service,service.version=1.0.0\n ports:\n - 7003:80\n - 5003:5003\n depends_on:\n - postgres\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n product-service:\n image: rosie91/stemify:product-service\n build:\n context: .\n dockerfile: src/Services/ProductService/Product.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5005\n - ConnectionStrings__stemifyproduct=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_product\n - Cloudinary__CloudName=${CLOUDINARY_CLOUD_NAME}\n - Cloudinary__ApiKey=${CLOUDINARY_API_KEY}\n - Cloudinary__ApiSecret=${CLOUDINARY_API_SECRET}\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcResourceUrl=http://resource-service:5003\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=product-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=product-service,service.version=1.0.0\n ports:\n - 7005:80\n - 5005:5005\n depends_on:\n - postgres\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n order-service:\n image: rosie91/stemify:order-service\n build:\n context: .\n dockerfile: src/Services/OrderService/Order.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5006\n - ConnectionStrings__stemifyorder=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_order\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcResourceUrl=http://resource-service:5003\n - GrpcProductUrl=http://product-service:5005\n - GrpcClassroomUrl=http://classroom-service:5001\n - Cloudinary__CloudName=${CLOUDINARY_CLOUD_NAME}\n - Cloudinary__ApiKey=${CLOUDINARY_API_KEY}\n - Cloudinary__ApiSecret=${CLOUDINARY_API_SECRET}\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=order-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=order-service,service.version=1.0.0\n\n ports:\n - 7006:80\n - 5006:5006\n depends_on:\n - postgres\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n notification-service:\n image: rosie91/stemify:notification-service\n build:\n context: .\n dockerfile: src/Services/Notification/Notification.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5004\n - ConnectionStrings__stemifynotification=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_notification\n # SendGrid configuration\n - SendGridSettings__ApiKey=${SENDGRID_API_KEY}\n - SendGridSettings__FromEmail=stemify30062025@gmail.com\n - SendGridSettings__FromName=STEMify\n # Client App configuration\n - ClientApp__BaseUrl=https://localhost:3000\n - ClientApp=https://localhost:3000\n - Issuer=http://localhost:5000/\n - IdentityServiceUrl=http://identity-service\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=notification-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=notification-service,service.version=1.0.0\n ports:\n - 7004:80\n - 5004:5004\n depends_on:\n - postgres\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n gateway-service:\n image: rosie91/stemify:gateway-service\n build:\n context: .\n dockerfile: src/ApiGateways/ApiGateway/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Docker\n - ASPNETCORE_URLS=http://+:80\n - IdentityServiceUrl=http://identity-service\n - Issuer=http://localhost:5000/\n - ClientApp=https://localhost:3000\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=gateway-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=gateway-service,service.version=1.0.0\n ports:\n - 6001:80\n depends_on:\n - otelcollector\n restart: unless-stopped\n identity-api-service:\n image: rosie91/stemify:identity-api-service\n build:\n context: .\n dockerfile: src/Services/Identity/Identity.Api/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5002\n - ConnectionStrings__stemifyidentity=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_identity\n # RabbitMQ Configuration (for MassTransit)\n - RabbitMq__Url=${RABBITMQ_URL}\n # OpenIddict Configuration\n - OpenIddict__Authority=http://identity-service:80\n # Client App Configuration\n - ClientApp=https://localhost:3000\n # Email Settings\n - EmailSettings__From=${IDENTITY_EMAIL_FROM}\n - EmailSettings__DisplayName=${IDENTITY_EMAIL_DISPLAY_NAME}\n - EmailSettings__SMTPServer=${IDENTITY_EMAIL_SMTP_SERVER}\n - EmailSettings__Port=${IDENTITY_EMAIL_PORT}\n - EmailSettings__Username=${IDENTITY_EMAIL_USERNAME}\n - EmailSettings__Password=${IDENTITY_EMAIL_PASSWORD}\n - EmailSettings__UseSSL=${IDENTITY_EMAIL_USE_SSL}\n - EmailSettings__ApplicationName=${IDENTITY_EMAIL_APP_NAME}\n - EmailSettings__SupportEmail=${IDENTITY_EMAIL_SUPPORT}\n # Google Authentication\n - Authentication__Google__ClientId=${GOOGLE_CLIENT_ID}\n - Authentication__Google__ClientSecret=${GOOGLE_CLIENT_SECRET}\n - Authentication__Google__RedirectUri=${GOOGLE_REDIRECT_URI}\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=identity-api-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=identity-api-service,service.version=1.0.0\n - GrpcServices__OrderService__Address=http://order-service:5006\n - BackendUrl=${IDENTITY_BACKEND_URL:-http://localhost:5000}\n ports:\n - 7002:80\n - 5002:5002\n depends_on:\n - postgres\n - otelcollector\n restart: unless-stopped\n identity-service:\n image: rosie91/stemify:identity-service\n build:\n context: .\n dockerfile: src/Services/Identity/Identity.Web/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - ASPNETCORE_URLS=http://+:80\n - ConnectionStrings__stemifyidentity=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_identity\n # OpenIddict configuration for Docker\n - OpenIddict__Issuer=http://localhost:5000\n - OpenIddict__Authority=http://localhost:5000\n - OpenIddict__SigningKey=${OPENIDDICT_SIGNING_KEY}\n - OpenIddict__EncryptionKey=${OPENIDDICT_ENCRYPTION_KEY}\n - OpenIddict__ApiClientSecret=${OPENIDDICT_API_CLIENT_SECRET}\n - OpenIddict__ForceRecreateClients=${OPENIDDICT_FORCE_RECREATE_CLIENTS:-true}\n - OpenIddict__DisableHttpsRequirement=${OPENIDDICT_DISABLE_HTTPS:-true}\n # CORS for Docker environment\n - Cors__Origins=${IDENTITY_CORS_ORIGINS:-https://localhost:3000}\n - ClientApp=${IDENTITY_CLIENT_APP:-https://localhost:3000}\n - BackendUrl=${IDENTITY_BACKEND_URL:-http://localhost:5000}\n # Google Authentication\n - Authentication__Google__ClientId=${GOOGLE_CLIENT_ID}\n - Authentication__Google__ClientSecret=${GOOGLE_CLIENT_SECRET}\n - Authentication__Google__RedirectUri=${GOOGLE_REDIRECT_URI}\n # Email Settings\n - EmailSettings__SMTPServer=${IDENTITY_EMAIL_SMTP_SERVER}\n - EmailSettings__Port=${IDENTITY_EMAIL_PORT}\n - EmailSettings__UseSSL=${IDENTITY_EMAIL_USE_SSL}\n - EmailSettings__Username=${IDENTITY_EMAIL_USERNAME}\n - EmailSettings__Password=${IDENTITY_EMAIL_PASSWORD}\n - EmailSettings__From=${IDENTITY_EMAIL_FROM}\n - EmailSettings__DisplayName=${IDENTITY_EMAIL_DISPLAY_NAME}\n - EmailSettings__ApplicationName=${IDENTITY_EMAIL_APP_NAME}\n - EmailSettings__SupportEmail=${IDENTITY_EMAIL_SUPPORT}\n - EmailSettings__Enabled=${IDENTITY_EMAIL_ENABLED}\n - EmailSettings__EnableVerification=${IDENTITY_EMAIL_ENABLE_VERIFICATION}\n - EmailSettings__TimeoutMs=${IDENTITY_EMAIL_TIMEOUT_MS}\n - EmailSettings__MaxRetryAttempts=${IDENTITY_EMAIL_MAX_RETRY}\n - EmailSettings__LogEmailContent=${IDENTITY_EMAIL_LOG_CONTENT}\n # gRPC Services\n - GrpcServices__OrderService__Address=http://order-service:5006\n # Database Options\n - DatabaseOptions__MaxRetryCount=5\n - DatabaseOptions__MaxRetryDelay=00:00:10\n - DatabaseOptions__CommandTimeout=30\n - DatabaseOptions__EnableRetryOnFailure=true\n - DatabaseOptions__EnableSensitiveDataLogging=${DB_ENABLE_SENSITIVE_LOGGING}\n - DatabaseOptions__EnableDetailedErrors=${DB_ENABLE_DETAILED_ERRORS}\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=identity-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=identity-service,service.version=1.0.0\n - RabbitMq__Url=${RABBITMQ_URL}\n ports:\n - 5000:80\n depends_on:\n - postgres\n - otelcollector\n restart: unless-stopped\n cart-service:\n image: rosie91/stemify:cart-service\n build:\n context: .\n dockerfile: src/Services/CartService/Cart.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5007\n - ConnectionStrings__stemifycart=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_cart\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcProductUrl=http://product-service:5005\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=cart-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=cart-service,service.version=1.0.0\n ports:\n - 7007:80\n - 5007:5007\n depends_on:\n - postgres\n - redis\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n payment-service:\n image: rosie91/stemify:payment-service\n build:\n context: .\n dockerfile: src/Services/PaymentService/Payment.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - Logging__LogLevel__Microsoft.EntityFrameworkCore.Database.Command=Warning\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5008\n - ConnectionStrings__stemifypayment=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_payment\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n # PayOS configuration\n - PaymentGateways__PayOS__ClientId=${PAYOS_CLIENT_ID}\n - PaymentGateways__PayOS__ApiKey=${PAYOS_API_KEY}\n - PaymentGateways__PayOS__ChecksumKey=${PAYOS_CHECKSUM_KEY}\n - PaymentGateways__PayOS__BaseUrl=https://api-merchant.payos.vn\n # Stripe configuration\n - PaymentGateways__Stripe__SecretKey=${STRIPE_SECRET_KEY}\n - PaymentGateways__Stripe__PublishableKey=${STRIPE_PUBLISH_KEY}\n - PaymentGateways__Stripe__WebhookSecret=${STRIPE_WEBHOOK_KEY}\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=payment-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=payment-service,service.version=1.0.0\n ports:\n - 7008:80\n - 5008:5008\n depends_on:\n - postgres\n - redis\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n emulator-service:\n image: rosie91/stemify:emulator-service\n build:\n context: .\n dockerfile: src/Services/EmulatorService/Emulator.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5009\n # MongoDB configuration\n - MongoDb__ConnectionString=mongodb://root:mongopw@mongo\n - MongoDb__DatabaseName=stemify_emulator\n # Redis configuration\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n # gRPC endpoints\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcResourceUrl=http://resource-service:5003\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=emulator-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=emulator-service,service.version=1.0.0\n # Cloudinary Configuration\n - Cloudinary__CloudName=${CLOUDINARY_CLOUD_NAME}\n - Cloudinary__ApiKey=${CLOUDINARY_API_KEY}\n - Cloudinary__ApiSecret=${CLOUDINARY_API_SECRET}\n ports:\n - 7009:80\n - 5009:5009\n depends_on:\n - redis\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n\n # ========================================\n # MONITORING STACK\n # ========================================\n\n # Prometheus - Metrics Collection\n prometheus:\n image: prom/prometheus:v3.5.0\n container_name: stemify-prometheus\n ports:\n - '9090:9090'\n volumes:\n - ./config/prometheus:/etc/prometheus:ro\n - prometheus_data:/prometheus\n command:\n - '--config.file=/etc/prometheus/prometheus.yml'\n - '--storage.tsdb.path=/prometheus'\n - '--storage.tsdb.retention.time=15d'\n - '--storage.tsdb.retention.size=10GB'\n - '--web.enable-lifecycle'\n - '--web.enable-otlp-receiver'\n restart: unless-stopped\n\n # AlertManager - Alert Management\n alertmanager:\n image: prom/alertmanager:v0.27.0\n container_name: stemify-alertmanager\n ports:\n - '9093:9093'\n volumes:\n - ./config/alertmanager:/etc/alertmanager:ro\n - alertmanager_data:/alertmanager\n command:\n - '--config.file=/etc/alertmanager/alertmanager.yml'\n - '--storage.path=/alertmanager'\n - '--web.external-url=http://localhost:9093'\n restart: unless-stopped\n\n # Grafana - Visualization\n grafana:\n image: grafana/grafana:latest\n container_name: stemify-grafana\n ports:\n - '3001:3001'\n volumes:\n - grafana_data:/var/lib/grafana\n - ./config/grafana/config:/etc/grafana:ro\n - ./config/grafana/dashboards:/var/lib/grafana/dashboards:ro\n environment:\n - GF_SECURITY_ADMIN_USER=${GRAFANA_ADMIN_USERNAME}\n - GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_ADMIN_PASSWORD:-admin123}\n - GF_USERS_ALLOW_SIGN_UP=false\n - GF_INSTALL_PLUGINS=grafana-clock-panel,grafana-simple-json-datasource\n - PROMETHEUS_ENDPOINT=http://prometheus:9090\n restart: unless-stopped\n depends_on:\n - prometheus\n\n # OpenTelemetry Collector\n otelcollector:\n image: otel/opentelemetry-collector-contrib:latest\n container_name: stemify-otelcollector\n ports:\n - '8888:8888' # Prometheus metrics\n - '8889:8889' # Prometheus exporter\n - '4317:4317' # OTLP gRPC receiver\n - '4318:4318' # OTLP HTTP receiver\n volumes:\n - ./config/otelcollector:/etc/otelcol-contrib:ro\n environment:\n - PROMETHEUS_ENDPOINT=http://prometheus:9090/api/v1/write\n command: ['--config=/etc/otelcol-contrib/config.yaml']\n restart: unless-stopped\n depends_on:\n - prometheus\n\n # ========================================\n # AI SERVICE & VECTOR DATABASE\n # ========================================\n\n # Qdrant - Vector Database for RAG\n qdrant:\n image: qdrant/qdrant:latest\n container_name: stemify-qdrant\n ports:\n - '6333:6333' # HTTP API\n - '6334:6334' # gRPC\n volumes:\n - qdrant_data:/qdrant/storage\n environment:\n - QDRANT__SERVICE__GRPC_PORT=6334\n restart: unless-stopped\n\n neo4j:\n image: neo4j:5.20\n container_name: stemify-neo4j\n ports:\n - \"7474:7474\" # HTTP\n - \"7687:7687\" # Bolt\n environment:\n - NEO4J_AUTH=neo4j/password\n - NEO4J_PLUGINS=[\"apoc\"]\n - NEO4J_dbms_memory_heap_max__size=2G\n - NEO4J_dbms_memory_pagecache_size=1G\n volumes:\n - neo4j_data:/data\n - neo4j_logs:/logs\n restart: unless-stopped\n healthcheck:\n test: [\"CMD\", \"cypher-shell\", \"-u\", \"neo4j\", \"-p\", \"password\", \"RETURN 1\"]\n interval: 30s\n timeout: 10s\n retries: 3\n start_period: 40s\n\n # AI Service - Python FastAPI with gRPC\n ai-service:\n build:\n context: .\n dockerfile: src/Services/AIService/Dockerfile\n container_name: stemify-ai-service\n environment:\n - PYTHONUNBUFFERED=1\n - ENVIRONMENT=Development\n - QDRANT_URL=http://qdrant:6333\n - QDRANT_GRPC_URL=http://qdrant:6334\n - QDRANT_COLLECTION=classroom_insights\n - NEO4J_URI=bolt://neo4j:7687\n - NEO4J_USER=neo4j\n - NEO4J_PASSWORD=password\n - OPENAI_API_KEY=${OPENAI_API_KEY}\n - OPENAI_BASE_URL=${OPENAI_BASE_URL:-https://api.openai.com/v1}\n - EMBEDDING_MODEL=paraphrase-multilingual-MiniLM-L12-v2\n - LLM_MODEL=gpt-4o-mini\n - LOG_LEVEL=INFO\n # gRPC Configuration\n - GRPC_PORT=5010\n - GRPC_HOST=0.0.0.0\n ports:\n - '7010:80' # HTTP API (FastAPI)\n - '5010:5010' # gRPC\n depends_on:\n - qdrant\n - neo4j\n restart: unless-stopped\n healthcheck:\n test: [\"CMD\", \"curl\", \"-f\", \"http://localhost:80/health\"]\n interval: 30s\n timeout: 10s\n retries: 3\n start_period: 40s\n\nvolumes:\n redis-data:\n postgres_data:\n mongodata:\n prometheus_data:\n alertmanager_data:\n grafana_data:\n qdrant_data:\n neo4j_data:\n neo4j_logs:\n"}, "c8eb177cb0a4abfc4b12ab4203129d57fd3c98d9": {"t": 1785748779.0568216, "n": 22792, "h": "a8399bd457f39c91", "c": "services:\n postgres:\n image: postgres:17\n container_name: stemify-postgres\n environment:\n - POSTGRES_PASSWORD=${DB_PASSWORD}\n ports:\n - '5432:5432'\n volumes:\n - postgres_data:/var/lib/postgresql/data\n restart: unless-stopped\n # RabbitMQ is now using CloudAMQP online service\n # rabbitmq:\n # image: rabbitmq:3-management-alpine\n # ports:\n # - 5672:5672\n # - 15672:15672\n # environment:\n # - RABBITMQ_DEFAULT_USER=guest\n # - RABBITMQ_DEFAULT_PASS=guest\n # healthcheck:\n # test: [\"CMD\", \"rabbitmq-diagnostics\", \"ping\"]\n # interval: 30s\n # timeout: 10s\n # retries: 5\n # start_period: 30s\n # restart: unless-stopped\n mongo:\n image: mongo\n environment:\n - MONGO_INITDB_ROOT_USERNAME=root\n - MONGO_INITDB_ROOT_PASSWORD=mongopw\n ports:\n - 27017:27017\n volumes:\n - mongodata:/var/lib/mongodb/data\n redis:\n image: redis:latest\n ports:\n - 6379:6379\n command: ['redis-server', '--appendonly', 'yes']\n volumes:\n - redis-data:/data\n redis-commander:\n image: rediscommander/redis-commander:latest\n environment:\n - REDIS_HOSTS=local:redis:6379\n - HTTP_USER=root\n - HTTP_PASSWORD=secret\n ports:\n - 8082:8082\n depends_on:\n - redis\n classroom-service:\n image: rosie91/stemify:classroom-service\n build:\n context: .\n dockerfile: src/Services/ClassroomService/Classroom.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5001\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcResourceUrl=http://resource-service:5003\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcOrderUrl=http://order-service:5006\n - ConnectionStrings__stemifyclassroom=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_classroom\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=classroom-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=classroom-service,service.version=1.0.0\n # Cloudinary Configuration\n - Cloudinary__CloudName=${CLOUDINARY_CLOUD_NAME}\n - Cloudinary__ApiKey=${CLOUDINARY_API_KEY}\n - Cloudinary__ApiSecret=${CLOUDINARY_API_SECRET}\n ports:\n - 7001:80\n - 5001:5001\n depends_on:\n - postgres\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n resource-service:\n image: rosie91/stemify:resource-service\n build:\n context: .\n dockerfile: src/Services/Resource/Resource.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5003\n - ConnectionStrings__stemifyresource=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_resource\n - Cloudinary__CloudName=${CLOUDINARY_CLOUD_NAME}\n - Cloudinary__ApiKey=${CLOUDINARY_API_KEY}\n - Cloudinary__ApiSecret=${CLOUDINARY_API_SECRET}\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n - ClientApp=https://localhost:3000\n - Gemini__BaseUrl=https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent\n - Gemini__ApiKey=${GEMINI_API_KEY}\n - Gemini__Model=gemini-2.5-flash\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=resource-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=resource-service,service.version=1.0.0\n ports:\n - 7003:80\n - 5003:5003\n depends_on:\n - postgres\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n product-service:\n image: rosie91/stemify:product-service\n build:\n context: .\n dockerfile: src/Services/ProductService/Product.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5005\n - ConnectionStrings__stemifyproduct=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_product\n - Cloudinary__CloudName=${CLOUDINARY_CLOUD_NAME}\n - Cloudinary__ApiKey=${CLOUDINARY_API_KEY}\n - Cloudinary__ApiSecret=${CLOUDINARY_API_SECRET}\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcResourceUrl=http://resource-service:5003\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=product-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=product-service,service.version=1.0.0\n ports:\n - 7005:80\n - 5005:5005\n depends_on:\n - postgres\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n order-service:\n image: rosie91/stemify:order-service\n build:\n context: .\n dockerfile: src/Services/OrderService/Order.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5006\n - ConnectionStrings__stemifyorder=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_order\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcResourceUrl=http://resource-service:5003\n - GrpcProductUrl=http://product-service:5005\n - GrpcClassroomUrl=http://classroom-service:5001\n - Cloudinary__CloudName=${CLOUDINARY_CLOUD_NAME}\n - Cloudinary__ApiKey=${CLOUDINARY_API_KEY}\n - Cloudinary__ApiSecret=${CLOUDINARY_API_SECRET}\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=order-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=order-service,service.version=1.0.0\n\n ports:\n - 7006:80\n - 5006:5006\n depends_on:\n - postgres\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n notification-service:\n image: rosie91/stemify:notification-service\n build:\n context: .\n dockerfile: src/Services/Notification/Notification.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5004\n - ConnectionStrings__stemifynotification=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_notification\n # SendGrid configuration\n - SendGridSettings__ApiKey=${SENDGRID_API_KEY}\n - SendGridSettings__FromEmail=stemify30062025@gmail.com\n - SendGridSettings__FromName=STEMify\n # Client App configuration\n - ClientApp__BaseUrl=https://localhost:3000\n - ClientApp=https://localhost:3000\n - Issuer=http://localhost:5000/\n - IdentityServiceUrl=http://identity-service\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=notification-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=notification-service,service.version=1.0.0\n ports:\n - 7004:80\n - 5004:5004\n depends_on:\n - postgres\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n gateway-service:\n image: rosie91/stemify:gateway-service\n build:\n context: .\n dockerfile: src/ApiGateways/ApiGateway/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Docker\n - ASPNETCORE_URLS=http://+:80\n - IdentityServiceUrl=http://identity-service\n - Issuer=http://localhost:5000/\n - ClientApp=https://localhost:3000\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=gateway-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=gateway-service,service.version=1.0.0\n ports:\n - 6001:80\n depends_on:\n - otelcollector\n restart: unless-stopped\n identity-api-service:\n image: rosie91/stemify:identity-api-service\n build:\n context: .\n dockerfile: src/Services/Identity/Identity.Api/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5002\n - ConnectionStrings__stemifyidentity=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_identity\n # RabbitMQ Configuration (for MassTransit)\n - RabbitMq__Url=${RABBITMQ_URL}\n # OpenIddict Configuration\n - OpenIddict__Authority=http://identity-service:80\n # Client App Configuration\n - ClientApp=https://localhost:3000\n # Email Settings\n - EmailSettings__From=${IDENTITY_EMAIL_FROM}\n - EmailSettings__DisplayName=${IDENTITY_EMAIL_DISPLAY_NAME}\n - EmailSettings__SMTPServer=${IDENTITY_EMAIL_SMTP_SERVER}\n - EmailSettings__Port=${IDENTITY_EMAIL_PORT}\n - EmailSettings__Username=${IDENTITY_EMAIL_USERNAME}\n - EmailSettings__Password=${IDENTITY_EMAIL_PASSWORD}\n - EmailSettings__UseSSL=${IDENTITY_EMAIL_USE_SSL}\n - EmailSettings__ApplicationName=${IDENTITY_EMAIL_APP_NAME}\n - EmailSettings__SupportEmail=${IDENTITY_EMAIL_SUPPORT}\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=identity-api-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=identity-api-service,service.version=1.0.0\n - GrpcServices__OrderService__Address=http://order-service:5006\n - BackendUrl=${IDENTITY_BACKEND_URL:-http://localhost:5000}\n ports:\n - 7002:80\n - 5002:5002\n depends_on:\n - postgres\n - otelcollector\n restart: unless-stopped\n identity-service:\n image: rosie91/stemify:identity-service\n build:\n context: .\n dockerfile: src/Services/Identity/Identity.Web/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - ASPNETCORE_URLS=http://+:80\n - ConnectionStrings__stemifyidentity=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_identity\n # OpenIddict configuration for Docker\n - OpenIddict__Issuer=http://localhost:5000\n - OpenIddict__Authority=http://localhost:5000\n - OpenIddict__SigningKey=${OPENIDDICT_SIGNING_KEY}\n - OpenIddict__EncryptionKey=${OPENIDDICT_ENCRYPTION_KEY}\n - OpenIddict__ApiClientSecret=${OPENIDDICT_API_CLIENT_SECRET}\n - OpenIddict__ForceRecreateClients=${OPENIDDICT_FORCE_RECREATE_CLIENTS:-true}\n - OpenIddict__DisableHttpsRequirement=${OPENIDDICT_DISABLE_HTTPS:-true}\n # CORS for Docker environment\n - Cors__Origins=${IDENTITY_CORS_ORIGINS:-https://localhost:3000}\n - ClientApp=${IDENTITY_CLIENT_APP:-https://localhost:3000}\n - BackendUrl=${IDENTITY_BACKEND_URL:-http://localhost:5000}\n # Google Authentication\n - Authentication__Google__ClientId=${GOOGLE_CLIENT_ID}\n - Authentication__Google__ClientSecret=${GOOGLE_CLIENT_SECRET}\n - Authentication__Google__RedirectUri=${GOOGLE_REDIRECT_URI}\n # Email Settings\n - EmailSettings__SMTPServer=${IDENTITY_EMAIL_SMTP_SERVER}\n - EmailSettings__Port=${IDENTITY_EMAIL_PORT}\n - EmailSettings__UseSSL=${IDENTITY_EMAIL_USE_SSL}\n - EmailSettings__Username=${IDENTITY_EMAIL_USERNAME}\n - EmailSettings__Password=${IDENTITY_EMAIL_PASSWORD}\n - EmailSettings__From=${IDENTITY_EMAIL_FROM}\n - EmailSettings__DisplayName=${IDENTITY_EMAIL_DISPLAY_NAME}\n - EmailSettings__ApplicationName=${IDENTITY_EMAIL_APP_NAME}\n - EmailSettings__SupportEmail=${IDENTITY_EMAIL_SUPPORT}\n - EmailSettings__Enabled=${IDENTITY_EMAIL_ENABLED}\n - EmailSettings__EnableVerification=${IDENTITY_EMAIL_ENABLE_VERIFICATION}\n - EmailSettings__TimeoutMs=${IDENTITY_EMAIL_TIMEOUT_MS}\n - EmailSettings__MaxRetryAttempts=${IDENTITY_EMAIL_MAX_RETRY}\n - EmailSettings__LogEmailContent=${IDENTITY_EMAIL_LOG_CONTENT}\n # gRPC Services\n - GrpcServices__OrderService__Address=http://order-service:5006\n # Database Options\n - DatabaseOptions__MaxRetryCount=5\n - DatabaseOptions__MaxRetryDelay=00:00:10\n - DatabaseOptions__CommandTimeout=30\n - DatabaseOptions__EnableRetryOnFailure=true\n - DatabaseOptions__EnableSensitiveDataLogging=${DB_ENABLE_SENSITIVE_LOGGING}\n - DatabaseOptions__EnableDetailedErrors=${DB_ENABLE_DETAILED_ERRORS}\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=identity-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=identity-service,service.version=1.0.0\n - RabbitMq__Url=${RABBITMQ_URL}\n ports:\n - 5000:80\n depends_on:\n - postgres\n - otelcollector\n restart: unless-stopped\n cart-service:\n image: rosie91/stemify:cart-service\n build:\n context: .\n dockerfile: src/Services/CartService/Cart.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5007\n - ConnectionStrings__stemifycart=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_cart\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcProductUrl=http://product-service:5005\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=cart-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=cart-service,service.version=1.0.0\n ports:\n - 7007:80\n - 5007:5007\n depends_on:\n - postgres\n - redis\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n payment-service:\n image: rosie91/stemify:payment-service\n build:\n context: .\n dockerfile: src/Services/PaymentService/Payment.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - Logging__LogLevel__Microsoft.EntityFrameworkCore.Database.Command=Warning\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5008\n - ConnectionStrings__stemifypayment=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_payment\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n # PayOS configuration\n - PaymentGateways__PayOS__ClientId=${PAYOS_CLIENT_ID}\n - PaymentGateways__PayOS__ApiKey=${PAYOS_API_KEY}\n - PaymentGateways__PayOS__ChecksumKey=${PAYOS_CHECKSUM_KEY}\n - PaymentGateways__PayOS__BaseUrl=https://api-merchant.payos.vn\n # Stripe configuration\n - PaymentGateways__Stripe__SecretKey=${STRIPE_SECRET_KEY}\n - PaymentGateways__Stripe__PublishableKey=${STRIPE_PUBLISH_KEY}\n - PaymentGateways__Stripe__WebhookSecret=${STRIPE_WEBHOOK_KEY}\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=payment-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=payment-service,service.version=1.0.0\n ports:\n - 7008:80\n - 5008:5008\n depends_on:\n - postgres\n - redis\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n emulator-service:\n image: rosie91/stemify:emulator-service\n build:\n context: .\n dockerfile: src/Services/EmulatorService/Emulator.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5009\n # MongoDB configuration\n - MongoDb__ConnectionString=mongodb://root:mongopw@mongo\n - MongoDb__DatabaseName=stemify_emulator\n # Redis configuration\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n # gRPC endpoints\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcResourceUrl=http://resource-service:5003\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=emulator-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=emulator-service,service.version=1.0.0\n # Cloudinary Configuration\n - Cloudinary__CloudName=${CLOUDINARY_CLOUD_NAME}\n - Cloudinary__ApiKey=${CLOUDINARY_API_KEY}\n - Cloudinary__ApiSecret=${CLOUDINARY_API_SECRET}\n ports:\n - 7009:80\n - 5009:5009\n depends_on:\n - redis\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n\n # ========================================\n # MONITORING STACK\n # ========================================\n\n # Prometheus - Metrics Collection\n prometheus:\n image: prom/prometheus:v3.5.0\n container_name: stemify-prometheus\n ports:\n - '9090:9090'\n volumes:\n - ./config/prometheus:/etc/prometheus:ro\n - prometheus_data:/prometheus\n command:\n - '--config.file=/etc/prometheus/prometheus.yml'\n - '--storage.tsdb.path=/prometheus'\n - '--storage.tsdb.retention.time=15d'\n - '--storage.tsdb.retention.size=10GB'\n - '--web.enable-lifecycle'\n - '--web.enable-otlp-receiver'\n restart: unless-stopped\n\n # AlertManager - Alert Management\n alertmanager:\n image: prom/alertmanager:v0.27.0\n container_name: stemify-alertmanager\n ports:\n - '9093:9093'\n volumes:\n - ./config/alertmanager:/etc/alertmanager:ro\n - alertmanager_data:/alertmanager\n command:\n - '--config.file=/etc/alertmanager/alertmanager.yml'\n - '--storage.path=/alertmanager'\n - '--web.external-url=http://localhost:9093'\n restart: unless-stopped\n\n # Grafana - Visualization\n grafana:\n image: grafana/grafana:latest\n container_name: stemify-grafana\n ports:\n - '3001:3001'\n volumes:\n - grafana_data:/var/lib/grafana\n - ./config/grafana/config:/etc/grafana:ro\n - ./config/grafana/dashboards:/var/lib/grafana/dashboards:ro\n environment:\n - GF_SECURITY_ADMIN_USER=${GRAFANA_ADMIN_USERNAME}\n - GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_ADMIN_PASSWORD:-admin123}\n - GF_USERS_ALLOW_SIGN_UP=false\n - GF_INSTALL_PLUGINS=grafana-clock-panel,grafana-simple-json-datasource\n - PROMETHEUS_ENDPOINT=http://prometheus:9090\n restart: unless-stopped\n depends_on:\n - prometheus\n\n # OpenTelemetry Collector\n otelcollector:\n image: otel/opentelemetry-collector-contrib:latest\n container_name: stemify-otelcollector\n ports:\n - '8888:8888' # Prometheus metrics\n - '8889:8889' # Prometheus exporter\n - '4317:4317' # OTLP gRPC receiver\n - '4318:4318' # OTLP HTTP receiver\n volumes:\n - ./config/otelcollector:/etc/otelcol-contrib:ro\n environment:\n - PROMETHEUS_ENDPOINT=http://prometheus:9090/api/v1/write\n command: ['--config=/etc/otelcol-contrib/config.yaml']\n restart: unless-stopped\n depends_on:\n - prometheus\n\n # ========================================\n # AI SERVICE & VECTOR DATABASE\n # ========================================\n\n # Qdrant - Vector Database for RAG\n qdrant:\n image: qdrant/qdrant:latest\n container_name: stemify-qdrant\n ports:\n - '6333:6333' # HTTP API\n - '6334:6334' # gRPC\n volumes:\n - qdrant_data:/qdrant/storage\n environment:\n - QDRANT__SERVICE__GRPC_PORT=6334\n restart: unless-stopped\n\n # AI Service - Python FastAPI with gRPC\n ai-service:\n build:\n context: .\n dockerfile: src/Services/AIService/Dockerfile\n container_name: stemify-ai-service\n environment:\n - PYTHONUNBUFFERED=1\n - ENVIRONMENT=Development\n - QDRANT_URL=http://qdrant:6333\n - QDRANT_GRPC_URL=http://qdrant:6334\n - OPENAI_API_KEY=${OPENAI_API_KEY}\n - OPENAI_BASE_URL=${OPENAI_BASE_URL:-https://api.openai.com/v1}\n - LOG_LEVEL=INFO\n # gRPC Configuration\n - GRPC_PORT=5010\n - GRPC_HOST=0.0.0.0\n ports:\n - '7010:80' # HTTP API (FastAPI)\n - '5010:5010' # gRPC\n depends_on:\n - qdrant\n restart: unless-stopped\n healthcheck:\n test: [\"CMD\", \"curl\", \"-f\", \"http://localhost:80/health\"]\n interval: 30s\n timeout: 10s\n retries: 3\n start_period: 40s\n\nvolumes:\n redis-data:\n postgres_data:\n mongodata:\n prometheus_data:\n alertmanager_data:\n grafana_data:\n qdrant_data:\n"}, "884667b373eb0b2f080be198db869b9349f90224": {"t": 1785748784.986926, "n": 21719, "h": "6de66f57648aca74", "c": "services:\n postgres:\n image: postgres:17\n container_name: stemify-postgres\n environment:\n - POSTGRES_PASSWORD=${DB_PASSWORD}\n ports:\n - '5432:5432'\n volumes:\n - postgres_data:/var/lib/postgresql/data\n restart: unless-stopped\n # RabbitMQ is now using CloudAMQP online service\n # rabbitmq:\n # image: rabbitmq:3-management-alpine\n # ports:\n # - 5672:5672\n # - 15672:15672\n # environment:\n # - RABBITMQ_DEFAULT_USER=guest\n # - RABBITMQ_DEFAULT_PASS=guest\n # healthcheck:\n # test: [\"CMD\", \"rabbitmq-diagnostics\", \"ping\"]\n # interval: 30s\n # timeout: 10s\n # retries: 5\n # start_period: 30s\n # restart: unless-stopped\n mongo:\n image: mongo\n environment:\n - MONGO_INITDB_ROOT_USERNAME=root\n - MONGO_INITDB_ROOT_PASSWORD=mongopw\n ports:\n - 27017:27017\n volumes:\n - mongodata:/var/lib/mongodb/data\n redis:\n image: redis:latest\n ports:\n - 6379:6379\n command: ['redis-server', '--appendonly', 'yes']\n volumes:\n - redis-data:/data\n redis-commander:\n image: rediscommander/redis-commander:latest\n environment:\n - REDIS_HOSTS=local:redis:6379\n - HTTP_USER=root\n - HTTP_PASSWORD=secret\n ports:\n - 8082:8082\n depends_on:\n - redis\n classroom-service:\n image: rosie91/stemify:classroom-service\n build:\n context: .\n dockerfile: src/Services/ClassroomService/Classroom.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5001\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcResourceUrl=http://resource-service:5003\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcOrderUrl=http://order-service:5006\n - ConnectionStrings__stemifyclassroom=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_classroom\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=classroom-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=classroom-service,service.version=1.0.0\n # Cloudinary Configuration\n - Cloudinary__CloudName=${CLOUDINARY_CLOUD_NAME}\n - Cloudinary__ApiKey=${CLOUDINARY_API_KEY}\n - Cloudinary__ApiSecret=${CLOUDINARY_API_SECRET}\n ports:\n - 7001:80\n - 5001:5001\n depends_on:\n - postgres\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n resource-service:\n image: rosie91/stemify:resource-service\n build:\n context: .\n dockerfile: src/Services/Resource/Resource.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5003\n - ConnectionStrings__stemifyresource=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_resource\n - Cloudinary__CloudName=${CLOUDINARY_CLOUD_NAME}\n - Cloudinary__ApiKey=${CLOUDINARY_API_KEY}\n - Cloudinary__ApiSecret=${CLOUDINARY_API_SECRET}\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n - ClientApp=https://localhost:3000\n - Gemini__BaseUrl=https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent\n - Gemini__ApiKey=${GEMINI_API_KEY}\n - Gemini__Model=gemini-2.5-flash\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=resource-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=resource-service,service.version=1.0.0\n ports:\n - 7003:80\n - 5003:5003\n depends_on:\n - postgres\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n product-service:\n image: rosie91/stemify:product-service\n build:\n context: .\n dockerfile: src/Services/ProductService/Product.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5005\n - ConnectionStrings__stemifyproduct=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_product\n - Cloudinary__CloudName=${CLOUDINARY_CLOUD_NAME}\n - Cloudinary__ApiKey=${CLOUDINARY_API_KEY}\n - Cloudinary__ApiSecret=${CLOUDINARY_API_SECRET}\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcResourceUrl=http://resource-service:5003\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=product-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=product-service,service.version=1.0.0\n ports:\n - 7005:80\n - 5005:5005\n depends_on:\n - postgres\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n order-service:\n image: rosie91/stemify:order-service\n build:\n context: .\n dockerfile: src/Services/OrderService/Order.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5006\n - ConnectionStrings__stemifyorder=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_order\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcResourceUrl=http://resource-service:5003\n - GrpcProductUrl=http://product-service:5005\n - GrpcClassroomUrl=http://classroom-service:5001\n - Cloudinary__CloudName=${CLOUDINARY_CLOUD_NAME}\n - Cloudinary__ApiKey=${CLOUDINARY_API_KEY}\n - Cloudinary__ApiSecret=${CLOUDINARY_API_SECRET}\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=order-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=order-service,service.version=1.0.0\n\n ports:\n - 7006:80\n - 5006:5006\n depends_on:\n - postgres\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n notification-service:\n image: rosie91/stemify:notification-service\n build:\n context: .\n dockerfile: src/Services/Notification/Notification.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5004\n - ConnectionStrings__stemifynotification=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_notification\n # SendGrid configuration\n - SendGridSettings__ApiKey=${SENDGRID_API_KEY}\n - SendGridSettings__FromEmail=stemify30062025@gmail.com\n - SendGridSettings__FromName=STEMify\n # Client App configuration\n - ClientApp__BaseUrl=https://localhost:3000\n - ClientApp=https://localhost:3000\n - Issuer=http://localhost:5000/\n - IdentityServiceUrl=http://identity-service\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=notification-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=notification-service,service.version=1.0.0\n ports:\n - 7004:80\n - 5004:5004\n depends_on:\n - postgres\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n gateway-service:\n image: rosie91/stemify:gateway-service\n build:\n context: .\n dockerfile: src/ApiGateways/ApiGateway/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Docker\n - ASPNETCORE_URLS=http://+:80\n - IdentityServiceUrl=http://identity-service\n - Issuer=http://localhost:5000/\n - ClientApp=https://localhost:3000\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=gateway-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=gateway-service,service.version=1.0.0\n ports:\n - 6001:80\n depends_on:\n - otelcollector\n restart: unless-stopped\n identity-api-service:\n image: rosie91/stemify:identity-api-service\n build:\n context: .\n dockerfile: src/Services/Identity/Identity.Api/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5002\n - ConnectionStrings__stemifyidentity=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_identity\n # RabbitMQ Configuration (for MassTransit)\n - RabbitMq__Url=${RABBITMQ_URL}\n # OpenIddict Configuration\n - OpenIddict__Authority=http://identity-service:80\n # Client App Configuration\n - ClientApp=https://localhost:3000\n # Email Settings\n - EmailSettings__From=${IDENTITY_EMAIL_FROM}\n - EmailSettings__DisplayName=${IDENTITY_EMAIL_DISPLAY_NAME}\n - EmailSettings__SMTPServer=${IDENTITY_EMAIL_SMTP_SERVER}\n - EmailSettings__Port=${IDENTITY_EMAIL_PORT}\n - EmailSettings__Username=${IDENTITY_EMAIL_USERNAME}\n - EmailSettings__Password=${IDENTITY_EMAIL_PASSWORD}\n - EmailSettings__UseSSL=${IDENTITY_EMAIL_USE_SSL}\n - EmailSettings__ApplicationName=${IDENTITY_EMAIL_APP_NAME}\n - EmailSettings__SupportEmail=${IDENTITY_EMAIL_SUPPORT}\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=identity-api-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=identity-api-service,service.version=1.0.0\n - GrpcServices__OrderService__Address=http://order-service:5006\n # Google Authentication\n - Authentication__Google__ClientId=${GOOGLE_CLIENT_ID}\n - Authentication__Google__ClientSecret=${GOOGLE_CLIENT_SECRET}\n - Authentication__Google__RedirectUri=${GOOGLE_REDIRECT_URI}\n - BackendUrl=${IDENTITY_BACKEND_URL:-http://localhost:6001}\n ports:\n - 7002:80\n - 5002:5002\n depends_on:\n - postgres\n - otelcollector\n restart: unless-stopped\n identity-service:\n image: rosie91/stemify:identity-service\n build:\n context: .\n dockerfile: src/Services/Identity/Identity.Web/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - ASPNETCORE_URLS=http://+:80\n - ConnectionStrings__stemifyidentity=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_identity\n # OpenIddict configuration for Docker\n - OpenIddict__Issuer=http://localhost:5000\n - OpenIddict__Authority=http://localhost:5000\n - OpenIddict__SigningKey=${OPENIDDICT_SIGNING_KEY}\n - OpenIddict__EncryptionKey=${OPENIDDICT_ENCRYPTION_KEY}\n - OpenIddict__ApiClientSecret=${OPENIDDICT_API_CLIENT_SECRET}\n - OpenIddict__ForceRecreateClients=${OPENIDDICT_FORCE_RECREATE_CLIENTS:-true}\n - OpenIddict__DisableHttpsRequirement=${OPENIDDICT_DISABLE_HTTPS:-true}\n # CORS for Docker environment\n - Cors__Origins=${IDENTITY_CORS_ORIGINS:-https://localhost:3000}\n - ClientApp=${IDENTITY_CLIENT_APP:-https://localhost:3000}\n - BackendUrl=${IDENTITY_BACKEND_URL:-http://localhost:5000}\n # Google Authentication\n - Authentication__Google__ClientId=${GOOGLE_CLIENT_ID}\n - Authentication__Google__ClientSecret=${GOOGLE_CLIENT_SECRET}\n - Authentication__Google__RedirectUri=${GOOGLE_REDIRECT_URI}\n # Email Settings\n - EmailSettings__SMTPServer=${IDENTITY_EMAIL_SMTP_SERVER}\n - EmailSettings__Port=${IDENTITY_EMAIL_PORT}\n - EmailSettings__UseSSL=${IDENTITY_EMAIL_USE_SSL}\n - EmailSettings__Username=${IDENTITY_EMAIL_USERNAME}\n - EmailSettings__Password=${IDENTITY_EMAIL_PASSWORD}\n - EmailSettings__From=${IDENTITY_EMAIL_FROM}\n - EmailSettings__DisplayName=${IDENTITY_EMAIL_DISPLAY_NAME}\n - EmailSettings__ApplicationName=${IDENTITY_EMAIL_APP_NAME}\n - EmailSettings__SupportEmail=${IDENTITY_EMAIL_SUPPORT}\n - EmailSettings__Enabled=${IDENTITY_EMAIL_ENABLED}\n - EmailSettings__EnableVerification=${IDENTITY_EMAIL_ENABLE_VERIFICATION}\n - EmailSettings__TimeoutMs=${IDENTITY_EMAIL_TIMEOUT_MS}\n - EmailSettings__MaxRetryAttempts=${IDENTITY_EMAIL_MAX_RETRY}\n - EmailSettings__LogEmailContent=${IDENTITY_EMAIL_LOG_CONTENT}\n # gRPC Services\n - GrpcServices__OrderService__Address=http://order-service:5006\n # Database Options\n - DatabaseOptions__MaxRetryCount=5\n - DatabaseOptions__MaxRetryDelay=00:00:10\n - DatabaseOptions__CommandTimeout=30\n - DatabaseOptions__EnableRetryOnFailure=true\n - DatabaseOptions__EnableSensitiveDataLogging=${DB_ENABLE_SENSITIVE_LOGGING}\n - DatabaseOptions__EnableDetailedErrors=${DB_ENABLE_DETAILED_ERRORS}\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=identity-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=identity-service,service.version=1.0.0\n - RabbitMq__Url=${RABBITMQ_URL}\n ports:\n - 5000:80\n depends_on:\n - postgres\n - otelcollector\n restart: unless-stopped\n cart-service:\n image: rosie91/stemify:cart-service\n build:\n context: .\n dockerfile: src/Services/CartService/Cart.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5007\n - ConnectionStrings__stemifycart=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_cart\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcProductUrl=http://product-service:5005\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=cart-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=cart-service,service.version=1.0.0\n ports:\n - 7007:80\n - 5007:5007\n depends_on:\n - postgres\n - redis\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n payment-service:\n image: rosie91/stemify:payment-service\n build:\n context: .\n dockerfile: src/Services/PaymentService/Payment.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - Logging__LogLevel__Microsoft.EntityFrameworkCore.Database.Command=Warning\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5008\n - ConnectionStrings__stemifypayment=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_payment\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n # PayOS configuration\n - PaymentGateways__PayOS__ClientId=${PAYOS_CLIENT_ID}\n - PaymentGateways__PayOS__ApiKey=${PAYOS_API_KEY}\n - PaymentGateways__PayOS__ChecksumKey=${PAYOS_CHECKSUM_KEY}\n - PaymentGateways__PayOS__BaseUrl=https://api-merchant.payos.vn\n # Stripe configuration\n - PaymentGateways__Stripe__SecretKey=${STRIPE_SECRET_KEY}\n - PaymentGateways__Stripe__PublishableKey=${STRIPE_PUBLISH_KEY}\n - PaymentGateways__Stripe__WebhookSecret=${STRIPE_WEBHOOK_KEY}\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=payment-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=payment-service,service.version=1.0.0\n ports:\n - 7008:80\n - 5008:5008\n depends_on:\n - postgres\n - redis\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n emulator-service:\n image: rosie91/stemify:emulator-service\n build:\n context: .\n dockerfile: src/Services/EmulatorService/Emulator.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5009\n # MongoDB configuration\n - MongoDb__ConnectionString=mongodb://root:mongopw@mongo\n - MongoDb__DatabaseName=stemify_emulator\n # Redis configuration\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n # gRPC endpoints\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcResourceUrl=http://resource-service:5003\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=emulator-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=emulator-service,service.version=1.0.0\n # Cloudinary Configuration\n - Cloudinary__CloudName=${CLOUDINARY_CLOUD_NAME}\n - Cloudinary__ApiKey=${CLOUDINARY_API_KEY}\n - Cloudinary__ApiSecret=${CLOUDINARY_API_SECRET}\n ports:\n - 7009:80\n - 5009:5009\n depends_on:\n - redis\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n\n # ========================================\n # MONITORING STACK\n # ========================================\n\n # Prometheus - Metrics Collection\n prometheus:\n image: prom/prometheus:v3.5.0\n container_name: stemify-prometheus\n ports:\n - '9090:9090'\n volumes:\n - ./config/prometheus:/etc/prometheus:ro\n - prometheus_data:/prometheus\n command:\n - '--config.file=/etc/prometheus/prometheus.yml'\n - '--storage.tsdb.path=/prometheus'\n - '--storage.tsdb.retention.time=15d'\n - '--storage.tsdb.retention.size=10GB'\n - '--web.enable-lifecycle'\n - '--web.enable-otlp-receiver'\n restart: unless-stopped\n\n # AlertManager - Alert Management\n alertmanager:\n image: prom/alertmanager:v0.27.0\n container_name: stemify-alertmanager\n ports:\n - '9093:9093'\n volumes:\n - ./config/alertmanager:/etc/alertmanager:ro\n - alertmanager_data:/alertmanager\n command:\n - '--config.file=/etc/alertmanager/alertmanager.yml'\n - '--storage.path=/alertmanager'\n - '--web.external-url=http://localhost:9093'\n restart: unless-stopped\n\n # Grafana - Visualization\n grafana:\n image: grafana/grafana:latest\n container_name: stemify-grafana\n ports:\n - '3001:3001'\n volumes:\n - grafana_data:/var/lib/grafana\n - ./config/grafana/config:/etc/grafana:ro\n - ./config/grafana/dashboards:/var/lib/grafana/dashboards:ro\n environment:\n - GF_SECURITY_ADMIN_USER=${GRAFANA_ADMIN_USERNAME}\n - GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_ADMIN_PASSWORD:-admin123}\n - GF_USERS_ALLOW_SIGN_UP=false\n - GF_INSTALL_PLUGINS=grafana-clock-panel,grafana-simple-json-datasource\n - PROMETHEUS_ENDPOINT=http://prometheus:9090\n restart: unless-stopped\n depends_on:\n - prometheus\n\n # OpenTelemetry Collector\n otelcollector:\n image: otel/opentelemetry-collector-contrib:latest\n container_name: stemify-otelcollector\n ports:\n - '8888:8888' # Prometheus metrics\n - '8889:8889' # Prometheus exporter\n - '4317:4317' # OTLP gRPC receiver\n - '4318:4318' # OTLP HTTP receiver\n volumes:\n - ./config/otelcollector:/etc/otelcol-contrib:ro\n environment:\n - PROMETHEUS_ENDPOINT=http://prometheus:9090/api/v1/write\n command: ['--config=/etc/otelcol-contrib/config.yaml']\n restart: unless-stopped\n depends_on:\n - prometheus\nvolumes:\n redis-data:\n postgres_data:\n mongodata:\n prometheus_data:\n alertmanager_data:\n grafana_data:\n"}, "e207387c6ae9ff46bd3306ee0aa6cbe0d0922b11": {"t": 1785748786.2357135, "n": 21723, "h": "daf0e0694bffe368", "c": "services:\n postgres:\n image: postgres:17\n container_name: stemify-postgres\n environment:\n - POSTGRES_PASSWORD=${DB_PASSWORD}\n ports:\n - '5432:5432'\n volumes:\n - postgres_data:/var/lib/postgresql/data\n restart: unless-stopped\n # RabbitMQ is now using CloudAMQP online service\n # rabbitmq:\n # image: rabbitmq:3-management-alpine\n # ports:\n # - 5672:5672\n # - 15672:15672\n # environment:\n # - RABBITMQ_DEFAULT_USER=guest\n # - RABBITMQ_DEFAULT_PASS=guest\n # healthcheck:\n # test: [\"CMD\", \"rabbitmq-diagnostics\", \"ping\"]\n # interval: 30s\n # timeout: 10s\n # retries: 5\n # start_period: 30s\n # restart: unless-stopped\n mongo:\n image: mongo\n environment:\n - MONGO_INITDB_ROOT_USERNAME=root\n - MONGO_INITDB_ROOT_PASSWORD=mongopw\n ports:\n - 27017:27017\n volumes:\n - mongodata:/var/lib/mongodb/data\n redis:\n image: redis:latest\n ports:\n - 6379:6379\n command: [ 'redis-server', '--appendonly', 'yes' ]\n volumes:\n - redis-data:/data\n redis-commander:\n image: rediscommander/redis-commander:latest\n environment:\n - REDIS_HOSTS=local:redis:6379\n - HTTP_USER=root\n - HTTP_PASSWORD=secret\n ports:\n - 8082:8082\n depends_on:\n - redis\n classroom-service:\n image: rosie91/stemify:classroom-service\n build:\n context: .\n dockerfile: src/Services/ClassroomService/Classroom.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5001\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcResourceUrl=http://resource-service:5003\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcOrderUrl=http://order-service:5006\n - ConnectionStrings__stemifyclassroom=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_classroom\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=classroom-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=classroom-service,service.version=1.0.0\n # Cloudinary Configuration\n - Cloudinary__CloudName=${CLOUDINARY_CLOUD_NAME}\n - Cloudinary__ApiKey=${CLOUDINARY_API_KEY}\n - Cloudinary__ApiSecret=${CLOUDINARY_API_SECRET}\n ports:\n - 7001:80\n - 5001:5001\n depends_on:\n - postgres\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n resource-service:\n image: rosie91/stemify:resource-service\n build:\n context: .\n dockerfile: src/Services/Resource/Resource.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5003\n - ConnectionStrings__stemifyresource=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_resource\n - Cloudinary__CloudName=${CLOUDINARY_CLOUD_NAME}\n - Cloudinary__ApiKey=${CLOUDINARY_API_KEY}\n - Cloudinary__ApiSecret=${CLOUDINARY_API_SECRET}\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n - ClientApp=https://localhost:3000\n - Gemini__BaseUrl=https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent\n - Gemini__ApiKey=${GEMINI_API_KEY}\n - Gemini__Model=gemini-2.5-flash\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=resource-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=resource-service,service.version=1.0.0\n ports:\n - 7003:80\n - 5003:5003\n depends_on:\n - postgres\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n product-service:\n image: rosie91/stemify:product-service\n build:\n context: .\n dockerfile: src/Services/ProductService/Product.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5005\n - ConnectionStrings__stemifyproduct=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_product\n - Cloudinary__CloudName=${CLOUDINARY_CLOUD_NAME}\n - Cloudinary__ApiKey=${CLOUDINARY_API_KEY}\n - Cloudinary__ApiSecret=${CLOUDINARY_API_SECRET}\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcResourceUrl=http://resource-service:5003\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=product-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=product-service,service.version=1.0.0\n ports:\n - 7005:80\n - 5005:5005\n depends_on:\n - postgres\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n order-service:\n image: rosie91/stemify:order-service\n build:\n context: .\n dockerfile: src/Services/OrderService/Order.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5006\n - ConnectionStrings__stemifyorder=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_order\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcResourceUrl=http://resource-service:5003\n - GrpcProductUrl=http://product-service:5005\n - GrpcClassroomUrl=http://classroom-service:5001\n - Cloudinary__CloudName=${CLOUDINARY_CLOUD_NAME}\n - Cloudinary__ApiKey=${CLOUDINARY_API_KEY}\n - Cloudinary__ApiSecret=${CLOUDINARY_API_SECRET}\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=order-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=order-service,service.version=1.0.0\n\n ports:\n - 7006:80\n - 5006:5006\n depends_on:\n - postgres\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n notification-service:\n image: rosie91/stemify:notification-service\n build:\n context: .\n dockerfile: src/Services/Notification/Notification.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5004\n - ConnectionStrings__stemifynotification=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_notification\n # SendGrid configuration\n - SendGridSettings__ApiKey=${SENDGRID_API_KEY}\n - SendGridSettings__FromEmail=stemify30062025@gmail.com\n - SendGridSettings__FromName=STEMify\n # Client App configuration\n - ClientApp__BaseUrl=https://localhost:3000\n - ClientApp=https://localhost:3000\n - Issuer=http://localhost:5000/\n - IdentityServiceUrl=http://identity-service\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=notification-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=notification-service,service.version=1.0.0\n ports:\n - 7004:80\n - 5004:5004\n depends_on:\n - postgres\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n gateway-service:\n image: rosie91/stemify:gateway-service\n build:\n context: .\n dockerfile: src/ApiGateways/ApiGateway/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Docker\n - ASPNETCORE_URLS=http://+:80\n - IdentityServiceUrl=http://identity-service\n - Issuer=http://localhost:5000/\n - ClientApp=https://localhost:3000\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=gateway-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=gateway-service,service.version=1.0.0\n ports:\n - 6001:80\n depends_on:\n - otelcollector\n restart: unless-stopped\n identity-api-service:\n image: rosie91/stemify:identity-api-service\n build:\n context: .\n dockerfile: src/Services/Identity/Identity.Api/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5002\n - ConnectionStrings__stemifyidentity=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_identity\n # RabbitMQ Configuration (for MassTransit)\n - RabbitMq__Url=${RABBITMQ_URL}\n # OpenIddict Configuration\n - OpenIddict__Authority=http://identity-service:80\n # Client App Configuration\n - ClientApp=https://localhost:3000\n # Email Settings\n - EmailSettings__From=${IDENTITY_EMAIL_FROM}\n - EmailSettings__DisplayName=${IDENTITY_EMAIL_DISPLAY_NAME}\n - EmailSettings__SMTPServer=${IDENTITY_EMAIL_SMTP_SERVER}\n - EmailSettings__Port=${IDENTITY_EMAIL_PORT}\n - EmailSettings__Username=${IDENTITY_EMAIL_USERNAME}\n - EmailSettings__Password=${IDENTITY_EMAIL_PASSWORD}\n - EmailSettings__UseSSL=${IDENTITY_EMAIL_USE_SSL}\n - EmailSettings__ApplicationName=${IDENTITY_EMAIL_APP_NAME}\n - EmailSettings__SupportEmail=${IDENTITY_EMAIL_SUPPORT}\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=identity-api-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=identity-api-service,service.version=1.0.0\n - GrpcServices__OrderService__Address=http://order-service:5006\n # Google Authentication\n - Authentication__Google__ClientId=${GOOGLE_CLIENT_ID}\n - Authentication__Google__ClientSecret=${GOOGLE_CLIENT_SECRET}\n - Authentication__Google__RedirectUri=${GOOGLE_REDIRECT_URI}\n - BackendUrl=${IDENTITY_BACKEND_URL:-http://localhost:6001}\n ports:\n - 7002:80\n - 5002:5002\n depends_on:\n - postgres\n - otelcollector\n restart: unless-stopped\n identity-service:\n image: rosie91/stemify:identity-service\n build:\n context: .\n dockerfile: src/Services/Identity/Identity.Web/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - ASPNETCORE_URLS=http://+:80\n - ConnectionStrings__stemifyidentity=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_identity\n # OpenIddict configuration for Docker\n - OpenIddict__Issuer=http://localhost:5000\n - OpenIddict__Authority=http://localhost:5000\n - OpenIddict__SigningKey=${OPENIDDICT_SIGNING_KEY}\n - OpenIddict__EncryptionKey=${OPENIDDICT_ENCRYPTION_KEY}\n - OpenIddict__ApiClientSecret=${OPENIDDICT_API_CLIENT_SECRET}\n - OpenIddict__ForceRecreateClients=${OPENIDDICT_FORCE_RECREATE_CLIENTS:-true}\n - OpenIddict__DisableHttpsRequirement=${OPENIDDICT_DISABLE_HTTPS:-true}\n # CORS for Docker environment\n - Cors__Origins=${IDENTITY_CORS_ORIGINS:-https://localhost:3000}\n - ClientApp=${IDENTITY_CLIENT_APP:-https://localhost:3000}\n - BackendUrl=${IDENTITY_BACKEND_URL:-http://localhost:5000}\n # Google Authentication\n - Authentication__Google__ClientId=${GOOGLE_CLIENT_ID}\n - Authentication__Google__ClientSecret=${GOOGLE_CLIENT_SECRET}\n - Authentication__Google__RedirectUri=${GOOGLE_REDIRECT_URI}\n # Email Settings\n - EmailSettings__SMTPServer=${IDENTITY_EMAIL_SMTP_SERVER}\n - EmailSettings__Port=${IDENTITY_EMAIL_PORT}\n - EmailSettings__UseSSL=${IDENTITY_EMAIL_USE_SSL}\n - EmailSettings__Username=${IDENTITY_EMAIL_USERNAME}\n - EmailSettings__Password=${IDENTITY_EMAIL_PASSWORD}\n - EmailSettings__From=${IDENTITY_EMAIL_FROM}\n - EmailSettings__DisplayName=${IDENTITY_EMAIL_DISPLAY_NAME}\n - EmailSettings__ApplicationName=${IDENTITY_EMAIL_APP_NAME}\n - EmailSettings__SupportEmail=${IDENTITY_EMAIL_SUPPORT}\n - EmailSettings__Enabled=${IDENTITY_EMAIL_ENABLED}\n - EmailSettings__EnableVerification=${IDENTITY_EMAIL_ENABLE_VERIFICATION}\n - EmailSettings__TimeoutMs=${IDENTITY_EMAIL_TIMEOUT_MS}\n - EmailSettings__MaxRetryAttempts=${IDENTITY_EMAIL_MAX_RETRY}\n - EmailSettings__LogEmailContent=${IDENTITY_EMAIL_LOG_CONTENT}\n # gRPC Services\n - GrpcServices__OrderService__Address=http://order-service:5006\n # Database Options\n - DatabaseOptions__MaxRetryCount=5\n - DatabaseOptions__MaxRetryDelay=00:00:10\n - DatabaseOptions__CommandTimeout=30\n - DatabaseOptions__EnableRetryOnFailure=true\n - DatabaseOptions__EnableSensitiveDataLogging=${DB_ENABLE_SENSITIVE_LOGGING}\n - DatabaseOptions__EnableDetailedErrors=${DB_ENABLE_DETAILED_ERRORS}\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=identity-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=identity-service,service.version=1.0.0\n - RabbitMq__Url=${RABBITMQ_URL}\n ports:\n - 5000:80\n depends_on:\n - postgres\n - otelcollector\n restart: unless-stopped\n cart-service:\n image: rosie91/stemify:cart-service\n build:\n context: .\n dockerfile: src/Services/CartService/Cart.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5007\n - ConnectionStrings__stemifycart=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_cart\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcProductUrl=http://product-service:5005\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=cart-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=cart-service,service.version=1.0.0\n ports:\n - 7007:80\n - 5007:5007\n depends_on:\n - postgres\n - redis\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n payment-service:\n image: rosie91/stemify:payment-service\n build:\n context: .\n dockerfile: src/Services/PaymentService/Payment.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - Logging__LogLevel__Microsoft.EntityFrameworkCore.Database.Command=Warning\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5008\n - ConnectionStrings__stemifypayment=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_payment\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n # PayOS configuration\n - PaymentGateways__PayOS__ClientId=${PAYOS_CLIENT_ID}\n - PaymentGateways__PayOS__ApiKey=${PAYOS_API_KEY}\n - PaymentGateways__PayOS__ChecksumKey=${PAYOS_CHECKSUM_KEY}\n - PaymentGateways__PayOS__BaseUrl=https://api-merchant.payos.vn\n # Stripe configuration\n - PaymentGateways__Stripe__SecretKey=${STRIPE_SECRET_KEY}\n - PaymentGateways__Stripe__PublishableKey=${STRIPE_PUBLISH_KEY}\n - PaymentGateways__Stripe__WebhookSecret=${STRIPE_WEBHOOK_KEY}\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=payment-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=payment-service,service.version=1.0.0\n ports:\n - 7008:80\n - 5008:5008\n depends_on:\n - postgres\n - redis\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n emulator-service:\n image: rosie91/stemify:emulator-service\n build:\n context: .\n dockerfile: src/Services/EmulatorService/Emulator.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5009\n # MongoDB configuration\n - MongoDb__ConnectionString=mongodb://root:mongopw@mongo\n - MongoDb__DatabaseName=stemify_emulator\n # Redis configuration\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n # gRPC endpoints\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcResourceUrl=http://resource-service:5003\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=emulator-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=emulator-service,service.version=1.0.0\n # Cloudinary Configuration\n - Cloudinary__CloudName=${CLOUDINARY_CLOUD_NAME}\n - Cloudinary__ApiKey=${CLOUDINARY_API_KEY}\n - Cloudinary__ApiSecret=${CLOUDINARY_API_SECRET}\n ports:\n - 7009:80\n - 5009:5009\n depends_on:\n - redis\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n\n # ========================================\n # MONITORING STACK\n # ========================================\n\n # Prometheus - Metrics Collection\n prometheus:\n image: prom/prometheus:v3.5.0\n container_name: stemify-prometheus\n ports:\n - '9090:9090'\n volumes:\n - ./config/prometheus:/etc/prometheus:ro\n - prometheus_data:/prometheus\n command:\n - '--config.file=/etc/prometheus/prometheus.yml'\n - '--storage.tsdb.path=/prometheus'\n - '--storage.tsdb.retention.time=15d'\n - '--storage.tsdb.retention.size=10GB'\n - '--web.enable-lifecycle'\n - '--web.enable-otlp-receiver'\n restart: unless-stopped\n\n # AlertManager - Alert Management\n alertmanager:\n image: prom/alertmanager:v0.27.0\n container_name: stemify-alertmanager\n ports:\n - '9093:9093'\n volumes:\n - ./config/alertmanager:/etc/alertmanager:ro\n - alertmanager_data:/alertmanager\n command:\n - '--config.file=/etc/alertmanager/alertmanager.yml'\n - '--storage.path=/alertmanager'\n - '--web.external-url=http://localhost:9093'\n restart: unless-stopped\n\n # Grafana - Visualization\n grafana:\n image: grafana/grafana:latest\n container_name: stemify-grafana\n ports:\n - '3001:3001'\n volumes:\n - grafana_data:/var/lib/grafana\n - ./config/grafana/config:/etc/grafana:ro\n - ./config/grafana/dashboards:/var/lib/grafana/dashboards:ro\n environment:\n - GF_SECURITY_ADMIN_USER=${GRAFANA_ADMIN_USERNAME}\n - GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_ADMIN_PASSWORD:-admin123}\n - GF_USERS_ALLOW_SIGN_UP=false\n - GF_INSTALL_PLUGINS=grafana-clock-panel,grafana-simple-json-datasource\n - PROMETHEUS_ENDPOINT=http://prometheus:9090\n restart: unless-stopped\n depends_on:\n - prometheus\n\n # OpenTelemetry Collector\n otelcollector:\n image: otel/opentelemetry-collector-contrib:latest\n container_name: stemify-otelcollector\n ports:\n - '8888:8888' # Prometheus metrics\n - '8889:8889' # Prometheus exporter\n - '4317:4317' # OTLP gRPC receiver\n - '4318:4318' # OTLP HTTP receiver\n volumes:\n - ./config/otelcollector:/etc/otelcol-contrib:ro\n environment:\n - PROMETHEUS_ENDPOINT=http://prometheus:9090/api/v1/write\n command: [ '--config=/etc/otelcol-contrib/config.yaml' ]\n restart: unless-stopped\n depends_on:\n - prometheus\nvolumes:\n redis-data:\n postgres_data:\n mongodata:\n prometheus_data:\n alertmanager_data:\n grafana_data:\n"}, "8fddf5a0042a48fd7bbe1beed6f4b3aba3a29e14": {"t": 1785748787.4498866, "n": 21492, "h": "02b7dffbd884a4cc", "c": "services:\n postgres:\n image: postgres:17\n container_name: stemify-postgres\n environment:\n - POSTGRES_PASSWORD=${DB_PASSWORD}\n ports:\n - '5432:5432'\n volumes:\n - postgres_data:/var/lib/postgresql/data\n restart: unless-stopped\n # RabbitMQ is now using CloudAMQP online service\n # rabbitmq:\n # image: rabbitmq:3-management-alpine\n # ports:\n # - 5672:5672\n # - 15672:15672\n # environment:\n # - RABBITMQ_DEFAULT_USER=guest\n # - RABBITMQ_DEFAULT_PASS=guest\n # healthcheck:\n # test: [\"CMD\", \"rabbitmq-diagnostics\", \"ping\"]\n # interval: 30s\n # timeout: 10s\n # retries: 5\n # start_period: 30s\n # restart: unless-stopped\n mongo:\n image: mongo\n environment:\n - MONGO_INITDB_ROOT_USERNAME=root\n - MONGO_INITDB_ROOT_PASSWORD=mongopw\n ports:\n - 27017:27017\n volumes:\n - mongodata:/var/lib/mongodb/data\n redis:\n image: redis:latest\n ports:\n - 6379:6379\n command: ['redis-server', '--appendonly', 'yes']\n volumes:\n - redis-data:/data\n redis-commander:\n image: rediscommander/redis-commander:latest\n environment:\n - REDIS_HOSTS=local:redis:6379\n - HTTP_USER=root\n - HTTP_PASSWORD=secret\n ports:\n - 8082:8082\n depends_on:\n - redis\n classroom-service:\n image: rosie91/stemify:classroom-service\n build:\n context: .\n dockerfile: src/Services/ClassroomService/Classroom.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5001\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcResourceUrl=http://resource-service:5003\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcOrderUrl=http://order-service:5006\n - ConnectionStrings__stemifyclassroom=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_classroom\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=classroom-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=classroom-service,service.version=1.0.0\n # Cloudinary Configuration\n - Cloudinary__CloudName=${CLOUDINARY_CLOUD_NAME}\n - Cloudinary__ApiKey=${CLOUDINARY_API_KEY}\n - Cloudinary__ApiSecret=${CLOUDINARY_API_SECRET}\n ports:\n - 7001:80\n - 5001:5001\n depends_on:\n - postgres\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n resource-service:\n image: rosie91/stemify:resource-service\n build:\n context: .\n dockerfile: src/Services/Resource/Resource.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5003\n - ConnectionStrings__stemifyresource=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_resource\n - Cloudinary__CloudName=${CLOUDINARY_CLOUD_NAME}\n - Cloudinary__ApiKey=${CLOUDINARY_API_KEY}\n - Cloudinary__ApiSecret=${CLOUDINARY_API_SECRET}\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n - ClientApp=https://localhost:3000\n - Gemini__BaseUrl=https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent\n - Gemini__ApiKey=${GEMINI_API_KEY}\n - Gemini__Model=gemini-2.5-flash\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=resource-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=resource-service,service.version=1.0.0\n ports:\n - 7003:80\n - 5003:5003\n depends_on:\n - postgres\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n product-service:\n image: rosie91/stemify:product-service\n build:\n context: .\n dockerfile: src/Services/ProductService/Product.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5005\n - ConnectionStrings__stemifyproduct=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_product\n - Cloudinary__CloudName=${CLOUDINARY_CLOUD_NAME}\n - Cloudinary__ApiKey=${CLOUDINARY_API_KEY}\n - Cloudinary__ApiSecret=${CLOUDINARY_API_SECRET}\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcResourceUrl=http://resource-service:5003\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=product-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=product-service,service.version=1.0.0\n ports:\n - 7005:80\n - 5005:5005\n depends_on:\n - postgres\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n order-service:\n image: rosie91/stemify:order-service\n build:\n context: .\n dockerfile: src/Services/OrderService/Order.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5006\n - ConnectionStrings__stemifyorder=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_order\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcResourceUrl=http://resource-service:5003\n - GrpcProductUrl=http://product-service:5005\n - GrpcClassroomUrl=http://classroom-service:5001\n - Cloudinary__CloudName=${CLOUDINARY_CLOUD_NAME}\n - Cloudinary__ApiKey=${CLOUDINARY_API_KEY}\n - Cloudinary__ApiSecret=${CLOUDINARY_API_SECRET}\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=order-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=order-service,service.version=1.0.0\n\n ports:\n - 7006:80\n - 5006:5006\n depends_on:\n - postgres\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n notification-service:\n image: rosie91/stemify:notification-service\n build:\n context: .\n dockerfile: src/Services/Notification/Notification.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5004\n - ConnectionStrings__stemifynotification=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_notification\n # SendGrid configuration\n - SendGridSettings__ApiKey=${SENDGRID_API_KEY}\n - SendGridSettings__FromEmail=stemify30062025@gmail.com\n - SendGridSettings__FromName=STEMify\n # Client App configuration\n - ClientApp__BaseUrl=https://localhost:3000\n - ClientApp=https://localhost:3000\n - Issuer=http://localhost:5000/\n - IdentityServiceUrl=http://identity-service\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=notification-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=notification-service,service.version=1.0.0\n ports:\n - 7004:80\n - 5004:5004\n depends_on:\n - postgres\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n gateway-service:\n image: rosie91/stemify:gateway-service\n build:\n context: .\n dockerfile: src/ApiGateways/ApiGateway/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Docker\n - ASPNETCORE_URLS=http://+:80\n - IdentityServiceUrl=http://identity-service\n - Issuer=http://localhost:5000/\n - ClientApp=https://localhost:3000\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=gateway-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=gateway-service,service.version=1.0.0\n ports:\n - 6001:80\n depends_on:\n - otelcollector\n restart: unless-stopped\n identity-api-service:\n image: rosie91/stemify:identity-api-service\n build:\n context: .\n dockerfile: src/Services/Identity/Identity.Api/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5002\n - ConnectionStrings__stemifyidentity=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_identity\n # RabbitMQ Configuration (for MassTransit)\n - RabbitMq__Url=${RABBITMQ_URL}\n # OpenIddict Configuration\n - OpenIddict__Authority=http://identity-service:80\n # Client App Configuration\n - ClientApp=https://localhost:3000\n # Email Settings\n - EmailSettings__From=${IDENTITY_EMAIL_FROM}\n - EmailSettings__DisplayName=${IDENTITY_EMAIL_DISPLAY_NAME}\n - EmailSettings__SMTPServer=${IDENTITY_EMAIL_SMTP_SERVER}\n - EmailSettings__Port=${IDENTITY_EMAIL_PORT}\n - EmailSettings__Username=${IDENTITY_EMAIL_USERNAME}\n - EmailSettings__Password=${IDENTITY_EMAIL_PASSWORD}\n - EmailSettings__UseSSL=${IDENTITY_EMAIL_USE_SSL}\n - EmailSettings__ApplicationName=${IDENTITY_EMAIL_APP_NAME}\n - EmailSettings__SupportEmail=${IDENTITY_EMAIL_SUPPORT}\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=identity-api-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=identity-api-service,service.version=1.0.0\n - GrpcServices__OrderService__Address=http://order-service:5006\n - BackendUrl=${IDENTITY_BACKEND_URL:-http://localhost:5000}\n ports:\n - 7002:80\n - 5002:5002\n depends_on:\n - postgres\n - otelcollector\n restart: unless-stopped\n identity-service:\n image: rosie91/stemify:identity-service\n build:\n context: .\n dockerfile: src/Services/Identity/Identity.Web/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - ASPNETCORE_URLS=http://+:80\n - ConnectionStrings__stemifyidentity=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_identity\n # OpenIddict configuration for Docker\n - OpenIddict__Issuer=http://localhost:5000\n - OpenIddict__Authority=http://localhost:5000\n - OpenIddict__SigningKey=${OPENIDDICT_SIGNING_KEY}\n - OpenIddict__EncryptionKey=${OPENIDDICT_ENCRYPTION_KEY}\n - OpenIddict__ApiClientSecret=${OPENIDDICT_API_CLIENT_SECRET}\n - OpenIddict__ForceRecreateClients=${OPENIDDICT_FORCE_RECREATE_CLIENTS:-true}\n - OpenIddict__DisableHttpsRequirement=${OPENIDDICT_DISABLE_HTTPS:-true}\n # CORS for Docker environment\n - Cors__Origins=${IDENTITY_CORS_ORIGINS:-https://localhost:3000}\n - ClientApp=${IDENTITY_CLIENT_APP:-https://localhost:3000}\n - BackendUrl=${IDENTITY_BACKEND_URL:-http://localhost:5000}\n # Google Authentication\n - Authentication__Google__ClientId=${GOOGLE_CLIENT_ID}\n - Authentication__Google__ClientSecret=${GOOGLE_CLIENT_SECRET}\n - Authentication__Google__RedirectUri=${GOOGLE_REDIRECT_URI}\n # Email Settings\n - EmailSettings__SMTPServer=${IDENTITY_EMAIL_SMTP_SERVER}\n - EmailSettings__Port=${IDENTITY_EMAIL_PORT}\n - EmailSettings__UseSSL=${IDENTITY_EMAIL_USE_SSL}\n - EmailSettings__Username=${IDENTITY_EMAIL_USERNAME}\n - EmailSettings__Password=${IDENTITY_EMAIL_PASSWORD}\n - EmailSettings__From=${IDENTITY_EMAIL_FROM}\n - EmailSettings__DisplayName=${IDENTITY_EMAIL_DISPLAY_NAME}\n - EmailSettings__ApplicationName=${IDENTITY_EMAIL_APP_NAME}\n - EmailSettings__SupportEmail=${IDENTITY_EMAIL_SUPPORT}\n - EmailSettings__Enabled=${IDENTITY_EMAIL_ENABLED}\n - EmailSettings__EnableVerification=${IDENTITY_EMAIL_ENABLE_VERIFICATION}\n - EmailSettings__TimeoutMs=${IDENTITY_EMAIL_TIMEOUT_MS}\n - EmailSettings__MaxRetryAttempts=${IDENTITY_EMAIL_MAX_RETRY}\n - EmailSettings__LogEmailContent=${IDENTITY_EMAIL_LOG_CONTENT}\n # gRPC Services\n - GrpcServices__OrderService__Address=http://order-service:5006\n # Database Options\n - DatabaseOptions__MaxRetryCount=5\n - DatabaseOptions__MaxRetryDelay=00:00:10\n - DatabaseOptions__CommandTimeout=30\n - DatabaseOptions__EnableRetryOnFailure=true\n - DatabaseOptions__EnableSensitiveDataLogging=${DB_ENABLE_SENSITIVE_LOGGING}\n - DatabaseOptions__EnableDetailedErrors=${DB_ENABLE_DETAILED_ERRORS}\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=identity-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=identity-service,service.version=1.0.0\n - RabbitMq__Url=${RABBITMQ_URL}\n ports:\n - 5000:80\n depends_on:\n - postgres\n - otelcollector\n restart: unless-stopped\n cart-service:\n image: rosie91/stemify:cart-service\n build:\n context: .\n dockerfile: src/Services/CartService/Cart.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5007\n - ConnectionStrings__stemifycart=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_cart\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcProductUrl=http://product-service:5005\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=cart-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=cart-service,service.version=1.0.0\n ports:\n - 7007:80\n - 5007:5007\n depends_on:\n - postgres\n - redis\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n payment-service:\n image: rosie91/stemify:payment-service\n build:\n context: .\n dockerfile: src/Services/PaymentService/Payment.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - Logging__LogLevel__Microsoft.EntityFrameworkCore.Database.Command=Warning\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5008\n - ConnectionStrings__stemifypayment=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_payment\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n # PayOS configuration\n - PaymentGateways__PayOS__ClientId=${PAYOS_CLIENT_ID}\n - PaymentGateways__PayOS__ApiKey=${PAYOS_API_KEY}\n - PaymentGateways__PayOS__ChecksumKey=${PAYOS_CHECKSUM_KEY}\n - PaymentGateways__PayOS__BaseUrl=https://api-merchant.payos.vn\n # Stripe configuration\n - PaymentGateways__Stripe__SecretKey=${STRIPE_SECRET_KEY}\n - PaymentGateways__Stripe__PublishableKey=${STRIPE_PUBLISH_KEY}\n - PaymentGateways__Stripe__WebhookSecret=${STRIPE_WEBHOOK_KEY}\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=payment-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=payment-service,service.version=1.0.0\n ports:\n - 7008:80\n - 5008:5008\n depends_on:\n - postgres\n - redis\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n emulator-service:\n image: rosie91/stemify:emulator-service\n build:\n context: .\n dockerfile: src/Services/EmulatorService/Emulator.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5009\n # MongoDB configuration\n - MongoDb__ConnectionString=mongodb://root:mongopw@mongo\n - MongoDb__DatabaseName=stemify_emulator\n # Redis configuration\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n # gRPC endpoints\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcResourceUrl=http://resource-service:5003\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=emulator-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=emulator-service,service.version=1.0.0\n # Cloudinary Configuration\n - Cloudinary__CloudName=${CLOUDINARY_CLOUD_NAME}\n - Cloudinary__ApiKey=${CLOUDINARY_API_KEY}\n - Cloudinary__ApiSecret=${CLOUDINARY_API_SECRET}\n ports:\n - 7009:80\n - 5009:5009\n depends_on:\n - redis\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n\n # ========================================\n # MONITORING STACK\n # ========================================\n\n # Prometheus - Metrics Collection\n prometheus:\n image: prom/prometheus:v3.5.0\n container_name: stemify-prometheus\n ports:\n - '9090:9090'\n volumes:\n - ./config/prometheus:/etc/prometheus:ro\n - prometheus_data:/prometheus\n command:\n - '--config.file=/etc/prometheus/prometheus.yml'\n - '--storage.tsdb.path=/prometheus'\n - '--storage.tsdb.retention.time=15d'\n - '--storage.tsdb.retention.size=10GB'\n - '--web.enable-lifecycle'\n - '--web.enable-otlp-receiver'\n restart: unless-stopped\n\n # AlertManager - Alert Management\n alertmanager:\n image: prom/alertmanager:v0.27.0\n container_name: stemify-alertmanager\n ports:\n - '9093:9093'\n volumes:\n - ./config/alertmanager:/etc/alertmanager:ro\n - alertmanager_data:/alertmanager\n command:\n - '--config.file=/etc/alertmanager/alertmanager.yml'\n - '--storage.path=/alertmanager'\n - '--web.external-url=http://localhost:9093'\n restart: unless-stopped\n\n # Grafana - Visualization\n grafana:\n image: grafana/grafana:latest\n container_name: stemify-grafana\n ports:\n - '3001:3001'\n volumes:\n - grafana_data:/var/lib/grafana\n - ./config/grafana/config:/etc/grafana:ro\n - ./config/grafana/dashboards:/var/lib/grafana/dashboards:ro\n environment:\n - GF_SECURITY_ADMIN_USER=${GRAFANA_ADMIN_USERNAME}\n - GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_ADMIN_PASSWORD:-admin123}\n - GF_USERS_ALLOW_SIGN_UP=false\n - GF_INSTALL_PLUGINS=grafana-clock-panel,grafana-simple-json-datasource\n - PROMETHEUS_ENDPOINT=http://prometheus:9090\n restart: unless-stopped\n depends_on:\n - prometheus\n\n # OpenTelemetry Collector\n otelcollector:\n image: otel/opentelemetry-collector-contrib:latest\n container_name: stemify-otelcollector\n ports:\n - '8888:8888' # Prometheus metrics\n - '8889:8889' # Prometheus exporter\n - '4317:4317' # OTLP gRPC receiver\n - '4318:4318' # OTLP HTTP receiver\n volumes:\n - ./config/otelcollector:/etc/otelcol-contrib:ro\n environment:\n - PROMETHEUS_ENDPOINT=http://prometheus:9090/api/v1/write\n command: ['--config=/etc/otelcol-contrib/config.yaml']\n restart: unless-stopped\n depends_on:\n - prometheus\nvolumes:\n redis-data:\n postgres_data:\n mongodata:\n prometheus_data:\n alertmanager_data:\n grafana_data:\n"}, "59f6c357b890b76f83c436dc2f1586271f391163": {"t": 1785748789.7425773, "n": 21430, "h": "57681f7466274463", "c": "services:\n postgres:\n image: postgres:17\n container_name: stemify-postgres\n environment:\n - POSTGRES_PASSWORD=${DB_PASSWORD}\n ports:\n - '5432:5432'\n volumes:\n - postgres_data:/var/lib/postgresql/data\n restart: unless-stopped\n # RabbitMQ is now using CloudAMQP online service\n # rabbitmq:\n # image: rabbitmq:3-management-alpine\n # ports:\n # - 5672:5672\n # - 15672:15672\n # environment:\n # - RABBITMQ_DEFAULT_USER=guest\n # - RABBITMQ_DEFAULT_PASS=guest\n # healthcheck:\n # test: [\"CMD\", \"rabbitmq-diagnostics\", \"ping\"]\n # interval: 30s\n # timeout: 10s\n # retries: 5\n # start_period: 30s\n # restart: unless-stopped\n mongo:\n image: mongo\n environment:\n - MONGO_INITDB_ROOT_USERNAME=root\n - MONGO_INITDB_ROOT_PASSWORD=mongopw\n ports:\n - 27017:27017\n volumes:\n - mongodata:/var/lib/mongodb/data\n redis:\n image: redis:latest\n ports:\n - 6379:6379\n command: [ 'redis-server', '--appendonly', 'yes' ]\n volumes:\n - redis-data:/data\n redis-commander:\n image: rediscommander/redis-commander:latest\n environment:\n - REDIS_HOSTS=local:redis:6379\n - HTTP_USER=root\n - HTTP_PASSWORD=secret\n ports:\n - 8082:8082\n depends_on:\n - redis\n classroom-service:\n image: rosie91/stemify:classroom-service\n build:\n context: .\n dockerfile: src/Services/ClassroomService/Classroom.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5001\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcResourceUrl=http://resource-service:5003\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcOrderUrl=http://order-service:5006\n - ConnectionStrings__stemifyclassroom=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_classroom\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=classroom-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=classroom-service,service.version=1.0.0\n # Cloudinary Configuration\n - Cloudinary__CloudName=${CLOUDINARY_CLOUD_NAME}\n - Cloudinary__ApiKey=${CLOUDINARY_API_KEY}\n - Cloudinary__ApiSecret=${CLOUDINARY_API_SECRET}\n ports:\n - 7001:80\n - 5001:5001\n depends_on:\n - postgres\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n resource-service:\n image: rosie91/stemify:resource-service\n build:\n context: .\n dockerfile: src/Services/Resource/Resource.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5003\n - ConnectionStrings__stemifyresource=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_resource\n - Cloudinary__CloudName=${CLOUDINARY_CLOUD_NAME}\n - Cloudinary__ApiKey=${CLOUDINARY_API_KEY}\n - Cloudinary__ApiSecret=${CLOUDINARY_API_SECRET}\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n - ClientApp=https://localhost:3000\n - Gemini__BaseUrl=https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent\n - Gemini__ApiKey=${GEMINI_API_KEY}\n - Gemini__Model=gemini-2.5-flash\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=resource-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=resource-service,service.version=1.0.0\n ports:\n - 7003:80\n - 5003:5003\n depends_on:\n - postgres\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n product-service:\n image: rosie91/stemify:product-service\n build:\n context: .\n dockerfile: src/Services/ProductService/Product.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5005\n - ConnectionStrings__stemifyproduct=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_product\n - Cloudinary__CloudName=${CLOUDINARY_CLOUD_NAME}\n - Cloudinary__ApiKey=${CLOUDINARY_API_KEY}\n - Cloudinary__ApiSecret=${CLOUDINARY_API_SECRET}\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcResourceUrl=http://resource-service:5003\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=product-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=product-service,service.version=1.0.0\n ports:\n - 7005:80\n - 5005:5005\n depends_on:\n - postgres\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n order-service:\n image: rosie91/stemify:order-service\n build:\n context: .\n dockerfile: src/Services/OrderService/Order.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5006\n - ConnectionStrings__stemifyorder=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_order\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcResourceUrl=http://resource-service:5003\n - GrpcProductUrl=http://product-service:5005\n - GrpcClassroomUrl=http://classroom-service:5001\n - Cloudinary__CloudName=${CLOUDINARY_CLOUD_NAME}\n - Cloudinary__ApiKey=${CLOUDINARY_API_KEY}\n - Cloudinary__ApiSecret=${CLOUDINARY_API_SECRET}\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=order-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=order-service,service.version=1.0.0\n\n ports:\n - 7006:80\n - 5006:5006\n depends_on:\n - postgres\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n notification-service:\n image: rosie91/stemify:notification-service\n build:\n context: .\n dockerfile: src/Services/Notification/Notification.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5004\n - ConnectionStrings__stemifynotification=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_notification\n # SendGrid configuration\n - SendGridSettings__ApiKey=${SENDGRID_API_KEY}\n - SendGridSettings__FromEmail=stemify30062025@gmail.com\n - SendGridSettings__FromName=STEMify\n # Client App configuration\n - ClientApp__BaseUrl=https://localhost:3000\n - ClientApp=https://localhost:3000\n - Issuer=http://localhost:5000/\n - IdentityServiceUrl=http://identity-service\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=notification-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=notification-service,service.version=1.0.0\n ports:\n - 7004:80\n - 5004:5004\n depends_on:\n - postgres\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n gateway-service:\n image: rosie91/stemify:gateway-service\n build:\n context: .\n dockerfile: src/ApiGateways/ApiGateway/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Docker\n - ASPNETCORE_URLS=http://+:80\n - IdentityServiceUrl=http://identity-service\n - Issuer=http://localhost:5000/\n - ClientApp=https://localhost:3000\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=gateway-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=gateway-service,service.version=1.0.0\n ports:\n - 6001:80\n depends_on:\n - otelcollector\n restart: unless-stopped\n identity-api-service:\n image: rosie91/stemify:identity-api-service\n build:\n context: .\n dockerfile: src/Services/Identity/Identity.Api/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5002\n - ConnectionStrings__stemifyidentity=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_identity\n # RabbitMQ Configuration (for MassTransit)\n - RabbitMq__Url=${RABBITMQ_URL}\n # OpenIddict Configuration\n - OpenIddict__Authority=http://identity-service:80\n # Client App Configuration\n - ClientApp=https://localhost:3000\n # Email Settings\n - EmailSettings__From=${IDENTITY_EMAIL_FROM}\n - EmailSettings__DisplayName=${IDENTITY_EMAIL_DISPLAY_NAME}\n - EmailSettings__SMTPServer=${IDENTITY_EMAIL_SMTP_SERVER}\n - EmailSettings__Port=${IDENTITY_EMAIL_PORT}\n - EmailSettings__Username=${IDENTITY_EMAIL_USERNAME}\n - EmailSettings__Password=${IDENTITY_EMAIL_PASSWORD}\n - EmailSettings__UseSSL=${IDENTITY_EMAIL_USE_SSL}\n - EmailSettings__ApplicationName=${IDENTITY_EMAIL_APP_NAME}\n - EmailSettings__SupportEmail=${IDENTITY_EMAIL_SUPPORT}\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=identity-api-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=identity-api-service,service.version=1.0.0\n - GrpcServices__OrderService__Address=http://order-service:5006\n ports:\n - 7002:80\n - 5002:5002\n depends_on:\n - postgres\n - otelcollector\n restart: unless-stopped\n identity-service:\n image: rosie91/stemify:identity-service\n build:\n context: .\n dockerfile: src/Services/Identity/Identity.Web/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - ASPNETCORE_URLS=http://+:80\n - ConnectionStrings__stemifyidentity=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_identity\n # OpenIddict configuration for Docker\n - OpenIddict__Issuer=http://localhost:5000\n - OpenIddict__Authority=http://localhost:5000\n - OpenIddict__SigningKey=${OPENIDDICT_SIGNING_KEY}\n - OpenIddict__EncryptionKey=${OPENIDDICT_ENCRYPTION_KEY}\n - OpenIddict__ApiClientSecret=${OPENIDDICT_API_CLIENT_SECRET}\n - OpenIddict__ForceRecreateClients=${OPENIDDICT_FORCE_RECREATE_CLIENTS:-true}\n - OpenIddict__DisableHttpsRequirement=${OPENIDDICT_DISABLE_HTTPS:-true}\n # CORS for Docker environment\n - Cors__Origins=${IDENTITY_CORS_ORIGINS:-https://localhost:3000}\n - ClientApp=${IDENTITY_CLIENT_APP:-https://localhost:3000}\n - BackendUrl=${IDENTITY_BACKEND_URL:-http://localhost:5000}\n # Google Authentication\n - Authentication__Google__ClientId=${GOOGLE_CLIENT_ID}\n - Authentication__Google__ClientSecret=${GOOGLE_CLIENT_SECRET}\n - Authentication__Google__RedirectUri=${GOOGLE_REDIRECT_URI}\n # Email Settings\n - EmailSettings__SMTPServer=${IDENTITY_EMAIL_SMTP_SERVER}\n - EmailSettings__Port=${IDENTITY_EMAIL_PORT}\n - EmailSettings__UseSSL=${IDENTITY_EMAIL_USE_SSL}\n - EmailSettings__Username=${IDENTITY_EMAIL_USERNAME}\n - EmailSettings__Password=${IDENTITY_EMAIL_PASSWORD}\n - EmailSettings__From=${IDENTITY_EMAIL_FROM}\n - EmailSettings__DisplayName=${IDENTITY_EMAIL_DISPLAY_NAME}\n - EmailSettings__ApplicationName=${IDENTITY_EMAIL_APP_NAME}\n - EmailSettings__SupportEmail=${IDENTITY_EMAIL_SUPPORT}\n - EmailSettings__Enabled=${IDENTITY_EMAIL_ENABLED}\n - EmailSettings__EnableVerification=${IDENTITY_EMAIL_ENABLE_VERIFICATION}\n - EmailSettings__TimeoutMs=${IDENTITY_EMAIL_TIMEOUT_MS}\n - EmailSettings__MaxRetryAttempts=${IDENTITY_EMAIL_MAX_RETRY}\n - EmailSettings__LogEmailContent=${IDENTITY_EMAIL_LOG_CONTENT}\n # gRPC Services\n - GrpcServices__OrderService__Address=http://order-service:5006\n # Database Options\n - DatabaseOptions__MaxRetryCount=5\n - DatabaseOptions__MaxRetryDelay=00:00:10\n - DatabaseOptions__CommandTimeout=30\n - DatabaseOptions__EnableRetryOnFailure=true\n - DatabaseOptions__EnableSensitiveDataLogging=${DB_ENABLE_SENSITIVE_LOGGING}\n - DatabaseOptions__EnableDetailedErrors=${DB_ENABLE_DETAILED_ERRORS}\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=identity-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=identity-service,service.version=1.0.0\n - RabbitMq__Url=${RABBITMQ_URL}\n ports:\n - 5000:80\n depends_on:\n - postgres\n - otelcollector\n restart: unless-stopped\n cart-service:\n image: rosie91/stemify:cart-service\n build:\n context: .\n dockerfile: src/Services/CartService/Cart.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5007\n - ConnectionStrings__stemifycart=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_cart\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcProductUrl=http://product-service:5005\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=cart-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=cart-service,service.version=1.0.0\n ports:\n - 7007:80\n - 5007:5007\n depends_on:\n - postgres\n - redis\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n payment-service:\n image: rosie91/stemify:payment-service\n build:\n context: .\n dockerfile: src/Services/PaymentService/Payment.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - Logging__LogLevel__Microsoft.EntityFrameworkCore.Database.Command=Warning\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5008\n - ConnectionStrings__stemifypayment=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_payment\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n # PayOS configuration\n - PaymentGateways__PayOS__ClientId=${PAYOS_CLIENT_ID}\n - PaymentGateways__PayOS__ApiKey=${PAYOS_API_KEY}\n - PaymentGateways__PayOS__ChecksumKey=${PAYOS_CHECKSUM_KEY}\n - PaymentGateways__PayOS__BaseUrl=https://api-merchant.payos.vn\n # Stripe configuration\n - PaymentGateways__Stripe__SecretKey=${STRIPE_SECRET_KEY}\n - PaymentGateways__Stripe__PublishableKey=${STRIPE_PUBLISH_KEY}\n - PaymentGateways__Stripe__WebhookSecret=${STRIPE_WEBHOOK_KEY}\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=payment-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=payment-service,service.version=1.0.0\n ports:\n - 7008:80\n - 5008:5008\n depends_on:\n - postgres\n - redis\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n emulator-service:\n image: rosie91/stemify:emulator-service\n build:\n context: .\n dockerfile: src/Services/EmulatorService/Emulator.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5009\n # MongoDB configuration\n - MongoDb__ConnectionString=mongodb://root:mongopw@mongo\n - MongoDb__DatabaseName=stemify_emulator\n # Redis configuration\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n # gRPC endpoints\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcResourceUrl=http://resource-service:5003\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=emulator-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=emulator-service,service.version=1.0.0\n # Cloudinary Configuration\n - Cloudinary__CloudName=${CLOUDINARY_CLOUD_NAME}\n - Cloudinary__ApiKey=${CLOUDINARY_API_KEY}\n - Cloudinary__ApiSecret=${CLOUDINARY_API_SECRET}\n ports:\n - 7009:80\n - 5009:5009\n depends_on:\n - redis\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n\n # ========================================\n # MONITORING STACK\n # ========================================\n\n # Prometheus - Metrics Collection\n prometheus:\n image: prom/prometheus:v3.5.0\n container_name: stemify-prometheus\n ports:\n - '9090:9090'\n volumes:\n - ./config/prometheus:/etc/prometheus:ro\n - prometheus_data:/prometheus\n command:\n - '--config.file=/etc/prometheus/prometheus.yml'\n - '--storage.tsdb.path=/prometheus'\n - '--storage.tsdb.retention.time=15d'\n - '--storage.tsdb.retention.size=10GB'\n - '--web.enable-lifecycle'\n - '--web.enable-otlp-receiver'\n restart: unless-stopped\n\n # AlertManager - Alert Management\n alertmanager:\n image: prom/alertmanager:v0.27.0\n container_name: stemify-alertmanager\n ports:\n - '9093:9093'\n volumes:\n - ./config/alertmanager:/etc/alertmanager:ro\n - alertmanager_data:/alertmanager\n command:\n - '--config.file=/etc/alertmanager/alertmanager.yml'\n - '--storage.path=/alertmanager'\n - '--web.external-url=http://localhost:9093'\n restart: unless-stopped\n\n # Grafana - Visualization\n grafana:\n image: grafana/grafana:latest\n container_name: stemify-grafana\n ports:\n - '3001:3001'\n volumes:\n - grafana_data:/var/lib/grafana\n - ./config/grafana/config:/etc/grafana:ro\n - ./config/grafana/dashboards:/var/lib/grafana/dashboards:ro\n environment:\n - GF_SECURITY_ADMIN_USER=${GRAFANA_ADMIN_USERNAME}\n - GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_ADMIN_PASSWORD:-admin123}\n - GF_USERS_ALLOW_SIGN_UP=false\n - GF_INSTALL_PLUGINS=grafana-clock-panel,grafana-simple-json-datasource\n - PROMETHEUS_ENDPOINT=http://prometheus:9090\n restart: unless-stopped\n depends_on:\n - prometheus\n\n # OpenTelemetry Collector\n otelcollector:\n image: otel/opentelemetry-collector-contrib:latest\n container_name: stemify-otelcollector\n ports:\n - '8888:8888' # Prometheus metrics\n - '8889:8889' # Prometheus exporter\n - '4317:4317' # OTLP gRPC receiver\n - '4318:4318' # OTLP HTTP receiver\n volumes:\n - ./config/otelcollector:/etc/otelcol-contrib:ro\n environment:\n - PROMETHEUS_ENDPOINT=http://prometheus:9090/api/v1/write\n command: [ '--config=/etc/otelcol-contrib/config.yaml' ]\n restart: unless-stopped\n depends_on:\n - prometheus\nvolumes:\n redis-data:\n postgres_data:\n mongodata:\n prometheus_data:\n alertmanager_data:\n grafana_data:\n"}, "8616372d4931c939d83e72d109c32287d8f2e4c5": {"t": 1785748791.993078, "n": 21238, "h": "0454ad9c7ec999f7", "c": "services:\n postgres:\n image: postgres:17\n container_name: stemify-postgres\n environment:\n - POSTGRES_PASSWORD=${DB_PASSWORD}\n ports:\n - '5432:5432'\n volumes:\n - postgres_data:/var/lib/postgresql/data\n restart: unless-stopped\n # RabbitMQ is now using CloudAMQP online service\n # rabbitmq:\n # image: rabbitmq:3-management-alpine\n # ports:\n # - 5672:5672\n # - 15672:15672\n # environment:\n # - RABBITMQ_DEFAULT_USER=guest\n # - RABBITMQ_DEFAULT_PASS=guest\n # healthcheck:\n # test: [\"CMD\", \"rabbitmq-diagnostics\", \"ping\"]\n # interval: 30s\n # timeout: 10s\n # retries: 5\n # start_period: 30s\n # restart: unless-stopped\n mongo:\n image: mongo\n environment:\n - MONGO_INITDB_ROOT_USERNAME=root\n - MONGO_INITDB_ROOT_PASSWORD=mongopw\n ports:\n - 27017:27017\n volumes:\n - mongodata:/var/lib/mongodb/data\n redis:\n image: redis:latest\n ports:\n - 6379:6379\n command: [ 'redis-server', '--appendonly', 'yes' ]\n volumes:\n - redis-data:/data\n redis-commander:\n image: rediscommander/redis-commander:latest\n environment:\n - REDIS_HOSTS=local:redis:6379\n - HTTP_USER=root\n - HTTP_PASSWORD=secret\n ports:\n - 8082:8082\n depends_on:\n - redis\n classroom-service:\n image: rosie91/stemify:classroom-service\n build:\n context: .\n dockerfile: src/Services/ClassroomService/Classroom.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5001\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcResourceUrl=http://resource-service:5003\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcOrderUrl=http://order-service:5006\n - ConnectionStrings__stemifyclassroom=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_classroom\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=classroom-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=classroom-service,service.version=1.0.0\n ports:\n - 7001:80\n - 5001:5001\n depends_on:\n - postgres\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n resource-service:\n image: rosie91/stemify:resource-service\n build:\n context: .\n dockerfile: src/Services/Resource/Resource.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5003\n - ConnectionStrings__stemifyresource=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_resource\n - Cloudinary__CloudName=${CLOUDINARY_CLOUD_NAME}\n - Cloudinary__ApiKey=${CLOUDINARY_API_KEY}\n - Cloudinary__ApiSecret=${CLOUDINARY_API_SECRET}\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n - ClientApp=https://localhost:3000\n - Gemini__BaseUrl=https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent\n - Gemini__ApiKey=${GEMINI_API_KEY}\n - Gemini__Model=gemini-2.5-flash\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=resource-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=resource-service,service.version=1.0.0\n ports:\n - 7003:80\n - 5003:5003\n depends_on:\n - postgres\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n product-service:\n image: rosie91/stemify:product-service\n build:\n context: .\n dockerfile: src/Services/ProductService/Product.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5005\n - ConnectionStrings__stemifyproduct=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_product\n - Cloudinary__CloudName=${CLOUDINARY_CLOUD_NAME}\n - Cloudinary__ApiKey=${CLOUDINARY_API_KEY}\n - Cloudinary__ApiSecret=${CLOUDINARY_API_SECRET}\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcResourceUrl=http://resource-service:5003\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=product-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=product-service,service.version=1.0.0\n ports:\n - 7005:80\n - 5005:5005\n depends_on:\n - postgres\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n order-service:\n image: rosie91/stemify:order-service\n build:\n context: .\n dockerfile: src/Services/OrderService/Order.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5006\n - ConnectionStrings__stemifyorder=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_order\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcResourceUrl=http://resource-service:5003\n - GrpcProductUrl=http://product-service:5005\n - GrpcClassroomUrl=http://classroom-service:5001\n - Cloudinary__CloudName=${CLOUDINARY_CLOUD_NAME}\n - Cloudinary__ApiKey=${CLOUDINARY_API_KEY}\n - Cloudinary__ApiSecret=${CLOUDINARY_API_SECRET}\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=order-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=order-service,service.version=1.0.0\n\n ports:\n - 7006:80\n - 5006:5006\n depends_on:\n - postgres\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n notification-service:\n image: rosie91/stemify:notification-service\n build:\n context: .\n dockerfile: src/Services/Notification/Notification.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5004\n - ConnectionStrings__stemifynotification=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_notification\n # SendGrid configuration\n - SendGridSettings__ApiKey=${SENDGRID_API_KEY}\n - SendGridSettings__FromEmail=stemify30062025@gmail.com\n - SendGridSettings__FromName=STEMify\n # Client App configuration\n - ClientApp__BaseUrl=https://localhost:3000\n - ClientApp=https://localhost:3000\n - Issuer=http://localhost:5000/\n - IdentityServiceUrl=http://identity-service\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=notification-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=notification-service,service.version=1.0.0\n ports:\n - 7004:80\n - 5004:5004\n depends_on:\n - postgres\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n gateway-service:\n image: rosie91/stemify:gateway-service\n build:\n context: .\n dockerfile: src/ApiGateways/ApiGateway/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Docker\n - ASPNETCORE_URLS=http://+:80\n - IdentityServiceUrl=http://identity-service\n - Issuer=http://localhost:5000/\n - ClientApp=https://localhost:3000\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=gateway-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=gateway-service,service.version=1.0.0\n ports:\n - 6001:80\n depends_on:\n - otelcollector\n restart: unless-stopped\n identity-api-service:\n image: rosie91/stemify:identity-api-service\n build:\n context: .\n dockerfile: src/Services/Identity/Identity.Api/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5002\n - ConnectionStrings__stemifyidentity=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_identity\n # RabbitMQ Configuration (for MassTransit)\n - RabbitMq__Url=${RABBITMQ_URL}\n # OpenIddict Configuration\n - OpenIddict__Authority=http://identity-service:80\n # Client App Configuration\n - ClientApp=https://localhost:3000\n # Email Settings\n - EmailSettings__From=${IDENTITY_EMAIL_FROM}\n - EmailSettings__DisplayName=${IDENTITY_EMAIL_DISPLAY_NAME}\n - EmailSettings__SMTPServer=${IDENTITY_EMAIL_SMTP_SERVER}\n - EmailSettings__Port=${IDENTITY_EMAIL_PORT}\n - EmailSettings__Username=${IDENTITY_EMAIL_USERNAME}\n - EmailSettings__Password=${IDENTITY_EMAIL_PASSWORD}\n - EmailSettings__UseSSL=${IDENTITY_EMAIL_USE_SSL}\n - EmailSettings__ApplicationName=${IDENTITY_EMAIL_APP_NAME}\n - EmailSettings__SupportEmail=${IDENTITY_EMAIL_SUPPORT}\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=identity-api-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=identity-api-service,service.version=1.0.0\n - GrpcServices__OrderService__Address=http://order-service:5006\n ports:\n - 7002:80\n - 5002:5002\n depends_on:\n - postgres\n - otelcollector\n restart: unless-stopped\n identity-service:\n image: rosie91/stemify:identity-service\n build:\n context: .\n dockerfile: src/Services/Identity/Identity.Web/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - ASPNETCORE_URLS=http://+:80\n - ConnectionStrings__stemifyidentity=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_identity\n # OpenIddict configuration for Docker\n - OpenIddict__Issuer=http://localhost:5000\n - OpenIddict__Authority=http://localhost:5000\n - OpenIddict__SigningKey=${OPENIDDICT_SIGNING_KEY}\n - OpenIddict__EncryptionKey=${OPENIDDICT_ENCRYPTION_KEY}\n - OpenIddict__ApiClientSecret=${OPENIDDICT_API_CLIENT_SECRET}\n - OpenIddict__ForceRecreateClients=${OPENIDDICT_FORCE_RECREATE_CLIENTS:-true}\n - OpenIddict__DisableHttpsRequirement=${OPENIDDICT_DISABLE_HTTPS:-true}\n # CORS for Docker environment\n - Cors__Origins=${IDENTITY_CORS_ORIGINS:-https://localhost:3000}\n - ClientApp=${IDENTITY_CLIENT_APP:-https://localhost:3000}\n - BackendUrl=${IDENTITY_BACKEND_URL:-http://localhost:5000}\n # Google Authentication\n - Authentication__Google__ClientId=${GOOGLE_CLIENT_ID}\n - Authentication__Google__ClientSecret=${GOOGLE_CLIENT_SECRET}\n - Authentication__Google__RedirectUri=${GOOGLE_REDIRECT_URI}\n # Email Settings\n - EmailSettings__SMTPServer=${IDENTITY_EMAIL_SMTP_SERVER}\n - EmailSettings__Port=${IDENTITY_EMAIL_PORT}\n - EmailSettings__UseSSL=${IDENTITY_EMAIL_USE_SSL}\n - EmailSettings__Username=${IDENTITY_EMAIL_USERNAME}\n - EmailSettings__Password=${IDENTITY_EMAIL_PASSWORD}\n - EmailSettings__From=${IDENTITY_EMAIL_FROM}\n - EmailSettings__DisplayName=${IDENTITY_EMAIL_DISPLAY_NAME}\n - EmailSettings__ApplicationName=${IDENTITY_EMAIL_APP_NAME}\n - EmailSettings__SupportEmail=${IDENTITY_EMAIL_SUPPORT}\n - EmailSettings__Enabled=${IDENTITY_EMAIL_ENABLED}\n - EmailSettings__EnableVerification=${IDENTITY_EMAIL_ENABLE_VERIFICATION}\n - EmailSettings__TimeoutMs=${IDENTITY_EMAIL_TIMEOUT_MS}\n - EmailSettings__MaxRetryAttempts=${IDENTITY_EMAIL_MAX_RETRY}\n - EmailSettings__LogEmailContent=${IDENTITY_EMAIL_LOG_CONTENT}\n # gRPC Services\n - GrpcServices__OrderService__Address=http://order-service:5006\n # Database Options\n - DatabaseOptions__MaxRetryCount=5\n - DatabaseOptions__MaxRetryDelay=00:00:10\n - DatabaseOptions__CommandTimeout=30\n - DatabaseOptions__EnableRetryOnFailure=true\n - DatabaseOptions__EnableSensitiveDataLogging=${DB_ENABLE_SENSITIVE_LOGGING}\n - DatabaseOptions__EnableDetailedErrors=${DB_ENABLE_DETAILED_ERRORS}\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=identity-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=identity-service,service.version=1.0.0\n - RabbitMq__Url=${RABBITMQ_URL}\n ports:\n - 5000:80\n depends_on:\n - postgres\n - otelcollector\n restart: unless-stopped\n cart-service:\n image: rosie91/stemify:cart-service\n build:\n context: .\n dockerfile: src/Services/CartService/Cart.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5007\n - ConnectionStrings__stemifycart=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_cart\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcProductUrl=http://product-service:5005\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=cart-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=cart-service,service.version=1.0.0\n ports:\n - 7007:80\n - 5007:5007\n depends_on:\n - postgres\n - redis\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n payment-service:\n image: rosie91/stemify:payment-service\n build:\n context: .\n dockerfile: src/Services/PaymentService/Payment.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - Logging__LogLevel__Microsoft.EntityFrameworkCore.Database.Command=Warning\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5008\n - ConnectionStrings__stemifypayment=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_payment\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n # PayOS configuration\n - PaymentGateways__PayOS__ClientId=${PAYOS_CLIENT_ID}\n - PaymentGateways__PayOS__ApiKey=${PAYOS_API_KEY}\n - PaymentGateways__PayOS__ChecksumKey=${PAYOS_CHECKSUM_KEY}\n - PaymentGateways__PayOS__BaseUrl=https://api-merchant.payos.vn\n # Stripe configuration\n - PaymentGateways__Stripe__SecretKey=${STRIPE_SECRET_KEY}\n - PaymentGateways__Stripe__PublishableKey=${STRIPE_PUBLISH_KEY}\n - PaymentGateways__Stripe__WebhookSecret=${STRIPE_WEBHOOK_KEY}\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=payment-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=payment-service,service.version=1.0.0\n ports:\n - 7008:80\n - 5008:5008\n depends_on:\n - postgres\n - redis\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n emulator-service:\n image: rosie91/stemify:emulator-service\n build:\n context: .\n dockerfile: src/Services/EmulatorService/Emulator.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5009\n # MongoDB configuration\n - MongoDb__ConnectionString=mongodb://root:mongopw@mongo\n - MongoDb__DatabaseName=stemify_emulator\n # Redis configuration\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n # gRPC endpoints\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcResourceUrl=http://resource-service:5003\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=emulator-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=emulator-service,service.version=1.0.0\n # Cloudinary Configuration\n - Cloudinary__CloudName=${CLOUDINARY_CLOUD_NAME}\n - Cloudinary__ApiKey=${CLOUDINARY_API_KEY}\n - Cloudinary__ApiSecret=${CLOUDINARY_API_SECRET}\n ports:\n - 7009:80\n - 5009:5009\n depends_on:\n - redis\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n\n # ========================================\n # MONITORING STACK\n # ========================================\n\n # Prometheus - Metrics Collection\n prometheus:\n image: prom/prometheus:v3.5.0\n container_name: stemify-prometheus\n ports:\n - '9090:9090'\n volumes:\n - ./config/prometheus:/etc/prometheus:ro\n - prometheus_data:/prometheus\n command:\n - '--config.file=/etc/prometheus/prometheus.yml'\n - '--storage.tsdb.path=/prometheus'\n - '--storage.tsdb.retention.time=15d'\n - '--storage.tsdb.retention.size=10GB'\n - '--web.enable-lifecycle'\n - '--web.enable-otlp-receiver'\n restart: unless-stopped\n\n # AlertManager - Alert Management\n alertmanager:\n image: prom/alertmanager:v0.27.0\n container_name: stemify-alertmanager\n ports:\n - '9093:9093'\n volumes:\n - ./config/alertmanager:/etc/alertmanager:ro\n - alertmanager_data:/alertmanager\n command:\n - '--config.file=/etc/alertmanager/alertmanager.yml'\n - '--storage.path=/alertmanager'\n - '--web.external-url=http://localhost:9093'\n restart: unless-stopped\n\n # Grafana - Visualization\n grafana:\n image: grafana/grafana:latest\n container_name: stemify-grafana\n ports:\n - '3001:3001'\n volumes:\n - grafana_data:/var/lib/grafana\n - ./config/grafana/config:/etc/grafana:ro\n - ./config/grafana/dashboards:/var/lib/grafana/dashboards:ro\n environment:\n - GF_SECURITY_ADMIN_USER=${GRAFANA_ADMIN_USERNAME}\n - GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_ADMIN_PASSWORD:-admin123}\n - GF_USERS_ALLOW_SIGN_UP=false\n - GF_INSTALL_PLUGINS=grafana-clock-panel,grafana-simple-json-datasource\n - PROMETHEUS_ENDPOINT=http://prometheus:9090\n restart: unless-stopped\n depends_on:\n - prometheus\n\n # OpenTelemetry Collector\n otelcollector:\n image: otel/opentelemetry-collector-contrib:latest\n container_name: stemify-otelcollector\n ports:\n - '8888:8888' # Prometheus metrics\n - '8889:8889' # Prometheus exporter\n - '4317:4317' # OTLP gRPC receiver\n - '4318:4318' # OTLP HTTP receiver\n volumes:\n - ./config/otelcollector:/etc/otelcol-contrib:ro\n environment:\n - PROMETHEUS_ENDPOINT=http://prometheus:9090/api/v1/write\n command: [ '--config=/etc/otelcol-contrib/config.yaml' ]\n restart: unless-stopped\n depends_on:\n - prometheus\nvolumes:\n redis-data:\n postgres_data:\n mongodata:\n prometheus_data:\n alertmanager_data:\n grafana_data:\n"}, "cc687113451f4d84b4d885d7fde19422fd6673d8": {"t": 1785748793.1372788, "n": 21179, "h": "054ff39be4a1ea4c", "c": "services:\n postgres:\n image: postgres:17\n container_name: stemify-postgres\n environment:\n - POSTGRES_PASSWORD=${DB_PASSWORD}\n ports:\n - '5432:5432'\n volumes:\n - postgres_data:/var/lib/postgresql/data\n restart: unless-stopped\n # RabbitMQ is now using CloudAMQP online service\n # rabbitmq:\n # image: rabbitmq:3-management-alpine\n # ports:\n # - 5672:5672\n # - 15672:15672\n # environment:\n # - RABBITMQ_DEFAULT_USER=guest\n # - RABBITMQ_DEFAULT_PASS=guest\n # healthcheck:\n # test: [\"CMD\", \"rabbitmq-diagnostics\", \"ping\"]\n # interval: 30s\n # timeout: 10s\n # retries: 5\n # start_period: 30s\n # restart: unless-stopped\n mongo:\n image: mongo\n environment:\n - MONGO_INITDB_ROOT_USERNAME=root\n - MONGO_INITDB_ROOT_PASSWORD=mongopw\n ports:\n - 27017:27017\n volumes:\n - mongodata:/var/lib/mongodb/data\n redis:\n image: redis:latest\n ports:\n - 6379:6379\n command: ['redis-server', '--appendonly', 'yes']\n volumes:\n - redis-data:/data\n redis-commander:\n image: rediscommander/redis-commander:latest\n environment:\n - REDIS_HOSTS=local:redis:6379\n - HTTP_USER=root\n - HTTP_PASSWORD=secret\n ports:\n - 8082:8082\n depends_on:\n - redis\n classroom-service:\n image: rosie91/stemify:classroom-service\n build:\n context: .\n dockerfile: src/Services/ClassroomService/Classroom.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5001\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcResourceUrl=http://resource-service:5003\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcOrderUrl=http://order-service:5006\n - ConnectionStrings__stemifyclassroom=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_classroom\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=classroom-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=classroom-service,service.version=1.0.0\n ports:\n - 7001:80\n - 5001:5001\n depends_on:\n - postgres\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n resource-service:\n image: rosie91/stemify:resource-service\n build:\n context: .\n dockerfile: src/Services/Resource/Resource.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5003\n - ConnectionStrings__stemifyresource=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_resource\n - Cloudinary__CloudName=${CLOUDINARY_CLOUD_NAME}\n - Cloudinary__ApiKey=${CLOUDINARY_API_KEY}\n - Cloudinary__ApiSecret=${CLOUDINARY_API_SECRET}\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n - ClientApp=https://localhost:3000\n - Gemini__BaseUrl=https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent\n - Gemini__ApiKey=${GEMINI_API_KEY}\n - Gemini__Model=gemini-2.5-flash\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=resource-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=resource-service,service.version=1.0.0\n ports:\n - 7003:80\n - 5003:5003\n depends_on:\n - postgres\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n product-service:\n image: rosie91/stemify:product-service\n build:\n context: .\n dockerfile: src/Services/ProductService/Product.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5005\n - ConnectionStrings__stemifyproduct=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_product\n - Cloudinary__CloudName=${CLOUDINARY_CLOUD_NAME}\n - Cloudinary__ApiKey=${CLOUDINARY_API_KEY}\n - Cloudinary__ApiSecret=${CLOUDINARY_API_SECRET}\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcResourceUrl=http://resource-service:5003\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=product-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=product-service,service.version=1.0.0\n ports:\n - 7005:80\n - 5005:5005\n depends_on:\n - postgres\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n order-service:\n image: rosie91/stemify:order-service\n build:\n context: .\n dockerfile: src/Services/OrderService/Order.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5006\n - ConnectionStrings__stemifyorder=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_order\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcResourceUrl=http://resource-service:5003\n - GrpcProductUrl=http://product-service:5005\n - Cloudinary__CloudName=${CLOUDINARY_CLOUD_NAME}\n - Cloudinary__ApiKey=${CLOUDINARY_API_KEY}\n - Cloudinary__ApiSecret=${CLOUDINARY_API_SECRET}\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=order-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=order-service,service.version=1.0.0\n\n ports:\n - 7006:80\n - 5006:5006\n depends_on:\n - postgres\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n notification-service:\n image: rosie91/stemify:notification-service\n build:\n context: .\n dockerfile: src/Services/Notification/Notification.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5004\n - ConnectionStrings__stemifynotification=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_notification\n # SendGrid configuration\n - SendGridSettings__ApiKey=${SENDGRID_API_KEY}\n - SendGridSettings__FromEmail=stemify30062025@gmail.com\n - SendGridSettings__FromName=STEMify\n # Client App configuration\n - ClientApp__BaseUrl=https://localhost:3000\n - ClientApp=https://localhost:3000\n - Issuer=http://localhost:5000/\n - IdentityServiceUrl=http://identity-service\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=notification-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=notification-service,service.version=1.0.0\n ports:\n - 7004:80\n - 5004:5004\n depends_on:\n - postgres\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n gateway-service:\n image: rosie91/stemify:gateway-service\n build:\n context: .\n dockerfile: src/ApiGateways/ApiGateway/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Docker\n - ASPNETCORE_URLS=http://+:80\n - IdentityServiceUrl=http://identity-service\n - Issuer=http://localhost:5000/\n - ClientApp=https://localhost:3000\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=gateway-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=gateway-service,service.version=1.0.0\n ports:\n - 6001:80\n depends_on:\n - otelcollector\n restart: unless-stopped\n identity-api-service:\n image: rosie91/stemify:identity-api-service\n build:\n context: .\n dockerfile: src/Services/Identity/Identity.Api/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5002\n - ConnectionStrings__stemifyidentity=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_identity\n # RabbitMQ Configuration (for MassTransit)\n - RabbitMq__Url=${RABBITMQ_URL}\n # OpenIddict Configuration\n - OpenIddict__Authority=http://identity-service:80\n # Client App Configuration\n - ClientApp=https://localhost:3000\n # Email Settings\n - EmailSettings__From=${IDENTITY_EMAIL_FROM}\n - EmailSettings__DisplayName=${IDENTITY_EMAIL_DISPLAY_NAME}\n - EmailSettings__SMTPServer=${IDENTITY_EMAIL_SMTP_SERVER}\n - EmailSettings__Port=${IDENTITY_EMAIL_PORT}\n - EmailSettings__Username=${IDENTITY_EMAIL_USERNAME}\n - EmailSettings__Password=${IDENTITY_EMAIL_PASSWORD}\n - EmailSettings__UseSSL=${IDENTITY_EMAIL_USE_SSL}\n - EmailSettings__ApplicationName=${IDENTITY_EMAIL_APP_NAME}\n - EmailSettings__SupportEmail=${IDENTITY_EMAIL_SUPPORT}\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=identity-api-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=identity-api-service,service.version=1.0.0\n - GrpcServices__OrderService__Address=http://order-service:5006\n ports:\n - 7002:80\n - 5002:5002\n depends_on:\n - postgres\n - otelcollector\n restart: unless-stopped\n identity-service:\n image: rosie91/stemify:identity-service\n build:\n context: .\n dockerfile: src/Services/Identity/Identity.Web/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - ASPNETCORE_URLS=http://+:80\n - ConnectionStrings__stemifyidentity=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_identity\n # OpenIddict configuration for Docker\n - OpenIddict__Issuer=http://localhost:5000\n - OpenIddict__Authority=http://localhost:5000\n - OpenIddict__SigningKey=${OPENIDDICT_SIGNING_KEY}\n - OpenIddict__EncryptionKey=${OPENIDDICT_ENCRYPTION_KEY}\n - OpenIddict__ApiClientSecret=${OPENIDDICT_API_CLIENT_SECRET}\n - OpenIddict__ForceRecreateClients=${OPENIDDICT_FORCE_RECREATE_CLIENTS:-true}\n - OpenIddict__DisableHttpsRequirement=${OPENIDDICT_DISABLE_HTTPS:-true}\n # CORS for Docker environment\n - Cors__Origins=${IDENTITY_CORS_ORIGINS:-https://localhost:3000}\n - ClientApp=${IDENTITY_CLIENT_APP:-https://localhost:3000}\n - BackendUrl=${IDENTITY_BACKEND_URL:-http://localhost:5000}\n # Google Authentication\n - Authentication__Google__ClientId=${GOOGLE_CLIENT_ID}\n - Authentication__Google__ClientSecret=${GOOGLE_CLIENT_SECRET}\n - Authentication__Google__RedirectUri=${GOOGLE_REDIRECT_URI}\n # Email Settings\n - EmailSettings__SMTPServer=${IDENTITY_EMAIL_SMTP_SERVER}\n - EmailSettings__Port=${IDENTITY_EMAIL_PORT}\n - EmailSettings__UseSSL=${IDENTITY_EMAIL_USE_SSL}\n - EmailSettings__Username=${IDENTITY_EMAIL_USERNAME}\n - EmailSettings__Password=${IDENTITY_EMAIL_PASSWORD}\n - EmailSettings__From=${IDENTITY_EMAIL_FROM}\n - EmailSettings__DisplayName=${IDENTITY_EMAIL_DISPLAY_NAME}\n - EmailSettings__ApplicationName=${IDENTITY_EMAIL_APP_NAME}\n - EmailSettings__SupportEmail=${IDENTITY_EMAIL_SUPPORT}\n - EmailSettings__Enabled=${IDENTITY_EMAIL_ENABLED}\n - EmailSettings__EnableVerification=${IDENTITY_EMAIL_ENABLE_VERIFICATION}\n - EmailSettings__TimeoutMs=${IDENTITY_EMAIL_TIMEOUT_MS}\n - EmailSettings__MaxRetryAttempts=${IDENTITY_EMAIL_MAX_RETRY}\n - EmailSettings__LogEmailContent=${IDENTITY_EMAIL_LOG_CONTENT}\n # gRPC Services\n - GrpcServices__OrderService__Address=http://order-service:5006\n # Database Options\n - DatabaseOptions__MaxRetryCount=5\n - DatabaseOptions__MaxRetryDelay=00:00:10\n - DatabaseOptions__CommandTimeout=30\n - DatabaseOptions__EnableRetryOnFailure=true\n - DatabaseOptions__EnableSensitiveDataLogging=${DB_ENABLE_SENSITIVE_LOGGING}\n - DatabaseOptions__EnableDetailedErrors=${DB_ENABLE_DETAILED_ERRORS}\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=identity-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=identity-service,service.version=1.0.0\n - RabbitMq__Url=${RABBITMQ_URL}\n ports:\n - 5000:80\n depends_on:\n - postgres\n - otelcollector\n restart: unless-stopped\n cart-service:\n image: rosie91/stemify:cart-service\n build:\n context: .\n dockerfile: src/Services/CartService/Cart.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5007\n - ConnectionStrings__stemifycart=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_cart\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcProductUrl=http://product-service:5005\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=cart-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=cart-service,service.version=1.0.0\n ports:\n - 7007:80\n - 5007:5007\n depends_on:\n - postgres\n - redis\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n payment-service:\n image: rosie91/stemify:payment-service\n build:\n context: .\n dockerfile: src/Services/PaymentService/Payment.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - Logging__LogLevel__Microsoft.EntityFrameworkCore.Database.Command=Warning\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5008\n - ConnectionStrings__stemifypayment=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_payment\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n # PayOS configuration\n - PaymentGateways__PayOS__ClientId=${PAYOS_CLIENT_ID}\n - PaymentGateways__PayOS__ApiKey=${PAYOS_API_KEY}\n - PaymentGateways__PayOS__ChecksumKey=${PAYOS_CHECKSUM_KEY}\n - PaymentGateways__PayOS__BaseUrl=https://api-merchant.payos.vn\n # Stripe configuration\n - PaymentGateways__Stripe__SecretKey=${STRIPE_SECRET_KEY}\n - PaymentGateways__Stripe__PublishableKey=${STRIPE_PUBLISH_KEY}\n - PaymentGateways__Stripe__WebhookSecret=${STRIPE_WEBHOOK_KEY}\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=payment-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=payment-service,service.version=1.0.0\n ports:\n - 7008:80\n - 5008:5008\n depends_on:\n - postgres\n - redis\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n emulator-service:\n image: rosie91/stemify:emulator-service\n build:\n context: .\n dockerfile: src/Services/EmulatorService/Emulator.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5009\n # MongoDB configuration\n - MongoDb__ConnectionString=mongodb://root:mongopw@mongo\n - MongoDb__DatabaseName=stemify_emulator\n # Redis configuration\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n # gRPC endpoints\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcResourceUrl=http://resource-service:5003\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=emulator-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=emulator-service,service.version=1.0.0\n # Cloudinary Configuration\n - Cloudinary__CloudName=${CLOUDINARY_CLOUD_NAME}\n - Cloudinary__ApiKey=${CLOUDINARY_API_KEY}\n - Cloudinary__ApiSecret=${CLOUDINARY_API_SECRET}\n ports:\n - 7009:80\n - 5009:5009\n depends_on:\n - redis\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n\n # ========================================\n # MONITORING STACK\n # ========================================\n\n # Prometheus - Metrics Collection\n prometheus:\n image: prom/prometheus:v3.5.0\n container_name: stemify-prometheus\n ports:\n - '9090:9090'\n volumes:\n - ./config/prometheus:/etc/prometheus:ro\n - prometheus_data:/prometheus\n command:\n - '--config.file=/etc/prometheus/prometheus.yml'\n - '--storage.tsdb.path=/prometheus'\n - '--storage.tsdb.retention.time=15d'\n - '--storage.tsdb.retention.size=10GB'\n - '--web.enable-lifecycle'\n - '--web.enable-otlp-receiver'\n restart: unless-stopped\n\n # AlertManager - Alert Management\n alertmanager:\n image: prom/alertmanager:v0.27.0\n container_name: stemify-alertmanager\n ports:\n - '9093:9093'\n volumes:\n - ./config/alertmanager:/etc/alertmanager:ro\n - alertmanager_data:/alertmanager\n command:\n - '--config.file=/etc/alertmanager/alertmanager.yml'\n - '--storage.path=/alertmanager'\n - '--web.external-url=http://localhost:9093'\n restart: unless-stopped\n\n # Grafana - Visualization\n grafana:\n image: grafana/grafana:latest\n container_name: stemify-grafana\n ports:\n - '3001:3001'\n volumes:\n - grafana_data:/var/lib/grafana\n - ./config/grafana/config:/etc/grafana:ro\n - ./config/grafana/dashboards:/var/lib/grafana/dashboards:ro\n environment:\n - GF_SECURITY_ADMIN_USER=${GRAFANA_ADMIN_USERNAME}\n - GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_ADMIN_PASSWORD:-admin123}\n - GF_USERS_ALLOW_SIGN_UP=false\n - GF_INSTALL_PLUGINS=grafana-clock-panel,grafana-simple-json-datasource\n - PROMETHEUS_ENDPOINT=http://prometheus:9090\n restart: unless-stopped\n depends_on:\n - prometheus\n\n # OpenTelemetry Collector\n otelcollector:\n image: otel/opentelemetry-collector-contrib:latest\n container_name: stemify-otelcollector\n ports:\n - '8888:8888' # Prometheus metrics\n - '8889:8889' # Prometheus exporter\n - '4317:4317' # OTLP gRPC receiver\n - '4318:4318' # OTLP HTTP receiver\n volumes:\n - ./config/otelcollector:/etc/otelcol-contrib:ro\n environment:\n - PROMETHEUS_ENDPOINT=http://prometheus:9090/api/v1/write\n command: ['--config=/etc/otelcol-contrib/config.yaml']\n restart: unless-stopped\n depends_on:\n - prometheus\nvolumes:\n redis-data:\n postgres_data:\n mongodata:\n prometheus_data:\n alertmanager_data:\n grafana_data:\n"}, "ff48563da357719bc7d1077476072cf3e7d65b6b": {"t": 1785748794.3719087, "n": 20987, "h": "f42d9b236ea3009b", "c": "services:\n postgres:\n image: postgres:17\n container_name: stemify-postgres\n environment:\n - POSTGRES_PASSWORD=${DB_PASSWORD}\n ports:\n - '5432:5432'\n volumes:\n - postgres_data:/var/lib/postgresql/data\n restart: unless-stopped\n # RabbitMQ is now using CloudAMQP online service\n # rabbitmq:\n # image: rabbitmq:3-management-alpine\n # ports:\n # - 5672:5672\n # - 15672:15672\n # environment:\n # - RABBITMQ_DEFAULT_USER=guest\n # - RABBITMQ_DEFAULT_PASS=guest\n # healthcheck:\n # test: [\"CMD\", \"rabbitmq-diagnostics\", \"ping\"]\n # interval: 30s\n # timeout: 10s\n # retries: 5\n # start_period: 30s\n # restart: unless-stopped\n mongo:\n image: mongo\n environment:\n - MONGO_INITDB_ROOT_USERNAME=root\n - MONGO_INITDB_ROOT_PASSWORD=mongopw\n ports:\n - 27017:27017\n volumes:\n - mongodata:/var/lib/mongodb/data\n redis:\n image: redis:latest\n ports:\n - 6379:6379\n command: ['redis-server', '--appendonly', 'yes']\n volumes:\n - redis-data:/data\n redis-commander:\n image: rediscommander/redis-commander:latest\n environment:\n - REDIS_HOSTS=local:redis:6379\n - HTTP_USER=root\n - HTTP_PASSWORD=secret\n ports:\n - 8082:8082\n depends_on:\n - redis\n classroom-service:\n image: rosie91/stemify:classroom-service\n build:\n context: .\n dockerfile: src/Services/ClassroomService/Classroom.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5001\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcResourceUrl=http://resource-service:5003\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcOrderUrl=http://order-service:5006\n - ConnectionStrings__stemifyclassroom=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_classroom\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=classroom-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=classroom-service,service.version=1.0.0\n ports:\n - 7001:80\n - 5001:5001\n depends_on:\n - postgres\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n resource-service:\n image: rosie91/stemify:resource-service\n build:\n context: .\n dockerfile: src/Services/Resource/Resource.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5003\n - ConnectionStrings__stemifyresource=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_resource\n - Cloudinary__CloudName=${CLOUDINARY_CLOUD_NAME}\n - Cloudinary__ApiKey=${CLOUDINARY_API_KEY}\n - Cloudinary__ApiSecret=${CLOUDINARY_API_SECRET}\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n - ClientApp=https://localhost:3000\n - Gemini__BaseUrl=https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent\n - Gemini__ApiKey=${GEMINI_API_KEY}\n - Gemini__Model=gemini-2.5-flash\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=resource-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=resource-service,service.version=1.0.0\n ports:\n - 7003:80\n - 5003:5003\n depends_on:\n - postgres\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n product-service:\n image: rosie91/stemify:product-service\n build:\n context: .\n dockerfile: src/Services/ProductService/Product.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5005\n - ConnectionStrings__stemifyproduct=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_product\n - Cloudinary__CloudName=${CLOUDINARY_CLOUD_NAME}\n - Cloudinary__ApiKey=${CLOUDINARY_API_KEY}\n - Cloudinary__ApiSecret=${CLOUDINARY_API_SECRET}\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcResourceUrl=http://resource-service:5003\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=product-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=product-service,service.version=1.0.0\n ports:\n - 7005:80\n - 5005:5005\n depends_on:\n - postgres\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n order-service:\n image: rosie91/stemify:order-service\n build:\n context: .\n dockerfile: src/Services/OrderService/Order.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5006\n - ConnectionStrings__stemifyorder=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_order\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcResourceUrl=http://resource-service:5003\n - GrpcProductUrl=http://product-service:5005\n - Cloudinary__CloudName=${CLOUDINARY_CLOUD_NAME}\n - Cloudinary__ApiKey=${CLOUDINARY_API_KEY}\n - Cloudinary__ApiSecret=${CLOUDINARY_API_SECRET}\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=order-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=order-service,service.version=1.0.0\n\n ports:\n - 7006:80\n - 5006:5006\n depends_on:\n - postgres\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n notification-service:\n image: rosie91/stemify:notification-service\n build:\n context: .\n dockerfile: src/Services/Notification/Notification.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5004\n - ConnectionStrings__stemifynotification=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_notification\n # SendGrid configuration\n - SendGridSettings__ApiKey=${SENDGRID_API_KEY}\n - SendGridSettings__FromEmail=stemify30062025@gmail.com\n - SendGridSettings__FromName=STEMify\n # Client App configuration\n - ClientApp__BaseUrl=https://localhost:3000\n - ClientApp=https://localhost:3000\n - Issuer=http://localhost:5000/\n - IdentityServiceUrl=http://identity-service\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=notification-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=notification-service,service.version=1.0.0\n ports:\n - 7004:80\n - 5004:5004\n depends_on:\n - postgres\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n gateway-service:\n image: rosie91/stemify:gateway-service\n build:\n context: .\n dockerfile: src/ApiGateways/ApiGateway/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Docker\n - ASPNETCORE_URLS=http://+:80\n - IdentityServiceUrl=http://identity-service\n - Issuer=http://localhost:5000/\n - ClientApp=https://localhost:3000\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=gateway-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=gateway-service,service.version=1.0.0\n ports:\n - 6001:80\n depends_on:\n - otelcollector\n restart: unless-stopped\n identity-api-service:\n image: rosie91/stemify:identity-api-service\n build:\n context: .\n dockerfile: src/Services/Identity/Identity.Api/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5002\n - ConnectionStrings__stemifyidentity=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_identity\n # RabbitMQ Configuration (for MassTransit)\n - RabbitMq__Url=${RABBITMQ_URL}\n # OpenIddict Configuration\n - OpenIddict__Authority=http://identity-service:80\n # Client App Configuration\n - ClientApp=https://localhost:3000\n # Email Settings\n - EmailSettings__From=${IDENTITY_EMAIL_FROM}\n - EmailSettings__DisplayName=${IDENTITY_EMAIL_DISPLAY_NAME}\n - EmailSettings__SMTPServer=${IDENTITY_EMAIL_SMTP_SERVER}\n - EmailSettings__Port=${IDENTITY_EMAIL_PORT}\n - EmailSettings__Username=${IDENTITY_EMAIL_USERNAME}\n - EmailSettings__Password=${IDENTITY_EMAIL_PASSWORD}\n - EmailSettings__UseSSL=${IDENTITY_EMAIL_USE_SSL}\n - EmailSettings__ApplicationName=${IDENTITY_EMAIL_APP_NAME}\n - EmailSettings__SupportEmail=${IDENTITY_EMAIL_SUPPORT}\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=identity-api-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=identity-api-service,service.version=1.0.0\n - GrpcServices__OrderService__Address=http://order-service:5006\n ports:\n - 7002:80\n - 5002:5002\n depends_on:\n - postgres\n - otelcollector\n restart: unless-stopped\n identity-service:\n image: rosie91/stemify:identity-service\n build:\n context: .\n dockerfile: src/Services/Identity/Identity.Web/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - ASPNETCORE_URLS=http://+:80\n - ConnectionStrings__stemifyidentity=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_identity\n # OpenIddict configuration for Docker\n - OpenIddict__Issuer=http://localhost:5000\n - OpenIddict__Authority=http://localhost:5000\n - OpenIddict__SigningKey=${OPENIDDICT_SIGNING_KEY}\n - OpenIddict__EncryptionKey=${OPENIDDICT_ENCRYPTION_KEY}\n - OpenIddict__ApiClientSecret=${OPENIDDICT_API_CLIENT_SECRET}\n - OpenIddict__ForceRecreateClients=${OPENIDDICT_FORCE_RECREATE_CLIENTS:-true}\n - OpenIddict__DisableHttpsRequirement=${OPENIDDICT_DISABLE_HTTPS:-true}\n # CORS for Docker environment\n - Cors__Origins=${IDENTITY_CORS_ORIGINS:-https://localhost:3000}\n - ClientApp=${IDENTITY_CLIENT_APP:-https://localhost:3000}\n - BackendUrl=${IDENTITY_BACKEND_URL:-http://localhost:5000}\n # Google Authentication\n - Authentication__Google__ClientId=${GOOGLE_CLIENT_ID}\n - Authentication__Google__ClientSecret=${GOOGLE_CLIENT_SECRET}\n - Authentication__Google__RedirectUri=${GOOGLE_REDIRECT_URI}\n # Email Settings\n - EmailSettings__SMTPServer=${IDENTITY_EMAIL_SMTP_SERVER}\n - EmailSettings__Port=${IDENTITY_EMAIL_PORT}\n - EmailSettings__UseSSL=${IDENTITY_EMAIL_USE_SSL}\n - EmailSettings__Username=${IDENTITY_EMAIL_USERNAME}\n - EmailSettings__Password=${IDENTITY_EMAIL_PASSWORD}\n - EmailSettings__From=${IDENTITY_EMAIL_FROM}\n - EmailSettings__DisplayName=${IDENTITY_EMAIL_DISPLAY_NAME}\n - EmailSettings__ApplicationName=${IDENTITY_EMAIL_APP_NAME}\n - EmailSettings__SupportEmail=${IDENTITY_EMAIL_SUPPORT}\n - EmailSettings__Enabled=${IDENTITY_EMAIL_ENABLED}\n - EmailSettings__EnableVerification=${IDENTITY_EMAIL_ENABLE_VERIFICATION}\n - EmailSettings__TimeoutMs=${IDENTITY_EMAIL_TIMEOUT_MS}\n - EmailSettings__MaxRetryAttempts=${IDENTITY_EMAIL_MAX_RETRY}\n - EmailSettings__LogEmailContent=${IDENTITY_EMAIL_LOG_CONTENT}\n # gRPC Services\n - GrpcServices__OrderService__Address=http://order-service:5006\n # Database Options\n - DatabaseOptions__MaxRetryCount=5\n - DatabaseOptions__MaxRetryDelay=00:00:10\n - DatabaseOptions__CommandTimeout=30\n - DatabaseOptions__EnableRetryOnFailure=true\n - DatabaseOptions__EnableSensitiveDataLogging=${DB_ENABLE_SENSITIVE_LOGGING}\n - DatabaseOptions__EnableDetailedErrors=${DB_ENABLE_DETAILED_ERRORS}\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=identity-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=identity-service,service.version=1.0.0\n - RabbitMq__Url=${RABBITMQ_URL}\n ports:\n - 5000:80\n depends_on:\n - postgres\n - otelcollector\n restart: unless-stopped\n cart-service:\n image: rosie91/stemify:cart-service\n build:\n context: .\n dockerfile: src/Services/CartService/Cart.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5007\n - ConnectionStrings__stemifycart=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_cart\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcProductUrl=http://product-service:5005\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=cart-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=cart-service,service.version=1.0.0\n ports:\n - 7007:80\n - 5007:5007\n depends_on:\n - postgres\n - redis\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n payment-service:\n image: rosie91/stemify:payment-service\n build:\n context: .\n dockerfile: src/Services/PaymentService/Payment.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - Logging__LogLevel__Microsoft.EntityFrameworkCore.Database.Command=Warning\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5008\n - ConnectionStrings__stemifypayment=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_payment\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n # PayOS configuration\n - PaymentGateways__PayOS__ClientId=${PAYOS_CLIENT_ID}\n - PaymentGateways__PayOS__ApiKey=${PAYOS_API_KEY}\n - PaymentGateways__PayOS__ChecksumKey=${PAYOS_CHECKSUM_KEY}\n - PaymentGateways__PayOS__BaseUrl=https://api-merchant.payos.vn\n # Stripe configuration\n - PaymentGateways__Stripe__SecretKey=${STRIPE_SECRET_KEY}\n - PaymentGateways__Stripe__PublishableKey=${STRIPE_PUBLISH_KEY}\n - PaymentGateways__Stripe__WebhookSecret=${STRIPE_WEBHOOK_KEY}\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=payment-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=payment-service,service.version=1.0.0\n ports:\n - 7008:80\n - 5008:5008\n depends_on:\n - postgres\n - redis\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n emulator-service:\n image: rosie91/stemify:emulator-service\n build:\n context: .\n dockerfile: src/Services/EmulatorService/Emulator.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5009\n # MongoDB configuration\n - MongoDb__ConnectionString=mongodb://root:mongopw@mongo\n - MongoDb__DatabaseName=stemify_emulator\n # Redis configuration\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n # gRPC endpoints\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcResourceUrl=http://resource-service:5003\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=emulator-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=emulator-service,service.version=1.0.0\n ports:\n - 7009:80\n - 5009:5009\n depends_on:\n - redis\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n\n # ========================================\n # MONITORING STACK\n # ========================================\n\n # Prometheus - Metrics Collection\n prometheus:\n image: prom/prometheus:v3.5.0\n container_name: stemify-prometheus\n ports:\n - '9090:9090'\n volumes:\n - ./config/prometheus:/etc/prometheus:ro\n - prometheus_data:/prometheus\n command:\n - '--config.file=/etc/prometheus/prometheus.yml'\n - '--storage.tsdb.path=/prometheus'\n - '--storage.tsdb.retention.time=15d'\n - '--storage.tsdb.retention.size=10GB'\n - '--web.enable-lifecycle'\n - '--web.enable-otlp-receiver'\n restart: unless-stopped\n\n # AlertManager - Alert Management\n alertmanager:\n image: prom/alertmanager:v0.27.0\n container_name: stemify-alertmanager\n ports:\n - '9093:9093'\n volumes:\n - ./config/alertmanager:/etc/alertmanager:ro\n - alertmanager_data:/alertmanager\n command:\n - '--config.file=/etc/alertmanager/alertmanager.yml'\n - '--storage.path=/alertmanager'\n - '--web.external-url=http://localhost:9093'\n restart: unless-stopped\n\n # Grafana - Visualization\n grafana:\n image: grafana/grafana:latest\n container_name: stemify-grafana\n ports:\n - '3001:3001'\n volumes:\n - grafana_data:/var/lib/grafana\n - ./config/grafana/config:/etc/grafana:ro\n - ./config/grafana/dashboards:/var/lib/grafana/dashboards:ro\n environment:\n - GF_SECURITY_ADMIN_USER=${GRAFANA_ADMIN_USERNAME}\n - GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_ADMIN_PASSWORD:-admin123}\n - GF_USERS_ALLOW_SIGN_UP=false\n - GF_INSTALL_PLUGINS=grafana-clock-panel,grafana-simple-json-datasource\n - PROMETHEUS_ENDPOINT=http://prometheus:9090\n restart: unless-stopped\n depends_on:\n - prometheus\n\n # OpenTelemetry Collector\n otelcollector:\n image: otel/opentelemetry-collector-contrib:latest\n container_name: stemify-otelcollector\n ports:\n - '8888:8888' # Prometheus metrics\n - '8889:8889' # Prometheus exporter\n - '4317:4317' # OTLP gRPC receiver\n - '4318:4318' # OTLP HTTP receiver\n volumes:\n - ./config/otelcollector:/etc/otelcol-contrib:ro\n environment:\n - PROMETHEUS_ENDPOINT=http://prometheus:9090/api/v1/write\n command: ['--config=/etc/otelcol-contrib/config.yaml']\n restart: unless-stopped\n depends_on:\n - prometheus\nvolumes:\n redis-data:\n postgres_data:\n mongodata:\n prometheus_data:\n alertmanager_data:\n grafana_data:\n"}, "3329ceafc90da77fde31effcc655726f4597dfff": {"t": 1785748795.526677, "n": 20891, "h": "f51261f1c21b2204", "c": "services:\n postgres:\n image: postgres:17\n container_name: stemify-postgres\n environment:\n - POSTGRES_PASSWORD=${DB_PASSWORD}\n ports:\n - '5432:5432'\n volumes:\n - postgres_data:/var/lib/postgresql/data\n restart: unless-stopped\n # RabbitMQ is now using CloudAMQP online service\n # rabbitmq:\n # image: rabbitmq:3-management-alpine\n # ports:\n # - 5672:5672\n # - 15672:15672\n # environment:\n # - RABBITMQ_DEFAULT_USER=guest\n # - RABBITMQ_DEFAULT_PASS=guest\n # healthcheck:\n # test: [\"CMD\", \"rabbitmq-diagnostics\", \"ping\"]\n # interval: 30s\n # timeout: 10s\n # retries: 5\n # start_period: 30s\n # restart: unless-stopped\n mongo:\n image: mongo\n environment:\n - MONGO_INITDB_ROOT_USERNAME=root\n - MONGO_INITDB_ROOT_PASSWORD=mongopw\n ports:\n - 27017:27017\n volumes:\n - mongodata:/var/lib/mongodb/data\n redis:\n image: redis:latest\n ports:\n - 6379:6379\n command: [ 'redis-server', '--appendonly', 'yes' ]\n volumes:\n - redis-data:/data\n redis-commander:\n image: rediscommander/redis-commander:latest\n environment:\n - REDIS_HOSTS=local:redis:6379\n - HTTP_USER=root\n - HTTP_PASSWORD=secret\n ports:\n - 8082:8082\n depends_on:\n - redis\n classroom-service:\n image: rosie91/stemify:classroom-service\n build:\n context: .\n dockerfile: src/Services/ClassroomService/Classroom.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5001\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcResourceUrl=http://resource-service:5003\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcOrderUrl=http://order-service:5006\n - ConnectionStrings__stemifyclassroom=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_classroom\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=classroom-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=classroom-service,service.version=1.0.0\n ports:\n - 7001:80\n - 5001:5001\n depends_on:\n - postgres\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n resource-service:\n image: rosie91/stemify:resource-service\n build:\n context: .\n dockerfile: src/Services/Resource/Resource.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5003\n - ConnectionStrings__stemifyresource=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_resource\n - Cloudinary__CloudName=${CLOUDINARY_CLOUD_NAME}\n - Cloudinary__ApiKey=${CLOUDINARY_API_KEY}\n - Cloudinary__ApiSecret=${CLOUDINARY_API_SECRET}\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n - ClientApp=https://localhost:3000\n - Gemini__BaseUrl=https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent\n - Gemini__ApiKey=${GEMINI_API_KEY}\n - Gemini__Model=gemini-2.5-flash\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=resource-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=resource-service,service.version=1.0.0\n ports:\n - 7003:80\n - 5003:5003\n depends_on:\n - postgres\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n product-service:\n image: rosie91/stemify:product-service\n build:\n context: .\n dockerfile: src/Services/ProductService/Product.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5005\n - ConnectionStrings__stemifyproduct=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_product\n - Cloudinary__CloudName=${CLOUDINARY_CLOUD_NAME}\n - Cloudinary__ApiKey=${CLOUDINARY_API_KEY}\n - Cloudinary__ApiSecret=${CLOUDINARY_API_SECRET}\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcResourceUrl=http://resource-service:5003\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=product-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=product-service,service.version=1.0.0\n ports:\n - 7005:80\n - 5005:5005\n depends_on:\n - postgres\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n order-service:\n image: rosie91/stemify:order-service\n build:\n context: .\n dockerfile: src/Services/OrderService/Order.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5006\n - ConnectionStrings__stemifyorder=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_order\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcResourceUrl=http://resource-service:5003\n - GrpcProductUrl=http://product-service:5005\n - Cloudinary__CloudName=${CLOUDINARY_CLOUD_NAME}\n - Cloudinary__ApiKey=${CLOUDINARY_API_KEY}\n - Cloudinary__ApiSecret=${CLOUDINARY_API_SECRET}\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=order-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=order-service,service.version=1.0.0\n\n ports:\n - 7006:80\n - 5006:5006\n depends_on:\n - postgres\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n notification-service:\n image: rosie91/stemify:notification-service\n build:\n context: .\n dockerfile: src/Services/Notification/Notification.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5004\n - ConnectionStrings__stemifynotification=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_notification\n # SendGrid configuration\n - SendGridSettings__ApiKey=${SENDGRID_API_KEY}\n - SendGridSettings__FromEmail=stemify30062025@gmail.com\n - SendGridSettings__FromName=STEMify\n # Client App configuration\n - ClientApp__BaseUrl=https://localhost:3000\n - ClientApp=https://localhost:3000\n - Issuer=http://localhost:5000/\n - IdentityServiceUrl=http://identity-service\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=notification-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=notification-service,service.version=1.0.0\n ports:\n - 7004:80\n - 5004:5004\n depends_on:\n - postgres\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n gateway-service:\n image: rosie91/stemify:gateway-service\n build:\n context: .\n dockerfile: src/ApiGateways/ApiGateway/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Docker\n - ASPNETCORE_URLS=http://+:80\n - IdentityServiceUrl=http://identity-service\n - Issuer=http://localhost:5000/\n - ClientApp=https://localhost:3000\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=gateway-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=gateway-service,service.version=1.0.0\n ports:\n - 6001:80\n depends_on:\n - otelcollector\n restart: unless-stopped\n identity-api-service:\n image: rosie91/stemify:identity-api-service\n build:\n context: .\n dockerfile: src/Services/Identity/Identity.Api/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5002\n - ConnectionStrings__stemifyidentity=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_identity\n # RabbitMQ Configuration (for MassTransit)\n - RabbitMq__Url=${RABBITMQ_URL}\n # OpenIddict Configuration\n - OpenIddict__Authority=http://identity-service:80\n # Client App Configuration\n - ClientApp=https://localhost:3000\n # Email Settings\n - EmailSettings__From=${IDENTITY_EMAIL_FROM}\n - EmailSettings__DisplayName=${IDENTITY_EMAIL_DISPLAY_NAME}\n - EmailSettings__SMTPServer=${IDENTITY_EMAIL_SMTP_SERVER}\n - EmailSettings__Port=${IDENTITY_EMAIL_PORT}\n - EmailSettings__Username=${IDENTITY_EMAIL_USERNAME}\n - EmailSettings__Password=${IDENTITY_EMAIL_PASSWORD}\n - EmailSettings__UseSSL=${IDENTITY_EMAIL_USE_SSL}\n - EmailSettings__ApplicationName=${IDENTITY_EMAIL_APP_NAME}\n - EmailSettings__SupportEmail=${IDENTITY_EMAIL_SUPPORT}\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=identity-api-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=identity-api-service,service.version=1.0.0\n ports:\n - 7002:80\n - 5002:5002\n depends_on:\n - postgres\n - otelcollector\n restart: unless-stopped\n identity-service:\n image: rosie91/stemify:identity-service\n build:\n context: .\n dockerfile: src/Services/Identity/Identity.Web/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - ASPNETCORE_URLS=http://+:80\n - ConnectionStrings__stemifyidentity=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_identity\n # OpenIddict configuration for Docker\n - OpenIddict__Issuer=http://localhost:5000\n - OpenIddict__Authority=http://localhost:5000\n - OpenIddict__SigningKey=${OPENIDDICT_SIGNING_KEY}\n - OpenIddict__EncryptionKey=${OPENIDDICT_ENCRYPTION_KEY}\n - OpenIddict__ApiClientSecret=${OPENIDDICT_API_CLIENT_SECRET}\n - OpenIddict__ForceRecreateClients=${OPENIDDICT_FORCE_RECREATE_CLIENTS:-true}\n - OpenIddict__DisableHttpsRequirement=${OPENIDDICT_DISABLE_HTTPS:-true}\n # CORS for Docker environment\n - Cors__Origins=${IDENTITY_CORS_ORIGINS:-https://localhost:3000}\n - ClientApp=${IDENTITY_CLIENT_APP:-https://localhost:3000}\n - BackendUrl=${IDENTITY_BACKEND_URL:-http://localhost:5000}\n # Google Authentication\n - Authentication__Google__ClientId=${GOOGLE_CLIENT_ID}\n - Authentication__Google__ClientSecret=${GOOGLE_CLIENT_SECRET}\n - Authentication__Google__RedirectUri=${GOOGLE_REDIRECT_URI}\n # Email Settings\n - EmailSettings__SMTPServer=${IDENTITY_EMAIL_SMTP_SERVER}\n - EmailSettings__Port=${IDENTITY_EMAIL_PORT}\n - EmailSettings__UseSSL=${IDENTITY_EMAIL_USE_SSL}\n - EmailSettings__Username=${IDENTITY_EMAIL_USERNAME}\n - EmailSettings__Password=${IDENTITY_EMAIL_PASSWORD}\n - EmailSettings__From=${IDENTITY_EMAIL_FROM}\n - EmailSettings__DisplayName=${IDENTITY_EMAIL_DISPLAY_NAME}\n - EmailSettings__ApplicationName=${IDENTITY_EMAIL_APP_NAME}\n - EmailSettings__SupportEmail=${IDENTITY_EMAIL_SUPPORT}\n - EmailSettings__Enabled=${IDENTITY_EMAIL_ENABLED}\n - EmailSettings__EnableVerification=${IDENTITY_EMAIL_ENABLE_VERIFICATION}\n - EmailSettings__TimeoutMs=${IDENTITY_EMAIL_TIMEOUT_MS}\n - EmailSettings__MaxRetryAttempts=${IDENTITY_EMAIL_MAX_RETRY}\n - EmailSettings__LogEmailContent=${IDENTITY_EMAIL_LOG_CONTENT}\n # gRPC Services\n - GrpcServices__OrderService__Address=http://order-service:5006\n # Database Options\n - DatabaseOptions__MaxRetryCount=5\n - DatabaseOptions__MaxRetryDelay=00:00:10\n - DatabaseOptions__CommandTimeout=30\n - DatabaseOptions__EnableRetryOnFailure=true\n - DatabaseOptions__EnableSensitiveDataLogging=${DB_ENABLE_SENSITIVE_LOGGING}\n - DatabaseOptions__EnableDetailedErrors=${DB_ENABLE_DETAILED_ERRORS}\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=identity-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=identity-service,service.version=1.0.0\n ports:\n - 5000:80\n depends_on:\n - postgres\n - otelcollector\n restart: unless-stopped\n cart-service:\n image: rosie91/stemify:cart-service\n build:\n context: .\n dockerfile: src/Services/CartService/Cart.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5007\n - ConnectionStrings__stemifycart=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_cart\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcProductUrl=http://product-service:5005\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=cart-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=cart-service,service.version=1.0.0\n ports:\n - 7007:80\n - 5007:5007\n depends_on:\n - postgres\n - redis\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n payment-service:\n image: rosie91/stemify:payment-service\n build:\n context: .\n dockerfile: src/Services/PaymentService/Payment.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - Logging__LogLevel__Microsoft.EntityFrameworkCore.Database.Command=Warning\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5008\n - ConnectionStrings__stemifypayment=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_payment\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n # PayOS configuration\n - PaymentGateways__PayOS__ClientId=${PAYOS_CLIENT_ID}\n - PaymentGateways__PayOS__ApiKey=${PAYOS_API_KEY}\n - PaymentGateways__PayOS__ChecksumKey=${PAYOS_CHECKSUM_KEY}\n - PaymentGateways__PayOS__BaseUrl=https://api-merchant.payos.vn\n # Stripe configuration\n - PaymentGateways__Stripe__SecretKey=${STRIPE_SECRET_KEY}\n - PaymentGateways__Stripe__PublishableKey=${STRIPE_PUBLISH_KEY}\n - PaymentGateways__Stripe__WebhookSecret=${STRIPE_WEBHOOK_KEY}\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=payment-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=payment-service,service.version=1.0.0\n ports:\n - 7008:80\n - 5008:5008\n depends_on:\n - postgres\n - redis\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n emulator-service:\n image: rosie91/stemify:emulator-service\n build:\n context: .\n dockerfile: src/Services/EmulatorService/Emulator.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5009\n # MongoDB configuration\n - MongoDb__ConnectionString=${MONGODB_CONNECTION_STRING}\n - MongoDb__DatabaseName=stemify_emulator\n # Redis configuration\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n # gRPC endpoints\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcResourceUrl=http://resource-service:5003\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=emulator-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=emulator-service,service.version=1.0.0\n ports:\n - 7009:80\n - 5009:5009\n depends_on:\n - redis\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n\n # ========================================\n # MONITORING STACK\n # ========================================\n \n # Prometheus - Metrics Collection\n prometheus:\n image: prom/prometheus:v3.5.0\n container_name: stemify-prometheus\n ports:\n - \"9090:9090\"\n volumes:\n - ./config/prometheus:/etc/prometheus:ro\n - prometheus_data:/prometheus\n command:\n - '--config.file=/etc/prometheus/prometheus.yml'\n - '--storage.tsdb.path=/prometheus'\n - '--storage.tsdb.retention.time=15d'\n - '--storage.tsdb.retention.size=10GB'\n - '--web.enable-lifecycle'\n - '--web.enable-otlp-receiver'\n restart: unless-stopped\n\n # AlertManager - Alert Management\n alertmanager:\n image: prom/alertmanager:v0.27.0\n container_name: stemify-alertmanager\n ports:\n - \"9093:9093\"\n volumes:\n - ./config/alertmanager:/etc/alertmanager:ro\n - alertmanager_data:/alertmanager\n command:\n - '--config.file=/etc/alertmanager/alertmanager.yml'\n - '--storage.path=/alertmanager'\n - '--web.external-url=http://localhost:9093'\n restart: unless-stopped\n\n # Grafana - Visualization\n grafana:\n image: grafana/grafana:latest\n container_name: stemify-grafana\n ports:\n - \"3000:3000\"\n volumes:\n - grafana_data:/var/lib/grafana\n - ./config/grafana/config:/etc/grafana:ro\n - ./config/grafana/dashboards:/var/lib/grafana/dashboards:ro\n environment:\n - GF_SECURITY_ADMIN_USER=${GRAFANA_ADMIN_USERNAME}\n - GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_ADMIN_PASSWORD:-admin123}\n - GF_USERS_ALLOW_SIGN_UP=false\n - GF_INSTALL_PLUGINS=grafana-clock-panel,grafana-simple-json-datasource\n - PROMETHEUS_ENDPOINT=http://prometheus:9090\n restart: unless-stopped\n depends_on:\n - prometheus\n\n # OpenTelemetry Collector\n otelcollector:\n image: otel/opentelemetry-collector-contrib:latest\n container_name: stemify-otelcollector\n ports:\n - \"8888:8888\" # Prometheus metrics\n - \"8889:8889\" # Prometheus exporter\n - \"4317:4317\" # OTLP gRPC receiver\n - \"4318:4318\" # OTLP HTTP receiver\n volumes:\n - ./config/otelcollector:/etc/otelcol-contrib:ro\n environment:\n - PROMETHEUS_ENDPOINT=http://prometheus:9090/api/v1/write\n command: [\"--config=/etc/otelcol-contrib/config.yaml\"]\n restart: unless-stopped\n depends_on:\n - prometheus\nvolumes:\n redis-data:\n postgres_data:\n mongodata:\n prometheus_data:\n alertmanager_data:\n grafana_data:\n"}, "72cc166e29fdf81270ad697964ae6e6748d0dc5b": {"t": 1785748796.738036, "n": 20758, "h": "c4019011fd299524", "c": "services:\n postgres:\n image: postgres:17\n container_name: stemify-postgres\n environment:\n - POSTGRES_PASSWORD=${DB_PASSWORD}\n ports:\n - '5432:5432'\n volumes:\n - postgres_data:/var/lib/postgresql/data\n restart: unless-stopped\n # RabbitMQ is now using CloudAMQP online service\n # rabbitmq:\n # image: rabbitmq:3-management-alpine\n # ports:\n # - 5672:5672\n # - 15672:15672\n # environment:\n # - RABBITMQ_DEFAULT_USER=guest\n # - RABBITMQ_DEFAULT_PASS=guest\n # healthcheck:\n # test: [\"CMD\", \"rabbitmq-diagnostics\", \"ping\"]\n # interval: 30s\n # timeout: 10s\n # retries: 5\n # start_period: 30s\n # restart: unless-stopped\n mongo:\n image: mongo\n environment:\n - MONGO_INITDB_ROOT_USERNAME=root\n - MONGO_INITDB_ROOT_PASSWORD=mongopw\n ports:\n - 27017:27017\n volumes:\n - mongodata:/var/lib/mongodb/data\n redis:\n image: redis:latest\n ports:\n - 6379:6379\n command: [ 'redis-server', '--appendonly', 'yes' ]\n volumes:\n - redis-data:/data\n redis-commander:\n image: rediscommander/redis-commander:latest\n environment:\n - REDIS_HOSTS=local:redis:6379\n - HTTP_USER=root\n - HTTP_PASSWORD=secret\n ports:\n - 8082:8082\n depends_on:\n - redis\n classroom-service:\n image: rosie91/stemify:classroom-service\n build:\n context: .\n dockerfile: src/Services/ClassroomService/Classroom.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5001\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcResourceUrl=http://resource-service:5003\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcOrderUrl=http://order-service:5006\n - ConnectionStrings__stemifyclassroom=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_classroom\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=classroom-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=classroom-service,service.version=1.0.0\n ports:\n - 7001:80\n - 5001:5001\n depends_on:\n - postgres\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n resource-service:\n image: rosie91/stemify:resource-service\n build:\n context: .\n dockerfile: src/Services/Resource/Resource.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5003\n - ConnectionStrings__stemifyresource=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_resource\n - Cloudinary__CloudName=${CLOUDINARY_CLOUD_NAME}\n - Cloudinary__ApiKey=${CLOUDINARY_API_KEY}\n - Cloudinary__ApiSecret=${CLOUDINARY_API_SECRET}\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n - ClientApp=https://localhost:3000\n - Gemini__BaseUrl=https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent\n - Gemini__ApiKey=${GEMINI_API_KEY}\n - Gemini__Model=gemini-2.5-flash\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=resource-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=resource-service,service.version=1.0.0\n ports:\n - 7003:80\n - 5003:5003\n depends_on:\n - postgres\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n product-service:\n image: rosie91/stemify:product-service\n build:\n context: .\n dockerfile: src/Services/ProductService/Product.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5005\n - ConnectionStrings__stemifyproduct=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_product\n - Cloudinary__CloudName=${CLOUDINARY_CLOUD_NAME}\n - Cloudinary__ApiKey=${CLOUDINARY_API_KEY}\n - Cloudinary__ApiSecret=${CLOUDINARY_API_SECRET}\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcResourceUrl=http://resource-service:5003\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=product-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=product-service,service.version=1.0.0\n ports:\n - 7005:80\n - 5005:5005\n depends_on:\n - postgres\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n order-service:\n image: rosie91/stemify:order-service\n build:\n context: .\n dockerfile: src/Services/OrderService/Order.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5006\n - ConnectionStrings__stemifyorder=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_order\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcResourceUrl=http://resource-service:5003\n - GrpcProductUrl=http://product-service:5005\n - Cloudinary__CloudName=${CLOUDINARY_CLOUD_NAME}\n - Cloudinary__ApiKey=${CLOUDINARY_API_KEY}\n - Cloudinary__ApiSecret=${CLOUDINARY_API_SECRET}\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=order-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=order-service,service.version=1.0.0\n\n ports:\n - 7006:80\n - 5006:5006\n depends_on:\n - postgres\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n notification-service:\n image: rosie91/stemify:notification-service\n build:\n context: .\n dockerfile: src/Services/Notification/Notification.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5004\n - ConnectionStrings__stemifynotification=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_notification\n # SendGrid configuration\n - SendGridSettings__ApiKey=${SENDGRID_API_KEY}\n - SendGridSettings__FromEmail=stemify30062025@gmail.com\n - SendGridSettings__FromName=STEMify\n # Client App configuration\n - ClientApp__BaseUrl=https://localhost:3000\n - ClientApp=https://localhost:3000\n - Issuer=http://localhost:5000/\n - IdentityServiceUrl=http://identity-service\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=notification-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=notification-service,service.version=1.0.0\n ports:\n - 7004:80\n - 5004:5004\n depends_on:\n - postgres\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n gateway-service:\n image: rosie91/stemify:gateway-service\n build:\n context: .\n dockerfile: src/ApiGateways/ApiGateway/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Docker\n - ASPNETCORE_URLS=http://+:80\n - IdentityServiceUrl=http://identity-service\n - Issuer=http://localhost:5000/\n - ClientApp=https://localhost:3000\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=gateway-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=gateway-service,service.version=1.0.0\n ports:\n - 6001:80\n depends_on:\n - otelcollector\n restart: unless-stopped\n identity-api-service:\n image: rosie91/stemify:identity-api-service\n build:\n context: .\n dockerfile: src/Services/Identity/Identity.Api/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5002\n - ConnectionStrings__stemifyidentity=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_identity\n # RabbitMQ Configuration (for MassTransit)\n - RabbitMq__Url=${RABBITMQ_URL}\n # OpenIddict Configuration\n - OpenIddict__Authority=http://identity-service:80\n # Client App Configuration\n - ClientApp=https://localhost:3000\n # Email Settings\n - EmailSettings__From=${IDENTITY_EMAIL_FROM}\n - EmailSettings__DisplayName=${IDENTITY_EMAIL_DISPLAY_NAME}\n - EmailSettings__SMTPServer=${IDENTITY_EMAIL_SMTP_SERVER}\n - EmailSettings__Port=${IDENTITY_EMAIL_PORT}\n - EmailSettings__Username=${IDENTITY_EMAIL_USERNAME}\n - EmailSettings__Password=${IDENTITY_EMAIL_PASSWORD}\n - EmailSettings__UseSSL=${IDENTITY_EMAIL_USE_SSL}\n - EmailSettings__ApplicationName=${IDENTITY_EMAIL_APP_NAME}\n - EmailSettings__SupportEmail=${IDENTITY_EMAIL_SUPPORT}\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=identity-api-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=identity-api-service,service.version=1.0.0\n ports:\n - 7002:80\n - 5002:5002\n depends_on:\n - postgres\n - otelcollector\n restart: unless-stopped\n identity-service:\n image: rosie91/stemify:identity-service\n build:\n context: .\n dockerfile: src/Services/Identity/Identity.Web/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - ASPNETCORE_URLS=http://+:80\n - ConnectionStrings__stemifyidentity=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_identity\n # OpenIddict configuration for Docker\n - OpenIddict__Issuer=http://localhost:5000\n - OpenIddict__Authority=http://localhost:5000\n - OpenIddict__SigningKey=${OPENIDDICT_SIGNING_KEY}\n - OpenIddict__EncryptionKey=${OPENIDDICT_ENCRYPTION_KEY}\n - OpenIddict__ApiClientSecret=${OPENIDDICT_API_CLIENT_SECRET}\n - OpenIddict__ForceRecreateClients=${OPENIDDICT_FORCE_RECREATE_CLIENTS:-true}\n - OpenIddict__DisableHttpsRequirement=${OPENIDDICT_DISABLE_HTTPS:-true}\n # CORS for Docker environment\n - Cors__Origins=${IDENTITY_CORS_ORIGINS:-https://localhost:3000}\n - ClientApp=${IDENTITY_CLIENT_APP:-https://localhost:3000}\n # Google Authentication\n - Authentication__Google__ClientId=${GOOGLE_CLIENT_ID}\n - Authentication__Google__ClientSecret=${GOOGLE_CLIENT_SECRET}\n # Email Settings\n - EmailSettings__SMTPServer=${IDENTITY_EMAIL_SMTP_SERVER}\n - EmailSettings__Port=${IDENTITY_EMAIL_PORT}\n - EmailSettings__UseSSL=${IDENTITY_EMAIL_USE_SSL}\n - EmailSettings__Username=${IDENTITY_EMAIL_USERNAME}\n - EmailSettings__Password=${IDENTITY_EMAIL_PASSWORD}\n - EmailSettings__From=${IDENTITY_EMAIL_FROM}\n - EmailSettings__DisplayName=${IDENTITY_EMAIL_DISPLAY_NAME}\n - EmailSettings__ApplicationName=${IDENTITY_EMAIL_APP_NAME}\n - EmailSettings__SupportEmail=${IDENTITY_EMAIL_SUPPORT}\n - EmailSettings__Enabled=${IDENTITY_EMAIL_ENABLED}\n - EmailSettings__EnableVerification=${IDENTITY_EMAIL_ENABLE_VERIFICATION}\n - EmailSettings__TimeoutMs=${IDENTITY_EMAIL_TIMEOUT_MS}\n - EmailSettings__MaxRetryAttempts=${IDENTITY_EMAIL_MAX_RETRY}\n - EmailSettings__LogEmailContent=${IDENTITY_EMAIL_LOG_CONTENT}\n # gRPC Services\n - GrpcServices__OrderService__Address=http://order-service:5006\n # Database Options\n - DatabaseOptions__MaxRetryCount=5\n - DatabaseOptions__MaxRetryDelay=00:00:10\n - DatabaseOptions__CommandTimeout=30\n - DatabaseOptions__EnableRetryOnFailure=true\n - DatabaseOptions__EnableSensitiveDataLogging=${DB_ENABLE_SENSITIVE_LOGGING}\n - DatabaseOptions__EnableDetailedErrors=${DB_ENABLE_DETAILED_ERRORS}\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=identity-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=identity-service,service.version=1.0.0\n ports:\n - 5000:80\n depends_on:\n - postgres\n - otelcollector\n restart: unless-stopped\n cart-service:\n image: rosie91/stemify:cart-service\n build:\n context: .\n dockerfile: src/Services/CartService/Cart.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5007\n - ConnectionStrings__stemifycart=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_cart\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcProductUrl=http://product-service:5005\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=cart-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=cart-service,service.version=1.0.0\n ports:\n - 7007:80\n - 5007:5007\n depends_on:\n - postgres\n - redis\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n payment-service:\n image: rosie91/stemify:payment-service\n build:\n context: .\n dockerfile: src/Services/PaymentService/Payment.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - Logging__LogLevel__Microsoft.EntityFrameworkCore.Database.Command=Warning\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5008\n - ConnectionStrings__stemifypayment=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_payment\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n # PayOS configuration\n - PaymentGateways__PayOS__ClientId=${PAYOS_CLIENT_ID}\n - PaymentGateways__PayOS__ApiKey=${PAYOS_API_KEY}\n - PaymentGateways__PayOS__ChecksumKey=${PAYOS_CHECKSUM_KEY}\n - PaymentGateways__PayOS__BaseUrl=https://api-merchant.payos.vn\n # Stripe configuration\n - PaymentGateways__Stripe__SecretKey=${STRIPE_SECRET_KEY}\n - PaymentGateways__Stripe__PublishableKey=${STRIPE_PUBLISH_KEY}\n - PaymentGateways__Stripe__WebhookSecret=${STRIPE_WEBHOOK_KEY}\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=payment-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=payment-service,service.version=1.0.0\n ports:\n - 7008:80\n - 5008:5008\n depends_on:\n - postgres\n - redis\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n emulator-service:\n image: rosie91/stemify:emulator-service\n build:\n context: .\n dockerfile: src/Services/EmulatorService/Emulator.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5009\n # MongoDB configuration\n - MongoDb__ConnectionString=${MONGODB_CONNECTION_STRING}\n - MongoDb__DatabaseName=stemify_emulator\n # Redis configuration\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n # gRPC endpoints\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcResourceUrl=http://resource-service:5003\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=emulator-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=emulator-service,service.version=1.0.0\n ports:\n - 7009:80\n - 5009:5009\n depends_on:\n - redis\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n\n # ========================================\n # MONITORING STACK\n # ========================================\n \n # Prometheus - Metrics Collection\n prometheus:\n image: prom/prometheus:v3.5.0\n container_name: stemify-prometheus\n ports:\n - \"9090:9090\"\n volumes:\n - ./config/prometheus:/etc/prometheus:ro\n - prometheus_data:/prometheus\n command:\n - '--config.file=/etc/prometheus/prometheus.yml'\n - '--storage.tsdb.path=/prometheus'\n - '--storage.tsdb.retention.time=15d'\n - '--storage.tsdb.retention.size=10GB'\n - '--web.enable-lifecycle'\n - '--web.enable-otlp-receiver'\n restart: unless-stopped\n\n # AlertManager - Alert Management\n alertmanager:\n image: prom/alertmanager:v0.27.0\n container_name: stemify-alertmanager\n ports:\n - \"9093:9093\"\n volumes:\n - ./config/alertmanager:/etc/alertmanager:ro\n - alertmanager_data:/alertmanager\n command:\n - '--config.file=/etc/alertmanager/alertmanager.yml'\n - '--storage.path=/alertmanager'\n - '--web.external-url=http://localhost:9093'\n restart: unless-stopped\n\n # Grafana - Visualization\n grafana:\n image: grafana/grafana:latest\n container_name: stemify-grafana\n ports:\n - \"3000:3000\"\n volumes:\n - grafana_data:/var/lib/grafana\n - ./config/grafana/config:/etc/grafana:ro\n - ./config/grafana/dashboards:/var/lib/grafana/dashboards:ro\n environment:\n - GF_SECURITY_ADMIN_USER=${GRAFANA_ADMIN_USERNAME}\n - GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_ADMIN_PASSWORD:-admin123}\n - GF_USERS_ALLOW_SIGN_UP=false\n - GF_INSTALL_PLUGINS=grafana-clock-panel,grafana-simple-json-datasource\n - PROMETHEUS_ENDPOINT=http://prometheus:9090\n restart: unless-stopped\n depends_on:\n - prometheus\n\n # OpenTelemetry Collector\n otelcollector:\n image: otel/opentelemetry-collector-contrib:latest\n container_name: stemify-otelcollector\n ports:\n - \"8888:8888\" # Prometheus metrics\n - \"8889:8889\" # Prometheus exporter\n - \"4317:4317\" # OTLP gRPC receiver\n - \"4318:4318\" # OTLP HTTP receiver\n volumes:\n - ./config/otelcollector:/etc/otelcol-contrib:ro\n environment:\n - PROMETHEUS_ENDPOINT=http://prometheus:9090/api/v1/write\n command: [\"--config=/etc/otelcol-contrib/config.yaml\"]\n restart: unless-stopped\n depends_on:\n - prometheus\nvolumes:\n redis-data:\n postgres_data:\n mongodata:\n prometheus_data:\n alertmanager_data:\n grafana_data:\n"}, "69720b16426c5a2f29ba535f986af4249f165ad8": {"t": 1785748797.8878324, "n": 18307, "h": "8199c6e9d514a45b", "c": "services:\n postgres:\n image: postgres:17\n container_name: stemify-postgres\n environment:\n - POSTGRES_PASSWORD=${DB_PASSWORD}\n ports:\n - '5432:5432'\n volumes:\n - postgres_data:/var/lib/postgresql/data\n restart: unless-stopped\n # RabbitMQ is now using CloudAMQP online service\n # rabbitmq:\n # image: rabbitmq:3-management-alpine\n # ports:\n # - 5672:5672\n # - 15672:15672\n # environment:\n # - RABBITMQ_DEFAULT_USER=guest\n # - RABBITMQ_DEFAULT_PASS=guest\n # healthcheck:\n # test: [\"CMD\", \"rabbitmq-diagnostics\", \"ping\"]\n # interval: 30s\n # timeout: 10s\n # retries: 5\n # start_period: 30s\n # restart: unless-stopped\n mongo:\n image: mongo\n environment:\n - MONGO_INITDB_ROOT_USERNAME=root\n - MONGO_INITDB_ROOT_PASSWORD=mongopw\n ports:\n - 27017:27017\n volumes:\n - mongodata:/var/lib/mongodb/data\n redis:\n image: redis:latest\n ports:\n - 6379:6379\n command: [ 'redis-server', '--appendonly', 'yes' ]\n volumes:\n - redis-data:/data\n redis-commander:\n image: rediscommander/redis-commander:latest\n environment:\n - REDIS_HOSTS=local:redis:6379\n - HTTP_USER=root\n - HTTP_PASSWORD=secret\n ports:\n - 8082:8082\n depends_on:\n - redis\n classroom-service:\n image: rosie91/stemify:classroom-service\n build:\n context: .\n dockerfile: src/Services/ClassroomService/Classroom.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5001\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcResourceUrl=http://resource-service:5003\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcOrderUrl=http://order-service:5006\n - ConnectionStrings__stemifyclassroom=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_classroom\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=classroom-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=classroom-service,service.version=1.0.0\n ports:\n - 7001:80\n - 5001:5001\n depends_on:\n - postgres\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n resource-service:\n image: rosie91/stemify:resource-service\n build:\n context: .\n dockerfile: src/Services/Resource/Resource.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5003\n - ConnectionStrings__stemifyresource=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_resource\n - Cloudinary__CloudName=${CLOUDINARY_CLOUD_NAME}\n - Cloudinary__ApiKey=${CLOUDINARY_API_KEY}\n - Cloudinary__ApiSecret=${CLOUDINARY_API_SECRET}\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n - ClientApp=https://localhost:3000\n - Gemini__BaseUrl=https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent\n - Gemini__ApiKey=${GEMINI_API_KEY}\n - Gemini__Model=gemini-2.5-flash\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=resource-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=resource-service,service.version=1.0.0\n ports:\n - 7003:80\n - 5003:5003\n depends_on:\n - postgres\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n product-service:\n image: rosie91/stemify:product-service\n build:\n context: .\n dockerfile: src/Services/ProductService/Product.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5005\n - ConnectionStrings__stemifyproduct=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_product\n - Cloudinary__CloudName=${CLOUDINARY_CLOUD_NAME}\n - Cloudinary__ApiKey=${CLOUDINARY_API_KEY}\n - Cloudinary__ApiSecret=${CLOUDINARY_API_SECRET}\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcResourceUrl=http://resource-service:5003\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=product-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=product-service,service.version=1.0.0\n ports:\n - 7005:80\n - 5005:5005\n depends_on:\n - postgres\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n order-service:\n image: rosie91/stemify:order-service\n build:\n context: .\n dockerfile: src/Services/OrderService/Order.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5006\n - ConnectionStrings__stemifyorder=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_order\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcResourceUrl=http://resource-service:5003\n - GrpcProductUrl=http://product-service:5005\n - Cloudinary__CloudName=${CLOUDINARY_CLOUD_NAME}\n - Cloudinary__ApiKey=${CLOUDINARY_API_KEY}\n - Cloudinary__ApiSecret=${CLOUDINARY_API_SECRET}\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=order-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=order-service,service.version=1.0.0\n\n ports:\n - 7006:80\n - 5006:5006\n depends_on:\n - postgres\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n notification-service:\n image: rosie91/stemify:notification-service\n build:\n context: .\n dockerfile: src/Services/Notification/Notification.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5004\n - ConnectionStrings__stemifynotification=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_notification\n # SendGrid configuration\n - SendGridSettings__ApiKey=${SENDGRID_API_KEY}\n - SendGridSettings__FromEmail=stemify30062025@gmail.com\n - SendGridSettings__FromName=STEMify\n # Client App configuration\n - ClientApp__BaseUrl=https://localhost:3000\n - ClientApp=https://localhost:3000\n - Issuer=http://localhost:5000/\n - IdentityServiceUrl=http://identity-service\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=notification-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=notification-service,service.version=1.0.0\n ports:\n - 7004:80\n - 5004:5004\n depends_on:\n - postgres\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n gateway-service:\n image: rosie91/stemify:gateway-service\n build:\n context: .\n dockerfile: src/ApiGateways/ApiGateway/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Docker\n - ASPNETCORE_URLS=http://+:80\n - IdentityServiceUrl=http://identity-service\n - Issuer=http://localhost:5000/\n - ClientApp=https://localhost:3000\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=gateway-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=gateway-service,service.version=1.0.0\n ports:\n - 6001:80\n depends_on:\n - otelcollector\n restart: unless-stopped\n identity-api-service:\n image: rosie91/stemify:identity-api-service\n build:\n context: .\n dockerfile: src/Services/Identity/Identity.Api/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5002\n - ConnectionStrings__stemifyidentity=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_identity\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=identity-api-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=identity-api-service,service.version=1.0.0\n ports:\n - 7002:80\n - 5002:5002\n depends_on:\n - postgres\n - otelcollector\n restart: unless-stopped\n identity-service:\n image: rosie91/stemify:identity-service\n build:\n context: .\n dockerfile: src/Services/Identity/Identity.Web/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - ASPNETCORE_URLS=http://+:80\n - ConnectionStrings__stemifyidentity=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_identity\n # OpenIddict configuration for Docker\n - OpenIddict__Issuer=http://localhost:5000\n - OpenIddict__SigningKey=your-development-signing-key-32-chars\n - OpenIddict__EncryptionKey=your-development-encryption-key-32\n - OpenIddict__ApiClientSecret=stemify-api-secret-key-2024-dev\n - OpenIddict__ForceRecreateClients=true\n - OpenIddict__DisableHttpsRequirement=true\n # CORS for Docker environment (adjust if needed)\n - Cors__Origins=https://localhost:3000\n - ClientApp=https://localhost:3000\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=identity-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=identity-service,service.version=1.0.0\n ports:\n - 5000:80\n depends_on:\n - postgres\n - otelcollector\n restart: unless-stopped\n cart-service:\n image: rosie91/stemify:cart-service\n build:\n context: .\n dockerfile: src/Services/CartService/Cart.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5007\n - ConnectionStrings__stemifycart=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_cart\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcProductUrl=http://product-service:5005\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=cart-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=cart-service,service.version=1.0.0\n ports:\n - 7007:80\n - 5007:5007\n depends_on:\n - postgres\n - redis\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n payment-service:\n image: rosie91/stemify:payment-service\n build:\n context: .\n dockerfile: src/Services/PaymentService/Payment.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - Logging__LogLevel__Microsoft.EntityFrameworkCore.Database.Command=Warning\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5008\n - ConnectionStrings__stemifypayment=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_payment\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n # PayOS configuration\n - PaymentGateways__PayOS__ClientId=${PAYOS_CLIENT_ID}\n - PaymentGateways__PayOS__ApiKey=${PAYOS_API_KEY}\n - PaymentGateways__PayOS__ChecksumKey=${PAYOS_CHECKSUM_KEY}\n - PaymentGateways__PayOS__BaseUrl=https://api-merchant.payos.vn\n # Stripe configuration\n - PaymentGateways__Stripe__SecretKey=${STRIPE_SECRET_KEY}\n - PaymentGateways__Stripe__PublishableKey=${STRIPE_PUBLISH_KEY}\n - PaymentGateways__Stripe__WebhookSecret=${STRIPE_WEBHOOK_KEY}\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=payment-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=payment-service,service.version=1.0.0\n ports:\n - 7008:80\n - 5008:5008\n depends_on:\n - postgres\n - redis\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n emulator-service:\n image: rosie91/stemify:emulator-service\n build:\n context: .\n dockerfile: src/Services/EmulatorService/Emulator.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5009\n # MongoDB configuration\n - MongoDb__ConnectionString=${MONGODB_CONNECTION_STRING}\n - MongoDb__DatabaseName=stemify_emulator\n # Redis configuration\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n # gRPC endpoints\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcResourceUrl=http://resource-service:5003\n # OpenTelemetry Configuration\n - OTEL_EXPORTER_OTLP_ENDPOINT=http://otelcollector:4317\n - OTEL_EXPORTER_OTLP_PROTOCOL=grpc\n - OTEL_SERVICE_NAME=emulator-service\n - OTEL_RESOURCE_ATTRIBUTES=service.name=emulator-service,service.version=1.0.0\n ports:\n - 7009:80\n - 5009:5009\n depends_on:\n - redis\n - otelcollector\n #- rabbitmq\n restart: unless-stopped\n\n # ========================================\n # MONITORING STACK\n # ========================================\n \n # Prometheus - Metrics Collection\n prometheus:\n image: prom/prometheus:v3.5.0\n container_name: stemify-prometheus\n ports:\n - \"9090:9090\"\n volumes:\n - ./config/prometheus:/etc/prometheus:ro\n - prometheus_data:/prometheus\n command:\n - '--config.file=/etc/prometheus/prometheus.yml'\n - '--storage.tsdb.path=/prometheus'\n - '--storage.tsdb.retention.time=15d'\n - '--storage.tsdb.retention.size=10GB'\n - '--web.enable-lifecycle'\n - '--web.enable-otlp-receiver'\n restart: unless-stopped\n\n # AlertManager - Alert Management\n alertmanager:\n image: prom/alertmanager:v0.27.0\n container_name: stemify-alertmanager\n ports:\n - \"9093:9093\"\n volumes:\n - ./config/alertmanager:/etc/alertmanager:ro\n - alertmanager_data:/alertmanager\n command:\n - '--config.file=/etc/alertmanager/alertmanager.yml'\n - '--storage.path=/alertmanager'\n - '--web.external-url=http://localhost:9093'\n restart: unless-stopped\n\n # Grafana - Visualization\n grafana:\n image: grafana/grafana:latest\n container_name: stemify-grafana\n ports:\n - \"3000:3000\"\n volumes:\n - grafana_data:/var/lib/grafana\n - ./config/grafana/config:/etc/grafana:ro\n - ./config/grafana/dashboards:/var/lib/grafana/dashboards:ro\n environment:\n - GF_SECURITY_ADMIN_USER=${GRAFANA_ADMIN_USERNAME}\n - GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_ADMIN_PASSWORD:-admin123}\n - GF_USERS_ALLOW_SIGN_UP=false\n - GF_INSTALL_PLUGINS=grafana-clock-panel,grafana-simple-json-datasource\n - PROMETHEUS_ENDPOINT=http://prometheus:9090\n restart: unless-stopped\n depends_on:\n - prometheus\n\n # OpenTelemetry Collector\n otelcollector:\n image: otel/opentelemetry-collector-contrib:latest\n container_name: stemify-otelcollector\n ports:\n - \"8888:8888\" # Prometheus metrics\n - \"8889:8889\" # Prometheus exporter\n - \"4317:4317\" # OTLP gRPC receiver\n - \"4318:4318\" # OTLP HTTP receiver\n volumes:\n - ./config/otelcollector:/etc/otelcol-contrib:ro\n environment:\n - PROMETHEUS_ENDPOINT=http://prometheus:9090/api/v1/write\n command: [\"--config=/etc/otelcol-contrib/config.yaml\"]\n restart: unless-stopped\n depends_on:\n - prometheus\nvolumes:\n redis-data:\n postgres_data:\n mongodata:\n prometheus_data:\n alertmanager_data:\n grafana_data:\n"}, "93bec6f050946c856c5db6647b8d84fc3cf1d222": {"t": 1785748798.98839, "n": 12660, "h": "2e63aa21c5a9a3f4", "c": "services:\n postgres:\n image: postgres:17\n container_name: stemify-postgres\n environment:\n - POSTGRES_PASSWORD=${DB_PASSWORD}\n ports:\n - '5432:5432'\n volumes:\n - postgres_data:/var/lib/postgresql/data\n restart: unless-stopped\n # RabbitMQ is now using CloudAMQP online service\n # rabbitmq:\n # image: rabbitmq:3-management-alpine\n # ports:\n # - 5672:5672\n # - 15672:15672\n # environment:\n # - RABBITMQ_DEFAULT_USER=guest\n # - RABBITMQ_DEFAULT_PASS=guest\n # healthcheck:\n # test: [\"CMD\", \"rabbitmq-diagnostics\", \"ping\"]\n # interval: 30s\n # timeout: 10s\n # retries: 5\n # start_period: 30s\n # restart: unless-stopped\n mongo:\n image: mongo\n environment:\n - MONGO_INITDB_ROOT_USERNAME=root\n - MONGO_INITDB_ROOT_PASSWORD=mongopw\n ports:\n - 27017:27017\n volumes:\n - mongodata:/var/lib/mongodb/data\n redis:\n image: redis:latest\n ports:\n - 6379:6379\n command: [ 'redis-server', '--appendonly', 'yes' ]\n volumes:\n - redis-data:/data\n redis-commander:\n image: rediscommander/redis-commander:latest\n environment:\n - REDIS_HOSTS=local:redis:6379\n - HTTP_USER=root\n - HTTP_PASSWORD=secret\n ports:\n - 8082:8082\n depends_on:\n - redis\n classroom-service:\n image: rosie91/stemify:classroom-service\n build:\n context: .\n dockerfile: src/Services/ClassroomService/Classroom.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5001\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcResourceUrl=http://resource-service:5003\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcOrderUrl=http://order-service:5006\n - ConnectionStrings__stemifyclassroom=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_classroom\n ports:\n - 7001:80\n - 5001:5001\n depends_on:\n - postgres\n #- rabbitmq\n restart: unless-stopped\n resource-service:\n image: rosie91/stemify:resource-service\n build:\n context: .\n dockerfile: src/Services/Resource/Resource.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5003\n - ConnectionStrings__stemifyresource=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_resource\n - Cloudinary__CloudName=${CLOUDINARY_CLOUD_NAME}\n - Cloudinary__ApiKey=${CLOUDINARY_API_KEY}\n - Cloudinary__ApiSecret=${CLOUDINARY_API_SECRET}\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n - ClientApp=https://localhost:3000\n - Gemini__BaseUrl=https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent\n - Gemini__ApiKey=${GEMINI_API_KEY}\n - Gemini__Model=gemini-2.5-flash\n ports:\n - 7003:80\n - 5003:5003\n depends_on:\n - postgres\n #- rabbitmq\n restart: unless-stopped\n product-service:\n image: rosie91/stemify:product-service\n build:\n context: .\n dockerfile: src/Services/ProductService/Product.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5005\n - ConnectionStrings__stemifyproduct=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_product\n - Cloudinary__CloudName=${CLOUDINARY_CLOUD_NAME}\n - Cloudinary__ApiKey=${CLOUDINARY_API_KEY}\n - Cloudinary__ApiSecret=${CLOUDINARY_API_SECRET}\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcResourceUrl=http://resource-service:5003\n ports:\n - 7005:80\n - 5005:5005\n depends_on:\n - postgres\n #- rabbitmq\n restart: unless-stopped\n order-service:\n image: rosie91/stemify:order-service\n build:\n context: .\n dockerfile: src/Services/OrderService/Order.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5006\n - ConnectionStrings__stemifyorder=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_order\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcResourceUrl=http://resource-service:5003\n - GrpcProductUrl=http://product-service:5005\n - Cloudinary__CloudName=${CLOUDINARY_CLOUD_NAME}\n - Cloudinary__ApiKey=${CLOUDINARY_API_KEY}\n - Cloudinary__ApiSecret=${CLOUDINARY_API_SECRET}\n ports:\n - 7006:80\n - 5006:5006\n depends_on:\n - postgres\n #- rabbitmq\n restart: unless-stopped\n notification-service:\n image: rosie91/stemify:notification-service\n build:\n context: .\n dockerfile: src/Services/Notification/Notification.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5004\n - ConnectionStrings__stemifynotification=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_notification\n # SendGrid configuration\n - SendGridSettings__ApiKey=${SENDGRID_API_KEY}\n - SendGridSettings__FromEmail=stemify30062025@gmail.com\n - SendGridSettings__FromName=STEMify\n # Client App configuration\n - ClientApp__BaseUrl=https://localhost:3000\n - ClientApp=https://localhost:3000\n - Issuer=http://localhost:5000/\n - IdentityServiceUrl=http://identity-service\n ports:\n - 7004:80\n - 5004:5004\n depends_on:\n - postgres\n #- rabbitmq\n restart: unless-stopped\n gateway-service:\n image: rosie91/stemify:gateway-service\n build:\n context: .\n dockerfile: src/ApiGateways/ApiGateway/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Docker\n - ASPNETCORE_URLS=http://+:80\n - IdentityServiceUrl=http://identity-service\n - Issuer=http://localhost:5000/\n - ClientApp=https://localhost:3000\n ports:\n - 6001:80\n restart: unless-stopped\n identity-api-service:\n image: rosie91/stemify:identity-api-service\n build:\n context: .\n dockerfile: src/Services/Identity/Identity.Api/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5002\n - ConnectionStrings__stemifyidentity=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_identity\n ports:\n - 7002:80\n - 5002:5002\n depends_on:\n - postgres\n restart: unless-stopped\n identity-service:\n image: rosie91/stemify:identity-service\n build:\n context: .\n dockerfile: src/Services/Identity/Identity.Web/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - ASPNETCORE_URLS=http://+:80\n - ConnectionStrings__stemifyidentity=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_identity\n # OpenIddict configuration for Docker\n - OpenIddict__Issuer=http://localhost:5000\n - OpenIddict__SigningKey=your-development-signing-key-32-chars\n - OpenIddict__EncryptionKey=your-development-encryption-key-32\n - OpenIddict__ApiClientSecret=stemify-api-secret-key-2024-dev\n - OpenIddict__ForceRecreateClients=true\n - OpenIddict__DisableHttpsRequirement=true\n # CORS for Docker environment (adjust if needed)\n - Cors__Origins=https://localhost:3000\n - ClientApp=https://localhost:3000\n ports:\n - 5000:80\n depends_on:\n - postgres\n restart: unless-stopped\n cart-service:\n image: rosie91/stemify:cart-service\n build:\n context: .\n dockerfile: src/Services/CartService/Cart.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5007\n - ConnectionStrings__stemifycart=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_cart\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcProductUrl=http://product-service:5005\n ports:\n - 7007:80\n - 5007:5007\n depends_on:\n - postgres\n - redis\n #- rabbitmq\n restart: unless-stopped\n payment-service:\n image: rosie91/stemify:payment-service\n build:\n context: .\n dockerfile: src/Services/PaymentService/Payment.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - Logging__LogLevel__Microsoft.EntityFrameworkCore.Database.Command=Warning\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5008\n - ConnectionStrings__stemifypayment=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_payment\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n # PayOS configuration\n - PaymentGateways__PayOS__ClientId=${PAYOS_CLIENT_ID}\n - PaymentGateways__PayOS__ApiKey=${PAYOS_API_KEY}\n - PaymentGateways__PayOS__ChecksumKey=${PAYOS_CHECKSUM_KEY}\n - PaymentGateways__PayOS__BaseUrl=https://api-merchant.payos.vn\n # Stripe configuration\n - PaymentGateways__Stripe__SecretKey=${STRIPE_SECRET_KEY}\n - PaymentGateways__Stripe__PublishableKey=${STRIPE_PUBLISH_KEY}\n - PaymentGateways__Stripe__WebhookSecret=${STRIPE_WEBHOOK_KEY}\n ports:\n - 7008:80\n - 5008:5008\n depends_on:\n - postgres\n - redis\n #- rabbitmq\n restart: unless-stopped\n emulator-service:\n image: rosie91/stemify:emulator-service\n build:\n context: .\n dockerfile: src/Services/EmulatorService/Emulator.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5009\n # MongoDB configuration\n - MongoDb__ConnectionString=${MONGODB_CONNECTION_STRING}\n - MongoDb__DatabaseName=stemify_emulator\n # Redis configuration\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n # gRPC endpoints\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcResourceUrl=http://resource-service:5003\n ports:\n - 7009:80\n - 5009:5009\n depends_on:\n - redis\n #- rabbitmq\n restart: unless-stopped\nvolumes:\n redis-data:\n postgres_data:\n mongodata:\n"}, "8b0ce1b3779bbce0e6e1c38fd9662c92f3e7dc69": {"t": 1785748800.1078205, "n": 12611, "h": "2875aac67c372c0b", "c": "services:\n postgres:\n image: postgres:17\n container_name: stemify-postgres\n environment:\n - POSTGRES_PASSWORD=${DB_PASSWORD}\n ports:\n - '5432:5432'\n volumes:\n - postgres_data:/var/lib/postgresql/data\n restart: unless-stopped\n # RabbitMQ is now using CloudAMQP online service\n # rabbitmq:\n # image: rabbitmq:3-management-alpine\n # ports:\n # - 5672:5672\n # - 15672:15672\n # environment:\n # - RABBITMQ_DEFAULT_USER=guest\n # - RABBITMQ_DEFAULT_PASS=guest\n # healthcheck:\n # test: [\"CMD\", \"rabbitmq-diagnostics\", \"ping\"]\n # interval: 30s\n # timeout: 10s\n # retries: 5\n # start_period: 30s\n # restart: unless-stopped\n mongo:\n image: mongo\n environment:\n - MONGO_INITDB_ROOT_USERNAME=root\n - MONGO_INITDB_ROOT_PASSWORD=mongopw\n ports:\n - 27017:27017\n volumes:\n - mongodata:/var/lib/mongodb/data\n redis:\n image: redis:latest\n ports:\n - 6379:6379\n command: ['redis-server', '--appendonly', 'yes']\n volumes:\n - redis-data:/data\n redis-commander:\n image: rediscommander/redis-commander:latest\n environment:\n - REDIS_HOSTS=local:redis:6379\n - HTTP_USER=root\n - HTTP_PASSWORD=secret\n ports:\n - 8082:8082\n depends_on:\n - redis\n classroom-service:\n image: rosie91/stemify:classroom-service\n build:\n context: .\n dockerfile: src/Services/ClassroomService/Classroom.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5001\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcResourceUrl=http://resource-service:5003\n - GrpcIdentityUrl=http://identity-api-service:5002\n - ConnectionStrings__stemifyclassroom=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_classroom\n ports:\n - 7001:80\n - 5001:5001\n depends_on:\n - postgres\n #- rabbitmq\n restart: unless-stopped\n resource-service:\n image: rosie91/stemify:resource-service\n build:\n context: .\n dockerfile: src/Services/Resource/Resource.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5003\n - ConnectionStrings__stemifyresource=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_resource\n - Cloudinary__CloudName=${CLOUDINARY_CLOUD_NAME}\n - Cloudinary__ApiKey=${CLOUDINARY_API_KEY}\n - Cloudinary__ApiSecret=${CLOUDINARY_API_SECRET}\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n - ClientApp=https://localhost:3000\n - Gemini__BaseUrl=https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent\n - Gemini__ApiKey=${GEMINI_API_KEY}\n - Gemini__Model=gemini-2.5-flash\n ports:\n - 7003:80\n - 5003:5003\n depends_on:\n - postgres\n #- rabbitmq\n restart: unless-stopped\n product-service:\n image: rosie91/stemify:product-service\n build:\n context: .\n dockerfile: src/Services/ProductService/Product.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5005\n - ConnectionStrings__stemifyproduct=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_product\n - Cloudinary__CloudName=${CLOUDINARY_CLOUD_NAME}\n - Cloudinary__ApiKey=${CLOUDINARY_API_KEY}\n - Cloudinary__ApiSecret=${CLOUDINARY_API_SECRET}\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcResourceUrl=http://resource-service:5003\n ports:\n - 7005:80\n - 5005:5005\n depends_on:\n - postgres\n #- rabbitmq\n restart: unless-stopped\n order-service:\n image: rosie91/stemify:order-service\n build:\n context: .\n dockerfile: src/Services/OrderService/Order.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5006\n - ConnectionStrings__stemifyorder=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_order\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcResourceUrl=http://resource-service:5003\n - GrpcProductUrl=http://product-service:5005\n - Cloudinary__CloudName=${CLOUDINARY_CLOUD_NAME}\n - Cloudinary__ApiKey=${CLOUDINARY_API_KEY}\n - Cloudinary__ApiSecret=${CLOUDINARY_API_SECRET}\n ports:\n - 7006:80\n - 5006:5006\n depends_on:\n - postgres\n #- rabbitmq\n restart: unless-stopped\n notification-service:\n image: rosie91/stemify:notification-service\n build:\n context: .\n dockerfile: src/Services/Notification/Notification.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5004\n - ConnectionStrings__stemifynotification=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_notification\n # SendGrid configuration\n - SendGridSettings__ApiKey=${SENDGRID_API_KEY}\n - SendGridSettings__FromEmail=stemify30062025@gmail.com\n - SendGridSettings__FromName=STEMify\n # Client App configuration\n - ClientApp__BaseUrl=https://localhost:3000\n - ClientApp=https://localhost:3000\n - Issuer=http://localhost:5000/\n - IdentityServiceUrl=http://identity-service\n ports:\n - 7004:80\n - 5004:5004\n depends_on:\n - postgres\n #- rabbitmq\n restart: unless-stopped\n gateway-service:\n image: rosie91/stemify:gateway-service\n build:\n context: .\n dockerfile: src/ApiGateways/ApiGateway/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Docker\n - ASPNETCORE_URLS=http://+:80\n - IdentityServiceUrl=http://identity-service\n - Issuer=http://localhost:5000/\n - ClientApp=https://localhost:3000\n ports:\n - 6001:80\n restart: unless-stopped\n identity-api-service:\n image: rosie91/stemify:identity-api-service\n build:\n context: .\n dockerfile: src/Services/Identity/Identity.Api/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5002\n - ConnectionStrings__stemifyidentity=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_identity\n ports:\n - 7002:80\n - 5002:5002\n depends_on:\n - postgres\n restart: unless-stopped\n identity-service:\n image: rosie91/stemify:identity-service\n build:\n context: .\n dockerfile: src/Services/Identity/Identity.Web/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - ASPNETCORE_URLS=http://+:80\n - ConnectionStrings__stemifyidentity=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_identity\n # OpenIddict configuration for Docker\n - OpenIddict__Issuer=http://localhost:5000\n - OpenIddict__SigningKey=your-development-signing-key-32-chars\n - OpenIddict__EncryptionKey=your-development-encryption-key-32\n - OpenIddict__ApiClientSecret=stemify-api-secret-key-2024-dev\n - OpenIddict__ForceRecreateClients=true\n - OpenIddict__DisableHttpsRequirement=true\n # CORS for Docker environment (adjust if needed)\n - Cors__Origins=https://localhost:3000\n - ClientApp=https://localhost:3000\n ports:\n - 5000:80\n depends_on:\n - postgres\n restart: unless-stopped\n cart-service:\n image: rosie91/stemify:cart-service\n build:\n context: .\n dockerfile: src/Services/CartService/Cart.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5007\n - ConnectionStrings__stemifycart=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_cart\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcProductUrl=http://product-service:5005\n ports:\n - 7007:80\n - 5007:5007\n depends_on:\n - postgres\n - redis\n #- rabbitmq\n restart: unless-stopped\n payment-service:\n image: rosie91/stemify:payment-service\n build:\n context: .\n dockerfile: src/Services/PaymentService/Payment.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - Logging__LogLevel__Microsoft.EntityFrameworkCore.Database.Command=Warning\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5008\n - ConnectionStrings__stemifypayment=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_payment\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n # PayOS configuration\n - PaymentGateways__PayOS__ClientId=${PAYOS_CLIENT_ID}\n - PaymentGateways__PayOS__ApiKey=${PAYOS_API_KEY}\n - PaymentGateways__PayOS__ChecksumKey=${PAYOS_CHECKSUM_KEY}\n - PaymentGateways__PayOS__BaseUrl=https://api-merchant.payos.vn\n # Stripe configuration\n - PaymentGateways__Stripe__SecretKey=${STRIPE_SECRET_KEY}\n - PaymentGateways__Stripe__PublishableKey=${STRIPE_PUBLISH_KEY}\n - PaymentGateways__Stripe__WebhookSecret=${STRIPE_WEBHOOK_KEY}\n ports:\n - 7008:80\n - 5008:5008\n depends_on:\n - postgres\n - redis\n #- rabbitmq\n restart: unless-stopped\n emulator-service:\n image: rosie91/stemify:emulator-service\n build:\n context: .\n dockerfile: src/Services/EmulatorService/Emulator.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5009\n # MongoDB configuration\n - MongoDb__ConnectionString=${MONGODB_CONNECTION_STRING}\n - MongoDb__DatabaseName=stemify_emulator\n # Redis configuration\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n # gRPC endpoints\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcResourceUrl=http://resource-service:5003\n ports:\n - 7009:80\n - 5009:5009\n depends_on:\n - redis\n #- rabbitmq\n restart: unless-stopped\nvolumes:\n redis-data:\n postgres_data:\n mongodata:\n"}, "297413d1d7536b11b9a1b0569653a5135c4d59bd": {"t": 1785748801.23659, "n": 12350, "h": "f8b7a0d76489966d", "c": "services:\n postgres:\n image: postgres:17\n container_name: stemify-postgres\n environment:\n - POSTGRES_PASSWORD=${DB_PASSWORD}\n ports:\n - '5432:5432'\n volumes:\n - postgres_data:/var/lib/postgresql/data\n restart: unless-stopped\n # RabbitMQ is now using CloudAMQP online service\n # rabbitmq:\n # image: rabbitmq:3-management-alpine\n # ports:\n # - 5672:5672\n # - 15672:15672\n # environment:\n # - RABBITMQ_DEFAULT_USER=guest\n # - RABBITMQ_DEFAULT_PASS=guest\n # healthcheck:\n # test: [\"CMD\", \"rabbitmq-diagnostics\", \"ping\"]\n # interval: 30s\n # timeout: 10s\n # retries: 5\n # start_period: 30s\n # restart: unless-stopped\n mongo:\n image: mongo\n environment:\n - MONGO_INITDB_ROOT_USERNAME=root\n - MONGO_INITDB_ROOT_PASSWORD=mongopw\n ports:\n - 27017:27017\n volumes:\n - mongodata:/var/lib/mongodb/data\n redis:\n image: redis:latest\n ports:\n - 6379:6379\n command: [ 'redis-server', '--appendonly', 'yes' ]\n volumes:\n - redis-data:/data\n redis-commander:\n image: rediscommander/redis-commander:latest\n environment:\n - REDIS_HOSTS=local:redis:6379\n - HTTP_USER=root\n - HTTP_PASSWORD=secret\n ports:\n - 8082:8082\n depends_on:\n - redis\n classroom-service:\n image: rosie91/stemify:classroom-service\n build:\n context: .\n dockerfile: src/Services/ClassroomService/Classroom.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5001\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcResourceUrl=http://resource-service:5003\n - GrpcIdentityUrl=http://identity-api-service:5002\n - ConnectionStrings__stemifyclassroom=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_classroom\n ports:\n - 7001:80\n - 5001:5001\n depends_on:\n - postgres\n #- rabbitmq\n restart: unless-stopped\n resource-service:\n image: rosie91/stemify:resource-service\n build:\n context: .\n dockerfile: src/Services/Resource/Resource.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5003\n - ConnectionStrings__stemifyresource=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_resource\n - Cloudinary__CloudName=${CLOUDINARY_CLOUD_NAME}\n - Cloudinary__ApiKey=${CLOUDINARY_API_KEY}\n - Cloudinary__ApiSecret=${CLOUDINARY_API_SECRET}\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n - ClientApp=https://localhost:3000\n - Gemini__BaseUrl=https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent\n - Gemini__ApiKey=${GEMINI_API_KEY}\n - Gemini__Model=gemini-2.5-flash\n ports:\n - 7003:80\n - 5003:5003\n depends_on:\n - postgres\n #- rabbitmq\n restart: unless-stopped\n product-service:\n image: rosie91/stemify:product-service\n build:\n context: .\n dockerfile: src/Services/ProductService/Product.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5005\n - ConnectionStrings__stemifyproduct=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_product\n - Cloudinary__CloudName=${CLOUDINARY_CLOUD_NAME}\n - Cloudinary__ApiKey=${CLOUDINARY_API_KEY}\n - Cloudinary__ApiSecret=${CLOUDINARY_API_SECRET}\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcResourceUrl=http://resource-service:5003\n ports:\n - 7005:80\n - 5005:5005\n depends_on:\n - postgres\n #- rabbitmq\n restart: unless-stopped\n order-service:\n image: rosie91/stemify:order-service\n build:\n context: .\n dockerfile: src/Services/OrderService/Order.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5006\n - ConnectionStrings__stemifyorder=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_order\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n ports:\n - 7006:80\n - 5006:5006\n depends_on:\n - postgres\n #- rabbitmq\n restart: unless-stopped\n notification-service:\n image: rosie91/stemify:notification-service\n build:\n context: .\n dockerfile: src/Services/Notification/Notification.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5004\n - ConnectionStrings__stemifynotification=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_notification\n # SendGrid configuration\n - SendGridSettings__ApiKey=${SENDGRID_API_KEY}\n - SendGridSettings__FromEmail=stemify30062025@gmail.com\n - SendGridSettings__FromName=STEMify\n # Client App configuration\n - ClientApp__BaseUrl=https://localhost:3000\n - ClientApp=https://localhost:3000\n - Issuer=http://localhost:5000/\n - IdentityServiceUrl=http://identity-service\n ports:\n - 7004:80\n - 5004:5004\n depends_on:\n - postgres\n #- rabbitmq\n restart: unless-stopped\n gateway-service:\n image: rosie91/stemify:gateway-service\n build:\n context: .\n dockerfile: src/ApiGateways/ApiGateway/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Docker\n - ASPNETCORE_URLS=http://+:80\n - IdentityServiceUrl=http://identity-service\n - Issuer=http://localhost:5000/\n - ClientApp=https://localhost:3000\n ports:\n - 6001:80\n restart: unless-stopped\n identity-api-service:\n image: rosie91/stemify:identity-api-service\n build:\n context: .\n dockerfile: src/Services/Identity/Identity.Api/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5002\n - ConnectionStrings__stemifyidentity=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_identity\n ports:\n - 7002:80\n - 5002:5002\n depends_on:\n - postgres\n restart: unless-stopped\n identity-service:\n image: rosie91/stemify:identity-service\n build:\n context: .\n dockerfile: src/Services/Identity/Identity.Web/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - ASPNETCORE_URLS=http://+:80\n - ConnectionStrings__stemifyidentity=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_identity\n # OpenIddict configuration for Docker\n - OpenIddict__Issuer=http://localhost:5000\n - OpenIddict__SigningKey=your-development-signing-key-32-chars\n - OpenIddict__EncryptionKey=your-development-encryption-key-32\n - OpenIddict__ApiClientSecret=stemify-api-secret-key-2024-dev\n - OpenIddict__ForceRecreateClients=true\n - OpenIddict__DisableHttpsRequirement=true\n # CORS for Docker environment (adjust if needed)\n - Cors__Origins=https://localhost:3000\n - ClientApp=https://localhost:3000\n ports:\n - 5000:80\n depends_on:\n - postgres\n restart: unless-stopped\n cart-service:\n image: rosie91/stemify:cart-service\n build:\n context: .\n dockerfile: src/Services/CartService/Cart.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5007\n - ConnectionStrings__stemifycart=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_cart\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcProductUrl=http://product-service:5005\n ports:\n - 7007:80\n - 5007:5007\n depends_on:\n - postgres\n - redis\n #- rabbitmq\n restart: unless-stopped\n payment-service:\n image: rosie91/stemify:payment-service\n build:\n context: .\n dockerfile: src/Services/PaymentService/Payment.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - Logging__LogLevel__Microsoft.EntityFrameworkCore.Database.Command=Warning\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5008\n - ConnectionStrings__stemifypayment=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_payment\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n # PayOS configuration\n - PaymentGateways__PayOS__ClientId=${PAYOS_CLIENT_ID}\n - PaymentGateways__PayOS__ApiKey=${PAYOS_API_KEY}\n - PaymentGateways__PayOS__ChecksumKey=${PAYOS_CHECKSUM_KEY}\n - PaymentGateways__PayOS__BaseUrl=https://api-merchant.payos.vn\n # Stripe configuration\n - PaymentGateways__Stripe__SecretKey=${STRIPE_SECRET_KEY}\n - PaymentGateways__Stripe__PublishableKey=${STRIPE_PUBLISH_KEY}\n - PaymentGateways__Stripe__WebhookSecret=${STRIPE_WEBHOOK_KEY}\n ports:\n - 7008:80\n - 5008:5008\n depends_on:\n - postgres\n - redis\n #- rabbitmq\n restart: unless-stopped\n emulator-service:\n image: rosie91/stemify:emulator-service\n build:\n context: .\n dockerfile: src/Services/EmulatorService/Emulator.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5009\n # MongoDB configuration\n - MongoDb__ConnectionString=${MONGODB_CONNECTION_STRING}\n - MongoDb__DatabaseName=stemify_emulator\n # Redis configuration\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n # gRPC endpoints\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcResourceUrl=http://resource-service:5003\n ports:\n - 7009:80\n - 5009:5009\n depends_on:\n - redis\n #- rabbitmq\n restart: unless-stopped\nvolumes:\n redis-data:\n postgres_data:\n mongodata:\n"}, "55d003b416dedb1ce24dc55d584fcd762882630b": {"t": 1785748802.79182, "n": 12337, "h": "20a3610d1b6ffee1", "c": "services:\n postgres:\n image: postgres:17\n container_name: stemify-postgres\n environment:\n - POSTGRES_PASSWORD=${DB_PASSWORD}\n ports:\n - '5432:5432'\n volumes:\n - postgres_data:/var/lib/postgresql/data\n restart: unless-stopped\n # RabbitMQ is now using CloudAMQP online service\n # rabbitmq:\n # image: rabbitmq:3-management-alpine\n # ports:\n # - 5672:5672\n # - 15672:15672\n # environment:\n # - RABBITMQ_DEFAULT_USER=guest\n # - RABBITMQ_DEFAULT_PASS=guest\n # healthcheck:\n # test: [\"CMD\", \"rabbitmq-diagnostics\", \"ping\"]\n # interval: 30s\n # timeout: 10s\n # retries: 5\n # start_period: 30s\n # restart: unless-stopped\n mongo:\n image: mongo\n environment:\n - MONGO_INITDB_ROOT_USERNAME=root\n - MONGO_INITDB_ROOT_PASSWORD=mongopw\n ports:\n - 27017:27017\n volumes:\n - mongodata:/var/lib/mongodb/data\n redis:\n image: redis:latest\n ports:\n - 6379:6379\n command: [ 'redis-server', '--appendonly', 'yes' ]\n volumes:\n - redis-data:/data\n redis-commander:\n image: rediscommander/redis-commander:latest\n environment:\n - REDIS_HOSTS=local:redis:6379\n - HTTP_USER=root\n - HTTP_PASSWORD=secret\n ports:\n - 8082:8082\n depends_on:\n - redis\n classroom-service:\n image: rosie91/stemify:classroom-service\n build:\n context: .\n dockerfile: src/Services/ClassroomService/Classroom.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5001\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcResourceUrl=http://resource-service:5003\n - GrpcIdentityUrl=http://identity-api-service:5002\n - ConnectionStrings__stemifyclassroom=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_classroom\n ports:\n - 7001:80\n - 5001:5001\n depends_on:\n - postgres\n #- rabbitmq\n restart: unless-stopped\n resource-service:\n image: rosie91/stemify:resource-service\n build:\n context: .\n dockerfile: src/Services/Resource/Resource.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5003\n - ConnectionStrings__stemifyresource=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_resource\n - Cloudinary__CloudName=${CLOUDINARY_CLOUD_NAME}\n - Cloudinary__ApiKey=${CLOUDINARY_API_KEY}\n - Cloudinary__ApiSecret=${CLOUDINARY_API_SECRET}\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n - ClientApp=https://localhost:3000\n - Gemini__BaseUrl=https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent\n - Gemini__ApiKey=${GEMINI_API_KEY}\n - Gemini__Model=gemini-2.5-flash\n ports:\n - 7003:80\n - 5003:5003\n depends_on:\n - postgres\n #- rabbitmq\n restart: unless-stopped\n product-service:\n image: rosie91/stemify:product-service\n build:\n context: .\n dockerfile: src/Services/ProductService/Product.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5005\n - ConnectionStrings__stemifyproduct=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_product\n - Cloudinary__CloudName=${CLOUDINARY_CLOUD_NAME}\n - Cloudinary__ApiKey=${CLOUDINARY_API_KEY}\n - Cloudinary__ApiSecret=${CLOUDINARY_API_SECRET}\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcResourceUrl=http://resource-service:5003\n ports:\n - 7005:80\n - 5005:5005\n depends_on:\n - postgres\n #- rabbitmq\n restart: unless-stopped\n order-service:\n image: rosie91/stemify:order-service\n build:\n context: .\n dockerfile: src/Services/OrderService/Order.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5006\n - ConnectionStrings__stemifyorder=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_order\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n ports:\n - 7006:80\n - 5006:5006\n depends_on:\n - postgres\n #- rabbitmq\n restart: unless-stopped\n notification-service:\n image: rosie91/stemify:notification-service\n build:\n context: .\n dockerfile: src/Services/Notification/Notification.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5004\n - ConnectionStrings__stemifynotification=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_notification\n # SendGrid configuration\n - SendGridSettings__ApiKey=${SENDGRID_API_KEY}\n - SendGridSettings__FromEmail=stemify30062025@gmail.com\n - SendGridSettings__FromName=STEMify\n # Client App configuration\n - ClientApp__BaseUrl=https://localhost:3000\n - ClientApp=https://localhost:3000\n - Issuer=http://localhost:5000/\n - IdentityServiceUrl=http://identity-service\n ports:\n - 7004:80\n - 5004:5004\n depends_on:\n - postgres\n #- rabbitmq\n restart: unless-stopped\n gateway-service:\n image: rosie91/stemify:gateway-service\n build:\n context: .\n dockerfile: src/ApiGateways/ApiGateway/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Docker\n - ASPNETCORE_URLS=http://+:80\n - IdentityServiceUrl=http://identity-service\n - Issuer=http://localhost:5000/\n - ClientApp=https://localhost:3000\n ports:\n - 6001:80\n restart: unless-stopped\n identity-api-service:\n image: rosie91/stemify:identity-api-service\n build:\n context: .\n dockerfile: src/Services/Identity/Identity.Api/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5002\n - ConnectionStrings__stemifyidentity=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_identity\n ports:\n - 7002:80\n - 5002:5002\n depends_on:\n - postgres\n restart: unless-stopped\n identity-service:\n image: rosie91/stemify:identity-service\n build:\n context: .\n dockerfile: src/Services/Identity/Identity.Web/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - ASPNETCORE_URLS=http://+:80\n - ConnectionStrings__stemifyidentity=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_identity\n # OpenIddict configuration for Docker\n - OpenIddict__Issuer=http://localhost:5000\n - OpenIddict__SigningKey=your-development-signing-key-32-chars\n - OpenIddict__EncryptionKey=your-development-encryption-key-32\n - OpenIddict__ApiClientSecret=stemify-api-secret-key-2024-dev\n - OpenIddict__ForceRecreateClients=true\n - OpenIddict__DisableHttpsRequirement=true\n # CORS for Docker environment (adjust if needed)\n - Cors__Origins=https://localhost:3000\n - ClientApp=https://localhost:3000\n ports:\n - 5000:80\n depends_on:\n - postgres\n restart: unless-stopped\n cart-service:\n image: rosie91/stemify:cart-service\n build:\n context: .\n dockerfile: src/Services/CartService/Cart.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5007\n - ConnectionStrings__stemifycart=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_cart\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcProductUrl=http://product-service:5005\n ports:\n - 7007:80\n - 5007:5007\n depends_on:\n - postgres\n - redis\n #- rabbitmq\n restart: unless-stopped\n payment-service:\n image: rosie91/stemify:payment-service\n build:\n context: .\n dockerfile: src/Services/PaymentService/Payment.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - Logging__LogLevel__Microsoft.EntityFrameworkCore.Database.Command=Warning\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5008\n - ConnectionStrings__stemifypayment=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_payment\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n # PayOS configuration\n - PaymentGateways__PayOS__ClientId=${PAYOS_CLIENT_ID}\n - PaymentGateways__PayOS__ApiKey=${PAYOS_API_KEY}\n - PaymentGateways__PayOS__ChecksumKey=${PAYOS_CHECKSUM_KEY}\n - PaymentGateways__PayOS__BaseUrl=https://api-merchant.payos.vn\n # Stripe configuration\n - PaymentGateways__Stripe__SecretKey=${STRIPE_SECRET_KEY}\n - PaymentGateways__Stripe__PublishableKey=${STRIPE_PUBLISH_KEY}\n - PaymentGateways__Stripe__WebhookSecret=${STRIPE_WEBHOOK_KEY}\n ports:\n - 7008:80\n - 5008:5008\n depends_on:\n - postgres\n - redis\n #- rabbitmq\n restart: unless-stopped\n emulator-service:\n image: rosie91/stemify:emulator-service\n build:\n context: .\n dockerfile: src/Services/EmulatorService/Emulator.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5009\n # MongoDB configuration\n - MongoDb__ConnectionString=${MONGODB_CONNECTION_STRING}\n - MongoDb__DatabaseName=stemify_emulator\n # Redis configuration\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n # gRPC endpoints\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcResourceUrl=http://resource-service:5003\n ports:\n - 7009:80\n - 5009:5009\n depends_on:\n - redis\n #- rabbitmq\n restart: unless-stopped\nvolumes:\n redis-data:\n postgres_data:\n"}, "fdb46037de550ec18d6ea68e204f076a43cdf189": {"t": 1785748803.8931737, "n": 12390, "h": "8f258a592f2d707a", "c": "services:\n postgres:\n image: postgres:17\n container_name: stemify-postgres\n environment:\n - POSTGRES_PASSWORD=${DB_PASSWORD}\n ports:\n - '5432:5432'\n volumes:\n - postgres_data:/var/lib/postgresql/data\n restart: unless-stopped\n # RabbitMQ is now using CloudAMQP online service\n # rabbitmq:\n # image: rabbitmq:3-management-alpine\n # ports:\n # - 5672:5672\n # - 15672:15672\n # environment:\n # - RABBITMQ_DEFAULT_USER=guest\n # - RABBITMQ_DEFAULT_PASS=guest\n # healthcheck:\n # test: [\"CMD\", \"rabbitmq-diagnostics\", \"ping\"]\n # interval: 30s\n # timeout: 10s\n # retries: 5\n # start_period: 30s\n # restart: unless-stopped\n mongo:\n image: mongo\n environment:\n - MONGO_INITDB_ROOT_USERNAME=root\n - MONGO_INITDB_ROOT_PASSWORD=mongopw\n ports:\n - 27017:27017\n volumes:\n - mongodata:/var/lib/mongodb/data\n redis:\n image: redis:latest\n ports:\n - 6379:6379\n command: [ 'redis-server', '--appendonly', 'yes' ]\n volumes:\n - redis-data:/data\n redis-commander:\n image: rediscommander/redis-commander:latest\n environment:\n - REDIS_HOSTS=local:redis:6379\n - HTTP_USER=root\n - HTTP_PASSWORD=secret\n ports:\n - 8082:8082\n depends_on:\n - redis\n classroom-service:\n image: rosie91/stemify:classroom-service\n build:\n context: .\n dockerfile: src/Services/ClassroomService/Classroom.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5001\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcResourceUrl=http://resource-service:5003\n - GrpcIdentityUrl=http://identity-api-service:5002\n - ConnectionStrings__stemifyclassroom=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_classroom\n ports:\n - 7001:80\n - 5001:5001\n depends_on:\n - postgres\n #- rabbitmq\n restart: unless-stopped\n resource-service:\n image: rosie91/stemify:resource-service\n build:\n context: .\n dockerfile: src/Services/Resource/Resource.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5003\n - ConnectionStrings__stemifyresource=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_resource\n - Cloudinary__CloudName=${CLOUDINARY_CLOUD_NAME}\n - Cloudinary__ApiKey=${CLOUDINARY_API_KEY}\n - Cloudinary__ApiSecret=${CLOUDINARY_API_SECRET}\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n - ClientApp=https://localhost:3000\n - Gemini__BaseUrl=https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent\n - Gemini__ApiKey=${GEMINI_API_KEY}\n - Gemini__Model=gemini-2.5-flash\n ports:\n - 7003:80\n - 5003:5003\n depends_on:\n - postgres\n #- rabbitmq\n restart: unless-stopped\n product-service:\n image: rosie91/stemify:product-service\n build:\n context: .\n dockerfile: src/Services/ProductService/Product.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5005\n - ConnectionStrings__stemifyproduct=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_product\n - Cloudinary__CloudName=${CLOUDINARY_CLOUD_NAME}\n - Cloudinary__ApiKey=${CLOUDINARY_API_KEY}\n - Cloudinary__ApiSecret=${CLOUDINARY_API_SECRET}\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcResourceUrl=http://resource-service:5003\n ports:\n - 7005:80\n - 5005:5005\n depends_on:\n - postgres\n #- rabbitmq\n restart: unless-stopped\n order-service:\n image: rosie91/stemify:order-service\n build:\n context: .\n dockerfile: src/Services/OrderService/Order.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5006\n - ConnectionStrings__stemifyorder=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_order\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n ports:\n - 7006:80\n - 5006:5006\n depends_on:\n - postgres\n #- rabbitmq\n restart: unless-stopped\n notification-service:\n image: rosie91/stemify:notification-service\n build:\n context: .\n dockerfile: src/Services/Notification/Notification.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5004\n - ConnectionStrings__stemifynotification=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_notification\n # SendGrid configuration\n - SendGridSettings__ApiKey=${SENDGRID_API_KEY}\n - SendGridSettings__FromEmail=stemify30062025@gmail.com\n - SendGridSettings__FromName=STEMify\n # Client App configuration\n - ClientApp__BaseUrl=https://localhost:3000\n - ClientApp=https://localhost:3000\n - Issuer=http://localhost:5000/\n - IdentityServiceUrl=http://identity-service\n ports:\n - 7004:80\n - 5004:5004\n depends_on:\n - postgres\n #- rabbitmq\n restart: unless-stopped\n gateway-service:\n image: rosie91/stemify:gateway-service\n build:\n context: .\n dockerfile: src/ApiGateways/ApiGateway/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Docker\n - ASPNETCORE_URLS=http://+:80\n - IdentityServiceUrl=http://identity-service\n - Issuer=http://localhost:5000/\n - ClientApp=https://localhost:3000\n ports:\n - 6001:80\n restart: unless-stopped\n identity-api-service:\n image: rosie91/stemify:identity-api-service\n build:\n context: .\n dockerfile: src/Services/Identity/Identity.Api/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5002\n - ConnectionStrings__stemifyidentity=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_identity\n ports:\n - 7002:80\n - 5002:5002\n depends_on:\n - postgres\n restart: unless-stopped\n identity-service:\n image: rosie91/stemify:identity-service\n build:\n context: .\n dockerfile: src/Services/Identity/Identity.Web/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - ASPNETCORE_URLS=http://+:80\n - ConnectionStrings__stemifyidentity=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_identity\n # OpenIddict configuration for Docker\n - OpenIddict__Issuer=http://localhost:5000\n - OpenIddict__SigningKey=your-development-signing-key-32-chars\n - OpenIddict__EncryptionKey=your-development-encryption-key-32\n - OpenIddict__ApiClientSecret=stemify-api-secret-key-2024-dev\n - OpenIddict__ForceRecreateClients=true\n - OpenIddict__DisableHttpsRequirement=true\n # CORS for Docker environment (adjust if needed)\n - Cors__Origins=https://localhost:3000\n - ClientApp=https://localhost:3000\n ports:\n - 5000:80\n depends_on:\n - postgres\n restart: unless-stopped\n cart-service:\n image: rosie91/stemify:cart-service\n build:\n context: .\n dockerfile: src/Services/CartService/Cart.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5007\n - ConnectionStrings__stemifycart=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_cart\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcProductUrl=http://product-service:5005\n ports:\n - 7007:80\n - 5007:5007\n depends_on:\n - postgres\n - redis\n #- rabbitmq\n restart: unless-stopped\n payment-service:\n image: rosie91/stemify:payment-service\n build:\n context: .\n dockerfile: src/Services/PaymentService/Payment.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - Logging__LogLevel__Microsoft.EntityFrameworkCore.Database.Command=Warning\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5008\n - ConnectionStrings__stemifypayment=Server=postgres:5432;User Id=postgres;Password=${DB_PASSWORD};Database=stemify_payment\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n - GrpcIdentityUrl=http://identity-api-service:5002\n # PayOS configuration\n - PaymentGateways__PayOS__ClientId=${PAYOS_CLIENT_ID}\n - PaymentGateways__PayOS__ApiKey=${PAYOS_API_KEY}\n - PaymentGateways__PayOS__ChecksumKey=${PAYOS_CHECKSUM_KEY}\n - PaymentGateways__PayOS__BaseUrl=https://api-merchant.payos.vn\n # Stripe configuration\n - PaymentGateways__Stripe__SecretKey=${STRIPE_SECRET_KEY}\n - PaymentGateways__Stripe__PublishableKey=${STRIPE_PUBLISH_KEY}\n - PaymentGateways__Stripe__WebhookSecret=${STRIPE_WEBHOOK_KEY}\n ports:\n - 7008:80\n - 5008:5008\n depends_on:\n - postgres\n - redis\n #- rabbitmq\n restart: unless-stopped\n emulator-service:\n image: rosie91/stemify:emulator-service\n build:\n context: .\n dockerfile: src/Services/EmulatorService/Emulator.API/Dockerfile\n environment:\n - ASPNETCORE_ENVIRONMENT=Development\n - RabbitMq__Url=${RABBITMQ_URL}\n - Kestrel__Endpoints__WebApi__Protocols=Http1\n - Kestrel__Endpoints__WebApi__Url=http://+:80\n - Kestrel__Endpoints__Grpc__Protocols=Http2\n - Kestrel__Endpoints__Grpc__Url=http://+:5009\n # MongoDB configuration\n - MongoDb__ConnectionString=${MONGODB_CONNECTION_STRING}\n - MongoDb__DatabaseName=stemify_emulator\n # Redis configuration\n - RedisConfig__Host=redis\n - RedisConfig__Port=6379\n - RedisConfig__Password=secret\n - RedisConfig__Ssl=false\n # gRPC endpoints\n - GrpcIdentityUrl=http://identity-api-service:5002\n - GrpcResourceUrl=http://resource-service:5003\n ports:\n - 7009:80\n - 5009:5009\n depends_on:\n - redis\n #- rabbitmq\n restart: unless-stopped\nvolumes:\n redis-data:\n postgres_data:\n mongodata:\n"}}}}