This repository has no description
1import { EnvironmentConfigService } from '../../config/EnvironmentConfigService';
2import { jwtConfig, oauthConfig } from '../../config';
3import { JwtTokenService } from '../../../../modules/user/infrastructure/services/JwtTokenService';
4import {
5 AtProtoOAuthProcessor,
6 OAuthClientFactory,
7} from '../../../../modules/user/infrastructure';
8import { UserAuthenticationService } from '../../../../modules/user/infrastructure/services/UserAuthenticationService';
9import { ATProtoAgentService } from '../../../../modules/atproto/infrastructure/services/ATProtoAgentService';
10import { IFramelyMetadataService } from '../../../../modules/cards/infrastructure/IFramelyMetadataService';
11import { CitoidMetadataService } from '../../../../modules/cards/infrastructure/CitoidMetadataService';
12import { CompositeMetadataService } from '../../../../modules/cards/infrastructure/CompositeMetadataService';
13import { CachedMetadataService } from '../../../../modules/cards/infrastructure/CachedMetadataService';
14import { BlueskyProfileService } from '../../../../modules/atproto/infrastructure/services/BlueskyProfileService';
15import { CachedBlueskyProfileService } from '../../../../modules/atproto/infrastructure/services/CachedBlueskyProfileService';
16import { ATProtoCollectionPublisher } from '../../../../modules/atproto/infrastructure/publishers/ATProtoCollectionPublisher';
17import { ATProtoCardPublisher } from '../../../../modules/atproto/infrastructure/publishers/ATProtoCardPublisher';
18import { FakeCollectionPublisher } from '../../../../modules/cards/tests/utils/FakeCollectionPublisher';
19import { FakeCardPublisher } from '../../../../modules/cards/tests/utils/FakeCardPublisher';
20import { CardLibraryService } from '../../../../modules/cards/domain/services/CardLibraryService';
21import { CardCollectionService } from '../../../../modules/cards/domain/services/CardCollectionService';
22import { AuthMiddleware } from '../middleware/AuthMiddleware';
23import { Repositories } from './RepositoryFactory';
24import { NodeOAuthClient } from '@atproto/oauth-client-node';
25import { AppPasswordSessionService } from 'src/modules/atproto/infrastructure/services/AppPasswordSessionService';
26import { AtpAppPasswordProcessor } from 'src/modules/atproto/infrastructure/services/AtpAppPasswordProcessor';
27import { ICollectionPublisher } from 'src/modules/cards/application/ports/ICollectionPublisher';
28import { ICardPublisher } from 'src/modules/cards/application/ports/ICardPublisher';
29import { IMetadataService } from 'src/modules/cards/domain/services/IMetadataService';
30import { BullMQEventSubscriber } from '../../events/BullMQEventSubscriber';
31import { BullMQEventPublisher } from '../../events/BullMQEventPublisher';
32import { InMemoryEventPublisher } from '../../events/InMemoryEventPublisher';
33import { InMemoryEventSubscriber } from '../../events/InMemoryEventSubscriber';
34import Redis from 'ioredis';
35// Mock/Fake service imports
36import { FakeJwtTokenService } from '../../../../modules/user/infrastructure/services/FakeJwtTokenService';
37import { FakeAtProtoOAuthProcessor } from '../../../../modules/atproto/infrastructure/services/FakeAtProtoOAuthProcessor';
38import { FakeUserAuthenticationService } from '../../../../modules/user/infrastructure/services/FakeUserAuthenticationService';
39import { FakeAgentService } from '../../../../modules/atproto/infrastructure/services/FakeAgentService';
40import { FakeAppPasswordSessionService } from '../../../../modules/atproto/infrastructure/services/FakeAppPasswordSessionService';
41import { FakeAtpAppPasswordProcessor } from '../../../../modules/atproto/infrastructure/services/FakeAtpAppPasswordProcessor';
42import { ITokenService } from 'src/modules/user/application/services/ITokenService';
43import { IOAuthProcessor } from 'src/modules/user/application/services/IOAuthProcessor';
44import { IAppPasswordProcessor } from 'src/modules/atproto/application/IAppPasswordProcessor';
45import { IUserAuthenticationService } from 'src/modules/user/domain/services/IUserAuthenticationService';
46import { IAgentService } from 'src/modules/atproto/application/IAgentService';
47import { IProfileService } from 'src/modules/cards/domain/services/IProfileService';
48import { IEventPublisher } from '../../../application/events/IEventPublisher';
49import { QueueName } from '../../events/QueueConfig';
50import { RedisFactory } from '../../redis/RedisFactory';
51import { IEventSubscriber } from 'src/shared/application/events/IEventSubscriber';
52import { FeedService } from '../../../../modules/feeds/domain/services/FeedService';
53import { ATProtoIdentityResolutionService } from '../../../../modules/atproto/infrastructure/services/ATProtoIdentityResolutionService';
54import { IIdentityResolutionService } from '../../../../modules/atproto/domain/services/IIdentityResolutionService';
55import { CookieService } from '../services/CookieService';
56import { InMemorySagaStateStore } from '../../../../modules/feeds/infrastructure/InMemorySagaStateStore';
57import { RedisSagaStateStore } from '../../../../modules/feeds/infrastructure/RedisSagaStateStore';
58import { ISagaStateStore } from 'src/modules/feeds/application/sagas/ISagaStateStore';
59import { SearchService } from '../../../../modules/search/domain/services/SearchService';
60import { IVectorDatabase } from '../../../../modules/search/domain/IVectorDatabase';
61import { InMemoryVectorDatabase } from '../../../../modules/search/infrastructure/InMemoryVectorDatabase';
62import { UpstashVectorDatabase } from '../../../../modules/search/infrastructure/UpstashVectorDatabase';
63import { NotificationService } from '../../../../modules/notifications/domain/services/NotificationService';
64import { FakeLeafletSearchService } from 'src/modules/search/infrastructure/FakeLeafletSearchService';
65import { ILeafletSearchService } from 'src/modules/search/domain/services/ILeafletSearchService';
66import { ConstellationLeafletSearchService } from 'src/modules/search/domain/services/ConstellationLeafletSearchService';
67import { CachedLeafletSearchService } from 'src/modules/search/infrastructure/CachedLeafletSearchService';
68import { IAtProtoRepoService } from '../../../../modules/atproto/application/IAtProtoRepoService';
69import { ATProtoRepoService } from '../../../../modules/atproto/infrastructure/services/ATProtoRepoService';
70import { FakeAtProtoRepoService } from '../../../../modules/atproto/infrastructure/services/FakeAtProtoRepoService';
71import { DistributedLockServiceFactory } from '../../locking/DistributedLockServiceFactory';
72
73// Shared services needed by both web app and workers
74export interface SharedServices {
75 tokenService: ITokenService;
76 userAuthService: IUserAuthenticationService;
77 atProtoAgentService: IAgentService;
78 atProtoRepoService: IAtProtoRepoService;
79 metadataService: IMetadataService;
80 profileService: IProfileService;
81 feedService: FeedService;
82 notificationService: NotificationService;
83 nodeOauthClient: NodeOAuthClient;
84 identityResolutionService: IIdentityResolutionService;
85 configService: EnvironmentConfigService;
86 cookieService: CookieService;
87 searchService: SearchService;
88 leafletSearchService: ILeafletSearchService;
89 cardLibraryService: CardLibraryService;
90 cardCollectionService: CardCollectionService;
91 eventPublisher: IEventPublisher;
92 collectionPublisher: ICollectionPublisher;
93}
94
95// Web app specific services (includes publishers, auth middleware)
96export interface WebAppServices extends SharedServices {
97 oauthProcessor: IOAuthProcessor;
98 appPasswordProcessor: IAppPasswordProcessor;
99 collectionPublisher: ICollectionPublisher;
100 cardPublisher: ICardPublisher;
101 authMiddleware: AuthMiddleware;
102}
103
104// Worker specific services (includes subscribers)
105export interface WorkerServices extends SharedServices {
106 redisConnection: Redis | null;
107 createEventSubscriber: (queueName: QueueName) => IEventSubscriber;
108 sagaStateStore: ISagaStateStore;
109}
110
111// Legacy interface for backward compatibility
112export interface Services extends WebAppServices {}
113
114export class ServiceFactory {
115 static create(
116 configService: EnvironmentConfigService,
117 repositories: Repositories,
118 ): Services {
119 return this.createForWebApp(configService, repositories);
120 }
121
122 static createForWebApp(
123 configService: EnvironmentConfigService,
124 repositories: Repositories,
125 ): WebAppServices {
126 const sharedServices = this.createSharedServices(
127 configService,
128 repositories,
129 );
130
131 const useMockAuth = configService.shouldUseMockAuth();
132
133 // App Password Session Service
134 const appPasswordSessionService = useMockAuth
135 ? new FakeAppPasswordSessionService()
136 : new AppPasswordSessionService(
137 repositories.appPasswordSessionRepository,
138 );
139
140 // App Password Processor
141 const appPasswordProcessor = useMockAuth
142 ? new FakeAtpAppPasswordProcessor()
143 : new AtpAppPasswordProcessor(appPasswordSessionService);
144
145 // OAuth Processor
146 const oauthProcessor = useMockAuth
147 ? new FakeAtProtoOAuthProcessor(sharedServices.tokenService)
148 : new AtProtoOAuthProcessor(sharedServices.nodeOauthClient);
149
150 const useFakePublishers = configService.shouldUseFakePublishers();
151 const collections = configService.getAtProtoCollections();
152
153 const collectionPublisher = useFakePublishers
154 ? new FakeCollectionPublisher()
155 : new ATProtoCollectionPublisher(
156 sharedServices.atProtoAgentService,
157 collections.collection,
158 collections.collectionLink,
159 collections.collectionLinkRemoval,
160 );
161
162 const cardPublisher = useFakePublishers
163 ? new FakeCardPublisher()
164 : new ATProtoCardPublisher(
165 sharedServices.atProtoAgentService,
166 collections.card,
167 );
168
169 const authMiddleware = new AuthMiddleware(
170 sharedServices.tokenService,
171 sharedServices.cookieService,
172 );
173
174 return {
175 ...sharedServices,
176 oauthProcessor,
177 appPasswordProcessor,
178 collectionPublisher,
179 cardPublisher,
180 authMiddleware,
181 };
182 }
183
184 static createForWorker(
185 configService: EnvironmentConfigService,
186 repositories: Repositories,
187 ): WorkerServices {
188 const sharedServices = this.createSharedServices(
189 configService,
190 repositories,
191 );
192
193 const useInMemoryEvents = configService.shouldUseInMemoryEvents();
194
195 let eventPublisher: IEventPublisher;
196 let redisConnection: Redis | null = null;
197 let createEventSubscriber: (queueName: QueueName) => IEventSubscriber;
198
199 if (useInMemoryEvents) {
200 eventPublisher = new InMemoryEventPublisher();
201 createEventSubscriber = (queueName: QueueName) => {
202 return new InMemoryEventSubscriber();
203 };
204 } else {
205 // Redis connection is required for BullMQ workers
206 if (!process.env.REDIS_URL) {
207 throw new Error(
208 'REDIS_URL environment variable is required for BullMQ worker services',
209 );
210 }
211
212 const redisConfig = configService.getWorkersConfig().redisConfig;
213 redisConnection = RedisFactory.createConnection(redisConfig);
214 eventPublisher = new BullMQEventPublisher(redisConnection);
215
216 createEventSubscriber = (queueName: QueueName) => {
217 return new BullMQEventSubscriber(redisConnection!, { queueName });
218 };
219 }
220
221 // Create appropriate saga state store
222 const sagaStateStore = useInMemoryEvents
223 ? new InMemorySagaStateStore()
224 : new RedisSagaStateStore(redisConnection!);
225
226 return {
227 ...sharedServices,
228 redisConnection: redisConnection,
229 createEventSubscriber,
230 sagaStateStore,
231 };
232 }
233
234 private static createSharedServices(
235 configService: EnvironmentConfigService,
236 repositories: Repositories,
237 ): SharedServices {
238 const useMockAuth = configService.shouldUseMockAuth();
239
240 const nodeOauthClient = OAuthClientFactory.createClient(
241 repositories.oauthStateStore,
242 repositories.oauthSessionStore,
243 oauthConfig.baseUrl,
244 );
245
246 // Token Service
247 const tokenService = useMockAuth
248 ? new FakeJwtTokenService(repositories.tokenRepository)
249 : new JwtTokenService(
250 repositories.tokenRepository,
251 jwtConfig.jwtSecret,
252 jwtConfig.accessTokenExpiresIn,
253 jwtConfig.refreshTokenExpiresIn,
254 );
255
256 // User Authentication Service
257 const userAuthService = useMockAuth
258 ? new FakeUserAuthenticationService(repositories.userRepository)
259 : new UserAuthenticationService(repositories.userRepository);
260
261 // App Password Session Service (needed for ATProto Agent Service)
262 const appPasswordSessionService = useMockAuth
263 ? new FakeAppPasswordSessionService()
264 : new AppPasswordSessionService(
265 repositories.appPasswordSessionRepository,
266 );
267
268 // ATProto Agent Service
269 const atProtoAgentService = useMockAuth
270 ? new FakeAgentService()
271 : new ATProtoAgentService(
272 nodeOauthClient,
273 appPasswordSessionService,
274 configService,
275 );
276
277 // ATProto Repo Service
278 const atProtoRepoService = useMockAuth
279 ? new FakeAtProtoRepoService()
280 : new ATProtoRepoService(atProtoAgentService);
281
282 // Create individual metadata services
283 const baseIframelyService = new IFramelyMetadataService(
284 configService.getIFramelyApiKey(),
285 );
286 const baseCitoidService = new CitoidMetadataService();
287
288 // Apply caching conditionally (similar to profile service)
289 const useMockPersistence = configService.shouldUseMockPersistence();
290
291 let iframelyService: IMetadataService;
292 let citoidService: IMetadataService;
293
294 if (useMockPersistence) {
295 // No caching for mock persistence
296 iframelyService = baseIframelyService;
297 citoidService = baseCitoidService;
298 } else {
299 // Create Redis connection for caching
300 const redisConfig = configService.getRedisConfig();
301 const redis = RedisFactory.createConnection(redisConfig);
302
303 // Wrap services with caching - different TTLs for different services
304 iframelyService = new CachedMetadataService(
305 baseIframelyService,
306 redis,
307 'iframely',
308 3600 * 24 * 7, // 7 day TTL
309 );
310 citoidService = new CachedMetadataService(
311 baseCitoidService,
312 redis,
313 'citoid',
314 3600 * 24 * 7, // 7 day TTL
315 );
316 }
317
318 // Create composite metadata service
319 const metadataService = new CompositeMetadataService(
320 iframelyService,
321 citoidService,
322 );
323
324 // Profile Service with Redis caching
325 const baseProfileService = new BlueskyProfileService(atProtoAgentService);
326
327 let profileService: IProfileService;
328
329 // caching requires persistence
330 if (useMockPersistence) {
331 profileService = baseProfileService;
332 } else {
333 // Create Redis connection for caching
334 const redisConfig = configService.getRedisConfig();
335 const redis = RedisFactory.createConnection(redisConfig);
336 profileService = new CachedBlueskyProfileService(
337 baseProfileService,
338 redis,
339 );
340 }
341
342 // Feed Service with distributed locking
343 const distributedLockService = DistributedLockServiceFactory.create();
344 const feedService = new FeedService(
345 repositories.feedRepository,
346 distributedLockService,
347 );
348
349 // Notification Service
350 const notificationService = new NotificationService(
351 repositories.notificationRepository,
352 );
353
354 // Identity Resolution Service
355 const identityResolutionService = new ATProtoIdentityResolutionService(
356 atProtoAgentService,
357 );
358
359 // Cookie Service
360 const cookieService = new CookieService(configService);
361
362 // Create vector database and search service (shared by both web app and workers)
363 const useMockVectorDb = configService.shouldUseMockVectorDb();
364
365 const vectorDatabase: IVectorDatabase = useMockVectorDb
366 ? InMemoryVectorDatabase.getInstance()
367 : new UpstashVectorDatabase(
368 configService.getUpstashConfig().vectorUrl,
369 configService.getUpstashConfig().vectorToken,
370 );
371
372 const searchService = new SearchService(
373 vectorDatabase,
374 metadataService,
375 repositories.cardQueryRepository,
376 );
377
378 // Create LeafletSearchService with caching
379 let leafletSearchService: ILeafletSearchService;
380
381 if (useMockPersistence) {
382 // Use fake implementation for mock persistence
383 leafletSearchService = new FakeLeafletSearchService();
384 } else {
385 // Use real Constellation service with caching
386 const baseLeafletSearchService = new ConstellationLeafletSearchService(
387 metadataService,
388 );
389 const redisConfig = configService.getRedisConfig();
390 const redis = RedisFactory.createConnection(redisConfig);
391 leafletSearchService = new CachedLeafletSearchService(
392 baseLeafletSearchService,
393 redis,
394 );
395 }
396
397 // Create publishers needed for shared services
398 const useFakePublishers = configService.shouldUseFakePublishers();
399 const collections = configService.getAtProtoCollections();
400
401 const collectionPublisher = useFakePublishers
402 ? new FakeCollectionPublisher()
403 : new ATProtoCollectionPublisher(
404 atProtoAgentService,
405 collections.collection,
406 collections.collectionLink,
407 collections.collectionLinkRemoval,
408 );
409
410 const cardPublisher = useFakePublishers
411 ? new FakeCardPublisher()
412 : new ATProtoCardPublisher(atProtoAgentService, collections.card);
413
414 // Create domain services
415 const cardCollectionService = new CardCollectionService(
416 repositories.collectionRepository,
417 collectionPublisher,
418 repositories.cardRepository,
419 );
420 const cardLibraryService = new CardLibraryService(
421 repositories.cardRepository,
422 cardPublisher,
423 repositories.collectionRepository,
424 cardCollectionService,
425 );
426
427 // Create event publisher
428 const useInMemoryEvents = configService.shouldUseInMemoryEvents();
429
430 let eventPublisher: IEventPublisher;
431 if (useInMemoryEvents) {
432 eventPublisher = new InMemoryEventPublisher();
433 } else {
434 const redisConnection = RedisFactory.createConnection(
435 configService.getWorkersConfig().redisConfig,
436 );
437 eventPublisher = new BullMQEventPublisher(redisConnection);
438 }
439
440 return {
441 tokenService,
442 userAuthService,
443 atProtoAgentService,
444 atProtoRepoService,
445 metadataService,
446 profileService,
447 feedService,
448 notificationService,
449 nodeOauthClient,
450 identityResolutionService,
451 configService,
452 cookieService,
453 searchService,
454 leafletSearchService,
455 cardLibraryService,
456 cardCollectionService,
457 eventPublisher,
458 collectionPublisher,
459 };
460 }
461}