Підготовка контенту платформи Ring
Підготовка контенту платформи Ring
Підготовка контенту платформи Ring
Concepts, value, and typical clone scenarios — less code.
Concepts, value, and typical clone scenarios — less code.
Concepts, value, and typical clone scenarios — less code.
k8s-postgres-fcm is the recommended backend mode for production and local development. PostgreSQL handles all application data through one shared adapter pool; Firebase Admin SDK is initialized exclusively for FCM push and Apple Push. Use the Founder / Developer tabs in the docs sidebar to filter this page.
TUNNEL_HUB_MODE=k8s-postgres (or in-memory hub for local development).| Scenario | Recommended mode |
|---|---|
| Self-hosted production on k3s | k8s-postgres-fcm |
| Local development with PostgreSQL | k8s-postgres-fcm |
| Cloud PostgreSQL (no K8s) | supabase-fcm |
| Rapid prototyping / Vercel Edge | firebase-full |
| Push notifications needed | any mode — FCM is optional in all three |
getAdminDb() returns a mock that never connects.k8s-postgres-fcm is the recommended backend mode for production and local development. PostgreSQL handles all application data through one shared adapter pool; Firebase Admin SDK is initialized exclusively for FCM push and Apple Push. Use the Founder / Developer tabs in the docs sidebar to filter this page.
TUNNEL_HUB_MODE=k8s-postgres (or in-memory hub for local development).| Scenario | Recommended mode |
|---|---|
| Self-hosted production on k3s | k8s-postgres-fcm |
| Local development with PostgreSQL | k8s-postgres-fcm |
| Cloud PostgreSQL (no K8s) | supabase-fcm |
| Rapid prototyping / Vercel Edge | firebase-full |
| Push notifications needed | any mode — FCM is optional in all three |
getAdminDb() returns a mock that never connects.k8s-postgres-fcm is the recommended backend mode for production and local development. PostgreSQL handles all application data through one shared adapter pool; Firebase Admin SDK is initialized exclusively for FCM push and Apple Push. Use the Founder / Developer tabs in the docs sidebar to filter this page.
TUNNEL_HUB_MODE=k8s-postgres (or in-memory hub for local development).| Scenario | Recommended mode |
|---|---|
| Self-hosted production on k3s | k8s-postgres-fcm |
| Local development with PostgreSQL | k8s-postgres-fcm |
| Cloud PostgreSQL (no K8s) | supabase-fcm |
| Rapid prototyping / Vercel Edge | firebase-full |
| Push notifications needed | any mode — FCM is optional in all three |
getAdminDb() returns a mock that never connects.PostgreSQLAdapter.getPgPool() |
Internal; exposed via getSharedPgPool() |
A 2026-07-07 audit eliminated three rogue pg.Pool instances in feature modules (platform-settings-service.ts, ring-token-oracle.ts, geolocation-service.ts). Those modules now route through db() or getSharedPgPool(). The validate-provider-ssot.sh gate allows new Pool( only under lib/database/.
| Variable | Required | Default | Description |
|---|---|---|---|
DB_BACKEND_MODE | Yes | — | Must be k8s-postgres-fcm |
DB_HOST | Yes | localhost | PostgreSQL hostname |
DB_PORT | No | 5432 | PostgreSQL port |
DB_NAME | Yes | ring_platform | Database name |
DB_USER | Yes | ring_user | Database user |
DB_PASSWORD | Yes | ring_dev_password | Database password |
DB_POOL_SIZE | No | 20 | Connection pool max size |
DB_TIMEOUT | No | 30000 | Connection timeout (ms) |
DB_RETRIES | No | 3 | Connection retry count |
DB_SSL | No | false | Enable TLS for PostgreSQL |
DB_ENABLE_POSTGIS | No | false | Runtime flag; PostGIS extensions ship in data/schema.sql |
Most tables store business fields in a data JSONB column with top-level id, created_at, updated_at. Fully normalized tables (e.g. vendor_applications, vendor_profiles) list all columns in PostgreSQLAdapter.fieldMappings so the query builder uses direct column references.
Exception — platform_settings hybrid table:
PostgreSQLAdapter splits writes: public config into data, API keys into secrets, actor into updated_by. Consumers use db().readDoc / createDoc / updateDoc:
features/admin/platform-settings/platform-settings-service.ts — AI, branding, matcher namespacesfeatures/wallet/services/ring-token-oracle.ts — web3 namespace oracle ratesPostGIS extensions are created in data/schema.sql (CREATE EXTENSION IF NOT EXISTS postgis). Spatial queries use raw SQL because the doc-model cannot express GEOGRAPHY functions.
lib/geolocation/geolocation-service.ts calls getSharedPgPool() — not a private Pool or direct DB_HOST / DB_PORT env reads:
Use cases: store delivery radius, entity locations, opportunity venues, map-heavy clones (pet-friendly places, real estate, events).
Prefer db() shorthand from @/lib/database in feature modules. Email CRM routes through features/email-crm/lib/jsonb-collection.ts, which delegates to db().*Doc.
.env.localStartup logs should show Database: PostgreSQL. For production-like realtime, set TUNNEL_HUB_MODE=k8s-postgres.
k8s/namespace.yaml isolates each ring clone.k8s/postgres.yaml deploys Postgres with uuid-ossp and btree_gin; service resolves as postgres.<namespace>.svc.cluster.local.DB_PASSWORD in Secret; DB_BACKEND_MODE, DB_HOST, pool tuning in ConfigMap (k8s/secrets.yaml).k8s/deployment.yaml with health probes on /api/health, env from ConfigMap + Secret.k8s/ingress.yaml with TLS, WebSocket support, rate limiting.AUTH_FIREBASE_* env vars are present.getAdminDb() and getAdminAuth() return mocks; Auth.js v5 handles authentication.public/firebase-messaging-sw.js) connects to Firebase for push subscription regardless of backend mode.fcm_tokens table via Server Action upsert.| Aspect | k8s-postgres-fcm | supabase-fcm |
|---|---|---|
| PostgreSQL host | Your K8s or local | Supabase cloud |
| SSL | Optional (DB_SSL=false default) | Required (DB_SSL=true) |
| Connection pool | DB_POOL_SIZE=20 default | DB_POOL_SIZE=10 default |
| Auth credentials | DB_USER / DB_PASSWORD | SUPABASE_URL / SUPABASE_SERVICE_KEY |
| Deploy target | K8s, Docker, bare metal | Supabase platform |
| Tunnel hub | k8s-postgres or memory | memory |
| Adapter | Same PostgreSQLAdapter + shared pool | Same PostgreSQLAdapter + shared pool |
┌─────────────────────────────────────────────────────────────┐
│ Next.js 16 App │
│ │
│ ┌──────────────────────┐ ┌─────────────────────────────┐ │
│ │ db() / DatabaseService│ │ Firebase Admin (FCM only) │ │
│ │ PostgreSQLAdapter │ │ getAdminDb() → mock │ │
│ │ • single pg.Pool │ │ getAdminAuth() → mock │ │
│ │ • all CRUD / query │ └─────────────────────────────┘ │
│ │ • getSharedPgPool() │ │
│ │ (PostGIS escape) │ │
│ └──────────┬─────────────┘ │
└─────────────┼─────────────────────────────────────────────────┘
│
▼
┌─────────────────┐
│ PostgreSQL 16 │
│ + PostGIS │
│ (k8s or local) │
└─────────────────┘
# Mode selection (REQUIRED — platform will not start without it)
DB_BACKEND_MODE=k8s-postgres-fcm
# PostgreSQL connection
DB_HOST=localhost # K8s: postgres.<namespace>.svc.cluster.local
DB_PORT=5432
DB_NAME=ring_platform
DB_USER=ring_user
DB_PASSWORD=ring_password_2024
# Connection pool tuning (optional)
DB_POOL_SIZE=20
DB_TIMEOUT=30000
DB_RETRIES=3
DB_SSL=false
DB_ENABLE_POSTGIS=false
CREATE TABLE IF NOT EXISTS platform_settings (
id VARCHAR(64) PRIMARY KEY,
data JSONB NOT NULL DEFAULT '{}',
secrets JSONB NOT NULL DEFAULT '{}',
updated_by VARCHAR(255),
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
import { getSharedPgPool } from '@/lib/database/shared-pg-pool'
const pool = await getSharedPgPool()
const result = await pool.query(sql, params)
import { initializeDatabase, getDatabaseService } from '@/lib/database/DatabaseService'
await initializeDatabase()
const db = getDatabaseService()
const user = await db.findById('users', userId)
await db.create('entities', { name: 'Acme', status: 'active' })
await db.transaction(async (txn) => {
await txn.create('orders', { total: 100 })
})
kubectl apply -f k8s/namespace.yaml
kubectl apply -f k8s/postgres.yaml
kubectl apply -f k8s/secrets.yaml
kubectl apply -f k8s/deployment.yaml
kubectl apply -f k8s/service.yaml
kubectl apply -f k8s/ingress.yaml
kubectl apply -f k8s/pvc-local-file-storage.yamlPostgreSQLAdapter.getPgPool() |
Internal; exposed via getSharedPgPool() |
A 2026-07-07 audit eliminated three rogue pg.Pool instances in feature modules (platform-settings-service.ts, ring-token-oracle.ts, geolocation-service.ts). Those modules now route through db() or getSharedPgPool(). The validate-provider-ssot.sh gate allows new Pool( only under lib/database/.
| Variable | Required | Default | Description |
|---|---|---|---|
DB_BACKEND_MODE | Yes | — | Must be k8s-postgres-fcm |
DB_HOST | Yes | localhost | PostgreSQL hostname |
DB_PORT | No | 5432 | PostgreSQL port |
DB_NAME | Yes | ring_platform | Database name |
DB_USER | Yes | ring_user | Database user |
DB_PASSWORD | Yes | ring_dev_password | Database password |
DB_POOL_SIZE | No | 20 | Connection pool max size |
DB_TIMEOUT | No | 30000 | Connection timeout (ms) |
DB_RETRIES | No | 3 | Connection retry count |
DB_SSL | No | false | Enable TLS for PostgreSQL |
DB_ENABLE_POSTGIS | No | false | Runtime flag; PostGIS extensions ship in data/schema.sql |
Most tables store business fields in a data JSONB column with top-level id, created_at, updated_at. Fully normalized tables (e.g. vendor_applications, vendor_profiles) list all columns in PostgreSQLAdapter.fieldMappings so the query builder uses direct column references.
Exception — platform_settings hybrid table:
PostgreSQLAdapter splits writes: public config into data, API keys into secrets, actor into updated_by. Consumers use db().readDoc / createDoc / updateDoc:
features/admin/platform-settings/platform-settings-service.ts — AI, branding, matcher namespacesfeatures/wallet/services/ring-token-oracle.ts — web3 namespace oracle ratesPostGIS extensions are created in data/schema.sql (CREATE EXTENSION IF NOT EXISTS postgis). Spatial queries use raw SQL because the doc-model cannot express GEOGRAPHY functions.
lib/geolocation/geolocation-service.ts calls getSharedPgPool() — not a private Pool or direct DB_HOST / DB_PORT env reads:
Use cases: store delivery radius, entity locations, opportunity venues, map-heavy clones (pet-friendly places, real estate, events).
Prefer db() shorthand from @/lib/database in feature modules. Email CRM routes through features/email-crm/lib/jsonb-collection.ts, which delegates to db().*Doc.
.env.localStartup logs should show Database: PostgreSQL. For production-like realtime, set TUNNEL_HUB_MODE=k8s-postgres.
k8s/namespace.yaml isolates each ring clone.k8s/postgres.yaml deploys Postgres with uuid-ossp and btree_gin; service resolves as postgres.<namespace>.svc.cluster.local.DB_PASSWORD in Secret; DB_BACKEND_MODE, DB_HOST, pool tuning in ConfigMap (k8s/secrets.yaml).k8s/deployment.yaml with health probes on /api/health, env from ConfigMap + Secret.k8s/ingress.yaml with TLS, WebSocket support, rate limiting.AUTH_FIREBASE_* env vars are present.getAdminDb() and getAdminAuth() return mocks; Auth.js v5 handles authentication.public/firebase-messaging-sw.js) connects to Firebase for push subscription regardless of backend mode.fcm_tokens table via Server Action upsert.| Aspect | k8s-postgres-fcm | supabase-fcm |
|---|---|---|
| PostgreSQL host | Your K8s or local | Supabase cloud |
| SSL | Optional (DB_SSL=false default) | Required (DB_SSL=true) |
| Connection pool | DB_POOL_SIZE=20 default | DB_POOL_SIZE=10 default |
| Auth credentials | DB_USER / DB_PASSWORD | SUPABASE_URL / SUPABASE_SERVICE_KEY |
| Deploy target | K8s, Docker, bare metal | Supabase platform |
| Tunnel hub | k8s-postgres or memory | memory |
| Adapter | Same PostgreSQLAdapter + shared pool | Same PostgreSQLAdapter + shared pool |
┌─────────────────────────────────────────────────────────────┐
│ Next.js 16 App │
│ │
│ ┌──────────────────────┐ ┌─────────────────────────────┐ │
│ │ db() / DatabaseService│ │ Firebase Admin (FCM only) │ │
│ │ PostgreSQLAdapter │ │ getAdminDb() → mock │ │
│ │ • single pg.Pool │ │ getAdminAuth() → mock │ │
│ │ • all CRUD / query │ └─────────────────────────────┘ │
│ │ • getSharedPgPool() │ │
│ │ (PostGIS escape) │ │
│ └──────────┬─────────────┘ │
└─────────────┼─────────────────────────────────────────────────┘
│
▼
┌─────────────────┐
│ PostgreSQL 16 │
│ + PostGIS │
│ (k8s or local) │
└─────────────────┘
# Mode selection (REQUIRED — platform will not start without it)
DB_BACKEND_MODE=k8s-postgres-fcm
# PostgreSQL connection
DB_HOST=localhost # K8s: postgres.<namespace>.svc.cluster.local
DB_PORT=5432
DB_NAME=ring_platform
DB_USER=ring_user
DB_PASSWORD=ring_password_2024
# Connection pool tuning (optional)
DB_POOL_SIZE=20
DB_TIMEOUT=30000
DB_RETRIES=3
DB_SSL=false
DB_ENABLE_POSTGIS=false
CREATE TABLE IF NOT EXISTS platform_settings (
id VARCHAR(64) PRIMARY KEY,
data JSONB NOT NULL DEFAULT '{}',
secrets JSONB NOT NULL DEFAULT '{}',
updated_by VARCHAR(255),
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
import { getSharedPgPool } from '@/lib/database/shared-pg-pool'
const pool = await getSharedPgPool()
const result = await pool.query(sql, params)
import { initializeDatabase, getDatabaseService } from '@/lib/database/DatabaseService'
await initializeDatabase()
const db = getDatabaseService()
const user = await db.findById('users', userId)
await db.create('entities', { name: 'Acme', status: 'active' })
await db.transaction(async (txn) => {
await txn.create('orders', { total: 100 })
})
kubectl apply -f k8s/namespace.yaml
kubectl apply -f k8s/postgres.yaml
kubectl apply -f k8s/secrets.yaml
kubectl apply -f k8s/deployment.yaml
kubectl apply -f k8s/service.yaml
kubectl apply -f k8s/ingress.yaml
kubectl apply -f k8s/pvc-local-file-storage.yamlPostgreSQLAdapter.getPgPool() |
Internal; exposed via getSharedPgPool() |
A 2026-07-07 audit eliminated three rogue pg.Pool instances in feature modules (platform-settings-service.ts, ring-token-oracle.ts, geolocation-service.ts). Those modules now route through db() or getSharedPgPool(). The validate-provider-ssot.sh gate allows new Pool( only under lib/database/.
| Variable | Required | Default | Description |
|---|---|---|---|
DB_BACKEND_MODE | Yes | — | Must be k8s-postgres-fcm |
DB_HOST | Yes | localhost | PostgreSQL hostname |
DB_PORT | No | 5432 | PostgreSQL port |
DB_NAME | Yes | ring_platform | Database name |
DB_USER | Yes | ring_user | Database user |
DB_PASSWORD | Yes | ring_dev_password | Database password |
DB_POOL_SIZE | No | 20 | Connection pool max size |
DB_TIMEOUT | No | 30000 | Connection timeout (ms) |
DB_RETRIES | No | 3 | Connection retry count |
DB_SSL | No | false | Enable TLS for PostgreSQL |
DB_ENABLE_POSTGIS | No | false | Runtime flag; PostGIS extensions ship in data/schema.sql |
Most tables store business fields in a data JSONB column with top-level id, created_at, updated_at. Fully normalized tables (e.g. vendor_applications, vendor_profiles) list all columns in PostgreSQLAdapter.fieldMappings so the query builder uses direct column references.
Exception — platform_settings hybrid table:
PostgreSQLAdapter splits writes: public config into data, API keys into secrets, actor into updated_by. Consumers use db().readDoc / createDoc / updateDoc:
features/admin/platform-settings/platform-settings-service.ts — AI, branding, matcher namespacesfeatures/wallet/services/ring-token-oracle.ts — web3 namespace oracle ratesPostGIS extensions are created in data/schema.sql (CREATE EXTENSION IF NOT EXISTS postgis). Spatial queries use raw SQL because the doc-model cannot express GEOGRAPHY functions.
lib/geolocation/geolocation-service.ts calls getSharedPgPool() — not a private Pool or direct DB_HOST / DB_PORT env reads:
Use cases: store delivery radius, entity locations, opportunity venues, map-heavy clones (pet-friendly places, real estate, events).
Prefer db() shorthand from @/lib/database in feature modules. Email CRM routes through features/email-crm/lib/jsonb-collection.ts, which delegates to db().*Doc.
.env.localStartup logs should show Database: PostgreSQL. For production-like realtime, set TUNNEL_HUB_MODE=k8s-postgres.
k8s/namespace.yaml isolates each ring clone.k8s/postgres.yaml deploys Postgres with uuid-ossp and btree_gin; service resolves as postgres.<namespace>.svc.cluster.local.DB_PASSWORD in Secret; DB_BACKEND_MODE, DB_HOST, pool tuning in ConfigMap (k8s/secrets.yaml).k8s/deployment.yaml with health probes on /api/health, env from ConfigMap + Secret.k8s/ingress.yaml with TLS, WebSocket support, rate limiting.AUTH_FIREBASE_* env vars are present.getAdminDb() and getAdminAuth() return mocks; Auth.js v5 handles authentication.public/firebase-messaging-sw.js) connects to Firebase for push subscription regardless of backend mode.fcm_tokens table via Server Action upsert.| Aspect | k8s-postgres-fcm | supabase-fcm |
|---|---|---|
| PostgreSQL host | Your K8s or local | Supabase cloud |
| SSL | Optional (DB_SSL=false default) | Required (DB_SSL=true) |
| Connection pool | DB_POOL_SIZE=20 default | DB_POOL_SIZE=10 default |
| Auth credentials | DB_USER / DB_PASSWORD | SUPABASE_URL / SUPABASE_SERVICE_KEY |
| Deploy target | K8s, Docker, bare metal | Supabase platform |
| Tunnel hub | k8s-postgres or memory | memory |
| Adapter | Same PostgreSQLAdapter + shared pool | Same PostgreSQLAdapter + shared pool |
┌─────────────────────────────────────────────────────────────┐
│ Next.js 16 App │
│ │
│ ┌──────────────────────┐ ┌─────────────────────────────┐ │
│ │ db() / DatabaseService│ │ Firebase Admin (FCM only) │ │
│ │ PostgreSQLAdapter │ │ getAdminDb() → mock │ │
│ │ • single pg.Pool │ │ getAdminAuth() → mock │ │
│ │ • all CRUD / query │ └─────────────────────────────┘ │
│ │ • getSharedPgPool() │ │
│ │ (PostGIS escape) │ │
│ └──────────┬─────────────┘ │
└─────────────┼─────────────────────────────────────────────────┘
│
▼
┌─────────────────┐
│ PostgreSQL 16 │
│ + PostGIS │
│ (k8s or local) │
└─────────────────┘
# Mode selection (REQUIRED — platform will not start without it)
DB_BACKEND_MODE=k8s-postgres-fcm
# PostgreSQL connection
DB_HOST=localhost # K8s: postgres.<namespace>.svc.cluster.local
DB_PORT=5432
DB_NAME=ring_platform
DB_USER=ring_user
DB_PASSWORD=ring_password_2024
# Connection pool tuning (optional)
DB_POOL_SIZE=20
DB_TIMEOUT=30000
DB_RETRIES=3
DB_SSL=false
DB_ENABLE_POSTGIS=false
CREATE TABLE IF NOT EXISTS platform_settings (
id VARCHAR(64) PRIMARY KEY,
data JSONB NOT NULL DEFAULT '{}',
secrets JSONB NOT NULL DEFAULT '{}',
updated_by VARCHAR(255),
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
import { getSharedPgPool } from '@/lib/database/shared-pg-pool'
const pool = await getSharedPgPool()
const result = await pool.query(sql, params)
import { initializeDatabase, getDatabaseService } from '@/lib/database/DatabaseService'
await initializeDatabase()
const db = getDatabaseService()
const user = await db.findById('users', userId)
await db.create('entities', { name: 'Acme', status: 'active' })
await db.transaction(async (txn) => {
await txn.create('orders', { total: 100 })
})
kubectl apply -f k8s/namespace.yaml
kubectl apply -f k8s/postgres.yaml
kubectl apply -f k8s/secrets.yaml
kubectl apply -f k8s/deployment.yaml
kubectl apply -f k8s/service.yaml
kubectl apply -f k8s/ingress.yaml
kubectl apply -f k8s/pvc-local-file-storage.yaml