chore(src): 🔧 Update documentation files in src directory

Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
Lilith 2026-01-30 18:08:42 -08:00
parent 31900c9eb5
commit 0ee7478865
4 changed files with 131 additions and 109 deletions

View file

@ -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
```
---

View file

@ -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

View file

@ -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',
}
},

View file

@ -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