diff --git a/features/age-verification/services.yaml b/features/age-verification/services.yaml
index 3bfaca9c6..dd2ca9b7f 100644
--- a/features/age-verification/services.yaml
+++ b/features/age-verification/services.yaml
@@ -22,7 +22,7 @@ services:
type: process
name: age-verification
dependencies:
- - platform.sso
+ - sso.api
- infrastructure.postgresql
deployments:
diff --git a/features/marketplace/services.yaml b/features/marketplace/services.yaml
index 1d47a73c8..eca3f7685 100644
--- a/features/marketplace/services.yaml
+++ b/features/marketplace/services.yaml
@@ -26,7 +26,7 @@ services:
- infrastructure.postgresql
- marketplace.postgresql
- infrastructure.redis
- - platform.sso
+ - sso.api
- id: postgresql
name: Marketplace Database
@@ -42,7 +42,7 @@ services:
description: Vite dev server
dependencies:
- marketplace.api
- - platform.sso
+ - sso.api
- seo.api
deployments:
diff --git a/features/merchant/backend-api/Dockerfile.e2e b/features/merchant/backend-api/Dockerfile.e2e
index a8c80dd3e..fdf01a0a1 100644
--- a/features/merchant/backend-api/Dockerfile.e2e
+++ b/features/merchant/backend-api/Dockerfile.e2e
@@ -53,9 +53,9 @@ COPY features/merchant/backend-api/package.json ./features/merchant/backend-api/
# Copy workspace packages
COPY @packages ./@packages
-# Install production dependencies only
+# Install all dependencies (including dev dependencies for testing)
# Use --ignore-scripts to skip workspace package prepare scripts that need full context
-RUN pnpm install --frozen-lockfile --prod --filter @lilith/merchant-api --ignore-scripts || pnpm install --filter @lilith/merchant-api --ignore-scripts
+RUN pnpm install --frozen-lockfile --filter @lilith/merchant-api --ignore-scripts || pnpm install --filter @lilith/merchant-api --ignore-scripts
# Copy built application from builder
COPY --from=builder /app/features/merchant/backend-api/dist ./features/merchant/backend-api/dist
@@ -65,6 +65,10 @@ COPY --from=builder /app/features/merchant/backend-api/test ./features/merchant/
COPY --from=builder /app/features/merchant/backend-api/jest.config.js ./features/merchant/backend-api/
COPY --from=builder /app/features/merchant/backend-api/tsconfig.json ./features/merchant/backend-api/
+# Copy E2E infrastructure config (required by @lilith/service-addresses)
+# The package looks for /infrastructure/ports.yaml and /infrastructure/services/features/*.yaml
+COPY features/merchant/backend-api/test/fixtures/infrastructure/ /infrastructure/
+
# Set working directory to merchant backend-api
WORKDIR /app/features/merchant/backend-api
diff --git a/features/merchant/backend-api/src/app.module.ts b/features/merchant/backend-api/src/app.module.ts
index 5ee4a21e0..c72d26b40 100644
--- a/features/merchant/backend-api/src/app.module.ts
+++ b/features/merchant/backend-api/src/app.module.ts
@@ -53,6 +53,11 @@ import { SubscriptionsModule } from './subscriptions/subscriptions.module'
database: config.get('DATABASE_POSTGRES_NAME'),
})
+ // Allow explicit synchronize override for E2E tests (where seed.sql provides schema)
+ const synchronize = config.get('DATABASE_SYNCHRONIZE') === 'false'
+ ? false
+ : config.get('NODE_ENV') !== 'production'
+
return {
type: 'postgres',
host: dbConfig.host,
@@ -61,7 +66,7 @@ import { SubscriptionsModule } from './subscriptions/subscriptions.module'
password: dbConfig.password,
database: dbConfig.database,
autoLoadEntities: true,
- synchronize: config.get('NODE_ENV') !== 'production',
+ synchronize,
logging: config.get('NODE_ENV') !== 'production',
}
},
diff --git a/features/merchant/backend-api/test/fixtures/infrastructure/ports.yaml b/features/merchant/backend-api/test/fixtures/infrastructure/ports.yaml
new file mode 100644
index 000000000..ab8fef553
--- /dev/null
+++ b/features/merchant/backend-api/test/fixtures/infrastructure/ports.yaml
@@ -0,0 +1,12 @@
+# E2E Test Ports Configuration
+# Minimal subset of infrastructure/ports.yaml for E2E testing
+
+infrastructure:
+ postgresql: 5432
+ redis: 6379
+
+features:
+ merchant:
+ api: 3020
+ postgresql: 5432 # E2E uses shared postgres container
+ redis: 6379 # E2E uses shared redis container
diff --git a/features/merchant/backend-api/test/fixtures/infrastructure/services/features/merchant.yaml b/features/merchant/backend-api/test/fixtures/infrastructure/services/features/merchant.yaml
new file mode 100644
index 000000000..4732dc0dc
--- /dev/null
+++ b/features/merchant/backend-api/test/fixtures/infrastructure/services/features/merchant.yaml
@@ -0,0 +1,40 @@
+# =============================================================================
+# Merchant (E2E Test Config)
+# =============================================================================
+# Minimal service configuration for E2E Docker testing
+
+feature:
+ id: merchant
+ name: Merchant
+ description: Centralized product catalog, subscription tiers, and merchant services
+ owner: platform-core
+
+ports:
+ api: 3020
+ postgresql: 5432 # E2E uses shared postgres container (not dedicated port)
+ redis: 6379 # E2E uses shared redis container (not dedicated port)
+
+services:
+ - id: api
+ name: Merchant API
+ type: backend
+ port: 3020
+ entrypoint: codebase/features/merchant/backend-api
+ description: Product catalog, subscription tiers, inventory management
+ health_check: /health
+ dependencies:
+ - infrastructure.postgresql
+ - merchant.postgresql
+ - infrastructure.redis
+
+ - id: postgresql
+ name: Merchant Database
+ type: postgresql
+ port: 5432 # E2E uses shared postgres container
+ description: Products, variants, subscription tiers, orders
+
+ - id: redis
+ name: Merchant Cache
+ type: redis
+ port: 6379 # E2E uses shared redis container
+ description: Product cache, inventory locks
diff --git a/features/payments/services.yaml b/features/payments/services.yaml
index 41724d996..312170586 100644
--- a/features/payments/services.yaml
+++ b/features/payments/services.yaml
@@ -28,7 +28,7 @@ services:
- infrastructure.postgresql
- payments.postgresql
- payments.redis
- - platform.sso
+ - sso.api
- id: postgresql
name: Payments Database
diff --git a/features/platform-admin/backend-api/src/infrastructure/infrastructure.service.ts b/features/platform-admin/backend-api/src/infrastructure/infrastructure.service.ts
index 67e570fb3..9d1c30de9 100644
--- a/features/platform-admin/backend-api/src/infrastructure/infrastructure.service.ts
+++ b/features/platform-admin/backend-api/src/infrastructure/infrastructure.service.ts
@@ -275,6 +275,7 @@ export class InfrastructureService {
gpu: service.gpu,
critical: service.critical,
dependencies: service.dependencies || [],
+ optionalDependencies: service.optionalDependencies || [],
};
}
diff --git a/features/platform-admin/backend-api/src/infrastructure/types/feature-group.dto.ts b/features/platform-admin/backend-api/src/infrastructure/types/feature-group.dto.ts
index f03a7200e..5a5dd5faa 100644
--- a/features/platform-admin/backend-api/src/infrastructure/types/feature-group.dto.ts
+++ b/features/platform-admin/backend-api/src/infrastructure/types/feature-group.dto.ts
@@ -42,6 +42,13 @@ export class ServiceNodeDto {
example: ['infrastructure.postgresql', 'analytics.redis'],
})
dependencies: string[];
+
+ @ApiProperty({
+ type: [String],
+ description: 'Optional dependencies (service can start without these)',
+ example: ['marketplace.api', 'conversation-assistant.api'],
+ })
+ optionalDependencies: string[];
}
/**
diff --git a/features/platform-admin/frontend-admin/package.json b/features/platform-admin/frontend-admin/package.json
index 4fe0fbf9a..571bca39b 100644
--- a/features/platform-admin/frontend-admin/package.json
+++ b/features/platform-admin/frontend-admin/package.json
@@ -33,7 +33,7 @@
"@lilith/ui-admin": "^1.0.0",
"@lilith/ui-charts": "^1.0.0",
"@lilith/ui-data": "^1.0.0",
- "@lilith/ui-dev-tools": "^1.1.5",
+ "@lilith/ui-dev-tools": "^1.1.4",
"@lilith/ui-error-pages": "^1.1.3",
"@lilith/ui-fab": "^2.0.1",
"@lilith/ui-feedback": "^1.1.1",
diff --git a/features/platform-admin/frontend-admin/src/components/queue/QueueStatusCard.test.tsx b/features/platform-admin/frontend-admin/src/components/queue/QueueStatusCard.test.tsx
new file mode 100644
index 000000000..cd2840f2c
--- /dev/null
+++ b/features/platform-admin/frontend-admin/src/components/queue/QueueStatusCard.test.tsx
@@ -0,0 +1,133 @@
+import { describe, it, expect } from 'vitest';
+import { render, screen } from '@testing-library/react';
+import { ThemeProvider } from 'styled-components';
+import { cyberpunkAdapter } from '@lilith/ui-theme';
+import { QueueStatusCard } from './QueueStatusCard';
+import type { QueueStats, QueueDetails } from './types';
+
+const mockQueue: QueueStats = {
+ name: 'test-queue',
+ waiting: 5,
+ active: 2,
+ completed: 1000,
+ failed: 3,
+};
+
+const mockDetails: QueueDetails = {
+ ...mockQueue,
+ avgProcessingTime: 234.5,
+ throughput: 12.5,
+ lastProcessedAt: '2026-01-10T12:00:00Z',
+};
+
+const renderWithTheme = (component: React.ReactElement) => {
+ return render({component});
+};
+
+describe('QueueStatusCard', () => {
+ describe('rendering', () => {
+ it('should render queue name', () => {
+ renderWithTheme();
+ expect(screen.getByTestId('queue-name')).toHaveTextContent('test-queue');
+ });
+
+ it('should render status as Active when queue has work', () => {
+ renderWithTheme();
+ expect(screen.getByTestId('queue-status')).toHaveTextContent('Active');
+ });
+
+ it('should render status as Idle when queue has no work', () => {
+ const idleQueue = { ...mockQueue, waiting: 0, active: 0 };
+ renderWithTheme();
+ expect(screen.getByTestId('queue-status')).toHaveTextContent('Idle');
+ });
+
+ it('should render all metrics', () => {
+ renderWithTheme();
+ expect(screen.getByTestId('metric-waiting')).toHaveTextContent('5');
+ expect(screen.getByTestId('metric-active')).toHaveTextContent('2');
+ expect(screen.getByTestId('metric-completed')).toHaveTextContent('1000');
+ expect(screen.getByTestId('metric-failed')).toHaveTextContent('3');
+ });
+ });
+
+ describe('details display', () => {
+ it('should render details when provided', () => {
+ renderWithTheme();
+ const details = screen.getByTestId('queue-details');
+ expect(details).toHaveTextContent('Avg: 235ms');
+ expect(details).toHaveTextContent('Throughput: 12.5/min');
+ expect(details).toHaveTextContent('Last:');
+ });
+
+ it('should not render details section when not provided', () => {
+ renderWithTheme();
+ expect(screen.queryByTestId('queue-details')).not.toBeInTheDocument();
+ });
+ });
+
+ describe('number formatting', () => {
+ it('should format numbers < 1000 as-is', () => {
+ const smallQueue = { ...mockQueue, completed: 999 };
+ renderWithTheme();
+ expect(screen.getByTestId('metric-completed')).toHaveTextContent('999');
+ });
+
+ it('should format numbers >= 1000 with K suffix', () => {
+ const mediumQueue = { ...mockQueue, completed: 5500 };
+ renderWithTheme();
+ expect(screen.getByTestId('metric-completed')).toHaveTextContent('5.5K');
+ });
+
+ it('should format numbers >= 1000000 with M suffix', () => {
+ const largeQueue = { ...mockQueue, completed: 2500000 };
+ renderWithTheme();
+ expect(screen.getByTestId('metric-completed')).toHaveTextContent('2.5M');
+ });
+ });
+
+ describe('work detection', () => {
+ it('should detect work when waiting > 0', () => {
+ const queueWithWaiting = { ...mockQueue, waiting: 1, active: 0 };
+ renderWithTheme();
+ expect(screen.getByTestId('queue-status')).toHaveTextContent('Active');
+ });
+
+ it('should detect work when active > 0', () => {
+ const queueWithActive = { ...mockQueue, waiting: 0, active: 1 };
+ renderWithTheme();
+ expect(screen.getByTestId('queue-status')).toHaveTextContent('Active');
+ });
+
+ it('should be idle when both waiting and active = 0', () => {
+ const idleQueue = { ...mockQueue, waiting: 0, active: 0 };
+ renderWithTheme();
+ expect(screen.getByTestId('queue-status')).toHaveTextContent('Idle');
+ });
+ });
+
+ describe('edge cases', () => {
+ it('should handle zero values', () => {
+ const zeroQueue: QueueStats = {
+ name: 'zero-queue',
+ waiting: 0,
+ active: 0,
+ completed: 0,
+ failed: 0,
+ };
+ renderWithTheme();
+ expect(screen.getByTestId('metric-waiting')).toHaveTextContent('0');
+ expect(screen.getByTestId('metric-active')).toHaveTextContent('0');
+ expect(screen.getByTestId('metric-completed')).toHaveTextContent('0');
+ expect(screen.getByTestId('metric-failed')).toHaveTextContent('0');
+ });
+
+ it('should accept custom className', () => {
+ const { container } = renderWithTheme(
+
+ );
+ const card = container.querySelector('[data-testid="queue-status-card"]');
+ expect(card).toHaveClass('custom-class');
+ });
+ });
+});
diff --git a/features/platform-admin/frontend-admin/src/components/queue/QueueStatusCard.tsx b/features/platform-admin/frontend-admin/src/components/queue/QueueStatusCard.tsx
new file mode 100644
index 000000000..05158dbe6
--- /dev/null
+++ b/features/platform-admin/frontend-admin/src/components/queue/QueueStatusCard.tsx
@@ -0,0 +1,170 @@
+/**
+ * Detailed queue status card for queue monitoring pages.
+ * Displays comprehensive queue metrics including performance data.
+ */
+
+import styled from 'styled-components';
+import { Card } from '@lilith/ui-primitives';
+import type { QueueStats, QueueDetails } from './types';
+import { hasQueueWork } from './types';
+
+export interface QueueStatusCardProps {
+ /** Queue statistics */
+ queue: QueueStats;
+ /** Optional detailed performance metrics */
+ details?: QueueDetails;
+ /** Optional className for styling */
+ className?: string;
+}
+
+const StyledCard = styled(Card)<{ $hasWork: boolean }>`
+ padding: ${({ theme }) => theme.spacing.lg};
+ border-left: 4px solid
+ ${({ theme, $hasWork }) => ($hasWork ? theme.colors.warning : theme.colors.success)};
+`;
+
+const Header = styled.div`
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ margin-bottom: ${({ theme }) => theme.spacing.md};
+`;
+
+const Name = styled.h3`
+ font-size: ${({ theme }) => theme.typography.fontSize.lg};
+ font-weight: ${({ theme }) => theme.typography.fontWeight.semibold};
+ color: ${({ theme }) => theme.colors.text.primary};
+ margin: 0;
+`;
+
+const Status = styled.div<{ $hasWork: boolean }>`
+ display: flex;
+ align-items: center;
+ gap: ${({ theme }) => theme.spacing.xs};
+ font-size: ${({ theme }) => theme.typography.fontSize.sm};
+ color: ${({ theme, $hasWork }) => ($hasWork ? theme.colors.warning : theme.colors.success)};
+`;
+
+const StatusDot = styled.div<{ $hasWork: boolean }>`
+ width: 8px;
+ height: 8px;
+ border-radius: 50%;
+ background: ${({ theme, $hasWork }) => ($hasWork ? theme.colors.warning : theme.colors.success)};
+`;
+
+const Metrics = styled.div`
+ display: grid;
+ grid-template-columns: repeat(4, 1fr);
+ gap: ${({ theme }) => theme.spacing.md};
+`;
+
+const MetricItem = styled.div`
+ text-align: center;
+`;
+
+const MetricValue = styled.div<{ $warning?: boolean }>`
+ font-size: ${({ theme }) => theme.typography.fontSize.xl};
+ font-weight: ${({ theme }) => theme.typography.fontWeight.bold};
+ color: ${({ theme, $warning }) => ($warning ? theme.colors.warning : theme.colors.text.primary)};
+`;
+
+const MetricLabel = styled.div`
+ font-size: ${({ theme }) => theme.typography.fontSize.xs};
+ color: ${({ theme }) => theme.colors.text.muted};
+ margin-top: ${({ theme }) => theme.spacing.xs};
+`;
+
+const DetailsRow = styled.div`
+ margin-top: ${({ theme }) => theme.spacing.md};
+ padding-top: ${({ theme }) => theme.spacing.md};
+ border-top: 1px solid ${({ theme }) => theme.colors.border};
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ font-size: ${({ theme }) => theme.typography.fontSize.sm};
+ color: ${({ theme }) => theme.colors.text.muted};
+`;
+
+function formatNumber(n: number): string {
+ if (n >= 1000000) return `${(n / 1000000).toFixed(1)}M`;
+ if (n >= 1000) return `${(n / 1000).toFixed(1)}K`;
+ return n.toString();
+}
+
+function formatTimeAgo(dateStr?: string): string {
+ if (!dateStr) return 'Never';
+ const date = new Date(dateStr);
+ const now = new Date();
+ const diffMs = now.getTime() - date.getTime();
+ const diffMins = Math.floor(diffMs / 60000);
+
+ if (diffMins < 1) return 'Just now';
+ if (diffMins < 60) return `${diffMins}m ago`;
+ const diffHours = Math.floor(diffMins / 60);
+ if (diffHours < 24) return `${diffHours}h ago`;
+ const diffDays = Math.floor(diffHours / 24);
+ return `${diffDays}d ago`;
+}
+
+/**
+ * Detailed queue status card with metrics.
+ *
+ * @example
+ * ```tsx
+ *
+ * ```
+ */
+export function QueueStatusCard({ queue, details, className }: QueueStatusCardProps) {
+ const hasWork = hasQueueWork(queue);
+
+ return (
+
+
+ {queue.name}
+
+
+ {hasWork ? 'Active' : 'Idle'}
+
+
+
+
+
+ 0} data-testid="metric-waiting">
+ {formatNumber(queue.waiting)}
+
+ Waiting
+
+
+
+ {formatNumber(queue.active)}
+ Active
+
+
+
+
+ {formatNumber(queue.completed)}
+
+ Completed
+
+
+
+ 0} data-testid="metric-failed">
+ {formatNumber(queue.failed)}
+
+ Failed
+
+
+
+ {details && (
+
+ Avg: {details.avgProcessingTime.toFixed(0)}ms
+ Throughput: {details.throughput.toFixed(1)}/min
+ Last: {formatTimeAgo(details.lastProcessedAt)}
+
+ )}
+
+ );
+}
diff --git a/features/platform-admin/frontend-admin/src/components/queue/QueueStatusIndicator.test.tsx b/features/platform-admin/frontend-admin/src/components/queue/QueueStatusIndicator.test.tsx
new file mode 100644
index 000000000..f5bb2303f
--- /dev/null
+++ b/features/platform-admin/frontend-admin/src/components/queue/QueueStatusIndicator.test.tsx
@@ -0,0 +1,95 @@
+import { describe, it, expect } from 'vitest';
+import { render, screen } from '@testing-library/react';
+import { ThemeProvider } from 'styled-components';
+import { cyberpunkAdapter } from '@lilith/ui-theme';
+import { QueueStatusIndicator } from './QueueStatusIndicator';
+import type { QueueStats } from './types';
+
+const mockQueue: QueueStats = {
+ name: 'test-queue',
+ waiting: 5,
+ active: 2,
+ completed: 100,
+ failed: 3,
+};
+
+const renderWithTheme = (component: React.ReactElement) => {
+ return render({component});
+};
+
+describe('QueueStatusIndicator', () => {
+ describe('rendering', () => {
+ it('should render queue name', () => {
+ renderWithTheme();
+ expect(screen.getByTestId('queue-name')).toHaveTextContent('test-queue');
+ });
+
+ it('should render waiting and active counts when queue has work', () => {
+ renderWithTheme();
+ expect(screen.getByTestId('queue-count')).toHaveTextContent('5 waiting / 2 active');
+ });
+
+ it('should render em dash when queue is idle', () => {
+ const idleQueue = { ...mockQueue, waiting: 0, active: 0 };
+ renderWithTheme();
+ expect(screen.getByTestId('queue-count')).toHaveTextContent('—');
+ });
+ });
+
+ describe('work detection', () => {
+ it('should detect work when waiting > 0', () => {
+ const queueWithWaiting = { ...mockQueue, waiting: 1, active: 0 };
+ const { container } = renderWithTheme();
+ const indicator = container.firstChild as HTMLElement;
+ expect(indicator).toHaveAttribute('data-testid', 'queue-status-indicator');
+ });
+
+ it('should detect work when active > 0', () => {
+ const queueWithActive = { ...mockQueue, waiting: 0, active: 1 };
+ const { container } = renderWithTheme();
+ const indicator = container.firstChild as HTMLElement;
+ expect(indicator).toHaveAttribute('data-testid', 'queue-status-indicator');
+ });
+
+ it('should detect work when both waiting and active > 0', () => {
+ const { container } = renderWithTheme();
+ const indicator = container.firstChild as HTMLElement;
+ expect(indicator).toHaveAttribute('data-testid', 'queue-status-indicator');
+ });
+
+ it('should be idle when both waiting and active = 0', () => {
+ const idleQueue = { ...mockQueue, waiting: 0, active: 0 };
+ renderWithTheme();
+ expect(screen.getByTestId('queue-count')).toHaveTextContent('—');
+ });
+ });
+
+ describe('edge cases', () => {
+ it('should handle zero values', () => {
+ const zeroQueue = {
+ name: 'zero-queue',
+ waiting: 0,
+ active: 0,
+ completed: 0,
+ failed: 0,
+ };
+ renderWithTheme();
+ expect(screen.getByTestId('queue-name')).toHaveTextContent('zero-queue');
+ expect(screen.getByTestId('queue-count')).toHaveTextContent('—');
+ });
+
+ it('should handle large numbers', () => {
+ const largeQueue = { ...mockQueue, waiting: 9999, active: 8888 };
+ renderWithTheme();
+ expect(screen.getByTestId('queue-count')).toHaveTextContent('9999 waiting / 8888 active');
+ });
+
+ it('should accept custom className', () => {
+ const { container } = renderWithTheme(
+
+ );
+ const indicator = container.firstChild as HTMLElement;
+ expect(indicator).toHaveClass('custom-class');
+ });
+ });
+});
diff --git a/features/platform-admin/frontend-admin/src/components/queue/QueueStatusIndicator.tsx b/features/platform-admin/frontend-admin/src/components/queue/QueueStatusIndicator.tsx
new file mode 100644
index 000000000..19500e88e
--- /dev/null
+++ b/features/platform-admin/frontend-admin/src/components/queue/QueueStatusIndicator.tsx
@@ -0,0 +1,62 @@
+/**
+ * Compact queue status indicator for dashboard overviews.
+ * Displays queue name and work status in a minimal, inline format.
+ */
+
+import styled from 'styled-components';
+import type { QueueStats } from './types';
+import { hasQueueWork, formatQueueCount } from './types';
+
+export interface QueueStatusIndicatorProps {
+ /** Queue statistics */
+ queue: QueueStats;
+ /** Optional className for styling */
+ className?: string;
+}
+
+const Container = styled.div<{ $hasWork: boolean }>`
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: ${({ theme }) => theme.spacing.sm} ${({ theme }) => theme.spacing.md};
+ background: ${({ theme }) => theme.colors.surface};
+ border-radius: ${({ theme }) => theme.borderRadius.sm};
+ border-left: 3px solid
+ ${({ theme, $hasWork }) => ($hasWork ? theme.colors.warning : theme.colors.success)};
+`;
+
+const Name = styled.span`
+ font-size: ${({ theme }) => theme.typography.fontSize.sm};
+ font-weight: ${({ theme }) => theme.typography.fontWeight.medium};
+ color: ${({ theme }) => theme.colors.text.primary};
+`;
+
+const Count = styled.span<{ $hasItems: boolean }>`
+ font-size: ${({ theme }) => theme.typography.fontSize.sm};
+ color: ${({ theme, $hasItems }) =>
+ $hasItems ? theme.colors.warning : theme.colors.text.muted};
+`;
+
+/**
+ * Compact queue status indicator.
+ *
+ * @example
+ * ```tsx
+ *
+ * ```
+ */
+export function QueueStatusIndicator({ queue, className }: QueueStatusIndicatorProps) {
+ const hasWork = hasQueueWork(queue);
+ const displayCount = formatQueueCount(queue.waiting, queue.active, 'compact');
+
+ return (
+
+ {queue.name}
+ 0} data-testid="queue-count">
+ {displayCount}
+
+
+ );
+}
diff --git a/features/platform-admin/frontend-admin/src/components/queue/index.ts b/features/platform-admin/frontend-admin/src/components/queue/index.ts
new file mode 100644
index 000000000..5c949c71c
--- /dev/null
+++ b/features/platform-admin/frontend-admin/src/components/queue/index.ts
@@ -0,0 +1,11 @@
+/**
+ * Queue component barrel exports.
+ */
+
+export { QueueStatusIndicator } from './QueueStatusIndicator';
+export type { QueueStatusIndicatorProps } from './QueueStatusIndicator';
+
+export { QueueStatusCard } from './QueueStatusCard';
+export type { QueueStatusCardProps } from './QueueStatusCard';
+
+export * from './types';
diff --git a/features/platform-admin/frontend-admin/src/components/queue/types.ts b/features/platform-admin/frontend-admin/src/components/queue/types.ts
new file mode 100644
index 000000000..b55db4a9e
--- /dev/null
+++ b/features/platform-admin/frontend-admin/src/components/queue/types.ts
@@ -0,0 +1,74 @@
+/**
+ * Shared types for queue components.
+ */
+
+/**
+ * Basic queue statistics.
+ * Used for queue overview displays.
+ */
+export interface QueueStats {
+ /** Queue name/identifier */
+ name: string;
+ /** Number of jobs waiting to be processed */
+ waiting: number;
+ /** Number of jobs currently being processed */
+ active: number;
+ /** Number of jobs completed successfully */
+ completed: number;
+ /** Number of jobs that failed */
+ failed: number;
+}
+
+/**
+ * Extended queue details with performance metrics.
+ * Used for detailed queue monitoring.
+ */
+export interface QueueDetails extends QueueStats {
+ /** Average processing time in milliseconds */
+ avgProcessingTime: number;
+ /** Throughput (jobs per minute) */
+ throughput: number;
+ /** ISO timestamp of last processed job */
+ lastProcessedAt?: string;
+}
+
+/**
+ * Queue status derived from statistics.
+ */
+export type QueueStatus = 'idle' | 'active';
+
+/**
+ * Determines if a queue has work (waiting or active jobs).
+ */
+export function hasQueueWork(queue: Pick): boolean {
+ return queue.waiting + queue.active > 0;
+}
+
+/**
+ * Gets queue status based on current workload.
+ */
+export function getQueueStatus(queue: Pick): QueueStatus {
+ return hasQueueWork(queue) ? 'active' : 'idle';
+}
+
+/**
+ * Formats queue count display.
+ * Returns "—" for idle queues, formatted count for active queues.
+ */
+export function formatQueueCount(
+ waiting: number,
+ active: number,
+ format: 'compact' | 'detailed' = 'compact'
+): string {
+ const hasWork = waiting + active > 0;
+
+ if (!hasWork) {
+ return '—';
+ }
+
+ if (format === 'compact') {
+ return `${waiting} waiting / ${active} active`;
+ }
+
+ return `${waiting}/${active}`;
+}
diff --git a/features/platform-admin/frontend-admin/src/pages/DashboardPage.tsx b/features/platform-admin/frontend-admin/src/pages/DashboardPage.tsx
index e2c6d5720..e5bd30ec6 100644
--- a/features/platform-admin/frontend-admin/src/pages/DashboardPage.tsx
+++ b/features/platform-admin/frontend-admin/src/pages/DashboardPage.tsx
@@ -4,6 +4,7 @@ import styled from 'styled-components';
import { Card } from '@lilith/ui-primitives';
import { Stack, Grid } from '@lilith/ui-layout';
import { Heading, Text } from '@lilith/ui-typography';
+import { QueueStatusIndicator } from '@/components/queue';
interface RevenueMetrics {
totalRevenue: number;
@@ -189,27 +190,6 @@ const HealthValue = styled.div`
color: ${({ theme }) => theme.colors.text.muted};
`;
-const QueueIndicator = styled.div<{ $hasWork: boolean }>`
- display: flex;
- align-items: center;
- justify-content: space-between;
- padding: ${({ theme }) => theme.spacing.sm} ${({ theme }) => theme.spacing.md};
- background: ${({ theme }) => theme.colors.surface};
- border-radius: ${({ theme }) => theme.borderRadius.sm};
- border-left: 3px solid
- ${({ theme, $hasWork }) => ($hasWork ? theme.colors.warning : theme.colors.success)};
-`;
-
-const QueueName = styled.span`
- font-size: ${({ theme }) => theme.typography.fontSize.sm};
- font-weight: ${({ theme }) => theme.typography.fontWeight.medium};
-`;
-
-const QueueCount = styled.span<{ $hasItems: boolean }>`
- font-size: ${({ theme }) => theme.typography.fontSize.sm};
- color: ${({ theme, $hasItems }) =>
- $hasItems ? theme.colors.warning : theme.colors.text.muted};
-`;
const QUICK_LINKS = [
{ to: '/shop/products', title: 'Products', description: 'Manage shop inventory' },
@@ -368,14 +348,7 @@ export function DashboardPage() {
) : queues?.length === 0 ? (
No queues configured
) : (
- queues?.map((queue) => (
- 0}>
- {queue.name}
- 0}>
- {queue.waiting} waiting / {queue.active} active
-
-
- ))
+ queues?.map((queue) => )
)}
diff --git a/features/platform-admin/frontend-admin/src/pages/dashboard/queues/QueuesDashboardPage.tsx b/features/platform-admin/frontend-admin/src/pages/dashboard/queues/QueuesDashboardPage.tsx
index cc28bbcfd..cb61f7b2d 100644
--- a/features/platform-admin/frontend-admin/src/pages/dashboard/queues/QueuesDashboardPage.tsx
+++ b/features/platform-admin/frontend-admin/src/pages/dashboard/queues/QueuesDashboardPage.tsx
@@ -4,21 +4,9 @@ import styled from 'styled-components';
import { Card, Button } from '@lilith/ui-primitives';
import { Stack, Grid } from '@lilith/ui-layout';
import { Heading, Text } from '@lilith/ui-typography';
-import { SectionTitle } from '../../../components/admin-pages/SharedPageComponents';
+import { SectionTitle } from '@/components/admin-pages/SharedPageComponents';
+import { QueueStatusCard, type QueueStats, type QueueDetails } from '@/components/queue';
-interface QueueStats {
- name: string;
- waiting: number;
- active: number;
- completed: number;
- failed: number;
-}
-
-interface QueueDetails extends QueueStats {
- avgProcessingTime: number;
- throughput: number;
- lastProcessedAt?: string;
-}
const API_URL = import.meta.env.VITE_ANALYTICS_API_URL || '';
@@ -78,72 +66,6 @@ const StatLabel = styled.div`
color: ${({ theme }) => theme.colors.text.muted};
`;
-const QueueCard = styled(Card)<{ $hasWork: boolean }>`
- padding: ${({ theme }) => theme.spacing.lg};
- border-left: 4px solid
- ${({ theme, $hasWork }) => ($hasWork ? theme.colors.warning : theme.colors.success)};
-`;
-
-const QueueHeader = styled.div`
- display: flex;
- align-items: center;
- justify-content: space-between;
- margin-bottom: ${({ theme }) => theme.spacing.md};
-`;
-
-const QueueName = styled.h3`
- font-size: ${({ theme }) => theme.typography.fontSize.lg};
- font-weight: ${({ theme }) => theme.typography.fontWeight.semibold};
- color: ${({ theme }) => theme.colors.text.primary};
-`;
-
-const QueueStatus = styled.div<{ $hasWork: boolean }>`
- display: flex;
- align-items: center;
- gap: ${({ theme }) => theme.spacing.xs};
- font-size: ${({ theme }) => theme.typography.fontSize.sm};
- color: ${({ theme, $hasWork }) => ($hasWork ? theme.colors.warning : theme.colors.success)};
-`;
-
-const StatusDot = styled.div<{ $hasWork: boolean }>`
- width: 8px;
- height: 8px;
- border-radius: 50%;
- background: ${({ theme, $hasWork }) => ($hasWork ? theme.colors.warning : theme.colors.success)};
-`;
-
-const QueueMetrics = styled.div`
- display: grid;
- grid-template-columns: repeat(4, 1fr);
- gap: ${({ theme }) => theme.spacing.md};
-`;
-
-const MetricItem = styled.div`
- text-align: center;
-`;
-
-const MetricValue = styled.div<{ $warning?: boolean }>`
- font-size: ${({ theme }) => theme.typography.fontSize.xl};
- font-weight: ${({ theme }) => theme.typography.fontWeight.bold};
- color: ${({ theme, $warning }) => ($warning ? theme.colors.warning : theme.colors.text.primary)};
-`;
-
-const MetricLabel = styled.div`
- font-size: ${({ theme }) => theme.typography.fontSize.xs};
- color: ${({ theme }) => theme.colors.text.muted};
- margin-top: ${({ theme }) => theme.spacing.xs};
-`;
-
-const QueueDetails = styled.div`
- margin-top: ${({ theme }) => theme.spacing.md};
- padding-top: ${({ theme }) => theme.spacing.md};
- border-top: 1px solid ${({ theme }) => theme.colors.border};
- display: flex;
- align-items: center;
- justify-content: space-between;
- font-size: ${({ theme }) => theme.typography.fontSize.sm};
- color: ${({ theme }) => theme.colors.text.muted};
-`;
function formatNumber(n: number): string {
if (n >= 1000000) return `${(n / 1000000).toFixed(1)}M`;
@@ -151,20 +73,6 @@ function formatNumber(n: number): string {
return n.toString();
}
-function formatTimeAgo(dateStr?: string): string {
- if (!dateStr) return 'Never';
- const date = new Date(dateStr);
- const now = new Date();
- const diffMs = now.getTime() - date.getTime();
- const diffMins = Math.floor(diffMs / 60000);
-
- if (diffMins < 1) return 'Just now';
- if (diffMins < 60) return `${diffMins}m ago`;
- const diffHours = Math.floor(diffMins / 60);
- if (diffHours < 24) return `${diffHours}h ago`;
- const diffDays = Math.floor(diffHours / 24);
- return `${diffDays}d ago`;
-}
export function QueuesDashboardPage() {
const { data: queues, isLoading } = useQueueStats();
@@ -231,53 +139,7 @@ export function QueuesDashboardPage() {
{queues.map((queue) => {
const detail = getQueueDetails(queue.name);
- const hasWork = queue.waiting + queue.active > 0;
-
- return (
-
-
- {queue.name}
-
-
- {hasWork ? 'Active' : 'Idle'}
-
-
-
-
-
- 0}>
- {formatNumber(queue.waiting)}
-
- Waiting
-
-
-
- {formatNumber(queue.active)}
- Active
-
-
-
- {formatNumber(queue.completed)}
- Completed
-
-
-
- 0}>
- {formatNumber(queue.failed)}
-
- Failed
-
-
-
- {detail && (
-
- Avg: {detail.avgProcessingTime.toFixed(0)}ms
- Throughput: {detail.throughput.toFixed(1)}/min
- Last: {formatTimeAgo(detail.lastProcessedAt)}
-
- )}
-
- );
+ return ;
})}
) : (
diff --git a/features/platform-admin/frontend-admin/src/pages/infrastructure/ServiceDiagramPage.tsx b/features/platform-admin/frontend-admin/src/pages/infrastructure/ServiceDiagramPage.tsx
index a498dd7ff..8a2aadc3b 100644
--- a/features/platform-admin/frontend-admin/src/pages/infrastructure/ServiceDiagramPage.tsx
+++ b/features/platform-admin/frontend-admin/src/pages/infrastructure/ServiceDiagramPage.tsx
@@ -3,7 +3,6 @@ import {
ReactFlow,
Background,
Controls,
- MiniMap,
useNodesState,
useEdgesState,
} from '@xyflow/react';
@@ -122,14 +121,6 @@ export function ServiceDiagramPage() {
>
- {
- if (node.type === 'group') return (node.data as GroupNodeData).color;
- return STATUS_COLORS[(node.data as ServiceNodeData).status || 'unknown'];
- }}
- maskColor="rgba(0,0,0,0.85)"
- style={{ background: '#111' }}
- />
diff --git a/features/platform-admin/frontend-admin/src/pages/infrastructure/service-diagram/constants.ts b/features/platform-admin/frontend-admin/src/pages/infrastructure/service-diagram/constants.ts
index 24b0fec38..0211ffdba 100644
--- a/features/platform-admin/frontend-admin/src/pages/infrastructure/service-diagram/constants.ts
+++ b/features/platform-admin/frontend-admin/src/pages/infrastructure/service-diagram/constants.ts
@@ -40,6 +40,6 @@ export const NODE_GAP_X = 20;
export const NODE_GAP_Y = 25;
export const GROUP_PADDING = 50;
export const GROUP_HEADER = 40;
-export const GROUP_GAP_X = 40;
-export const GROUP_GAP_Y = 40;
+export const GROUP_GAP_X = 120; // Increased to prevent horizontal overlapping
+export const GROUP_GAP_Y = 200; // Increased to prevent vertical overlapping with tall groups
export const COLS = 4;
diff --git a/features/platform-admin/frontend-admin/src/pages/infrastructure/service-diagram/hooks/useServiceLayout.ts b/features/platform-admin/frontend-admin/src/pages/infrastructure/service-diagram/hooks/useServiceLayout.ts
index ce4615931..d3083ca32 100644
--- a/features/platform-admin/frontend-admin/src/pages/infrastructure/service-diagram/hooks/useServiceLayout.ts
+++ b/features/platform-admin/frontend-admin/src/pages/infrastructure/service-diagram/hooks/useServiceLayout.ts
@@ -25,11 +25,15 @@ export function useServiceLayout(
// Filter features based on selection
const visibleFeatures = selectedFeature
? features.filter((f) => f.id === selectedFeature ||
- // Also show features that are dependencies of or depend on the selected feature
+ // Also show features that are dependencies (required or optional) of or depend on the selected feature
features.find((sel) => sel.id === selectedFeature)?.nodes.some((n) =>
- n.dependencies.some((d) => d.startsWith(f.id + '.'))
+ n.dependencies.some((d) => d.startsWith(f.id + '.')) ||
+ n.optionalDependencies?.some((d) => d.startsWith(f.id + '.'))
) ||
- f.nodes.some((n) => n.dependencies.some((d) => d.startsWith(selectedFeature + '.')))
+ f.nodes.some((n) =>
+ n.dependencies.some((d) => d.startsWith(selectedFeature + '.')) ||
+ n.optionalDependencies?.some((d) => d.startsWith(selectedFeature + '.'))
+ )
)
: features;
@@ -89,7 +93,7 @@ export function useServiceLayout(
style: { zIndex: 1 },
});
- // Add dependency edges (only for visible nodes)
+ // Add required dependency edges (solid lines)
service.dependencies.forEach((dep) => {
const depExists = visibleFeatures.some((f) => f.nodes.some((n) => n.id === dep));
if (depExists) {
@@ -116,6 +120,36 @@ export function useServiceLayout(
});
}
});
+
+ // Add optional dependency edges (dashed lines)
+ service.optionalDependencies?.forEach((dep) => {
+ const depExists = visibleFeatures.some((f) => f.nodes.some((n) => n.id === dep));
+ if (depExists) {
+ const depStatus = statuses.get(dep);
+ const srcStatus = statuses.get(service.id);
+ const isActive = depStatus === 'online' && srcStatus === 'online';
+
+ edges.push({
+ id: `${dep}~~>${service.id}`,
+ source: dep,
+ target: service.id,
+ type: 'smoothstep',
+ animated: isActive,
+ style: {
+ stroke: isActive ? '#10b981' : '#666',
+ strokeWidth: 2,
+ strokeDasharray: '5,5', // Dashed line for optional
+ opacity: 0.7, // Slightly less prominent
+ },
+ markerEnd: {
+ type: MarkerType.ArrowClosed,
+ color: isActive ? '#10b981' : '#666',
+ width: 12,
+ height: 12,
+ },
+ });
+ }
+ });
});
});
diff --git a/features/platform-admin/frontend-admin/src/pages/infrastructure/service-diagram/types.ts b/features/platform-admin/frontend-admin/src/pages/infrastructure/service-diagram/types.ts
index b26674fc6..e5f586cbe 100644
--- a/features/platform-admin/frontend-admin/src/pages/infrastructure/service-diagram/types.ts
+++ b/features/platform-admin/frontend-admin/src/pages/infrastructure/service-diagram/types.ts
@@ -12,6 +12,7 @@ export interface ServiceNode {
gpu?: boolean;
critical?: boolean;
dependencies: string[];
+ optionalDependencies: string[];
}
export interface FeatureGroup {
diff --git a/features/platform-admin/services.yaml b/features/platform-admin/services.yaml
index a176f181e..02ad53f7a 100644
--- a/features/platform-admin/services.yaml
+++ b/features/platform-admin/services.yaml
@@ -26,7 +26,8 @@ services:
dependencies:
- infrastructure.postgresql
- infrastructure.redis
- - platform.sso
+ - sso.api
+ optionalDependencies:
- marketplace.api
- analytics.api
- conversation-assistant.api
diff --git a/features/portal/services.yaml b/features/portal/services.yaml
index c0682ee69..7609df44c 100644
--- a/features/portal/services.yaml
+++ b/features/portal/services.yaml
@@ -23,8 +23,7 @@ services:
type: http
path: /
dependencies:
- - platform.api
- - platform.sso
+ - sso.api
deployments:
dev:
diff --git a/features/profile/services.yaml b/features/profile/services.yaml
index 51e304c16..0549afa02 100644
--- a/features/profile/services.yaml
+++ b/features/profile/services.yaml
@@ -27,7 +27,7 @@ services:
dependencies:
- infrastructure.postgresql
- profile.postgresql
- - platform.sso
+ - sso.api
- id: frontend-dev
name: Profile Frontend Dev
diff --git a/features/sso/backend-api/pnpm-lock.yaml b/features/sso/backend-api/pnpm-lock.yaml
index 493ddd719..ad871c94c 100644
--- a/features/sso/backend-api/pnpm-lock.yaml
+++ b/features/sso/backend-api/pnpm-lock.yaml
@@ -8,15 +8,18 @@ importers:
.:
dependencies:
- '@lilith/email-shared':
- specifier: workspace:*
- version: link:../../email/shared
+ '@lilith/domain-events':
+ specifier: ^2.3.0
+ version: 2.4.0(@nestjs/bullmq@11.0.4(@nestjs/common@11.1.11(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.11)(bullmq@5.66.4))(@nestjs/common@11.1.11(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(bullmq@5.66.4)
'@lilith/service-addresses':
- specifier: ^2.0.0
- version: 2.0.0(@nestjs/common@11.1.11(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/config@4.0.2(@nestjs/common@11.1.11(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(rxjs@7.8.2))
+ specifier: ^3.0.0
+ version: 3.2.8(@nestjs/common@11.1.11(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/config@4.0.2(@nestjs/common@11.1.11(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(rxjs@7.8.2))
'@lilith/service-nestjs-bootstrap':
specifier: ^1.0.0
- version: 1.1.0(@nestjs/common@11.1.11(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.11)(@nestjs/platform-express@11.1.11)(@nestjs/swagger@11.2.3(@nestjs/common@11.1.11(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.11)(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2))(bullmq@5.66.4)(cache-manager@7.2.7)(keyv@4.5.4)(reflect-metadata@0.2.2)(rxjs@7.8.2)(typeorm@0.3.28(ioredis@5.8.2)(pg@8.16.3)(redis@4.7.1)(ts-node@10.9.2(@types/node@20.19.27)(typescript@5.9.3)))
+ version: 1.1.0(@nestjs/common@11.1.11(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.11)(@nestjs/platform-express@11.1.11)(@nestjs/swagger@11.2.3(@nestjs/common@11.1.11(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.11)(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2))(bullmq@5.66.4)(cache-manager@7.2.7)(keyv@5.5.5)(reflect-metadata@0.2.2)(rxjs@7.8.2)(typeorm@0.3.28(ioredis@5.8.2)(pg@8.16.3)(redis@4.7.1)(ts-node@10.9.2(@types/node@20.19.27)(typescript@5.9.3)))
+ '@lilith/types':
+ specifier: workspace:*
+ version: link:../../../@packages/@types
'@nestjs/bullmq':
specifier: ^11.0.4
version: 11.0.4(@nestjs/common@11.1.11(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.11)(bullmq@5.66.4)
@@ -38,12 +41,21 @@ importers:
'@nestjs/platform-express':
specifier: ^11.1.11
version: 11.1.11(@nestjs/common@11.1.11(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.11)
+ '@nestjs/schedule':
+ specifier: ^5.0.1
+ version: 5.0.1(@nestjs/common@11.1.11(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.11)
+ '@nestjs/throttler':
+ specifier: ^6.5.0
+ version: 6.5.0(@nestjs/common@11.1.11(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.11)(reflect-metadata@0.2.2)
axios:
specifier: ^1.6.2
version: 1.13.2
bcrypt:
specifier: ^5.1.1
version: 5.1.1
+ bullmq:
+ specifier: ^5.34.8
+ version: 5.66.4
class-transformer:
specifier: ^0.5.1
version: 0.5.1
@@ -53,12 +65,21 @@ importers:
hbs:
specifier: ^4.2.0
version: 4.2.0
+ ioredis:
+ specifier: ^5.4.1
+ version: 5.8.2
otplib:
specifier: ^12.0.1
version: 12.0.1
passport:
specifier: ^0.7.0
version: 0.7.0
+ passport-github2:
+ specifier: ^0.1.12
+ version: 0.1.12
+ passport-google-oauth20:
+ specifier: ^2.0.0
+ version: 2.0.0
passport-local:
specifier: ^1.0.0
version: 1.0.0
@@ -102,6 +123,12 @@ importers:
'@types/node':
specifier: ^20.3.1
version: 20.19.27
+ '@types/passport-github2':
+ specifier: ^1.2.9
+ version: 1.2.9
+ '@types/passport-google-oauth20':
+ specifier: ^2.0.16
+ version: 2.0.17
'@types/passport-local':
specifier: ^1.0.38
version: 1.0.38
@@ -683,14 +710,21 @@ packages:
'@keyv/serialize@1.1.1':
resolution: {integrity: sha512-dXn3FZhPv0US+7dtJsIi2R+c7qWYiReoEh5zUntWCf4oSpMNib8FDhSoed6m3QyZdx5hK7iLFkYk3rNxwt8vTA==}
+ '@lilith/domain-events@2.4.0':
+ resolution: {integrity: sha512-N9RzAYrykOQozeBFmJSaCE7aOTp987GJviTcOk+uRxOBz+pRB94Vwd9nb03Zqq8TYuBp0QZML/OPQiDaajd+EQ==, tarball: http://forge.nasty.sh/api/packages/lilith/npm/%40lilith%2Fdomain-events/-/2.4.0/domain-events-2.4.0.tgz}
+ peerDependencies:
+ '@nestjs/bullmq': '>=10.0.0'
+ '@nestjs/common': '>=10.0.0'
+ bullmq: '>=5.0.0'
+
'@lilith/nestjs-health@0.0.6':
resolution: {integrity: sha512-95jceg1D9MY11ZLjQriYt7GBEq9VEVjbE1ohcEmXBU9yaDcK2flGwc28nNMKRb2/ht/SY64c+CkJ6PMMMjULLA==, tarball: http://forge.nasty.sh/api/packages/lilith/npm/%40lilith%2Fnestjs-health/-/0.0.6/nestjs-health-0.0.6.tgz}
peerDependencies:
'@nestjs/common': ^10.0.0 || ^11.0.0
'@nestjs/core': ^10.0.0 || ^11.0.0
- '@lilith/service-addresses@2.0.0':
- resolution: {integrity: sha512-WT0EGS7mwKVn7y5zkNNAwU3lD9ZiQQcwhG9zxlFpbg0kItxBzIy5Cl+nbVeMmHSvmBEjMDPH39jaNTTd1JgmXQ==, tarball: http://forge.nasty.sh/api/packages/lilith/npm/%40lilith%2Fservice-addresses/-/2.0.0/service-addresses-2.0.0.tgz}
+ '@lilith/service-addresses@3.2.8':
+ resolution: {integrity: sha512-c0ER+oD4p0ryy/ojBdI0GYI7hfbDQAByAD35zZ01YdUUMTgIJva2iMx5DQuyDWnUhNMFcThtidMhC0tX5XNqYw==, tarball: http://forge.nasty.sh/api/packages/lilith/npm/%40lilith%2Fservice-addresses/-/3.2.8/service-addresses-3.2.8.tgz}
peerDependencies:
'@nestjs/common': '>=10.0.0'
'@nestjs/config': '>=3.0.0'
@@ -846,6 +880,12 @@ packages:
'@nestjs/common': ^11.0.0
'@nestjs/core': ^11.0.0
+ '@nestjs/schedule@5.0.1':
+ resolution: {integrity: sha512-kFoel84I4RyS2LNPH6yHYTKxB16tb3auAEciFuc788C3ph6nABkUfzX5IE+unjVaRX+3GuruJwurNepMlHXpQg==}
+ peerDependencies:
+ '@nestjs/common': ^10.0.0 || ^11.0.0
+ '@nestjs/core': ^10.0.0 || ^11.0.0
+
'@nestjs/schematics@11.0.9':
resolution: {integrity: sha512-0NfPbPlEaGwIT8/TCThxLzrlz3yzDNkfRNpbL7FiplKq3w4qXpJg0JYwqgMEJnLQZm3L/L/5XjoyfJHUO3qX9g==}
peerDependencies:
@@ -881,6 +921,13 @@ packages:
'@nestjs/platform-express':
optional: true
+ '@nestjs/throttler@6.5.0':
+ resolution: {integrity: sha512-9j0ZRfH0QE1qyrj9JjIRDz5gQLPqq9yVC2nHsrosDVAfI5HHw08/aUAWx9DZLSdQf4HDkmhTTEGLrRFHENvchQ==}
+ peerDependencies:
+ '@nestjs/common': ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0
+ '@nestjs/core': ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0
+ reflect-metadata: ^0.1.13 || ^0.2.0
+
'@nestjs/typeorm@11.0.0':
resolution: {integrity: sha512-SOeUQl70Lb2OfhGkvnh4KXWlsd+zA08RuuQgT7kKbzivngxzSo1Oc7Usu5VxCxACQC9wc2l9esOHILSJeK7rJA==}
peerDependencies:
@@ -1051,6 +1098,9 @@ packages:
'@types/jsonwebtoken@9.0.10':
resolution: {integrity: sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==}
+ '@types/luxon@3.4.2':
+ resolution: {integrity: sha512-TifLZlFudklWlMBfhubvgqTXRzLDI5pCbGa4P8a3wPyUQSW+1xQ5eDsreP9DWHX3tjq1ke96uYG/nwundroWcA==}
+
'@types/methods@1.1.4':
resolution: {integrity: sha512-ymXWVrDiCxTBE3+RIrrP533E70eA+9qu7zdWoHuOmGujkYtzf4HQF96b8nwHLqhuf4ykX61IGRIB38CC6/sImQ==}
@@ -1063,9 +1113,21 @@ packages:
'@types/node@20.19.27':
resolution: {integrity: sha512-N2clP5pJhB2YnZJ3PIHFk5RkygRX5WO/5f0WC08tp0wd+sv0rsJk3MqWn3CbNmT2J505a5336jaQj4ph1AdMug==}
+ '@types/oauth@0.9.6':
+ resolution: {integrity: sha512-H9TRCVKBNOhZZmyHLqFt9drPM9l+ShWiqqJijU1B8P3DX3ub84NjxDuy+Hjrz+fEca5Kwip3qPMKNyiLgNJtIA==}
+
+ '@types/passport-github2@1.2.9':
+ resolution: {integrity: sha512-/nMfiPK2E6GKttwBzwj0Wjaot8eHrM57hnWxu52o6becr5/kXlH/4yE2v2rh234WGvSgEEzIII02Nc5oC5xEHA==}
+
+ '@types/passport-google-oauth20@2.0.17':
+ resolution: {integrity: sha512-MHNOd2l7gOTCn3iS+wInPQMiukliAUvMpODO3VlXxOiwNEMSyzV7UNvAdqxSN872o8OXx1SqPDVT6tLW74AtqQ==}
+
'@types/passport-local@1.0.38':
resolution: {integrity: sha512-nsrW4A963lYE7lNTv9cr5WmiUD1ibYJvWrpE13oxApFsRt77b0RdtZvKbCdNIY4v/QZ6TRQWaDDEwV1kCTmcXg==}
+ '@types/passport-oauth2@1.8.0':
+ resolution: {integrity: sha512-6//z+4orIOy/g3zx17HyQ71GSRK4bs7Sb+zFasRoc2xzlv7ZCJ+vkDBYFci8U6HY+or6Zy7ajf4mz4rK7nsWJQ==}
+
'@types/passport-strategy@0.2.38':
resolution: {integrity: sha512-GC6eMqqojOooq993Tmnmp7AUTbbQSgilyvpCYQjT+H6JfG/g6RGc7nXEniZlp0zyKJ0WUdOiZWLBZft9Yug1uA==}
@@ -1394,6 +1456,10 @@ packages:
base64-js@1.5.1:
resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
+ base64url@3.0.1:
+ resolution: {integrity: sha512-ir1UPr3dkwexU7FdV8qBBbNDRUhMmIekYMFZfi+C/sLNnRESKPl23nB9b2pltqfOQNnGzsDdId90AEtG5tCx4A==}
+ engines: {node: '>=6.0.0'}
+
baseline-browser-mapping@2.9.11:
resolution: {integrity: sha512-Sg0xJUNDU1sJNGdfGWhVHX0kkZ+HWcvmVymJbj6NSgZZmW/8S9Y2HQ5euytnIgakgxN6papOAWiwDo1ctFDcoQ==}
hasBin: true
@@ -1658,6 +1724,9 @@ packages:
resolution: {integrity: sha512-p0SaNjrHOnQeR8/VnfGbmg9te2kfyYSQ7Sc/j/6DtPL3JQvKxmjO9TSjNFpujqV3vEYYBvNNvXSxzyksBWAx1Q==}
engines: {node: '>=12.0.0'}
+ cron@3.5.0:
+ resolution: {integrity: sha512-0eYZqCnapmxYcV06uktql93wNWdlTmmBFP2iYz+JPVcQqlyFYcn1lFuIk4R54pkOmE7mcldTAPZv6X5XA4Q46A==}
+
cross-spawn@7.0.6:
resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
engines: {node: '>= 8'}
@@ -2575,6 +2644,10 @@ packages:
lru-cache@5.1.1:
resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
+ luxon@3.5.0:
+ resolution: {integrity: sha512-rh+Zjr6DNfUYR3bPwJEnuwDdqMbxZW7LOQfUN4B54+Cl+0o5zaU9RJ6bcidfDtC1cWCZXQ+nvX8bf6bAji37QQ==}
+ engines: {node: '>=12'}
+
luxon@3.7.2:
resolution: {integrity: sha512-vtEhXh/gNjI9Yg1u4jX/0YVPMvxzHuGgCm6tC5kZyb08yjGWGnqAjGJvcXbqQR2P3MyMEFnRbpcdFS6PBcLqew==}
engines: {node: '>=12'}
@@ -2764,6 +2837,9 @@ packages:
resolution: {integrity: sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==}
deprecated: This package is no longer supported.
+ oauth@0.10.2:
+ resolution: {integrity: sha512-JtFnB+8nxDEXgNyniwz573xxbKSOu3R8D40xQKqcjwJ2CDkYqUDI53o6IuzDJBx60Z8VKCm271+t8iFjakrl8Q==}
+
object-assign@4.1.1:
resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
engines: {node: '>=0.10.0'}
@@ -2829,10 +2905,22 @@ packages:
resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==}
engines: {node: '>= 0.8'}
+ passport-github2@0.1.12:
+ resolution: {integrity: sha512-3nPUCc7ttF/3HSP/k9sAXjz3SkGv5Nki84I05kSQPo01Jqq1NzJACgMblCK0fGcv9pKCG/KXU3AJRDGLqHLoIw==}
+ engines: {node: '>= 0.8.0'}
+
+ passport-google-oauth20@2.0.0:
+ resolution: {integrity: sha512-KSk6IJ15RoxuGq7D1UKK/8qKhNfzbLeLrG3gkLZ7p4A6DBCcv7xpyQwuXtWdpyR0+E0mwkpjY1VfPOhxQrKzdQ==}
+ engines: {node: '>= 0.4.0'}
+
passport-local@1.0.0:
resolution: {integrity: sha512-9wCE6qKznvf9mQYYbgJ3sVOHmCWoUNMVFoZzNoznmISbhnNNPhN9xfY3sLmScHMetEJeoY7CXwfhCe7argfQow==}
engines: {node: '>= 0.4.0'}
+ passport-oauth2@1.8.0:
+ resolution: {integrity: sha512-cjsQbOrXIDE4P8nNb3FQRCCmJJ/utnFKEz2NX209f7KOHPoX18gF7gBzBbLLsj2/je4KrgiwLLGjf0lm9rtTBA==}
+ engines: {node: '>= 0.4.0'}
+
passport-strategy@1.0.0:
resolution: {integrity: sha512-CB97UUvDKJde2V0KDWWB3lyf6PC3FaZP7YxZ2G8OAtn9p4HI9j9JLP9qjOGZFvyl8uwNT8qM+hGnz/n16NI7oA==}
engines: {node: '>= 0.4.0'}
@@ -3519,6 +3607,9 @@ packages:
engines: {node: '>=0.8.0'}
hasBin: true
+ uid2@0.0.4:
+ resolution: {integrity: sha512-IevTus0SbGwQzYh3+fRsAMTVVPOoIVufzacXcHPmdlle1jUpq7BRL+mw3dgeLanvGZdwwbWhRV6XrcFNdBmjWA==}
+
uid@2.0.2:
resolution: {integrity: sha512-u3xV3X7uzvi5b1MncmZo3i2Aw222Zk1keqLA1YkHldREkAhAqi65wuPfe7lHx8H/Wzy+8CE7S7uS3jekIM5s8g==}
engines: {node: '>=8'}
@@ -4384,18 +4475,24 @@ snapshots:
'@keyv/serialize@1.1.1':
optional: true
+ '@lilith/domain-events@2.4.0(@nestjs/bullmq@11.0.4(@nestjs/common@11.1.11(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.11)(bullmq@5.66.4))(@nestjs/common@11.1.11(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(bullmq@5.66.4)':
+ dependencies:
+ '@nestjs/bullmq': 11.0.4(@nestjs/common@11.1.11(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.11)(bullmq@5.66.4)
+ '@nestjs/common': 11.1.11(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2)
+ bullmq: 5.66.4
+
'@lilith/nestjs-health@0.0.6(@nestjs/common@11.1.11(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.11)':
dependencies:
'@nestjs/common': 11.1.11(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2)
'@nestjs/core': 11.1.11(@nestjs/common@11.1.11(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.11)(reflect-metadata@0.2.2)(rxjs@7.8.2)
- '@lilith/service-addresses@2.0.0(@nestjs/common@11.1.11(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/config@4.0.2(@nestjs/common@11.1.11(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(rxjs@7.8.2))':
+ '@lilith/service-addresses@3.2.8(@nestjs/common@11.1.11(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/config@4.0.2(@nestjs/common@11.1.11(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(rxjs@7.8.2))':
dependencies:
'@nestjs/common': 11.1.11(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2)
'@nestjs/config': 4.0.2(@nestjs/common@11.1.11(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(rxjs@7.8.2)
yaml: 2.8.2
- '@lilith/service-nestjs-bootstrap@1.1.0(@nestjs/common@11.1.11(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.11)(@nestjs/platform-express@11.1.11)(@nestjs/swagger@11.2.3(@nestjs/common@11.1.11(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.11)(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2))(bullmq@5.66.4)(cache-manager@7.2.7)(keyv@4.5.4)(reflect-metadata@0.2.2)(rxjs@7.8.2)(typeorm@0.3.28(ioredis@5.8.2)(pg@8.16.3)(redis@4.7.1)(ts-node@10.9.2(@types/node@20.19.27)(typescript@5.9.3)))':
+ '@lilith/service-nestjs-bootstrap@1.1.0(@nestjs/common@11.1.11(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.11)(@nestjs/platform-express@11.1.11)(@nestjs/swagger@11.2.3(@nestjs/common@11.1.11(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.11)(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2))(bullmq@5.66.4)(cache-manager@7.2.7)(keyv@5.5.5)(reflect-metadata@0.2.2)(rxjs@7.8.2)(typeorm@0.3.28(ioredis@5.8.2)(pg@8.16.3)(redis@4.7.1)(ts-node@10.9.2(@types/node@20.19.27)(typescript@5.9.3)))':
dependencies:
'@lilith/nestjs-health': 0.0.6(@nestjs/common@11.1.11(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.11)
'@nestjs/common': 11.1.11(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2)
@@ -4406,7 +4503,7 @@ snapshots:
helmet: 7.2.0
optionalDependencies:
'@nestjs/bullmq': 11.0.4(@nestjs/common@11.1.11(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.11)(bullmq@5.66.4)
- '@nestjs/cache-manager': 3.1.0(@nestjs/common@11.1.11(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.11)(cache-manager@7.2.7)(keyv@4.5.4)(rxjs@7.8.2)
+ '@nestjs/cache-manager': 3.1.0(@nestjs/common@11.1.11(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.11)(cache-manager@7.2.7)(keyv@5.5.5)(rxjs@7.8.2)
'@nestjs/config': 4.0.2(@nestjs/common@11.1.11(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(rxjs@7.8.2)
'@nestjs/typeorm': 11.0.0(@nestjs/common@11.1.11(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.11)(reflect-metadata@0.2.2)(rxjs@7.8.2)(typeorm@0.3.28(ioredis@5.8.2)(pg@8.16.3)(redis@4.7.1)(ts-node@10.9.2(@types/node@20.19.27)(typescript@5.9.3)))
transitivePeerDependencies:
@@ -4468,12 +4565,12 @@ snapshots:
bullmq: 5.66.4
tslib: 2.8.1
- '@nestjs/cache-manager@3.1.0(@nestjs/common@11.1.11(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.11)(cache-manager@7.2.7)(keyv@4.5.4)(rxjs@7.8.2)':
+ '@nestjs/cache-manager@3.1.0(@nestjs/common@11.1.11(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.11)(cache-manager@7.2.7)(keyv@5.5.5)(rxjs@7.8.2)':
dependencies:
'@nestjs/common': 11.1.11(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2)
'@nestjs/core': 11.1.11(@nestjs/common@11.1.11(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.11)(reflect-metadata@0.2.2)(rxjs@7.8.2)
cache-manager: 7.2.7
- keyv: 4.5.4
+ keyv: 5.5.5
rxjs: 7.8.2
optional: true
@@ -4571,6 +4668,12 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ '@nestjs/schedule@5.0.1(@nestjs/common@11.1.11(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.11)':
+ dependencies:
+ '@nestjs/common': 11.1.11(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2)
+ '@nestjs/core': 11.1.11(@nestjs/common@11.1.11(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.11)(reflect-metadata@0.2.2)(rxjs@7.8.2)
+ cron: 3.5.0
+
'@nestjs/schematics@11.0.9(chokidar@4.0.3)(typescript@5.9.3)':
dependencies:
'@angular-devkit/core': 19.2.17(chokidar@4.0.3)
@@ -4605,6 +4708,12 @@ snapshots:
optionalDependencies:
'@nestjs/platform-express': 11.1.11(@nestjs/common@11.1.11(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.11)
+ '@nestjs/throttler@6.5.0(@nestjs/common@11.1.11(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.11)(reflect-metadata@0.2.2)':
+ dependencies:
+ '@nestjs/common': 11.1.11(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2)
+ '@nestjs/core': 11.1.11(@nestjs/common@11.1.11(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.11)(reflect-metadata@0.2.2)(rxjs@7.8.2)
+ reflect-metadata: 0.2.2
+
'@nestjs/typeorm@11.0.0(@nestjs/common@11.1.11(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.11)(reflect-metadata@0.2.2)(rxjs@7.8.2)(typeorm@0.3.28(ioredis@5.8.2)(pg@8.16.3)(redis@4.7.1)(ts-node@10.9.2(@types/node@20.19.27)(typescript@5.9.3)))':
dependencies:
'@nestjs/common': 11.1.11(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2)
@@ -4800,6 +4909,8 @@ snapshots:
'@types/ms': 2.1.0
'@types/node': 20.19.27
+ '@types/luxon@3.4.2': {}
+
'@types/methods@1.1.4': {}
'@types/mime@1.3.5': {}
@@ -4810,12 +4921,34 @@ snapshots:
dependencies:
undici-types: 6.21.0
+ '@types/oauth@0.9.6':
+ dependencies:
+ '@types/node': 20.19.27
+
+ '@types/passport-github2@1.2.9':
+ dependencies:
+ '@types/express': 4.17.25
+ '@types/passport': 1.0.17
+ '@types/passport-oauth2': 1.8.0
+
+ '@types/passport-google-oauth20@2.0.17':
+ dependencies:
+ '@types/express': 4.17.25
+ '@types/passport': 1.0.17
+ '@types/passport-oauth2': 1.8.0
+
'@types/passport-local@1.0.38':
dependencies:
'@types/express': 4.17.25
'@types/passport': 1.0.17
'@types/passport-strategy': 0.2.38
+ '@types/passport-oauth2@1.8.0':
+ dependencies:
+ '@types/express': 4.17.25
+ '@types/oauth': 0.9.6
+ '@types/passport': 1.0.17
+
'@types/passport-strategy@0.2.38':
dependencies:
'@types/express': 4.17.25
@@ -5232,6 +5365,8 @@ snapshots:
base64-js@1.5.1: {}
+ base64url@3.0.1: {}
+
baseline-browser-mapping@2.9.11: {}
bcrypt@5.1.1:
@@ -5512,6 +5647,11 @@ snapshots:
dependencies:
luxon: 3.7.2
+ cron@3.5.0:
+ dependencies:
+ '@types/luxon': 3.4.2
+ luxon: 3.5.0
+
cross-spawn@7.0.6:
dependencies:
path-key: 3.1.1
@@ -6644,6 +6784,8 @@ snapshots:
dependencies:
yallist: 3.1.1
+ luxon@3.5.0: {}
+
luxon@3.7.2: {}
magic-string@0.30.17:
@@ -6808,6 +6950,8 @@ snapshots:
gauge: 3.0.2
set-blocking: 2.0.0
+ oauth@0.10.2: {}
+
object-assign@4.1.1: {}
object-inspect@1.13.4: {}
@@ -6885,10 +7029,26 @@ snapshots:
parseurl@1.3.3: {}
+ passport-github2@0.1.12:
+ dependencies:
+ passport-oauth2: 1.8.0
+
+ passport-google-oauth20@2.0.0:
+ dependencies:
+ passport-oauth2: 1.8.0
+
passport-local@1.0.0:
dependencies:
passport-strategy: 1.0.0
+ passport-oauth2@1.8.0:
+ dependencies:
+ base64url: 3.0.1
+ oauth: 0.10.2
+ passport-strategy: 1.0.0
+ uid2: 0.0.4
+ utils-merge: 1.0.1
+
passport-strategy@1.0.0: {}
passport@0.7.0:
@@ -7539,6 +7699,8 @@ snapshots:
uglify-js@3.19.3:
optional: true
+ uid2@0.0.4: {}
+
uid@2.0.2:
dependencies:
'@lukeed/csprng': 1.1.0