fix: align backend APIs and upload flow
This commit is contained in:
@@ -0,0 +1,39 @@
|
||||
param([switch]$Docker)
|
||||
$ErrorActionPreference = 'Stop'
|
||||
& (Join-Path $PSScriptRoot 'Initialize-Local.ps1')
|
||||
$secrets = @{}
|
||||
Get-Content -LiteralPath (Join-Path $PSScriptRoot '.env.local') | ForEach-Object { if ($_ -match '^([^#=]+)=(.*)$') { $secrets[$Matches[1]] = $Matches[2] } }
|
||||
$serviceMap = @{ admin=@('Admin.WebApi','Admin.WebApi.dll',5180); user=@('User.WebApi','IdentityService.WebApi.dll',5181); contact=@('ContactService.WebApi','ContactService.WebApi.dll',5182); group=@('GroupService.WebApi','GroupService.WebApi.dll',5183); message=@('MessageService.WebApi','MessageService.WebApi.dll',5184); file=@('FileService.WebApi','FileService.WebApi.dll',5185); connector=@('ConnectorService','ConnectorService.dll',5186) }
|
||||
$hosts = @{}
|
||||
foreach ($name in $serviceMap.Keys) { $hosts[$name] = if ($Docker) { "http://$($name):8080" } else { "http://127.0.0.1:$($serviceMap[$name][2])" } }
|
||||
$dbHost = if ($Docker) { 'mysql;Port=3306' } else { '127.0.0.1;Port=13306' }
|
||||
$redisAddress = if ($Docker) { 'redis:6379' } else { '127.0.0.1:16379' }
|
||||
$rabbitHost = if ($Docker) { 'rabbitmq' } else { '127.0.0.1' }
|
||||
$rabbitPort = if ($Docker) { 5672 } else { 15672 }
|
||||
$consulUrl = if ($Docker) { 'http://consul:8500' } else { 'http://127.0.0.1:18500' }
|
||||
$storageRoot = if ($Docker) { '/data/files' } else { Join-Path $PSScriptRoot 'data/files' }
|
||||
$keysRoot = if ($Docker) { '/data/keyring' } else { Join-Path $PSScriptRoot 'data/keyring' }
|
||||
$cert = [Security.Cryptography.X509Certificates.X509Certificate2]::CreateFromPem([IO.File]::ReadAllText((Join-Path $PSScriptRoot 'data/certs/smtp.crt')))
|
||||
$smtpPin = $cert.GetCertHashString([Security.Cryptography.HashAlgorithmName]::SHA256)
|
||||
$cert.Dispose()
|
||||
$connection = "Server=$dbHost;Database=im_local;User=im_local;Password=$($secrets.MYSQL_PASSWORD);Allow User Variables=true"
|
||||
$config = @{
|
||||
ConnectionStrings = @{ DefaultConnection=$connection; Admin=$connection; Redis=$redisAddress }
|
||||
Jwt = @{ Key=$secrets.JWT_KEY; Issuer='IM.Local'; Audience='IM.Client'; AccessTokenMinutes=30; RefreshTokenDays=7 }
|
||||
RabbitMQOptions = @{ Host=$rabbitHost; Port=$rabbitPort; Username='im_local'; Password=$secrets.RABBITMQ_PASSWORD; QuequeName='im-local' }
|
||||
Cors = @{ Origins=@('http://127.0.0.1:5178','http://localhost:5173','http://127.0.0.1:5173','http://localhost:5174') }
|
||||
GrpcConfigs = @{
|
||||
IdentityServiceUrl = $(if ($Docker) {'http://user:8081'} else {'http://127.0.0.1:5281'})
|
||||
ContactServiceUrl = $(if ($Docker) {'http://contact:8081'} else {'http://127.0.0.1:5282'})
|
||||
MessageServiceUrl = $(if ($Docker) {'http://message:8081'} else {'http://127.0.0.1:5284'})
|
||||
}
|
||||
InternalApiKey = $secrets.MANAGEMENT_INTERNAL_KEY
|
||||
InternalServices = @{ GroupServiceBaseUrl=$hosts.group }
|
||||
Management = @{ Enabled=$true; InternalKey=$secrets.MANAGEMENT_INTERNAL_KEY; CredentialKey=$secrets.CREDENTIAL_KEY; KeyRingPath=$keysRoot; Services=$hosts; AdminPublicUrl='http://127.0.0.1:5178'; AllowedInfrastructureHosts=@('localhost','127.0.0.1','minio','smtp'); AllowedStorageRoots=@($storageRoot); RabbitHost=$rabbitHost; RabbitPort=$rabbitPort; ConsulUrl=$consulUrl; DevelopmentSmtpCertificateSha256=$smtpPin }
|
||||
StorageOptions = @{ DefaultProviderCode='Local'; Providers=@{ Local=@{ ProviderCode='Local'; ProviderType=1; Enabled=$true; Bucket='private'; PublicBucket='public'; Region='local'; LocalRootPath=$storageRoot; LocalUploadApiBaseUrl=$hosts.file; PublicBaseUrl=$hosts.file; MaxObjectSizeBytes=1073741824; MinPartSizeBytes=5242880; DefaultPartSizeBytes=5242880; MaxPartCount=10000 } } }
|
||||
}
|
||||
$json = $config | ConvertTo-Json -Depth 12
|
||||
Invoke-RestMethod -Uri 'http://127.0.0.1:18500/v1/kv/IM/Development/appsettings.json' -Method Put -ContentType 'application/json' -Body ([Text.Encoding]::UTF8.GetBytes($json)) | Out-Null
|
||||
$path = Join-Path $PSScriptRoot 'data/startup.local.json'
|
||||
[IO.File]::WriteAllText($path,$json)
|
||||
Write-Output 'Local startup configuration was written to the isolated Consul instance.'
|
||||
@@ -0,0 +1,31 @@
|
||||
# Creates only local development secrets and a SMTP test certificate. No default administrator password.
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$root = $PSScriptRoot
|
||||
$envPath = Join-Path $root '.env.local'
|
||||
if (!(Test-Path -LiteralPath $envPath)) {
|
||||
function New-Secret { [Convert]::ToHexString([Security.Cryptography.RandomNumberGenerator]::GetBytes(32)) }
|
||||
$entries = @(
|
||||
('MYSQL_ROOT_PASSWORD=' + (New-Secret))
|
||||
('MYSQL_PASSWORD=' + (New-Secret))
|
||||
('RABBITMQ_PASSWORD=' + (New-Secret))
|
||||
('S3_PASSWORD=' + (New-Secret))
|
||||
('MANAGEMENT_INTERNAL_KEY=' + (New-Secret))
|
||||
('JWT_KEY=' + (New-Secret))
|
||||
('CREDENTIAL_KEY=' + [Convert]::ToBase64String([Security.Cryptography.RandomNumberGenerator]::GetBytes(32)))
|
||||
)
|
||||
[IO.File]::WriteAllLines($envPath, $entries)
|
||||
}
|
||||
$certRoot = Join-Path $root 'data/certs'
|
||||
New-Item -ItemType Directory -Force -Path $certRoot | Out-Null
|
||||
if (!(Test-Path -LiteralPath (Join-Path $certRoot 'smtp.crt'))) {
|
||||
$rsa = [Security.Cryptography.RSA]::Create(2048)
|
||||
$request = [Security.Cryptography.X509Certificates.CertificateRequest]::new('CN=localhost', $rsa, [Security.Cryptography.HashAlgorithmName]::SHA256, [Security.Cryptography.RSASignaturePadding]::Pkcs1)
|
||||
$san = [Security.Cryptography.X509Certificates.SubjectAlternativeNameBuilder]::new()
|
||||
$san.AddDnsName('localhost'); $san.AddDnsName('smtp'); $san.AddIpAddress([Net.IPAddress]::Loopback)
|
||||
$request.CertificateExtensions.Add($san.Build())
|
||||
$cert = $request.CreateSelfSigned([DateTimeOffset]::UtcNow.AddMinutes(-5), [DateTimeOffset]::UtcNow.AddMonths(6))
|
||||
[IO.File]::WriteAllText((Join-Path $certRoot 'smtp.crt'), $cert.ExportCertificatePem())
|
||||
[IO.File]::WriteAllText((Join-Path $certRoot 'smtp.key'), $rsa.ExportPkcs8PrivateKeyPem())
|
||||
$rsa.Dispose(); $cert.Dispose()
|
||||
}
|
||||
Write-Output 'Local dependency secrets and SMTP certificate are ready. No administrator account was created.'
|
||||
@@ -0,0 +1,43 @@
|
||||
param([switch]$Migrate, [switch]$InitializeAdmin, [switch]$ResetAdmin, [switch]$Start)
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$repo = Split-Path $PSScriptRoot
|
||||
$configPath = Join-Path $PSScriptRoot 'data/startup.local.json'
|
||||
if (!(Test-Path -LiteralPath $configPath)) { throw 'Run Configure-Local.ps1 first.' }
|
||||
$config = Get-Content -LiteralPath $configPath -Raw | ConvertFrom-Json -AsHashtable
|
||||
function Set-ConfigEnvironment($node, $prefix = '') {
|
||||
foreach ($key in $node.Keys) {
|
||||
$name = if ($prefix) { $prefix + '__' + $key } else { $key }
|
||||
$value = $node[$key]
|
||||
if ($value -is [System.Collections.IDictionary]) { Set-ConfigEnvironment $value $name }
|
||||
elseif ($value -is [array]) { for($i=0;$i -lt $value.Count;$i++) { [Environment]::SetEnvironmentVariable($name+'__'+$i,[string]$value[$i],'Process') } }
|
||||
else { [Environment]::SetEnvironmentVariable($name,[string]$value,'Process') }
|
||||
}
|
||||
}
|
||||
Set-ConfigEnvironment $config
|
||||
$env:ASPNETCORE_ENVIRONMENT = 'Development'
|
||||
$env:CONSUL_URL = 'http://127.0.0.1:18500'
|
||||
$services = @(@('admin','Admin.WebApi','Admin.WebApi.dll',5180),@('user','User.WebApi','IdentityService.WebApi.dll',5181),@('contact','ContactService.WebApi','ContactService.WebApi.dll',5182),@('group','GroupService.WebApi','GroupService.WebApi.dll',5183),@('message','MessageService.WebApi','MessageService.WebApi.dll',5184),@('file','FileService.WebApi','FileService.WebApi.dll',5185),@('connector','ConnectorService','ConnectorService.dll',5186))
|
||||
$logs = Join-Path $PSScriptRoot 'data/logs'
|
||||
New-Item -ItemType Directory -Force -Path $logs | Out-Null
|
||||
foreach ($service in $services) {
|
||||
$name,$project,$dll,$port = $service
|
||||
$env:Management__ServiceName = $name
|
||||
$env:Kestrel__Endpoints__Http__Url = "http://127.0.0.1:$port"
|
||||
$env:Kestrel__Endpoints__Http__Protocols = 'Http1'
|
||||
$env:Kestrel__Endpoints__Grpc__Url = 'http://127.0.0.1:' + ($port+100)
|
||||
$env:Kestrel__Endpoints__Grpc__Protocols = 'Http2'
|
||||
$binary = Join-Path $repo "$project/bin/Debug/net8.0/$dll"
|
||||
if (!(Test-Path -LiteralPath $binary)) { throw "Build $project before starting." }
|
||||
if ($Migrate -and $name -ne 'connector') { & dotnet $binary --migrate; if ($LASTEXITCODE -ne 0) { throw "Migration failed: $name" } }
|
||||
if ($name -eq 'admin' -and ($InitializeAdmin -or $ResetAdmin)) {
|
||||
if (!$env:IM_ADMIN_ACCOUNT -or !$env:IM_ADMIN_PASSWORD) { throw 'Set IM_ADMIN_ACCOUNT and IM_ADMIN_PASSWORD in this process first; no default credentials are created.' }
|
||||
$action = if ($InitializeAdmin) { '--init-admin' } else { '--reset-admin' }
|
||||
& dotnet $binary $action; if ($LASTEXITCODE -ne 0) { throw 'Administrator initialization failed.' }
|
||||
}
|
||||
if ($Start) {
|
||||
if (Get-NetTCPConnection -LocalPort $port -State Listen -ErrorAction SilentlyContinue) { throw "Port $port is already in use; refusing to start a duplicate $name service." }
|
||||
$process = Start-Process -FilePath 'dotnet' -ArgumentList @('"'+$binary+'"') -WorkingDirectory (Split-Path $binary) -WindowStyle Hidden -PassThru -RedirectStandardOutput (Join-Path $logs "$name.out.log") -RedirectStandardError (Join-Path $logs "$name.err.log")
|
||||
[IO.File]::WriteAllText((Join-Path $logs "$name.pid"),[string]$process.Id)
|
||||
Write-Output "$name started on port $port (PID $($process.Id))."
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
name: im-admin-local
|
||||
services:
|
||||
mysql:
|
||||
image: mysql:8.0
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:?Run Initialize-Local.ps1 first}
|
||||
MYSQL_DATABASE: im_local
|
||||
MYSQL_USER: im_local
|
||||
MYSQL_PASSWORD: ${MYSQL_PASSWORD:?Run Initialize-Local.ps1 first}
|
||||
ports: ["127.0.0.1:13306:3306"]
|
||||
volumes: ["mysql:/var/lib/mysql"]
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "MYSQL_PWD=$$MYSQL_PASSWORD mysql -u im_local -e 'SELECT 1' im_local"]
|
||||
interval: 3s
|
||||
timeout: 3s
|
||||
retries: 40
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
ports: ["127.0.0.1:16379:6379"]
|
||||
command: redis-server --appendonly yes
|
||||
volumes: ["redis:/data"]
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "ping"]
|
||||
interval: 3s
|
||||
timeout: 3s
|
||||
retries: 20
|
||||
rabbitmq:
|
||||
image: rabbitmq:3-management
|
||||
environment:
|
||||
RABBITMQ_DEFAULT_USER: im_local
|
||||
RABBITMQ_DEFAULT_PASS: ${RABBITMQ_PASSWORD:?Run Initialize-Local.ps1 first}
|
||||
ports: ["127.0.0.1:15672:5672", "127.0.0.1:15674:15672"]
|
||||
healthcheck:
|
||||
test: ["CMD", "rabbitmq-diagnostics", "-q", "ping"]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 30
|
||||
consul:
|
||||
image: hashicorp/consul:1.20
|
||||
command: agent -dev -client=0.0.0.0
|
||||
ports: ["127.0.0.1:18500:8500"]
|
||||
minio:
|
||||
image: minio/minio:RELEASE.2025-04-22T22-12-26Z
|
||||
command: server /data --console-address :9001
|
||||
environment:
|
||||
MINIO_ROOT_USER: im_local
|
||||
MINIO_ROOT_PASSWORD: ${S3_PASSWORD:?Run Initialize-Local.ps1 first}
|
||||
ports: ["127.0.0.1:19000:9000", "127.0.0.1:19001:9001"]
|
||||
volumes: ["minio:/data"]
|
||||
smtp:
|
||||
image: axllent/mailpit:v1.27
|
||||
environment:
|
||||
MP_SMTP_TLS_CERT: /certs/smtp.crt
|
||||
MP_SMTP_TLS_KEY: /certs/smtp.key
|
||||
MP_SMTP_REQUIRE_STARTTLS: "true"
|
||||
ports: ["127.0.0.1:11025:1025", "127.0.0.1:18025:8025"]
|
||||
volumes: ["./data/certs:/certs:ro"]
|
||||
volumes:
|
||||
mysql:
|
||||
redis:
|
||||
minio:
|
||||
Reference in New Issue
Block a user