This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

semble / src / shared / infrastructure / http / factories / ServiceFactory.ts
19 kB 461 lines
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}