Merge branch 'main' of ssh://git.trai-infra.comstack.de:9922/bob.molitor/frontend

This commit is contained in:
2025-10-31 11:19:03 +01:00
11 changed files with 1154 additions and 143 deletions
+58
View File
@@ -0,0 +1,58 @@
name: Build and Push Docker Image only on Conventional Commits
on:
push:
branches:
- main
jobs:
build:
runs-on: ubuntu-latest
env:
DOCKER_REGISTRY: git.trai-infra.comstack.de
DOCKER_IMAGE: marian.lippitz/tap-frontend
steps:
- name: Checkout Code
uses: actions/checkout@v3
- name: Validate Conventional Commit
id: validate
run: |
if git log -1 --pretty=%B | grep -Eq "^(feat|fix|docs|style|refactor|perf|test|chore|build|ci|revert)(\(.+\))?: .+"; then
echo "valid=true" >> $GITHUB_OUTPUT
else
echo "valid=false" >> $GITHUB_OUTPUT
fi
- name: Stop if not a Conventional Commit
if: ${{ steps.validate.outputs.valid == 'false' }}
run: |
echo "Latest commit is not a valid Conventional Commit. Skipping build."
exit 0
- name: Docker Login
env:
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
run: |
echo "$DOCKER_PASSWORD" | docker login $DOCKER_REGISTRY -u "$DOCKER_USERNAME" --password-stdin
- name: Build Docker Image
run: |
docker build -t "$DOCKER_REGISTRY/$DOCKER_IMAGE:latest" .
- name: Push Docker Image
run: |
docker push "$DOCKER_REGISTRY/$DOCKER_IMAGE:latest"
- name: Trigger Kubernetes Redeployment
uses: alpine/kubectl:1.34.1
env:
KUBECONFIG: ${{ secrets.KUBECONFIG }}
with:
args: >
patch deployment tap-frontend -n frontend
-p '{"spec":{"template":{"metadata":{"annotations":{"kubectl.kubernetes.io/restartedAt":"'"$(date -u +'%Y-%m-%dT%H:%M:%SZ')"'"}}}}}'
&& kubectl rollout status deployment tap-frontend -n frontend --timeout=120s
+12 -4
View File
@@ -24,9 +24,11 @@
# Provide php alias
RUN ln -sf /usr/bin/php82 /usr/bin/php
# Add custom PHP logging configuration
COPY deploy/php-fpm/zz-logging.ini /etc/php82/conf.d/
# Create user, dirs, and sockets
RUN addgroup -S web && adduser -S web -G web \
&& mkdir -p /run/nginx /run/php /var/www/html /var/log/supervisor
RUN mkdir -p /run/nginx /run/php /var/www/html /var/log/supervisor
# Configure PHP-FPM socket
RUN sed -i 's|^listen = 127\\.0\\.0\\.1:9000|listen = /run/php/php-fpm.sock|' /etc/php82/php-fpm.d/www.conf \
@@ -51,8 +53,14 @@
# Permissions for Laravel writable directories
RUN mkdir -p storage bootstrap/cache \
&& chown -R nginx:nginx storage bootstrap/cache \
&& chmod -R ug+rwX storage bootstrap/cache
&& chmod -R ug+rwX storage bootstrap/cache
# Create Nginx error log file with correct permissions
RUN mkdir -p /var/log/nginx \
&& touch /var/log/nginx/error.log \
&& chown -R nginx:nginx /var/log/nginx \
&& chown -R nginx:nginx /var/log/php82 \
&& chmod -R 664 /var/log/nginx/error.log
# Build frontend assets
RUN npm ci && npm run build
+77 -16
View File
@@ -1,27 +1,88 @@
#!/bin/sh
# Docker entrypoint for Nginx + PHP/Laravel
set -eu
SERVER_NAME="${SERVER_NAME:-$(hostname -f 2>/dev/null || hostname)}"
# -------- defaults (override via env) --------
: "${APP_DIR:=/var/www/html}"
: "${NGINX_TEMPLATE:=/etc/nginx/templates/default.conf.template}"
: "${NGINX_HTTPD_DIR:=/etc/nginx/http.d}"
: "${NGINX_CONF_OUT:=${NGINX_HTTPD_DIR}/default.conf}"
: "${HTPASSWD_PATH:=/etc/nginx/htpasswd}"
: "${BASIC_AUTH_REALM:=Restricted Content}"
: "${SUPERVISOR_CONF:=/etc/supervisor/supervisord.conf}"
log() { printf '[entrypoint] %s\n' "$*"; }
err() { printf '[entrypoint][ERROR] %s\n' "$*" >&2; }
die() { err "$*"; exit 1; }
need() { command -v "$1" >/dev/null 2>&1 || die "Missing required command: $1"; }
# required tools
need envsubst; need nginx; need php; need supervisord
# server name
SERVER_NAME="${SERVER_NAME:-$(hostname -f 2>/dev/null || hostname 2>/dev/null || echo localhost)}"
export SERVER_NAME
# Ensure dirs
mkdir -p /etc/nginx/http.d /etc/nginx/conf.d /run/nginx /run/php
# truthy helper
is_truthy() {
case "${1:-}" in 1|true|TRUE|yes|YES|on|ON) return 0;; *) return 1;; esac
}
# Render vhost ONLY into http.d so it's inside http{} context
if [ -f /etc/nginx/templates/default.conf.template ]; then
envsubst '$SERVER_NAME' < /etc/nginx/templates/default.conf.template > /etc/nginx/http.d/default.conf
# -------- basic auth directives --------
BASIC_AUTH_DIRECTIVES=""
if is_truthy "${ENABLE_BASIC_AUTH:-}"; then
log "Enabling basic authentication"
[ -f "$HTPASSWD_PATH" ] || die "htpasswd not found: $HTPASSWD_PATH"
BASIC_AUTH_DIRECTIVES=$(cat <<'EOF'
auth_basic "__REALM__";
auth_basic_user_file __HTPASSWD__;
EOF
)
# safe placeholder substitution
esc() { printf '%s' "$1" | sed 's/[&/\]/\\&/g'; }
BASIC_AUTH_DIRECTIVES=$(printf '%s' "$BASIC_AUTH_DIRECTIVES" \
| sed "s|__REALM__|$(esc "$BASIC_AUTH_REALM")|g" \
| sed "s|__HTPASSWD__|$(esc "$HTPASSWD_PATH")|g")
else
echo "[entrypoint] ERROR: missing /etc/nginx/templates/default.conf.template" >&2
exit 1
log "Basic authentication is disabled"
fi
export BASIC_AUTH_DIRECTIVES
# -------- render nginx config (atomic) --------
[ -f "$NGINX_TEMPLATE" ] || die "Missing template: $NGINX_TEMPLATE"
[ -d "$NGINX_HTTPD_DIR" ] || die "Missing nginx conf dir: $NGINX_HTTPD_DIR"
tmpconf="$(mktemp "${NGINX_CONF_OUT}.XXXXXX")"
envsubst '$SERVER_NAME,$BASIC_AUTH_DIRECTIVES' <"$NGINX_TEMPLATE" >"$tmpconf" \
|| die "envsubst failed"
mv -f "$tmpconf" "$NGINX_CONF_OUT"
log "Wrote nginx config -> $NGINX_CONF_OUT"
# final sanity test against active config tree
nginx -t || die "nginx config test failed after install"
# -------- Laravel cache clears (best-effort) --------
if [ -f "$APP_DIR/artisan" ]; then
log "Clearing Laravel caches"
( cd "$APP_DIR" && php artisan config:clear || log "config:clear failed (continuing)" )
( cd "$APP_DIR" && php artisan cache:clear || log "cache:clear failed (continuing)" )
else
log "No Laravel app at $APP_DIR; skipping artisan"
fi
echo "[entrypoint] Using SERVER_NAME=$SERVER_NAME"
nginx -t || { echo "[entrypoint] nginx config test failed" >&2; cat /var/log/nginx/error.log || true; exit 1; }
# -------- entrypoint behavior --------
# If the first arg is an option (starts with -), assume supervisord.
if [ "${1:-}" ] && [ "${1#-}" != "$1" ]; then
set -- /usr/bin/supervisord -n -c "$SUPERVISOR_CONF" "$@"
fi
php artisan config:clear
php artisan cache:clear
# If they provided a command (e.g., sh, bash, php -v), run it.
if [ "${1:-}" ] && [ "$1" != "/usr/bin/supervisord" ] && [ "$1" != "supervisord" ]; then
log "Executing custom command: $*"
exec "$@"
fi
envsubst '$SERVER_NAME' < /etc/nginx/templates/default.conf.template > /etc/nginx/http.d/default.conf
exec /usr/bin/supervisord -n -c /etc/supervisor/supervisord.conf
# Default: start supervisord in the foreground (PID 1)
log "Starting supervisord"
exec /usr/bin/supervisord -n -c "$SUPERVISOR_CONF"
+8 -7
View File
@@ -1,7 +1,9 @@
user nginx;
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log warn;
pid /run/nginx.pid;
# Send logs to container fds (works regardless of privileges)
error_log /proc/self/fd/2 warn;
pid /run/nginx/nginx.pid;
events {
worker_connections 1024;
@@ -15,14 +17,13 @@ http {
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /dev/stdout main;
error_log /dev/stderr warn;
access_log /proc/self/fd/1 main;
# (No extra error_log here; the top-level one above is enough)
sendfile on;
keepalive_timeout 65;
# Server Blöcke sind NICHT hier!
# Stattdessen in:
# Include vhosts (your Laravel server block lives in one of these)
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/http.d/*.conf;
}
+59 -12
View File
@@ -5,42 +5,89 @@ server {
root /var/www/html/public;
index index.php;
# Cache-Control für statische Dateien
location ~* \.(?:ico|css|js|gif|jpe?g|png|svg|woff2?|eot|ttf|otf|webp|avif)$ {
# -------------------------------------------------
# Redirect all HTTP requests to HTTPS (optional, remove if TLS is handled upstream)
# -------------------------------------------------
# return 301 https://$host$request_uri;
# -------------------------------------------------
# Livewire + Laravel routes (must come before static block)
# -------------------------------------------------
location ^~ /livewire/ {
try_files $uri /index.php?$query_string;
expires off;
add_header Cache-Control "no-store";
}
# -------------------------------------------------
# Static assets with long caching
# -------------------------------------------------
location ~* \.(?:ico|css|js|gif|jpe?g|png|svg|woff2?|eot|ttf|otf|webp|avif)(\?.*)?$ {
expires 7d;
add_header Cache-Control "public, max-age=604800, immutable";
try_files $uri =404;
access_log off;
}
# Haupt-Entry-Point (Laravel)
# -------------------------------------------------
# Main Laravel entry point
# -------------------------------------------------
location / {
try_files $uri $uri/ /index.php?$query_string;
${BASIC_AUTH_DIRECTIVES}
}
# PHP-FPM Verarbeitung
# -------------------------------------------------
# PHP-FPM configuration (matches your Alpine socket setup)
# -------------------------------------------------
location ~ \.php$ {
include fastcgi_params;
fastcgi_index index.php;
fastcgi_pass 127.0.0.1:9000;
fastcgi_pass 127.0.0.1:9000; # <-- switch from socket to TCP
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param DOCUMENT_ROOT $document_root;
fastcgi_read_timeout 60s;
fastcgi_buffering on;
fastcgi_buffers 16 16k;
fastcgi_buffer_size 32k;
# Forward the X-Forwarded-Proto header
fastcgi_param HTTPS $http_x_forwarded_proto;
# --- Proxy headers for HTTPS and load balancers ---
fastcgi_param HTTP_X_FORWARDED_PROTO $http_x_forwarded_proto;
fastcgi_param HTTP_X_FORWARDED_HOST $http_host;
fastcgi_param HTTP_X_FORWARDED_FOR $proxy_add_x_forwarded_for;
# --- Tell PHP it's HTTPS when forwarded ---
set $https_flag "";
if ($http_x_forwarded_proto = "https") { set $https_flag "on"; }
fastcgi_param HTTPS $https_flag;
}
# Maximale Client Upload-Größe
# -------------------------------------------------
# Upload limits
# -------------------------------------------------
client_max_body_size 64m;
# Sicherheits-Header
# -------------------------------------------------
# Security headers
# -------------------------------------------------
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# Gzip-Kompression
}
# -------------------------------------------------
# Gzip compression
# -------------------------------------------------
gzip on;
gzip_types
text/plain
text/css
application/json
application/javascript
text/xml
application/xml
application/xml+rss
image/svg+xml;
gzip_min_length 1000;
gzip_proxied any;
gzip_vary on;
}
-3
View File
@@ -1,3 +0,0 @@
listen = 127.0.0.1:9000
user = nginx
group = nginx
-12
View File
@@ -1,12 +0,0 @@
#!/bin/sh
set -eu
PORT="${APP_PORT:-8080}"
# 1) App Health
curl -fsS "http://127.0.0.1:${PORT}/healthz" >/dev/null
# 2) PHP-FPM Ping via Nginx
curl -fsS "http://127.0.0.1:${PORT}/fpm-ping" >/dev/null
exit 0
-29
View File
@@ -1,29 +0,0 @@
# keine 'user' Direktive im non-root Betrieb
worker_processes auto;
error_log /dev/stderr warn;
pid /tmp/nginx.pid;
events { worker_connections 1024; }
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /dev/stdout main;
sendfile on;
keepalive_timeout 65;
server_tokens off;
# Temp/Cache-Pfade, die 'nginx' gehören
client_body_temp_path /var/lib/nginx/tmp/client_body 1 2;
proxy_temp_path /var/lib/nginx/tmp/proxy 1 2;
fastcgi_temp_path /var/lib/nginx/tmp/fastcgi 1 2;
scgi_temp_path /var/lib/nginx/tmp/scgi 1 2;
uwsgi_temp_path /var/lib/nginx/tmp/uwsgi 1 2;
include /etc/nginx/conf.d/*.conf;
}
-60
View File
@@ -1,60 +0,0 @@
server {
listen 8080;
server_name localhost;
root /var/www/html/public;
index index.php index.html;
# Health checks
location = /healthz {
access_log off;
return 200 "ok\n";
}
# FPM ping/status (optional, secure in prod)
location = /fpm-ping {
access_log off;
include fastcgi_params;
fastcgi_pass 127.0.0.1:9000;
fastcgi_param SCRIPT_FILENAME $document_root/index.php;
fastcgi_param SCRIPT_NAME /ping;
fastcgi_param PATH_INFO /ping;
}
location = /fpm-status {
access_log off;
include fastcgi_params;
fastcgi_pass 127.0.0.1:9000;
fastcgi_param SCRIPT_FILENAME $document_root/index.php;
fastcgi_param SCRIPT_NAME /status;
fastcgi_param PATH_INFO /status;
}
# Main app
location / {
try_files $uri $uri/ /index.php?$query_string;
}
# PHP handling
location ~ \.php$ {
include fastcgi_params;
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_read_timeout 60s;
fastcgi_buffers 16 16k;
}
# Deny hidden files
location ~ /\.ht {
deny all;
}
# Cache static assets
location ~* \.(?:css|js|jpg|jpeg|gif|png|svg|ico|webp|woff2?)$ {
access_log off;
expires 7d;
add_header Cache-Control "public";
try_files $uri =404;
}
}
+940
View File
@@ -0,0 +1,940 @@
openapi: 3.1.0
info:
title: Trusted AI Analyst Data API
version: '1.0.0'
description: >
Data endpoints powering the analyst dashboard, portfolio-wide transaction review,
company search, and per-company transaction drill-down experiences.
servers:
- url: http://trusted_ai.test/api
description: Local development (composer run dev)
- url: https://api.trusted-ai.com
description: Production
tags:
- name: Dashboard
description: KPIs and alert overviews for the landing dashboard.
- name: Transactions
description: Portfolio-wide transaction review tooling.
- name: Companies
description: Company discovery and drill-down data.
components:
securitySchemes:
sessionCookie:
type: apiKey
in: cookie
name: laravel_session
description: Browser session issued after login; required for all endpoints documented here.
schemas:
PaginationMeta:
type: object
required:
- page
- perPage
- total
- lastPage
properties:
page:
type: integer
minimum: 1
example: 1
perPage:
type: integer
minimum: 1
example: 12
total:
type: integer
minimum: 0
example: 128
lastPage:
type: integer
minimum: 1
example: 11
CountAmountSummary:
type: object
required:
- count
- amount
properties:
count:
type: integer
minimum: 0
example: 42
amount:
type: number
format: double
example: 2750000.5
description: Monetary volume in EUR unless noted otherwise.
StatusOption:
type: object
required:
- value
- label
properties:
value:
type: string
example: true_positive
label:
type: string
example: Bestätigte Treffer
ChannelOption:
type: object
required:
- value
- label
properties:
value:
type: string
example: SEPA
label:
type: string
example: SEPA
RiskSegment:
type: object
required:
- label
- count
- volume
properties:
label:
type: string
example: Kritisch (≥80)
count:
type: integer
example: 17
volume:
type: number
format: double
example: 8900000.0
TopCompanyAlert:
type: object
required:
- companyId
- companyName
- ticker
- sector
- alerts
- alertVolume
- avgRiskScore
properties:
companyId:
type: integer
example: 24
companyName:
type: string
example: Allianz SE
ticker:
type: string
example: ALV
sector:
type: string
example: Insurance
alerts:
type: integer
example: 6
alertVolume:
type: number
format: double
example: 1450000.0
avgRiskScore:
type: integer
minimum: 0
maximum: 100
example: 82
DashboardOverview:
type: object
required:
- generatedAt
- activeAnalysts
- totalTransactions
- totalVolume
- alertsToday
- averageAlertsPerAnalyst
- precisionRate
- activeAlerts
- riskSegments
- topCompanies
- runbooksExecutedToday
properties:
generatedAt:
type: string
format: date-time
example: '2025-01-19T07:30:00Z'
activeAnalysts:
type: integer
example: 24
totalTransactions:
type: integer
example: 312
totalVolume:
type: number
format: double
example: 98765432.1
alertsToday:
type: integer
example: 56
averageAlertsPerAnalyst:
type: integer
example: 3
precisionRate:
type: integer
minimum: 0
maximum: 100
example: 84
activeAlerts:
type: object
required:
- count
- highRiskCount
properties:
count:
type: integer
example: 128
highRiskCount:
type: integer
example: 19
riskSegments:
type: array
items:
$ref: '#/components/schemas/RiskSegment'
topCompanies:
type: array
maxItems: 3
items:
$ref: '#/components/schemas/TopCompanyAlert'
runbooksExecutedToday:
type: integer
example: 41
TransactionStatus:
type: string
enum:
- true_positive
- false_positive
- cleared
CompanySummary:
type: object
required:
- id
- name
- legalName
- ticker
- sector
- country
- headquarters
- kycRiskLevel
- summary
properties:
id:
type: integer
example: 12
name:
type: string
example: Allianz
legalName:
type: string
example: Allianz SE
ticker:
type: string
example: ALV
sector:
type: string
example: Insurance
country:
type: string
example: Germany
headquarters:
type: string
example: Munich
kycRiskLevel:
type: string
example: high
summary:
type: string
example: Multinational insurance provider with EMEA focus.
CompanyDetail:
allOf:
- $ref: '#/components/schemas/CompanySummary'
- type: object
properties:
createdAt:
type: string
format: date-time
nullable: true
updatedAt:
type: string
format: date-time
nullable: true
TransactionPreview:
type: object
required:
- id
- company
- counterparty
- counterpartyCountry
- status
- statusLabel
- requiresReview
- riskScore
- amount
- currency
- reference
- channel
- executedAt
properties:
id:
type: integer
example: 512
company:
$ref: '#/components/schemas/CompanySummary'
counterparty:
type: string
example: Alpine Holdings Ltd.
counterpartyCountry:
type: string
example: Switzerland
status:
$ref: '#/components/schemas/TransactionStatus'
statusLabel:
type: string
example: Bestätigter Treffer
requiresReview:
type: boolean
example: true
riskScore:
type: integer
minimum: 0
maximum: 100
example: 87
amount:
type: number
format: double
example: 245000.75
currency:
type: string
example: EUR
reference:
type: string
example: PAY-2024-10-1942
channel:
type: string
example: SWIFT
executedAt:
type: string
format: date-time
example: '2025-01-18T09:14:00Z'
flaggedReason:
type: string
nullable: true
example: Counterparty on sanctions watchlist
TransactionSignal:
type: object
required:
- type
- value
properties:
type:
type: string
example: Adverse Media
value:
type: string
example: Enforcement action reported in 2024-11
weight:
type: number
format: double
nullable: true
example: 0.8
AlertSummary:
type: object
required:
- id
- counterparty
- executedAt
- channel
- status
- statusLabel
properties:
id:
type: integer
example: 911
counterparty:
type: string
example: Northbridge Trading Ltd.
executedAt:
type: string
format: date-time
example: '2025-01-18T14:52:00Z'
channel:
type: string
example: SWIFT
status:
$ref: '#/components/schemas/TransactionStatus'
statusLabel:
type: string
example: Bestätigter Treffer
riskScore:
type: integer
minimum: 0
maximum: 100
example: 92
TransactionHistoryItem:
type: object
required:
- id
- executedAt
- channel
- reference
- amount
- currency
- riskScore
properties:
id:
type: integer
example: 877
executedAt:
type: string
format: date-time
example: '2024-12-22T15:37:00Z'
channel:
type: string
example: SWIFT
reference:
type: string
example: PAY-2024-12-1187
amount:
type: number
format: double
example: 99000.0
currency:
type: string
example: EUR
riskScore:
type: integer
minimum: 0
maximum: 100
example: 74
RecommendedAction:
type: object
required:
- title
- description
properties:
title:
type: string
example: Verdachtsmeldung vorbereiten
description:
type: string
example: Erstellen Sie den Meldeentwurf für die FIU und sichern Sie Belege.
TransactionDetail:
allOf:
- $ref: '#/components/schemas/TransactionPreview'
- type: object
required:
- flaggedBy
- flaggedReason
- signals
properties:
flaggedBy:
type: string
example: Screening Engine
flaggedReason:
type: string
example: Counterparty matched to EU sanctions list
signals:
type: array
items:
$ref: '#/components/schemas/TransactionSignal'
TransactionCaseFile:
allOf:
- $ref: '#/components/schemas/TransactionDetail'
- type: object
properties:
recommendedActions:
type: array
items:
$ref: '#/components/schemas/RecommendedAction'
counterpartyHistory:
type: array
items:
$ref: '#/components/schemas/TransactionHistoryItem'
TransactionMetrics:
type: object
required:
- total
- byStatus
properties:
total:
$ref: '#/components/schemas/CountAmountSummary'
byStatus:
type: object
additionalProperties:
$ref: '#/components/schemas/CountAmountSummary'
description: Breakdown keyed by transaction status.
CompanyTransactionMetrics:
type: object
required:
- totalCount
- totalVolume
- openAlerts
- highRiskShare
- last30Days
- byStatus
properties:
totalCount:
type: integer
example: 96
totalVolume:
type: number
format: double
example: 7200000.0
openAlerts:
$ref: '#/components/schemas/CountAmountSummary'
highRiskShare:
type: integer
minimum: 0
maximum: 100
example: 28
last30Days:
$ref: '#/components/schemas/CountAmountSummary'
byStatus:
type: object
additionalProperties:
$ref: '#/components/schemas/CountAmountSummary'
TransactionCollectionResponse:
type: object
required:
- filters
- metrics
- data
- pagination
properties:
filters:
type: object
properties:
statusOptions:
type: array
items:
$ref: '#/components/schemas/StatusOption'
perPage:
type: integer
example: 12
metrics:
$ref: '#/components/schemas/TransactionMetrics'
data:
type: array
items:
$ref: '#/components/schemas/TransactionPreview'
pagination:
$ref: '#/components/schemas/PaginationMeta'
selectedTransactionId:
type: integer
nullable: true
example: 512
selectedTransaction:
allOf:
- $ref: '#/components/schemas/TransactionDetail'
nullable: true
CompanySearchOverview:
type: object
required:
- totalCompanies
- openAlerts
- openAlertVolume
- averageRiskScore
- watchlistHits
- automationShare
properties:
totalCompanies:
type: integer
example: 40
openAlerts:
type: integer
example: 112
openAlertVolume:
type: number
format: double
example: 5640000.0
averageRiskScore:
type: integer
minimum: 0
maximum: 100
example: 71
watchlistHits:
type: integer
example: 9
automationShare:
type: integer
minimum: 0
maximum: 100
example: 82
CompanyAlertPreview:
type: object
required:
- transactionId
- counterparty
- executedAt
- channel
- flaggedReason
properties:
transactionId:
type: integer
example: 731
counterparty:
type: string
example: Baltic Commodities LLC
executedAt:
type: string
format: date-time
example: '2025-01-17T11:48:00Z'
channel:
type: string
example: SWIFT
flaggedReason:
type: string
example: Pattern matches sanctions typology
riskScore:
type: integer
minimum: 0
maximum: 100
example: 88
CompanySearchResult:
allOf:
- $ref: '#/components/schemas/CompanySummary'
- type: object
required:
- alertCount
- alertVolume
- alertRiskScore
- latestAlert
properties:
alertCount:
type: integer
example: 7
alertVolume:
type: number
format: double
example: 980000.0
alertRiskScore:
type: integer
minimum: 0
maximum: 100
example: 79
latestAlert:
allOf:
- $ref: '#/components/schemas/CompanyAlertPreview'
CompanySearchResponse:
type: object
required:
- overview
- results
- meta
properties:
overview:
$ref: '#/components/schemas/CompanySearchOverview'
results:
type: array
items:
$ref: '#/components/schemas/CompanySearchResult'
meta:
type: object
required:
- query
- resultCount
- limit
properties:
query:
type: string
nullable: true
example: Allianz
resultCount:
type: integer
example: 6
limit:
type: integer
example: 6
CompanyTransactionsResponse:
type: object
required:
- company
- filters
- metrics
- data
- pagination
properties:
company:
$ref: '#/components/schemas/CompanyDetail'
filters:
type: object
properties:
statusOptions:
type: array
items:
$ref: '#/components/schemas/StatusOption'
channelOptions:
type: array
items:
$ref: '#/components/schemas/ChannelOption'
metrics:
$ref: '#/components/schemas/CompanyTransactionMetrics'
data:
type: array
items:
$ref: '#/components/schemas/TransactionPreview'
pagination:
$ref: '#/components/schemas/PaginationMeta'
selectedTransactionId:
type: integer
nullable: true
example: 877
selectedTransaction:
allOf:
- $ref: '#/components/schemas/TransactionCaseFile'
nullable: true
recentAlerts:
type: array
items:
$ref: '#/components/schemas/AlertSummary'
paths:
/dashboard/overview:
get:
tags:
- Dashboard
operationId: getDashboardOverview
summary: Retrieve dashboard overview metrics
description: >
Aggregated KPIs and alert slices needed for the dashboard hero cards and side panels.
security:
- sessionCookie: []
responses:
'200':
description: Overview data ready for dashboard rendering.
content:
application/json:
schema:
$ref: '#/components/schemas/DashboardOverview'
'401':
description: Session invalid or expired.
/transactions:
get:
tags:
- Transactions
operationId: listTransactions
summary: List transactions with global filters
description: >
Returns the paginated transaction stream together with per-status metrics and default selection,
mirroring the Livewire transaction review experience.
security:
- sessionCookie: []
parameters:
- name: search
in: query
schema:
type: string
description: Match against reference, counterparty, or company identifiers.
- name: status
in: query
schema:
type: string
enum:
- all
- true_positive
- false_positive
- cleared
default: all
description: Filter by review status; `all` keeps every status.
- name: page
in: query
schema:
type: integer
minimum: 1
default: 1
- name: perPage
in: query
schema:
type: integer
minimum: 1
maximum: 100
default: 12
responses:
'200':
description: Paginated transaction set with metrics.
content:
application/json:
schema:
$ref: '#/components/schemas/TransactionCollectionResponse'
'401':
description: Session invalid or expired.
/transactions/{transactionId}:
get:
tags:
- Transactions
operationId: getTransactionCaseFile
summary: Fetch a single transaction case file
description: >
Detailed transaction record used when an analyst expands a case in the transaction review workspace.
security:
- sessionCookie: []
parameters:
- name: transactionId
in: path
required: true
schema:
type: integer
responses:
'200':
description: Transaction detail with detection signals.
content:
application/json:
schema:
$ref: '#/components/schemas/TransactionDetail'
'401':
description: Session invalid or expired.
'404':
description: Transaction not found.
/companies/search:
get:
tags:
- Companies
operationId: searchCompanies
summary: Search companies and retrieve screening overview
description: >
Provides the metrics and top results required to populate the company search tiles.
security:
- sessionCookie: []
parameters:
- name: q
in: query
schema:
type: string
description: Free-text query across name, legal name, ticker, or sector.
- name: limit
in: query
schema:
type: integer
minimum: 1
maximum: 25
default: 6
responses:
'200':
description: Matching companies with alert context.
content:
application/json:
schema:
$ref: '#/components/schemas/CompanySearchResponse'
'401':
description: Session invalid or expired.
/companies/{companyId}:
get:
tags:
- Companies
operationId: getCompany
summary: Retrieve a company profile
description: >
Returns core firmographics and KYC risk tier needed for per-company transaction pages.
security:
- sessionCookie: []
parameters:
- name: companyId
in: path
required: true
schema:
type: integer
responses:
'200':
description: Company detail.
content:
application/json:
schema:
$ref: '#/components/schemas/CompanyDetail'
'401':
description: Session invalid or expired.
'404':
description: Company not found.
/companies/{companyId}/transactions:
get:
tags:
- Companies
operationId: listCompanyTransactions
summary: List transactions for a specific company
description: >
Supplies per-company metrics, channel/status filters, paginated cases, and the current selection
for the company drill-down view.
security:
- sessionCookie: []
parameters:
- name: companyId
in: path
required: true
schema:
type: integer
- name: status
in: query
schema:
type: string
enum:
- all
- true_positive
- false_positive
- cleared
default: all
- name: channel
in: query
schema:
type: string
default: all
description: Channel label returned in channelOptions; use `all` for no filtering.
- name: page
in: query
schema:
type: integer
minimum: 1
default: 1
- name: perPage
in: query
schema:
type: integer
minimum: 1
maximum: 100
default: 10
responses:
'200':
description: Company-specific transaction data with metrics.
content:
application/json:
schema:
$ref: '#/components/schemas/CompanyTransactionsResponse'
'401':
description: Session invalid or expired.
'404':
description: Company not found or no transactions.
/companies/{companyId}/transactions/{transactionId}:
get:
tags:
- Companies
operationId: getCompanyTransactionCaseFile
summary: Fetch a company transaction case file with history
description: >
Returns the selected transaction enriched with recommended actions and counterparty history
used in the right-hand panel of the company drill-down.
security:
- sessionCookie: []
parameters:
- name: companyId
in: path
required: true
schema:
type: integer
- name: transactionId
in: path
required: true
schema:
type: integer
responses:
'200':
description: Transaction case file for the company context.
content:
application/json:
schema:
$ref: '#/components/schemas/TransactionCaseFile'
'401':
description: Session invalid or expired.
'404':
description: Transaction not found for the given company.