mirror of
https://github.com/Dvorinka/Primora.git
synced 2026-10-28 15:51:23 +00:00
feat: phase 6 production hardening + DB-to-DB transfer
Security:
- Graceful shutdown: http.Server + signal.NotifyContext, 10s drain,
retention sweeper cancelled before pool close
- Scoped API keys (migration 00006): ingest/read/write/admin enforced in
auth middleware; scope picker + badges in Settings UI; scopeless legacy
keys act as admin. Read-only keys now get 403 on ingest (verified live)
- nginx: baseline security headers on all responses, CSP scoped to SPA;
fixed add_header inheritance shadowing
- better-auth rate limit enabled unconditionally (was gated on NODE_ENV,
which the shipped .env sets to development)
- Mailpit moved to docker-compose.dev.yml; prod compose cannot expose it
- setup.sh generates PRIMORA_ENCRYPTION_KEY
Reliability/ops:
- Retention: per-project retention_{events,audit,webhook}_days
(migration 00007, 0=off) + hourly RetentionSweeper; edit UI in Projects
- GET /api/v1/metrics Prometheus exposition
- compose: healthchecks + restart policies everywhere, dragonfly pinned
to v2.0.0, depends_on service_healthy
- backup.sh/restore.sh (pg_dump -Fc + storage volume); restore verified
against scratch postgres+volume
- .dockerignore: exclude target dirs (build context was 4.7GB)
Correctness/testing:
- DBX transfer endpoint: POST /db-connections/:id/transfer — SQL or Redis
target, 1000-row bound, server-side creds (verified live pg→dragonfly)
- Pagination audit: ListComponents capped at 500 (auto-registers)
- CI: goose up/reset/up job + compose e2e-smoke job (smoke-e2e.sh
verified locally: signup, bootstrap, scoped keys, ingest, metrics)
- k6 load sanity script: 6650 reqs, ~217/s, 0 real failures, p95 221ms
(429s = configured rate limiter working)
Docs: DEPLOYMENT_GUIDE.md, PRODUCTION_READINESS.md; verify script
repaired (stale refs + set -e arithmetic abort). ROADMAP: phase 2 +
phase 6 marked complete.
Authored By: TDvorak <[email protected]>
Generated with [Devin](https://devin.ai)
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
1 parent
1b34dd43bb
commit
55edbf8b56
64 files changed
+2058
-165
No files matched your search
@@ -22,6 +22,12 @@ export const $ApiKey = {
|
||||
type: 'string',
|
||||
isRequired: true,
|
||||
},
|
||||
scopes: {
|
||||
type: 'array',
|
||||
contains: {
|
||||
type: 'Enum',
|
||||
},
|
||||
},
|
||||
last_used_at: {
|
||||
type: 'string',
|
||||
isNullable: true,
|
||||
|
||||
@@ -8,5 +8,11 @@ export const $CreateApiKeyRequest = {
|
||||
type: 'string',
|
||||
isRequired: true,
|
||||
},
|
||||
scopes: {
|
||||
type: 'array',
|
||||
contains: {
|
||||
type: 'Enum',
|
||||
},
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
@@ -21,5 +21,11 @@ export const $CreatedApiKey = {
|
||||
type: 'string',
|
||||
isRequired: true,
|
||||
},
|
||||
scopes: {
|
||||
type: 'array',
|
||||
contains: {
|
||||
type: 'Enum',
|
||||
},
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
@@ -0,0 +1,42 @@
|
||||
/* generated using openapi-typescript-codegen -- do not edit */
|
||||
/* istanbul ignore file */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
export const $DBTransferRequest = {
|
||||
properties: {
|
||||
target_connection_id: {
|
||||
type: 'string',
|
||||
isRequired: true,
|
||||
format: 'uuid',
|
||||
},
|
||||
query: {
|
||||
type: 'string',
|
||||
description: `SELECT run against the source connection.`,
|
||||
isRequired: true,
|
||||
},
|
||||
database: {
|
||||
type: 'string',
|
||||
},
|
||||
target_table: {
|
||||
type: 'string',
|
||||
description: `Destination table for SQL targets (schema-qualified allowed).`,
|
||||
},
|
||||
key_pattern: {
|
||||
type: 'string',
|
||||
description: `Redis targets: SET key template with {column} placeholders, e.g. "user:{id}".`,
|
||||
},
|
||||
value_column: {
|
||||
type: 'string',
|
||||
description: `Redis targets: column whose cell becomes the value. Omitted stores the whole row as JSON.`,
|
||||
},
|
||||
ttl_seconds: {
|
||||
type: 'number',
|
||||
description: `Optional EX TTL applied to each Redis SET.`,
|
||||
},
|
||||
limit: {
|
||||
type: 'number',
|
||||
maximum: 1000,
|
||||
minimum: 1,
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
@@ -0,0 +1,24 @@
|
||||
/* generated using openapi-typescript-codegen -- do not edit */
|
||||
/* istanbul ignore file */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
export const $DBTransferResponse = {
|
||||
properties: {
|
||||
transferred: {
|
||||
type: 'number',
|
||||
isRequired: true,
|
||||
},
|
||||
truncated: {
|
||||
type: 'boolean',
|
||||
isRequired: true,
|
||||
},
|
||||
source: {
|
||||
type: 'string',
|
||||
isRequired: true,
|
||||
},
|
||||
target: {
|
||||
type: 'string',
|
||||
isRequired: true,
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
@@ -30,5 +30,14 @@ export const $Project = {
|
||||
type: 'string',
|
||||
isNullable: true,
|
||||
},
|
||||
retention_events_days: {
|
||||
type: 'number',
|
||||
},
|
||||
retention_audit_days: {
|
||||
type: 'number',
|
||||
},
|
||||
retention_webhook_days: {
|
||||
type: 'number',
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
@@ -16,5 +16,20 @@ export const $UpdateProjectRequest = {
|
||||
type: 'string',
|
||||
isNullable: true,
|
||||
},
|
||||
retention_events_days: {
|
||||
type: 'number',
|
||||
description: `Telemetry event retention; 0 disables the sweep.`,
|
||||
maximum: 3650,
|
||||
},
|
||||
retention_audit_days: {
|
||||
type: 'number',
|
||||
description: `Audit log retention; 0 disables the sweep.`,
|
||||
maximum: 3650,
|
||||
},
|
||||
retention_webhook_days: {
|
||||
type: 'number',
|
||||
description: `Webhook delivery retention; 0 disables the sweep.`,
|
||||
maximum: 3650,
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
Reference in new issue
Block a user