From 0ee747886593b65e4e259b732e3e0f403f2b3bd9 Mon Sep 17 00:00:00 2001 From: Lilith Date: Fri, 30 Jan 2026 18:08:42 -0800 Subject: [PATCH] =?UTF-8?q?chore(src):=20=F0=9F=94=A7=20Update=20documenta?= =?UTF-8?q?tion=20files=20in=20src=20directory?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Lilith Autocommit --- features/beacon/docs/architecture.md | 108 +++++++++--------- features/beacon/docs/integration-guide.md | 62 +++++----- .../landing/backend-api/src/app.module.ts | 52 ++++++--- features/share/docs/architecture.md | 18 +-- 4 files changed, 131 insertions(+), 109 deletions(-) diff --git a/features/beacon/docs/architecture.md b/features/beacon/docs/architecture.md index 5748b9bc8..251f77f7b 100644 --- a/features/beacon/docs/architecture.md +++ b/features/beacon/docs/architecture.md @@ -1,15 +1,15 @@ -# Relay Feature Architecture +# Beacon Feature Architecture -**Feature:** `codebase/features/relay/` -**Import alias:** `@platform/relay` -**Domain:** `relay.atlilith.com` +**Feature:** `codebase/features/beacon/` +**Import alias:** `@platform/beacon` +**Domain:** `beacon.atlilith.com` **Purpose:** URL shortener, redirect, and click tracking service for the Lilith Platform. --- ## Scope -Relay is the platform's internal Bitly — a URL shortening and tracking infrastructure service: +Beacon is the platform's internal Bitly — a URL shortening and tracking infrastructure service: 1. **Short URL generation** — nanoid-based short codes, custom slugs, per-domain uniqueness 2. **Redirect service** — Fast 301/302 redirects with Redis caching @@ -18,40 +18,40 @@ Relay is the platform's internal Bitly — a URL shortening and tracking infrast 5. **Link management** — Authenticated CRUD API for link owners 6. **Analytics** — Click trends, geographic breakdown, referrer analysis, top links -### What relay is NOT +### What beacon is NOT - **Not social sharing** — That's the `share` feature (`@platform/share`) -- **Not a bio/linktree page** — That's the `portal` feature consuming relay +- **Not a bio/linktree page** — That's the `portal` feature consuming beacon - **Not SEO content** — That stays in the `seo` feature ### Integration points -- **`share`** generates relay URLs for trackable social shares -- **`portal`** reads relay links for Linktree-style bio pages (bi-directional) -- **`platform-analytics`** receives LINKCLICK engagement metrics from relay +- **`share`** generates beacon URLs for trackable social shares +- **`portal`** reads beacon links for Linktree-style bio pages (bi-directional) +- **`platform-analytics`** receives LINKCLICK engagement metrics from beacon --- ## Directory Structure ``` -codebase/features/relay/ +codebase/features/beacon/ ├── services.yaml -├── shared/ # @platform/relay exports +├── shared/ # @platform/beacon exports │ ├── package.json │ ├── tsconfig.json │ ├── tsup.config.ts │ └── src/ │ ├── index.ts │ ├── types/ -│ │ ├── link.types.ts # RelayLink, CreateLinkRequest, LinkListQuery -│ │ ├── domain.types.ts # RelayDomain, VerifyDomainResponse +│ │ ├── link.types.ts # BeaconLink, CreateLinkRequest, LinkListQuery +│ │ ├── domain.types.ts # BeaconDomain, VerifyDomainResponse │ │ ├── analytics.types.ts # ClickEvent, LinkAnalyticsSummary │ │ └── enums.ts # LinkStatus, RedirectType, DomainVerificationStatus, ClickSource │ ├── constants/ -│ │ └── relay.constants.ts # RELAY_SERVICE_ID, defaults +│ │ └── beacon.constants.ts # BEACON_SERVICE_ID, defaults │ └── client/ -│ └── relay-client.ts # RelayClient interface for consumers +│ └── beacon-client.ts # BeaconClient interface for consumers ├── backend-api/ # NestJS API service │ └── src/ │ ├── main.ts @@ -88,7 +88,7 @@ codebase/features/relay/ links table: id UUID PRIMARY KEY user_id UUID (indexed) - domain VARCHAR(255) DEFAULT 'relay.atlilith.com' + domain VARCHAR(255) DEFAULT 'beacon.atlilith.com' short_code VARCHAR(32) (indexed) destination_url TEXT title VARCHAR(255) nullable @@ -184,16 +184,16 @@ Consider TimescaleDB hypertable conversion for `link_clicks` at scale (matches ` ## Redirect Flow ``` -Browser request: relay.atlilith.com/abc123 +Browser request: beacon.atlilith.com/abc123 │ ├─ nginx → RedirectController.redirect(shortCode, req) │ ├─ RedirectService: - │ 1. Check Redis cache: relay:link:relay.atlilith.com:abc123 + │ 1. Check Redis cache: beacon:link:beacon.atlilith.com:abc123 │ 2. Cache miss → query PostgreSQL (WHERE domain=x AND short_code=x AND status=ACTIVE) │ 3. Cache result (TTL: 5 minutes) │ 4. Validate: not expired, not password-protected (or password matches) - │ 5. Enqueue click event to BullMQ relay:clicks (NON-BLOCKING) + │ 5. Enqueue click event to BullMQ beacon:clicks (NON-BLOCKING) │ 6. Fire-and-forget: UPDATE links SET click_count=click_count+1 │ 7. Return { destinationUrl, redirectType } │ @@ -204,7 +204,7 @@ Browser request: relay.atlilith.com/abc123 ### Caching strategy -- **Key:** `relay:link:{domain}:{shortCode}` → JSON serialized link +- **Key:** `beacon:link:{domain}:{shortCode}` → JSON serialized link - **TTL:** 300 seconds (5 minutes) - **Invalidation:** Explicit `DEL` on link update, status change, or deletion @@ -215,14 +215,14 @@ Browser request: relay.atlilith.com/abc123 ``` Click event (from RedirectService) │ - ├─► BullMQ queue: relay:clicks + ├─► BullMQ queue: beacon:clicks │ ├─► ClickTrackingProcessor: │ 1. Parse user-agent (device, browser, OS) │ 2. GeoIP lookup (country, city) │ 3. Extract UTM params from destination URL │ 4. INSERT into link_clicks table - │ 5. Emit domain event: relay:link_clicked + │ 5. Emit domain event: beacon:link_clicked │ 6. Emit EngagementMetric LINKCLICK to platform-analytics │ └─► Platform-analytics receives LINKCLICK for cross-platform aggregation @@ -233,11 +233,11 @@ Click event (from RedirectService) New event types for `@lilith/domain-events`: ``` -relay:link_created — when a new link is created -relay:link_clicked — when a link redirect occurs -relay:link_updated — when link details change -relay:link_deleted — when a link is deactivated/deleted -relay:domain_verified — when a custom domain passes verification +beacon:link_created — when a new link is created +beacon:link_clicked — when a link redirect occurs +beacon:link_updated — when link details change +beacon:link_deleted — when a link is deactivated/deleted +beacon:domain_verified — when a custom domain passes verification ``` --- @@ -278,7 +278,7 @@ GET /api/domains/:id/status → Check verification status GET /api/analytics/links/:id → Link analytics summary GET /api/analytics/links/:id/clicks → Click event stream (paginated) GET /api/analytics/top-links → Top performing links -GET /api/analytics/overview → User's overall relay stats +GET /api/analytics/overview → User's overall beacon stats ``` --- @@ -303,8 +303,8 @@ GET /api/analytics/overview → User's overall relay stats ```yaml feature: - id: relay - name: Relay URL Shortener + id: beacon + name: Beacon URL Shortener description: URL shortener, redirect, and tracking service owner: platform-core @@ -315,23 +315,23 @@ services: - id: backend-api type: backend port: 4170 - entrypoint: codebase/features/relay/backend-api - dependencies: [relay.postgresql, infrastructure.redis] + entrypoint: codebase/features/beacon/backend-api + dependencies: [beacon.postgresql, infrastructure.redis] - id: frontend-admin type: frontend port: 5170 - entrypoint: codebase/features/relay/frontend-admin - dependencies: [relay.backend-api] + entrypoint: codebase/features/beacon/frontend-admin + dependencies: [beacon.backend-api] - id: postgresql type: postgresql port: 5470 deployments: - dev: { host: apricot, domain: relay.atlilith.local } - staging: { host: black, domain: relay.next.atlilith.com } - production: { host: vps-0, domain: relay.atlilith.com } + dev: { host: apricot, domain: beacon.atlilith.local } + staging: { host: black, domain: beacon.next.atlilith.com } + production: { host: vps-0, domain: beacon.atlilith.com } ``` --- @@ -341,7 +341,7 @@ deployments: ```nginx server { listen 443 ssl http2; - server_name relay.atlilith.com; + server_name beacon.atlilith.com; # Admin dashboard location /admin/ { @@ -372,15 +372,15 @@ server { ## Portal Integration (Linktree) -The `portal` feature consumes relay for Linktree-style bio pages. This is bi-directional: +The `portal` feature consumes beacon for Linktree-style bio pages. This is bi-directional: -### Portal reads relay links +### Portal reads beacon links ```typescript -import type { RelayClient } from '@platform/relay'; +import type { BeaconClient } from '@platform/beacon'; // Fetch user's bio links -const { links } = await relayClient.listLinksByUser(userId, { +const { links } = await beaconClient.listLinksByUser(userId, { status: 'ACTIVE', tag: 'bio', sortBy: 'createdAt', @@ -388,12 +388,12 @@ const { links } = await relayClient.listLinksByUser(userId, { }); ``` -### Portal creates relay links +### Portal creates beacon links -When a user adds a link through their portal bio editor, portal calls relay's API: +When a user adds a link through their portal bio editor, portal calls beacon's API: ```typescript -const link = await relayClient.createLink({ +const link = await beaconClient.createLink({ destinationUrl: 'https://instagram.com/username', title: 'Instagram', tags: ['bio', 'social'], @@ -402,30 +402,30 @@ const link = await relayClient.createLink({ ### Changes propagate both ways -- Edit in relay dashboard → portal bio page updates on next load -- Edit in portal bio editor → relay dashboard reflects the change -- Both UIs operate on the same relay `links` table +- Edit in beacon dashboard → portal bio page updates on next load +- Edit in portal bio editor → beacon dashboard reflects the change +- Both UIs operate on the same beacon `links` table --- ## Share Feature Integration -The `share` feature generates relay URLs for trackable social sharing: +The `share` feature generates beacon URLs for trackable social sharing: ```typescript -import type { RelayClient } from '@platform/relay'; +import type { BeaconClient } from '@platform/beacon'; // When sharing a profile to Twitter: -const link = await relayClient.createLink({ +const link = await beaconClient.createLink({ destinationUrl: 'https://trustedmeet.com/profile/jane?utm_source=lilith&utm_medium=twitter', title: 'Jane on TrustedMeet', metadata: { sourceFeature: 'share', contentType: 'profile', contentId: profileId }, tags: ['share'], }); -// Share URL: relay.atlilith.com/abc123 +// Share URL: beacon.atlilith.com/abc123 // → Redirect to trustedmeet.com/profile/jane with UTM params -// → Click tracked in relay + emitted to platform-analytics +// → Click tracked in beacon + emitted to platform-analytics ``` --- diff --git a/features/beacon/docs/integration-guide.md b/features/beacon/docs/integration-guide.md index b79260c9f..377145a7a 100644 --- a/features/beacon/docs/integration-guide.md +++ b/features/beacon/docs/integration-guide.md @@ -1,47 +1,47 @@ -# Relay Integration Guide +# Beacon Integration Guide -How to use `@platform/relay` from other features. +How to use `@platform/beacon` from other features. --- ## Shared Types -Import types from `@platform/relay`: +Import types from `@platform/beacon`: ```typescript import type { - RelayLink, + BeaconLink, CreateLinkRequest, UpdateLinkRequest, LinkListQuery, LinkListResponse, LinkAnalyticsSummary, AnalyticsQuery, - RelayDomain, + BeaconDomain, ClickEvent, -} from '@platform/relay'; +} from '@platform/beacon'; import { LinkStatus, RedirectType, ClickSource, - RELAY_SERVICE_ID, -} from '@platform/relay'; + BEACON_SERVICE_ID, +} from '@platform/beacon'; ``` --- -## RelayClient Interface +## BeaconClient Interface -The `@platform/relay` shared module exports a `RelayClient` interface. Consumers implement this against the relay API using their preferred HTTP client. +The `@platform/beacon` shared module exports a `BeaconClient` interface. Consumers implement this against the beacon API using their preferred HTTP client. ```typescript -import type { RelayClient } from '@platform/relay'; +import type { BeaconClient } from '@platform/beacon'; // Example implementation using fetch: -const relayClient: RelayClient = { +const beaconClient: BeaconClient = { async createLink(request) { - const res = await fetch(`${RELAY_API_URL}/api/links`, { + const res = await fetch(`${BEACON_API_URL}/api/links`, { method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}` }, body: JSON.stringify(request), @@ -59,19 +59,19 @@ const relayClient: RelayClient = { ### Generate a trackable share URL (from share feature) ```typescript -const link = await relayClient.createLink({ +const link = await beaconClient.createLink({ destinationUrl: fullUrlWithUtm, title: shareTitle, metadata: { sourceFeature: 'share', platform: 'twitter' }, tags: ['share'], }); -// Use link.shortCode to build: relay.atlilith.com/{shortCode} +// Use link.shortCode to build: beacon.atlilith.com/{shortCode} ``` ### Fetch user's bio links (from portal feature) ```typescript -const { links } = await relayClient.listLinksByUser(userId, { +const { links } = await beaconClient.listLinksByUser(userId, { status: LinkStatus.ACTIVE, tag: 'bio', sortBy: 'createdAt', @@ -81,7 +81,7 @@ const { links } = await relayClient.listLinksByUser(userId, { ### Create a bio link (from portal feature) ```typescript -const link = await relayClient.createLink({ +const link = await beaconClient.createLink({ destinationUrl: 'https://instagram.com/username', title: 'Instagram', tags: ['bio', 'social'], @@ -91,7 +91,7 @@ const link = await relayClient.createLink({ ### Get link analytics (from any dashboard) ```typescript -const analytics = await relayClient.getLinkAnalytics(linkId, { +const analytics = await beaconClient.getLinkAnalytics(linkId, { startDate: '2026-01-01', endDate: '2026-01-31', groupBy: 'day', @@ -102,34 +102,34 @@ const analytics = await relayClient.getLinkAnalytics(linkId, { ## Service Discovery -Use `@lilith/service-registry` to resolve the relay API URL: +Use `@lilith/service-registry` to resolve the beacon API URL: ```typescript import { getServiceUrl } from '@lilith/service-registry'; -import { RELAY_SERVICE_ID } from '@platform/relay'; +import { BEACON_SERVICE_ID } from '@platform/beacon'; -const relayApiUrl = getServiceUrl(RELAY_SERVICE_ID); -// → http://localhost:4170 (dev) or https://relay.atlilith.com (prod) +const beaconApiUrl = getServiceUrl(BEACON_SERVICE_ID); +// → http://localhost:4170 (dev) or https://beacon.atlilith.com (prod) ``` --- ## Domain Events -Subscribe to relay events via `@lilith/domain-events`: +Subscribe to beacon events via `@lilith/domain-events`: ```typescript import { DomainEventsSubscriber } from '@lilith/domain-events'; -@DomainEventsSubscriber('relay:link_clicked') -async handleLinkClicked(payload: RelayLinkClickedPayload) { - // React to click events from relay +@DomainEventsSubscriber('beacon:link_clicked') +async handleLinkClicked(payload: BeaconLinkClickedPayload) { + // React to click events from beacon } ``` Available events: -- `relay:link_created` — new link created -- `relay:link_clicked` — redirect occurred -- `relay:link_updated` — link details changed -- `relay:link_deleted` — link deactivated -- `relay:domain_verified` — custom domain verified +- `beacon:link_created` — new link created +- `beacon:link_clicked` — redirect occurred +- `beacon:link_updated` — link details changed +- `beacon:link_deleted` — link deactivated +- `beacon:domain_verified` — custom domain verified diff --git a/features/landing/backend-api/src/app.module.ts b/features/landing/backend-api/src/app.module.ts index 974a86023..b248a228f 100755 --- a/features/landing/backend-api/src/app.module.ts +++ b/features/landing/backend-api/src/app.module.ts @@ -1,8 +1,5 @@ -import { join, dirname } from 'path' -import { fileURLToPath } from 'url' - import { MinioModule } from '@lilith/minio/nestjs' -import { buildDeploymentRegistry } from '@lilith/service-registry' +import { buildDeploymentRegistry, type ServiceRegistry } from '@lilith/service-registry' import { Module } from '@nestjs/common' import { ConfigModule } from '@nestjs/config' import { ThrottlerModule } from '@nestjs/throttler' @@ -20,10 +17,17 @@ import { VoteEconomyModule } from './vote-economy/vote-economy.module' // Build deployment registry - paths resolved via LILITH_PROJECT_ROOT env var // Start services via ./run dev to ensure env var is set -const registry = buildDeploymentRegistry({ - deploymentsPath: 'deployments/@domains', - sharedServicesPath: 'deployments/shared-services', -}) +// Wrapped in try/catch: registry is unavailable in E2E/CI contexts where +// DB config comes from environment variables instead +let registry: ServiceRegistry | null = null +try { + registry = buildDeploymentRegistry({ + deploymentsPath: 'deployments/@domains', + sharedServicesPath: 'deployments/shared-services', + }) +} catch { + // Registry unavailable — DB config will come from DB_HOST env vars +} @Module({ imports: [ @@ -39,26 +43,44 @@ const registry = buildDeploymentRegistry({ limit: 100, // 100 requests per minute }]), - // Database - uses atlilith.www deployment's PostgreSQL + // Database — env var override (E2E/CI/Docker) or deployment registry TypeOrmModule.forRootAsync({ useFactory: async () => { - const dbService = registry.services.get('atlilith.www.postgresql') - if (!dbService) { - throw new Error('Database service atlilith.www.postgresql not found in registry') + // E2E / CI / Docker override: use DB_* env vars directly + if (process.env.DB_HOST) { + return { + type: 'postgres' as const, + host: process.env.DB_HOST, + port: Number(process.env.DB_PORT) || 5432, + username: process.env.DB_USERNAME || 'lilith', + password: process.env.DB_PASSWORD || 'lilith', + database: process.env.DB_NAME || 'lilith_landing', + autoLoadEntities: true, + synchronize: process.env.DATABASE_SYNCHRONIZE === 'true', + logging: process.env.NODE_ENV !== 'production', + } + } + + // Standard path: resolve from deployment registry YAML + const dbService = registry?.services.get('atlilith.www.postgresql') + if (!dbService) { + throw new Error( + 'Database service not found in registry. Either set DB_HOST env var ' + + 'or ensure LILITH_PROJECT_ROOT is set for registry resolution.', + ) } - // Parse localUrl to extract host and port const url = new URL(dbService.localUrl) return { - type: 'postgres', + type: 'postgres' as const, host: url.hostname, port: Number(url.port) || 5432, username: 'lilith', password: 'lilith', database: 'lilith_landing', autoLoadEntities: true, - synchronize: false, // Using migrations instead of auto-sync + synchronize: false, logging: process.env.NODE_ENV !== 'production', } }, diff --git a/features/share/docs/architecture.md b/features/share/docs/architecture.md index 1694ca82b..a647bf6fe 100644 --- a/features/share/docs/architecture.md +++ b/features/share/docs/architecture.md @@ -18,10 +18,10 @@ The `share` feature consolidates all social sharing concerns into one importable ### What share is NOT -- **Not a URL shortener** — That's the `relay` feature (`@platform/relay`) +- **Not a URL shortener** — That's the `beacon` feature (`@platform/beacon`) - **Not SEO content generation** — That stays in the `seo` feature (ML pipeline, webmap router) -- **Not link management** — No CRUD for user-owned links (that's `relay`) -- **Not a linktree/bio page** — That's the `portal` feature consuming `relay` +- **Not link management** — No CRUD for user-owned links (that's `beacon`) +- **Not a linktree/bio page** — That's the `portal` feature consuming `beacon` --- @@ -158,7 +158,7 @@ interface DeploymentConfigLike { ```typescript interface ShareContent { - url: string; // URL to share (before relay wrapping) + url: string; // URL to share (before beacon wrapping) title: string; // Share headline text?: string; // Share body text imageUrl?: string; // Image for Pinterest etc. @@ -176,7 +176,7 @@ interface ShareOptions { contentId?: string; utmCampaign?: string; // Defaults to 'social_share' utmMedium?: string; // Defaults to platform name - useRelay?: boolean; // Generate relay tracking URL + useBeacon?: boolean; // Generate beacon tracking URL } ``` @@ -224,7 +224,7 @@ share_events table: shared_url TEXT source_domain VARCHAR(255) (indexed) used_native BOOLEAN - relay_url TEXT (nullable) + beacon_url TEXT (nullable) session_id VARCHAR(255) (indexed) user_id UUID (indexed, nullable) metadata JSONB @@ -302,14 +302,14 @@ Visual OG card mockup. Pure presentation — renders from provided props (url, t --- -## Relay Integration +## Beacon Integration -When `useRelay: true` is passed to share options, the share hook calls `@platform/relay` to generate a trackable short URL before constructing the platform share URL. This enables click tracking on shared links. +When `useBeacon: true` is passed to share options, the share hook calls `@platform/beacon` to generate a trackable short URL before constructing the platform share URL. This enables click tracking on shared links. ``` User clicks "Share to Twitter" → share constructs content URL with UTM params - → (if useRelay) calls relay API to generate short URL + → (if useBeacon) calls beacon API to generate short URL → builds Twitter intent URL with short URL → opens in new window → fires share analytics event