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