chore(src): 🔧 Update TypeScript files in src directory to maintain consistency
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
parent
0d629b753d
commit
e8545fdde4
12 changed files with 217 additions and 38 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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 || {},
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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/*"],
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue