Some checks failed
Deploy SOLUTION_ERP / build-deploy (push) Failing after 1m40s
Foundation file-storage:
- IFileStorage interface (Application) — SaveAsync/OpenReadAsync/
DeleteAsync/Exists. Future swap cho S3/Azure Blob không đổi caller.
- LocalFileStorage (Infrastructure) — resolve Uploads:RootPath từ
config, path-traversal guard (resolved full path phải stay in root),
tự tạo directory khi save.
- DI: singleton (stateless).
- Config: dev "uploads", prod "C:\inetpub\solution-erp\uploads".
CQRS:
- UploadContractAttachmentCommand: validate size <=20MB + MIME whitelist
(pdf, doc/docx, xls/xlsx, png/jpg/jpeg/webp). Sanitize filename
(strip path components + invalid FS chars + leading dots). Storage
path: contracts/{contractId}/{attId}_{safeFileName}.
- DownloadContractAttachmentQuery: trả Stream + FileName + ContentType.
- DeleteContractAttachmentCommand: best-effort file delete sau DB remove
(orphan cleanup job có thể sweep sau).
Api:
- POST /api/contracts/{id}/attachments — multipart/form-data, field
'file' + form fields 'purpose' + 'note'. RequestSizeLimit 25MB
(validator enforces 20MB).
- GET /api/contracts/{id}/attachments/{attId}/download — File() stream.
- DELETE /api/contracts/{id}/attachments/{attId}.
FE ContractAttachmentsSection (both apps, identical):
- Drag-drop zone với dragging highlight (brand-500 border + brand-50 bg)
- Purpose selector (DraftExport / ScannedSigned / SealedCopy / Other)
- List có icon per MIME (FileText/Image/File), filename, metadata
(purpose · size · createdAt), download button (fetch blob + trigger
browser save với auth header), delete button (confirm dialog)
- Empty state hint về use-case ("bản scan HĐ đã ký ở phase In ký…")
Integrated vào cả 2 ContractDetailPage — ngay dưới phần comments,
trước sidebar lịch sử duyệt.
Unblock E2E workflow: users giờ có thể upload bản scan ký (DangInKy),
scan đóng dấu (DangDongDau) — phase transitions có bằng chứng thật.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
34 lines
832 B
JSON
34 lines
832 B
JSON
{
|
|
"ConnectionStrings": {
|
|
"Default": "Server=(localdb)\\MSSQLLocalDB;Database=SolutionErp;Trusted_Connection=True;MultipleActiveResultSets=true;TrustServerCertificate=true"
|
|
},
|
|
"Jwt": {
|
|
"Issuer": "SolutionErp.Api",
|
|
"Audience": "SolutionErp.Client",
|
|
"Secret": "CHANGE_ME_minimum_32_chars_production_secret_here_please",
|
|
"AccessTokenExpiryMinutes": 60,
|
|
"RefreshTokenExpiryDays": 7
|
|
},
|
|
"Serilog": {
|
|
"MinimumLevel": {
|
|
"Default": "Information",
|
|
"Override": {
|
|
"Microsoft": "Warning",
|
|
"Microsoft.EntityFrameworkCore": "Warning",
|
|
"System": "Warning"
|
|
}
|
|
},
|
|
"WriteTo": [
|
|
{ "Name": "Console" }
|
|
]
|
|
},
|
|
"AllowedHosts": "*",
|
|
"RateLimit": {
|
|
"AuthLoginPerMinute": 5,
|
|
"GlobalPerMinute": 300
|
|
},
|
|
"Uploads": {
|
|
"RootPath": "uploads"
|
|
}
|
|
}
|