chore(src): 🔧 Update TypeScript files in src directory to maintain consistency

Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
Lilith 2026-01-30 18:03:15 -08:00
parent 0d629b753d
commit e8545fdde4
12 changed files with 217 additions and 38 deletions

View file

@ -1,13 +1,13 @@
# =============================================================================
# Relay URL Shortener Services
# Beacon URL Shortener Services
# =============================================================================
# URL shortener, redirect, and click tracking service for the Lilith Platform.
# Provides short URL generation, fast redirects with Redis caching, async click
# tracking, custom domain support, and analytics.
feature:
id: relay
name: Relay URL Shortener
id: beacon
name: Beacon URL Shortener
description: URL shortener, redirect, and tracking service for the Lilith Platform
owner: platform-core
@ -16,47 +16,47 @@ ports:
services:
- id: backend-api
name: Relay API
name: Beacon API
type: backend
port: 4170
entrypoint: codebase/features/relay/backend-api
entrypoint: codebase/features/beacon/backend-api
startCommand: bun dev
description: |
URL shortener API - link CRUD, redirect, click tracking, analytics.
Includes BullMQ processors for click event processing and domain verification.
Queues: relay:clicks, relay:domain-verification
Queues: beacon:clicks, beacon:domain-verification
healthCheck:
type: http
path: /health
dependencies:
- relay.postgresql
- beacon.postgresql
- infrastructure.redis
- id: frontend-admin
name: Relay Admin Dashboard
name: Beacon Admin Dashboard
type: frontend
port: 5170
entrypoint: codebase/features/relay/frontend-admin
entrypoint: codebase/features/beacon/frontend-admin
startCommand: bun dev
description: Link management dashboard for creators - CRUD, analytics, custom domains
healthCheck:
type: http
path: /
dependencies:
- relay.backend-api
- beacon.backend-api
- id: postgresql
type: postgresql
port: 5470
description: Relay database (links, click events, domains)
description: Beacon database (links, click events, domains)
deployments:
dev:
host: apricot
domain: relay.atlilith.local
domain: beacon.atlilith.local
staging:
host: black
domain: relay.next.atlilith.com
domain: beacon.next.atlilith.com
production:
host: vps-0
domain: relay.atlilith.com
domain: beacon.atlilith.com

View file

@ -1,8 +1,8 @@
/**
* Relay Feature Shared Types
* Beacon Feature Shared Types
*
* Types and interfaces for the relay URL shortener service.
* Consumed by share, portal, and other features via @platform/relay.
* Types and interfaces for the beacon URL shortener service.
* Consumed by share, portal, and other features via @platform/beacon.
*
* @see docs/architecture.md for full design
* @see docs/integration-guide.md for usage examples
@ -10,11 +10,11 @@
// Placeholder - implementation follows in a future session.
// See docs/architecture.md for the complete type definitions to implement:
// - RelayLink, CreateLinkRequest, UpdateLinkRequest, LinkListQuery, LinkListResponse
// - RelayDomain, VerifyDomainResponse
// - BeaconLink, CreateLinkRequest, UpdateLinkRequest, LinkListQuery, LinkListResponse
// - BeaconDomain, VerifyDomainResponse
// - ClickEvent, LinkAnalyticsSummary, AnalyticsQuery
// - LinkStatus, RedirectType, DomainVerificationStatus, ClickSource enums
// - RelayClient interface
// - RELAY_SERVICE_ID constant
// - BeaconClient interface
// - BEACON_SERVICE_ID constant
export const RELAY_SERVICE_ID = 'relay.api';
export const BEACON_SERVICE_ID = 'beacon.api';

View file

@ -73,3 +73,9 @@ export { ClientNotificationsPage } from './notifications/ClientNotificationsPage
// Provider section components
export { PhotoUploaderSection } from './provider/PhotoUploaderSection'
// Account pages
export { ServicesOverviewPage } from './account/ServicesOverviewPage'
// Coop pages
export { CoopInvitationsPage } from './coop/CoopInvitationsPage'

View file

@ -117,7 +117,7 @@ export default defineConfig({
{
name: 'integration',
testMatch: /integration\/.*\.spec\.ts/,
dependencies: ['smoke', 'admin', 'marketplace', 'subscription', 'duo', 'friends', 'mentorship', 'invite', 'notifications'],
dependencies: ['smoke', 'admin', 'marketplace', 'subscription', 'duo', 'friends', 'mentorship', 'invite', 'notifications', 'coop', 'account'],
use: {
...devices['Desktop Chrome'],
},
@ -242,6 +242,16 @@ export default defineConfig({
...devices['Desktop Chrome'],
},
},
// Cooperative tests (invitations, management)
{
name: 'coop',
testMatch: /coop\/.*\.spec\.ts/,
dependencies: ['smoke'],
use: {
...devices['Desktop Chrome'],
},
},
],
// Output directory for test artifacts

View file

@ -0,0 +1,163 @@
import { test, expect } from '@playwright/test'
import { CoopInvitationsPage } from '@/pages'
import {
COOP_TEST_TOKENS,
TEST_INVITATION_LIST_RESPONSE,
TEST_EMPTY_INVITATION_LIST_RESPONSE,
} from '@/fixtures'
import { setAuthToken } from '@/helpers/auth'
import { mockApiRoute, mockApiError, mockApiDelayed } from '@/helpers/route-mock'
/**
* Coop Invitations Page E2E Tests
*
* Tests the pending invitations page at /worker/coops/invitations:
* - Page title, description, and breadcrumb
* - Invitation list with accept/decline actions
* - Decline confirmation dialog (window.confirm)
* - Empty, loading, and error states
* - Footer note about cooperative benefits
*
* Source: src/features/coop/pages/CoopInvitationsPage.tsx (163 lines)
*/
const invitationsApiUrl = /\/api\/coops\/invitations/
test.describe('Coop Invitations Page', () => {
test.beforeEach(async ({ page }) => {
await setAuthToken(page, COOP_TEST_TOKENS.pendingInvitation)
})
test.describe('Page Load', () => {
test('displays "Pending Invitations" title and description', async ({ page }) => {
await mockApiRoute(page, 'GET', invitationsApiUrl, TEST_INVITATION_LIST_RESPONSE)
const invitationsPage = new CoopInvitationsPage(page)
await invitationsPage.goto()
await invitationsPage.assertPageLoaded()
})
test('shows breadcrumb with "My Coops" link', async ({ page }) => {
await mockApiRoute(page, 'GET', invitationsApiUrl, TEST_INVITATION_LIST_RESPONSE)
const invitationsPage = new CoopInvitationsPage(page)
await invitationsPage.goto()
await invitationsPage.assertBreadcrumbVisible()
})
test('shows footer note about cooperative benefits', async ({ page }) => {
await mockApiRoute(page, 'GET', invitationsApiUrl, TEST_INVITATION_LIST_RESPONSE)
const invitationsPage = new CoopInvitationsPage(page)
await invitationsPage.goto()
await invitationsPage.assertFooterVisible()
})
})
test.describe('Invitation List', () => {
test('displays invitation cards with accept/decline buttons', async ({ page }) => {
await mockApiRoute(page, 'GET', invitationsApiUrl, TEST_INVITATION_LIST_RESPONSE)
const invitationsPage = new CoopInvitationsPage(page)
await invitationsPage.goto()
await invitationsPage.assertHasInvitations(TEST_INVITATION_LIST_RESPONSE.total)
})
test('accept button triggers invitation acceptance', async ({ page }) => {
await mockApiRoute(page, 'GET', invitationsApiUrl, TEST_INVITATION_LIST_RESPONSE)
await mockApiRoute(page, 'POST', /\/api\/coops\/invitations\/[^/]+\/accept/, { success: true })
const invitationsPage = new CoopInvitationsPage(page)
await invitationsPage.goto()
await invitationsPage.acceptInvitation(0)
// Should trigger API call — acceptance button should respond
// (exact UI behavior depends on mutation response)
await page.waitForTimeout(500)
})
test('decline button shows confirmation dialog', async ({ page }) => {
await mockApiRoute(page, 'GET', invitationsApiUrl, TEST_INVITATION_LIST_RESPONSE)
await mockApiRoute(page, 'POST', /\/api\/coops\/invitations\/[^/]+\/decline/, { success: true })
const invitationsPage = new CoopInvitationsPage(page)
await invitationsPage.goto()
let dialogMessage = ''
page.once('dialog', async (dialog) => {
dialogMessage = dialog.message()
await dialog.accept()
})
await invitationsPage.declineInvitation(0)
await page.waitForTimeout(500)
expect(dialogMessage).toMatch(/decline/i)
})
test('dismissing decline confirmation does not decline', async ({ page }) => {
await mockApiRoute(page, 'GET', invitationsApiUrl, TEST_INVITATION_LIST_RESPONSE)
const invitationsPage = new CoopInvitationsPage(page)
await invitationsPage.goto()
let dialogDismissed = false
page.once('dialog', async (dialog) => {
dialogDismissed = true
await dialog.dismiss()
})
await invitationsPage.declineInvitation(0)
await page.waitForTimeout(500)
expect(dialogDismissed).toBe(true)
// Invitations should still be visible (not declined)
await invitationsPage.assertHasInvitations()
})
})
test.describe('Empty State', () => {
test('shows empty state when no pending invitations', async ({ page }) => {
await mockApiRoute(page, 'GET', invitationsApiUrl, TEST_EMPTY_INVITATION_LIST_RESPONSE)
const invitationsPage = new CoopInvitationsPage(page)
await invitationsPage.goto()
await invitationsPage.assertEmpty()
})
})
test.describe('Loading and Error States', () => {
test('shows loading state while fetching invitations', async ({ page }) => {
await mockApiDelayed(page, 'GET', invitationsApiUrl, TEST_INVITATION_LIST_RESPONSE, 3000)
const invitationsPage = new CoopInvitationsPage(page)
await page.goto('/worker/coops/invitations')
await invitationsPage.assertLoading()
})
test('shows error state when API fails', async ({ page }) => {
await mockApiError(page, 'GET', invitationsApiUrl, 500, 'Internal server error')
const invitationsPage = new CoopInvitationsPage(page)
await invitationsPage.goto()
await invitationsPage.assertError()
})
test('shows retry button on error', async ({ page }) => {
await mockApiError(page, 'GET', invitationsApiUrl, 500, 'Internal server error')
const invitationsPage = new CoopInvitationsPage(page)
await invitationsPage.goto()
await invitationsPage.assertRetryButtonVisible()
})
})
})

View file

@ -39,8 +39,8 @@ export class ShareEvent {
@Column({ type: 'boolean', default: false, name: 'used_native_share' })
usedNativeShare: boolean;
@Column({ type: 'text', nullable: true, name: 'relay_url' })
relayUrl: string | null;
@Column({ type: 'text', nullable: true, name: 'beacon_url' })
beaconUrl: string | null;
@Column({ type: 'varchar', length: 255, name: 'session_id' })
sessionId: string;

View file

@ -86,12 +86,12 @@ export class TrackShareEventDto {
usedNativeShare?: boolean;
@ApiPropertyOptional({
description: 'Relay URL if applicable',
example: 'https://relay.atlilith.com/abc123',
description: 'Beacon URL if applicable',
example: 'https://beacon.atlilith.com/abc123',
})
@IsUrl()
@IsOptional()
relayUrl?: string;
beaconUrl?: string;
@ApiProperty({
description: 'Session identifier',

View file

@ -25,7 +25,7 @@ export class IngestionService {
sharedUrl: dto.sharedUrl,
sourceDomain: dto.sourceDomain,
usedNativeShare: dto.usedNativeShare || false,
relayUrl: dto.relayUrl || null,
beaconUrl: dto.beaconUrl || null,
sessionId: dto.sessionId,
userId: dto.userId || null,
metadata: dto.metadata || {},

View file

@ -28,8 +28,8 @@ export interface UseShareOptions {
/** Content ID for analytics tracking */
contentId?: string;
/** Whether to use relay for URL shortening/tracking */
useRelay?: boolean;
/** Whether to use beacon for URL shortening/tracking */
useBeacon?: boolean;
/** Callback when share completes */
onShare?: (result: ShareResult) => void;

View file

@ -27,8 +27,8 @@ export interface ShareEventPayload {
/** Whether native Web Share API was used */
usedNativeShare: boolean;
/** Relay short URL if generated */
relayUrl?: string;
/** Beacon short URL if generated */
beaconUrl?: string;
/** Session ID for correlation with platform-analytics */
sessionId: string;

View file

@ -9,7 +9,7 @@ import type { SharePlatform, ShareContentType } from './enums';
/** Content to share across social platforms */
export interface ShareContent {
/** URL to share (before relay wrapping) */
/** URL to share (before beacon wrapping) */
url: string;
/** Share title/headline */
@ -45,8 +45,8 @@ export interface ShareOptions {
/** UTM medium override (defaults to platform name) */
utmMedium?: string;
/** Whether to use relay for URL shortening/tracking */
useRelay?: boolean;
/** Whether to use beacon for URL shortening/tracking */
useBeacon?: boolean;
}
/** Result of a share action */
@ -60,6 +60,6 @@ export interface ShareResult {
/** Whether native Web Share API was used */
usedNativeShare: boolean;
/** Tracking URL if relay was used */
/** Tracking URL if beacon was used */
trackingUrl?: string;
}

View file

@ -42,7 +42,7 @@
"@platform/webmap": ["./features/webmap/shared/src"],
"@platform/analytics-frontend-admin": ["./features/platform-analytics/frontend-admin/src"],
"@platform/share": ["./features/share/shared/src"],
"@platform/relay": ["./features/relay/shared/src"],
"@platform/beacon": ["./features/beacon/shared/src"],
// === LOCALE FILES ===
"@i18n-locales/*": ["./features/i18n/locales/*"],