mirror of
https://github.com/Sea-Haven-Industries/proposal-system.git
synced 2026-10-07 00:02:01 +00:00
Compare commits
24 commits
801151d77c
...
be7533da09
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
be7533da09 | ||
|
|
68f77a6c0a | ||
|
|
ed6aed9aa7 | ||
|
|
e64da7ecd7 | ||
|
|
e96c80c78a | ||
|
|
28475d8529 | ||
|
|
fdaaf5ed4a | ||
|
|
69c989847f | ||
|
|
d38049a6ca | ||
|
|
8c692c7477 | ||
|
|
4444eeb678 | ||
|
|
b7ab9ccc9d | ||
|
|
c3a0868d2f | ||
|
|
7dd66caf61 | ||
|
|
c14d7c2f55 | ||
|
|
d40a58a943 | ||
|
|
ef8a3b5f48 | ||
|
|
7df81b4427 | ||
|
|
3250d4d927 | ||
|
|
6263551b02 | ||
|
|
7b4a909751 | ||
|
|
cc7dfa3d07 | ||
|
|
9994be3fed | ||
|
|
923b6a4712 |
21 changed files with 2982 additions and 991 deletions
14
.github/dependabot.yml
vendored
14
.github/dependabot.yml
vendored
|
|
@ -56,6 +56,20 @@ updates:
|
||||||
open-pull-requests-limit: 3
|
open-pull-requests-limit: 3
|
||||||
|
|
||||||
|
|
||||||
|
- package-ecosystem: pip
|
||||||
|
directory: /lambdas/suggestions
|
||||||
|
schedule:
|
||||||
|
interval: weekly
|
||||||
|
open-pull-requests-limit: 3
|
||||||
|
|
||||||
|
|
||||||
|
- package-ecosystem: pip
|
||||||
|
directory: /lambdas/oss-index-creator
|
||||||
|
schedule:
|
||||||
|
interval: weekly
|
||||||
|
open-pull-requests-limit: 3
|
||||||
|
|
||||||
|
|
||||||
- package-ecosystem: github-actions
|
- package-ecosystem: github-actions
|
||||||
directory: /
|
directory: /
|
||||||
schedule:
|
schedule:
|
||||||
|
|
|
||||||
3
.github/workflows/ci.yaml
vendored
3
.github/workflows/ci.yaml
vendored
|
|
@ -21,6 +21,7 @@ jobs:
|
||||||
with:
|
with:
|
||||||
working-directory: web
|
working-directory: web
|
||||||
cache-dependency-path: web/package-lock.json
|
cache-dependency-path: web/package-lock.json
|
||||||
|
node-version: "24"
|
||||||
run-cdk-synth: false
|
run-cdk-synth: false
|
||||||
run-conventions-check: false
|
run-conventions-check: false
|
||||||
|
|
||||||
|
|
@ -38,6 +39,7 @@ jobs:
|
||||||
with:
|
with:
|
||||||
working-directory: mobile
|
working-directory: mobile
|
||||||
cache-dependency-path: mobile/package-lock.json
|
cache-dependency-path: mobile/package-lock.json
|
||||||
|
node-version: "24"
|
||||||
run-cdk-synth: false
|
run-cdk-synth: false
|
||||||
run-conventions-check: false
|
run-conventions-check: false
|
||||||
|
|
||||||
|
|
@ -47,6 +49,7 @@ jobs:
|
||||||
with:
|
with:
|
||||||
working-directory: infra
|
working-directory: infra
|
||||||
cache-dependency-path: infra/package-lock.json
|
cache-dependency-path: infra/package-lock.json
|
||||||
|
node-version: "24"
|
||||||
dotnet-version: "8.0.x"
|
dotnet-version: "8.0.x"
|
||||||
dotnet-publish-project: api/src/ProposalSystem.Api/ProposalSystem.Api.csproj
|
dotnet-publish-project: api/src/ProposalSystem.Api/ProposalSystem.Api.csproj
|
||||||
run-typecheck: false
|
run-typecheck: false
|
||||||
|
|
|
||||||
4
.github/workflows/deploy-mobile.yaml
vendored
4
.github/workflows/deploy-mobile.yaml
vendored
|
|
@ -12,6 +12,10 @@ concurrency:
|
||||||
group: deploy-mobile
|
group: deploy-mobile
|
||||||
cancel-in-progress: false
|
cancel-in-progress: false
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
id-token: write
|
||||||
|
contents: read
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
deploy-ios:
|
deploy-ios:
|
||||||
name: Build & Upload to TestFlight
|
name: Build & Upload to TestFlight
|
||||||
|
|
|
||||||
6
.github/workflows/deploy.yaml
vendored
6
.github/workflows/deploy.yaml
vendored
|
|
@ -3,11 +3,17 @@ name: Deploy
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [main]
|
branches: [main]
|
||||||
|
paths-ignore:
|
||||||
|
- "mobile/**"
|
||||||
|
|
||||||
concurrency:
|
concurrency:
|
||||||
group: deploy-backend
|
group: deploy-backend
|
||||||
cancel-in-progress: false
|
cancel-in-progress: false
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
id-token: write
|
||||||
|
contents: read
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
deploy:
|
deploy:
|
||||||
name: Deploy to AWS
|
name: Deploy to AWS
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ Monorepo with five primary services:
|
||||||
- **.NET 8 API** -- Clean Architecture REST API hosted on Lambda behind API Gateway
|
- **.NET 8 API** -- Clean Architecture REST API hosted on Lambda behind API Gateway
|
||||||
- **React 19 Web** -- MUI v7 admin/dispatcher workspace served via CloudFront + S3
|
- **React 19 Web** -- MUI v7 admin/dispatcher workspace served via CloudFront + S3
|
||||||
- **React Native Mobile** -- iOS-first field app for dispatchers (offline-capable)
|
- **React Native Mobile** -- iOS-first field app for dispatchers (offline-capable)
|
||||||
- **Python Lambdas** -- PDF extraction, PDF generation, library ingestion, AI suggestions
|
- **Python Lambdas** -- PDF extraction, PDF generation, library ingestion, AI suggestions, AOSS index provisioning
|
||||||
- **CDK Infrastructure** -- Three TypeScript stacks managing all AWS resources
|
- **CDK Infrastructure** -- Three TypeScript stacks managing all AWS resources
|
||||||
|
|
||||||
## Repository Structure
|
## Repository Structure
|
||||||
|
|
@ -41,12 +41,12 @@ proposal-system/
|
||||||
|
|
||||||
## AWS Resources
|
## AWS Resources
|
||||||
|
|
||||||
All resources are in **us-east-1** (account 328440206208). CDK stacks are defined but not yet deployed to AWS.
|
All resources are in **us-east-1** (account 328440206208).
|
||||||
|
|
||||||
| CDK Stack | Key Resources |
|
| CDK Stack | Key Resources |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `proposal-system-foundation` | RDS PostgreSQL 15 (t4g.small), S3 buckets, SQS queue + DLQ, Cognito user pool, Secrets Manager |
|
| `proposal-system-foundation` | RDS PostgreSQL 15 (t4g.small), S3 buckets, SQS queue + DLQ, Cognito user pool, Secrets Manager |
|
||||||
| `proposal-system-compute` | API Gateway HTTP API, .NET 8 API Lambda, Python Lambdas (pdf-extract, pdf-generate, library-ingest, suggestions), Bedrock KB |
|
| `proposal-system-compute` | API Gateway HTTP API, .NET 8 API Lambda, Python Lambdas (pdf-extract, pdf-generate, library-ingest, suggestions, oss-index-creator), OpenSearch Serverless collection, Bedrock KB |
|
||||||
| `proposal-system-frontend` | CloudFront distribution (S3 OAC) |
|
| `proposal-system-frontend` | CloudFront distribution (S3 OAC) |
|
||||||
|
|
||||||
| Resource Type | Names |
|
| Resource Type | Names |
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.27" />
|
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.27" />
|
||||||
<PackageReference Include="FluentValidation.AspNetCore" Version="11.3.1" />
|
<PackageReference Include="FluentValidation.AspNetCore" Version="11.3.1" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.11" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.11" />
|
||||||
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="8.0.11" />
|
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" Version="8.0.27" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ import * as lambdaEventSources from 'aws-cdk-lib/aws-lambda-event-sources';
|
||||||
import * as bedrock from 'aws-cdk-lib/aws-bedrock';
|
import * as bedrock from 'aws-cdk-lib/aws-bedrock';
|
||||||
import * as opensearchserverless from 'aws-cdk-lib/aws-opensearchserverless';
|
import * as opensearchserverless from 'aws-cdk-lib/aws-opensearchserverless';
|
||||||
import * as logs from 'aws-cdk-lib/aws-logs';
|
import * as logs from 'aws-cdk-lib/aws-logs';
|
||||||
|
import * as cr from 'aws-cdk-lib/custom-resources';
|
||||||
import { Construct } from 'constructs';
|
import { Construct } from 'constructs';
|
||||||
|
|
||||||
export interface ComputeStackProps extends cdk.StackProps {
|
export interface ComputeStackProps extends cdk.StackProps {
|
||||||
|
|
@ -90,8 +91,31 @@ export class ComputeStack extends cdk.Stack {
|
||||||
resources: [`arn:aws:bedrock:us-east-1::foundation-model/amazon.titan-embed-text-v2:0`],
|
resources: [`arn:aws:bedrock:us-east-1::foundation-model/amazon.titan-embed-text-v2:0`],
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// OpenSearch Serverless data access policy
|
// Lambda to pre-create the vector index (retries until AOSS access policy propagates)
|
||||||
new opensearchserverless.CfnAccessPolicy(this, 'OssDataAccessPolicy', {
|
const indexCreatorFn = new lambda.Function(this, 'OssIndexCreator', {
|
||||||
|
functionName: 'proposal-system-oss-index-creator',
|
||||||
|
runtime: lambda.Runtime.PYTHON_3_12,
|
||||||
|
architecture: lambda.Architecture.ARM_64,
|
||||||
|
handler: 'app.handler',
|
||||||
|
code: lambda.Code.fromAsset('../lambdas/oss-index-creator', {
|
||||||
|
bundling: {
|
||||||
|
image: lambda.Runtime.PYTHON_3_12.bundlingImage,
|
||||||
|
command: [
|
||||||
|
'bash', '-c',
|
||||||
|
'pip install -r requirements.txt -t /asset-output && cp -au . /asset-output',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
timeout: cdk.Duration.minutes(6),
|
||||||
|
logRetention: logs.RetentionDays.TWO_MONTHS,
|
||||||
|
});
|
||||||
|
|
||||||
|
indexCreatorFn.addToRolePolicy(new iam.PolicyStatement({
|
||||||
|
actions: ['aoss:APIAccessAll'],
|
||||||
|
resources: [ossCollection.attrArn],
|
||||||
|
}));
|
||||||
|
|
||||||
|
const ossDataAccessPolicy = new opensearchserverless.CfnAccessPolicy(this, 'OssDataAccessPolicy', {
|
||||||
name: 'proposal-system-kb-access',
|
name: 'proposal-system-kb-access',
|
||||||
type: 'data',
|
type: 'data',
|
||||||
policy: JSON.stringify([{
|
policy: JSON.stringify([{
|
||||||
|
|
@ -99,11 +123,27 @@ export class ComputeStack extends cdk.Stack {
|
||||||
{ ResourceType: 'collection', Resource: ['collection/proposal-system-kb'], Permission: ['aoss:*'] },
|
{ ResourceType: 'collection', Resource: ['collection/proposal-system-kb'], Permission: ['aoss:*'] },
|
||||||
{ ResourceType: 'index', Resource: ['index/proposal-system-kb/*'], Permission: ['aoss:*'] },
|
{ ResourceType: 'index', Resource: ['index/proposal-system-kb/*'], Permission: ['aoss:*'] },
|
||||||
],
|
],
|
||||||
Principal: [kbRole.roleArn, `arn:aws:iam::${this.account}:root`],
|
Principal: [kbRole.roleArn, indexCreatorFn.role!.roleArn],
|
||||||
}]),
|
}]),
|
||||||
});
|
});
|
||||||
|
ossDataAccessPolicy.addDependency(ossCollection);
|
||||||
|
|
||||||
|
const indexProvider = new cr.Provider(this, 'OssIndexProvider', {
|
||||||
|
onEventHandler: indexCreatorFn,
|
||||||
|
});
|
||||||
|
|
||||||
|
const ossIndex = new cdk.CustomResource(this, 'OssIndex', {
|
||||||
|
serviceToken: indexProvider.serviceToken,
|
||||||
|
properties: {
|
||||||
|
Endpoint: ossCollection.attrCollectionEndpoint,
|
||||||
|
IndexName: 'proposal-system-index',
|
||||||
|
VectorField: 'embedding',
|
||||||
|
TextField: 'text',
|
||||||
|
MetadataField: 'metadata',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
ossIndex.node.addDependency(ossDataAccessPolicy);
|
||||||
|
|
||||||
// Bedrock Knowledge Base
|
|
||||||
const knowledgeBase = new bedrock.CfnKnowledgeBase(this, 'KnowledgeBase', {
|
const knowledgeBase = new bedrock.CfnKnowledgeBase(this, 'KnowledgeBase', {
|
||||||
name: 'proposal-system-kb',
|
name: 'proposal-system-kb',
|
||||||
roleArn: kbRole.roleArn,
|
roleArn: kbRole.roleArn,
|
||||||
|
|
@ -126,6 +166,7 @@ export class ComputeStack extends cdk.Stack {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
knowledgeBase.node.addDependency(ossIndex);
|
||||||
|
|
||||||
// KB Data Source (S3 library bucket)
|
// KB Data Source (S3 library bucket)
|
||||||
const dataSource = new bedrock.CfnDataSource(this, 'KbDataSource', {
|
const dataSource = new bedrock.CfnDataSource(this, 'KbDataSource', {
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,6 @@ then triggers a KB sync.
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import time
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
import boto3
|
import boto3
|
||||||
|
|
@ -199,27 +198,3 @@ def _api_headers() -> dict:
|
||||||
if api_key:
|
if api_key:
|
||||||
headers["X-Internal-Api-Key"] = api_key
|
headers["X-Internal-Api-Key"] = api_key
|
||||||
return headers
|
return headers
|
||||||
|
|
||||||
|
|
||||||
def _api_request(method: str, url: str, retries: int = 3, **kwargs) -> httpx.Response:
|
|
||||||
kwargs.setdefault("headers", _api_headers())
|
|
||||||
kwargs.setdefault("timeout", 10)
|
|
||||||
for attempt in range(retries):
|
|
||||||
try:
|
|
||||||
resp = httpx.request(method, url, **kwargs)
|
|
||||||
if resp.status_code < 500:
|
|
||||||
return resp
|
|
||||||
logger.warning(
|
|
||||||
"API returned %s on attempt %d for %s",
|
|
||||||
resp.status_code,
|
|
||||||
attempt + 1,
|
|
||||||
url,
|
|
||||||
)
|
|
||||||
except httpx.TransportError as e:
|
|
||||||
logger.warning(
|
|
||||||
"Transport error on attempt %d for %s: %s", attempt + 1, url, e
|
|
||||||
)
|
|
||||||
if attempt == retries - 1:
|
|
||||||
raise
|
|
||||||
time.sleep(min(2**attempt, 4))
|
|
||||||
return resp # type: ignore[possibly-undefined]
|
|
||||||
|
|
|
||||||
|
|
@ -1,2 +1,2 @@
|
||||||
boto3>=1.43.9,<2.0
|
boto3>=1.43.10,<2.0
|
||||||
httpx>=0.27.0,<1.0
|
httpx>=0.28.1,<1.0
|
||||||
|
|
|
||||||
71
lambdas/oss-index-creator/app.py
Normal file
71
lambdas/oss-index-creator/app.py
Normal file
|
|
@ -0,0 +1,71 @@
|
||||||
|
import time
|
||||||
|
import boto3
|
||||||
|
from opensearchpy import OpenSearch, RequestsHttpConnection
|
||||||
|
from requests_aws4auth import AWS4Auth
|
||||||
|
|
||||||
|
|
||||||
|
def handler(event, context):
|
||||||
|
if event["RequestType"] == "Delete":
|
||||||
|
return {"PhysicalResourceId": event.get("PhysicalResourceId", "none")}
|
||||||
|
|
||||||
|
props = event["ResourceProperties"]
|
||||||
|
endpoint = props["Endpoint"].replace("https://", "")
|
||||||
|
index_name = props["IndexName"]
|
||||||
|
vector_field = props["VectorField"]
|
||||||
|
text_field = props["TextField"]
|
||||||
|
metadata_field = props["MetadataField"]
|
||||||
|
|
||||||
|
session = boto3.Session()
|
||||||
|
credentials = session.get_credentials().get_frozen_credentials()
|
||||||
|
region = session.region_name
|
||||||
|
|
||||||
|
awsauth = AWS4Auth(
|
||||||
|
credentials.access_key,
|
||||||
|
credentials.secret_key,
|
||||||
|
region,
|
||||||
|
"aoss",
|
||||||
|
session_token=credentials.token,
|
||||||
|
)
|
||||||
|
|
||||||
|
client = OpenSearch(
|
||||||
|
hosts=[{"host": endpoint, "port": 443}],
|
||||||
|
http_auth=awsauth,
|
||||||
|
use_ssl=True,
|
||||||
|
verify_certs=True,
|
||||||
|
connection_class=RequestsHttpConnection,
|
||||||
|
timeout=30,
|
||||||
|
)
|
||||||
|
|
||||||
|
index_body = {
|
||||||
|
"settings": {"index": {"knn": True, "knn.algo_param.ef_search": 512}},
|
||||||
|
"mappings": {
|
||||||
|
"properties": {
|
||||||
|
vector_field: {
|
||||||
|
"type": "knn_vector",
|
||||||
|
"dimension": 1024,
|
||||||
|
"method": {
|
||||||
|
"engine": "faiss",
|
||||||
|
"name": "hnsw",
|
||||||
|
"space_type": "l2",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
text_field: {"type": "text"},
|
||||||
|
metadata_field: {"type": "text"},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for attempt in range(30):
|
||||||
|
try:
|
||||||
|
client.indices.create(index=index_name, body=index_body)
|
||||||
|
return {"PhysicalResourceId": index_name}
|
||||||
|
except Exception as e:
|
||||||
|
error_str = str(e)
|
||||||
|
if "resource_already_exists_exception" in error_str:
|
||||||
|
return {"PhysicalResourceId": index_name}
|
||||||
|
if "403" in error_str and attempt < 29:
|
||||||
|
time.sleep(10)
|
||||||
|
continue
|
||||||
|
raise
|
||||||
|
|
||||||
|
raise Exception("Timeout waiting for AOSS access policy propagation")
|
||||||
3
lambdas/oss-index-creator/requirements.txt
Normal file
3
lambdas/oss-index-creator/requirements.txt
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
opensearch-py>=3.2.0
|
||||||
|
requests-aws4auth>=1.3.2
|
||||||
|
requests>=2.34.2
|
||||||
|
|
@ -9,7 +9,6 @@ import json
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import tempfile
|
import tempfile
|
||||||
import time
|
|
||||||
|
|
||||||
import boto3
|
import boto3
|
||||||
import httpx
|
import httpx
|
||||||
|
|
@ -339,27 +338,3 @@ def _api_headers() -> dict:
|
||||||
if api_key:
|
if api_key:
|
||||||
headers["X-Internal-Api-Key"] = api_key
|
headers["X-Internal-Api-Key"] = api_key
|
||||||
return headers
|
return headers
|
||||||
|
|
||||||
|
|
||||||
def _api_request(method: str, url: str, retries: int = 3, **kwargs) -> httpx.Response:
|
|
||||||
kwargs.setdefault("headers", _api_headers())
|
|
||||||
kwargs.setdefault("timeout", 10)
|
|
||||||
for attempt in range(retries):
|
|
||||||
try:
|
|
||||||
resp = httpx.request(method, url, **kwargs)
|
|
||||||
if resp.status_code < 500:
|
|
||||||
return resp
|
|
||||||
logger.warning(
|
|
||||||
"API returned %s on attempt %d for %s",
|
|
||||||
resp.status_code,
|
|
||||||
attempt + 1,
|
|
||||||
url,
|
|
||||||
)
|
|
||||||
except httpx.TransportError as e:
|
|
||||||
logger.warning(
|
|
||||||
"Transport error on attempt %d for %s: %s", attempt + 1, url, e
|
|
||||||
)
|
|
||||||
if attempt == retries - 1:
|
|
||||||
raise
|
|
||||||
time.sleep(min(2**attempt, 4))
|
|
||||||
return resp # type: ignore[possibly-undefined]
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
pdfplumber>=0.11.9,<1.0
|
pdfplumber>=0.11.9,<1.0
|
||||||
boto3>=1.43.9,<2.0
|
boto3>=1.43.10,<2.0
|
||||||
httpx>=0.27.0,<1.0
|
httpx>=0.28.1,<1.0
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@ Triggered via SQS when an admin requests PDF generation.
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import time
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
|
||||||
|
|
@ -543,27 +542,3 @@ def _api_headers() -> dict:
|
||||||
if api_key:
|
if api_key:
|
||||||
headers["X-Internal-Api-Key"] = api_key
|
headers["X-Internal-Api-Key"] = api_key
|
||||||
return headers
|
return headers
|
||||||
|
|
||||||
|
|
||||||
def _api_request(method: str, url: str, retries: int = 3, **kwargs) -> httpx.Response:
|
|
||||||
kwargs.setdefault("headers", _api_headers())
|
|
||||||
kwargs.setdefault("timeout", 10)
|
|
||||||
for attempt in range(retries):
|
|
||||||
try:
|
|
||||||
resp = httpx.request(method, url, **kwargs)
|
|
||||||
if resp.status_code < 500:
|
|
||||||
return resp
|
|
||||||
logger.warning(
|
|
||||||
"API returned %s on attempt %d for %s",
|
|
||||||
resp.status_code,
|
|
||||||
attempt + 1,
|
|
||||||
url,
|
|
||||||
)
|
|
||||||
except httpx.TransportError as e:
|
|
||||||
logger.warning(
|
|
||||||
"Transport error on attempt %d for %s: %s", attempt + 1, url, e
|
|
||||||
)
|
|
||||||
if attempt == retries - 1:
|
|
||||||
raise
|
|
||||||
time.sleep(min(2**attempt, 4))
|
|
||||||
return resp # type: ignore[possibly-undefined]
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
reportlab>=4.5.1,<5.0
|
reportlab>=4.5.1,<5.0
|
||||||
boto3>=1.43.9,<2.0
|
boto3>=1.43.10,<2.0
|
||||||
httpx>=0.27.0,<1.0
|
httpx>=0.28.1,<1.0
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@ to generate line item suggestions for new proposals.
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import time
|
|
||||||
|
|
||||||
import boto3
|
import boto3
|
||||||
import httpx
|
import httpx
|
||||||
|
|
@ -320,27 +319,3 @@ def _api_headers() -> dict:
|
||||||
if api_key:
|
if api_key:
|
||||||
headers["X-Internal-Api-Key"] = api_key
|
headers["X-Internal-Api-Key"] = api_key
|
||||||
return headers
|
return headers
|
||||||
|
|
||||||
|
|
||||||
def _api_request(method: str, url: str, retries: int = 3, **kwargs) -> httpx.Response:
|
|
||||||
kwargs.setdefault("headers", _api_headers())
|
|
||||||
kwargs.setdefault("timeout", 10)
|
|
||||||
for attempt in range(retries):
|
|
||||||
try:
|
|
||||||
resp = httpx.request(method, url, **kwargs)
|
|
||||||
if resp.status_code < 500:
|
|
||||||
return resp
|
|
||||||
logger.warning(
|
|
||||||
"API returned %s on attempt %d for %s",
|
|
||||||
resp.status_code,
|
|
||||||
attempt + 1,
|
|
||||||
url,
|
|
||||||
)
|
|
||||||
except httpx.TransportError as e:
|
|
||||||
logger.warning(
|
|
||||||
"Transport error on attempt %d for %s: %s", attempt + 1, url, e
|
|
||||||
)
|
|
||||||
if attempt == retries - 1:
|
|
||||||
raise
|
|
||||||
time.sleep(min(2**attempt, 4))
|
|
||||||
return resp # type: ignore[possibly-undefined]
|
|
||||||
|
|
|
||||||
|
|
@ -1,2 +1,2 @@
|
||||||
boto3>=1.35.0,<2.0
|
boto3>=1.35.0,<2.0
|
||||||
httpx>=0.27.0,<1.0
|
httpx>=0.28.1,<1.0
|
||||||
|
|
|
||||||
3624
mobile/package-lock.json
generated
3624
mobile/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -19,7 +19,7 @@
|
||||||
"@tanstack/react-query": "^5.100.10",
|
"@tanstack/react-query": "^5.100.10",
|
||||||
"axios": "^1.16.0",
|
"axios": "^1.16.0",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
"react-native": "^0.79.0",
|
"react-native": "^0.85.3",
|
||||||
"react-native-app-auth": "^8.0.0",
|
"react-native-app-auth": "^8.0.0",
|
||||||
"react-native-document-picker": "^9.3.0",
|
"react-native-document-picker": "^9.3.0",
|
||||||
"react-native-haptic-feedback": "^3.0.0",
|
"react-native-haptic-feedback": "^3.0.0",
|
||||||
|
|
@ -33,6 +33,8 @@
|
||||||
"react-redux": "^9.2.0"
|
"react-redux": "^9.2.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@react-native-community/cli": "^20.1.3",
|
||||||
|
"@react-native-community/cli-platform-ios": "^20.1.3",
|
||||||
"@react-native/babel-preset": "^0.79.0",
|
"@react-native/babel-preset": "^0.79.0",
|
||||||
"@react-native/metro-config": "^0.79.0",
|
"@react-native/metro-config": "^0.79.0",
|
||||||
"@types/react": "^19.0.0",
|
"@types/react": "^19.0.0",
|
||||||
|
|
|
||||||
71
web/package-lock.json
generated
71
web/package-lock.json
generated
|
|
@ -13,7 +13,7 @@
|
||||||
"@mui/icons-material": "^7.3.1",
|
"@mui/icons-material": "^7.3.1",
|
||||||
"@mui/material": "^7.3.1",
|
"@mui/material": "^7.3.1",
|
||||||
"@reduxjs/toolkit": "^2.11.2",
|
"@reduxjs/toolkit": "^2.11.2",
|
||||||
"@tanstack/react-query": "^5.100.10",
|
"@tanstack/react-query": "^5.100.11",
|
||||||
"axios": "^1.16.0",
|
"axios": "^1.16.0",
|
||||||
"react": "^19.1.1",
|
"react": "^19.1.1",
|
||||||
"react-dom": "^19.1.1",
|
"react-dom": "^19.1.1",
|
||||||
|
|
@ -1319,9 +1319,6 @@
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
|
|
@ -1336,9 +1333,6 @@
|
||||||
"arm"
|
"arm"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"musl"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
|
|
@ -1353,9 +1347,6 @@
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
|
|
@ -1370,9 +1361,6 @@
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"musl"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
|
|
@ -1387,9 +1375,6 @@
|
||||||
"loong64"
|
"loong64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
|
|
@ -1404,9 +1389,6 @@
|
||||||
"loong64"
|
"loong64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"musl"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
|
|
@ -1421,9 +1403,6 @@
|
||||||
"ppc64"
|
"ppc64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
|
|
@ -1438,9 +1417,6 @@
|
||||||
"ppc64"
|
"ppc64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"musl"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
|
|
@ -1455,9 +1431,6 @@
|
||||||
"riscv64"
|
"riscv64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
|
|
@ -1472,9 +1445,6 @@
|
||||||
"riscv64"
|
"riscv64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"musl"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
|
|
@ -1489,9 +1459,6 @@
|
||||||
"s390x"
|
"s390x"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
|
|
@ -1506,9 +1473,6 @@
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"glibc"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
|
|
@ -1523,9 +1487,6 @@
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"libc": [
|
|
||||||
"musl"
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"os": [
|
"os": [
|
||||||
|
|
@ -1629,9 +1590,9 @@
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@tanstack/query-core": {
|
"node_modules/@tanstack/query-core": {
|
||||||
"version": "5.100.10",
|
"version": "5.100.11",
|
||||||
"resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.100.10.tgz",
|
"resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.100.11.tgz",
|
||||||
"integrity": "sha512-8UR0yJR+GiQ40m3lPhUr0xbfAupe6GSQiksSBSa9SM2NjezFyxXCIA69/lz8cSoNKZLrw1/PktIyQBJcVeMi3w==",
|
"integrity": "sha512-lmE0994apShXPj8CUxgx4ch5yUJhE9k/+tVwihBvPOyerACWdBocfFg24t8+0RhtlTd7tEgchDkhlCxNssvDxw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"funding": {
|
"funding": {
|
||||||
"type": "github",
|
"type": "github",
|
||||||
|
|
@ -1639,12 +1600,12 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@tanstack/react-query": {
|
"node_modules/@tanstack/react-query": {
|
||||||
"version": "5.100.10",
|
"version": "5.100.11",
|
||||||
"resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.100.10.tgz",
|
"resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.100.11.tgz",
|
||||||
"integrity": "sha512-FLaZf2RCrA/Zgp4aiu5tG3TyasTRO7aZ99skxQpr3Hg/zXOhu6yq5FZCYQ/tRaJtM9ylnoK8tFK7PolXQadv6Q==",
|
"integrity": "sha512-J0f9s5x3LE1450nNNfYx+e/n0DMa0uOBdFJUy5r0RvmsXd4nB/n0rbHtHI1vYXhikNFan+wf51p6Tmp4c8ucrg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@tanstack/query-core": "5.100.10"
|
"@tanstack/query-core": "5.100.11"
|
||||||
},
|
},
|
||||||
"funding": {
|
"funding": {
|
||||||
"type": "github",
|
"type": "github",
|
||||||
|
|
@ -3100,22 +3061,6 @@
|
||||||
"integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
|
"integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
|
||||||
"node_modules/yaml": {
|
|
||||||
"version": "2.9.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz",
|
|
||||||
"integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==",
|
|
||||||
"extraneous": true,
|
|
||||||
"license": "ISC",
|
|
||||||
"bin": {
|
|
||||||
"yaml": "bin.mjs"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 14.6"
|
|
||||||
},
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/sponsors/eemeli"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
"@mui/icons-material": "^7.3.1",
|
"@mui/icons-material": "^7.3.1",
|
||||||
"@mui/material": "^7.3.1",
|
"@mui/material": "^7.3.1",
|
||||||
"@reduxjs/toolkit": "^2.11.2",
|
"@reduxjs/toolkit": "^2.11.2",
|
||||||
"@tanstack/react-query": "^5.100.10",
|
"@tanstack/react-query": "^5.100.11",
|
||||||
"axios": "^1.16.0",
|
"axios": "^1.16.0",
|
||||||
"react": "^19.1.1",
|
"react": "^19.1.1",
|
||||||
"react-dom": "^19.1.1",
|
"react-dom": "^19.1.1",
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue