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
14 kB 351 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 { BlueskyProfileService } from '../../../../modules/atproto/infrastructure/services/BlueskyProfileService'; 12import { CachedBlueskyProfileService } from '../../../../modules/atproto/infrastructure/services/CachedBlueskyProfileService'; 13import { ATProtoCollectionPublisher } from '../../../../modules/atproto/infrastructure/publishers/ATProtoCollectionPublisher'; 14import { ATProtoCardPublisher } from '../../../../modules/atproto/infrastructure/publishers/ATProtoCardPublisher'; 15import { FakeCollectionPublisher } from '../../../../modules/cards/tests/utils/FakeCollectionPublisher'; 16import { FakeCardPublisher } from '../../../../modules/cards/tests/utils/FakeCardPublisher'; 17import { CardLibraryService } from '../../../../modules/cards/domain/services/CardLibraryService'; 18import { CardCollectionService } from '../../../../modules/cards/domain/services/CardCollectionService'; 19import { AuthMiddleware } from '../middleware/AuthMiddleware'; 20import { Repositories } from './RepositoryFactory'; 21import { NodeOAuthClient } from '@atproto/oauth-client-node'; 22import { AppPasswordSessionService } from 'src/modules/atproto/infrastructure/services/AppPasswordSessionService'; 23import { AtpAppPasswordProcessor } from 'src/modules/atproto/infrastructure/services/AtpAppPasswordProcessor'; 24import { ICollectionPublisher } from 'src/modules/cards/application/ports/ICollectionPublisher'; 25import { ICardPublisher } from 'src/modules/cards/application/ports/ICardPublisher'; 26import { IMetadataService } from 'src/modules/cards/domain/services/IMetadataService'; 27import { BullMQEventSubscriber } from '../../events/BullMQEventSubscriber'; 28import { BullMQEventPublisher } from '../../events/BullMQEventPublisher'; 29import { InMemoryEventPublisher } from '../../events/InMemoryEventPublisher'; 30import { InMemoryEventSubscriber } from '../../events/InMemoryEventSubscriber'; 31import Redis from 'ioredis'; 32// Mock/Fake service imports 33import { FakeJwtTokenService } from '../../../../modules/user/infrastructure/services/FakeJwtTokenService'; 34import { FakeAtProtoOAuthProcessor } from '../../../../modules/atproto/infrastructure/services/FakeAtProtoOAuthProcessor'; 35import { FakeUserAuthenticationService } from '../../../../modules/user/infrastructure/services/FakeUserAuthenticationService'; 36import { FakeAgentService } from '../../../../modules/atproto/infrastructure/services/FakeAgentService'; 37import { FakeBlueskyProfileService } from '../../../../modules/atproto/infrastructure/services/FakeBlueskyProfileService'; 38import { FakeAppPasswordSessionService } from '../../../../modules/atproto/infrastructure/services/FakeAppPasswordSessionService'; 39import { FakeAtpAppPasswordProcessor } from '../../../../modules/atproto/infrastructure/services/FakeAtpAppPasswordProcessor'; 40import { ITokenService } from 'src/modules/user/application/services/ITokenService'; 41import { IOAuthProcessor } from 'src/modules/user/application/services/IOAuthProcessor'; 42import { IAppPasswordProcessor } from 'src/modules/atproto/application/IAppPasswordProcessor'; 43import { IUserAuthenticationService } from 'src/modules/user/domain/services/IUserAuthenticationService'; 44import { IAgentService } from 'src/modules/atproto/application/IAgentService'; 45import { IProfileService } from 'src/modules/cards/domain/services/IProfileService'; 46import { IEventPublisher } from '../../../application/events/IEventPublisher'; 47import { QueueName } from '../../events/QueueConfig'; 48import { RedisFactory } from '../../redis/RedisFactory'; 49import { IEventSubscriber } from 'src/shared/application/events/IEventSubscriber'; 50import { FeedService } from '../../../../modules/feeds/domain/services/FeedService'; 51import { ATProtoIdentityResolutionService } from '../../../../modules/atproto/infrastructure/services/ATProtoIdentityResolutionService'; 52import { IIdentityResolutionService } from '../../../../modules/atproto/domain/services/IIdentityResolutionService'; 53import { CookieService } from '../services/CookieService'; 54import { InMemorySagaStateStore } from '../../../../modules/feeds/infrastructure/InMemorySagaStateStore'; 55import { RedisSagaStateStore } from '../../../../modules/feeds/infrastructure/RedisSagaStateStore'; 56import { ISagaStateStore } from 'src/modules/feeds/application/sagas/ISagaStateStore'; 57import { SearchService } from '../../../../modules/search/domain/services/SearchService'; 58import { IVectorDatabase } from '../../../../modules/search/domain/IVectorDatabase'; 59import { InMemoryVectorDatabase } from '../../../../modules/search/infrastructure/InMemoryVectorDatabase'; 60import { UpstashVectorDatabase } from '../../../../modules/search/infrastructure/UpstashVectorDatabase'; 61 62// Shared services needed by both web app and workers 63export interface SharedServices { 64 tokenService: ITokenService; 65 userAuthService: IUserAuthenticationService; 66 atProtoAgentService: IAgentService; 67 metadataService: IMetadataService; 68 profileService: IProfileService; 69 feedService: FeedService; 70 nodeOauthClient: NodeOAuthClient; 71 identityResolutionService: IIdentityResolutionService; 72 configService: EnvironmentConfigService; 73 cookieService: CookieService; 74 searchService: SearchService; 75} 76 77// Web app specific services (includes publishers, auth middleware) 78export interface WebAppServices extends SharedServices { 79 oauthProcessor: IOAuthProcessor; 80 appPasswordProcessor: IAppPasswordProcessor; 81 collectionPublisher: ICollectionPublisher; 82 cardPublisher: ICardPublisher; 83 cardLibraryService: CardLibraryService; 84 cardCollectionService: CardCollectionService; 85 authMiddleware: AuthMiddleware; 86 eventPublisher: IEventPublisher; 87 cookieService: CookieService; 88 searchService: SearchService; 89} 90 91// Worker specific services (includes subscribers) 92export interface WorkerServices extends SharedServices { 93 redisConnection: Redis | null; 94 eventPublisher: IEventPublisher; 95 createEventSubscriber: (queueName: QueueName) => IEventSubscriber; 96 sagaStateStore: ISagaStateStore; 97} 98 99// Legacy interface for backward compatibility 100export interface Services extends WebAppServices {} 101 102export class ServiceFactory { 103 static create( 104 configService: EnvironmentConfigService, 105 repositories: Repositories, 106 ): Services { 107 return this.createForWebApp(configService, repositories); 108 } 109 110 static createForWebApp( 111 configService: EnvironmentConfigService, 112 repositories: Repositories, 113 ): WebAppServices { 114 const sharedServices = this.createSharedServices( 115 configService, 116 repositories, 117 ); 118 119 const useMockAuth = configService.shouldUseMockAuth(); 120 121 // App Password Session Service 122 const appPasswordSessionService = useMockAuth 123 ? new FakeAppPasswordSessionService() 124 : new AppPasswordSessionService( 125 repositories.appPasswordSessionRepository, 126 ); 127 128 // App Password Processor 129 const appPasswordProcessor = useMockAuth 130 ? new FakeAtpAppPasswordProcessor() 131 : new AtpAppPasswordProcessor(appPasswordSessionService); 132 133 // OAuth Processor 134 const oauthProcessor = useMockAuth 135 ? new FakeAtProtoOAuthProcessor(sharedServices.tokenService) 136 : new AtProtoOAuthProcessor(sharedServices.nodeOauthClient); 137 138 const useFakePublishers = configService.shouldUseFakePublishers(); 139 const collections = configService.getAtProtoCollections(); 140 141 const collectionPublisher = useFakePublishers 142 ? new FakeCollectionPublisher() 143 : new ATProtoCollectionPublisher( 144 sharedServices.atProtoAgentService, 145 collections.collection, 146 collections.collectionLink, 147 ); 148 149 const cardPublisher = useFakePublishers 150 ? new FakeCardPublisher() 151 : new ATProtoCardPublisher( 152 sharedServices.atProtoAgentService, 153 collections.card, 154 ); 155 156 const cardCollectionService = new CardCollectionService( 157 repositories.collectionRepository, 158 collectionPublisher, 159 ); 160 const cardLibraryService = new CardLibraryService( 161 repositories.cardRepository, 162 cardPublisher, 163 repositories.collectionRepository, 164 cardCollectionService, 165 ); 166 167 const authMiddleware = new AuthMiddleware( 168 sharedServices.tokenService, 169 sharedServices.cookieService, 170 ); 171 172 const useInMemoryEvents = configService.shouldUseInMemoryEvents(); 173 174 let eventPublisher: IEventPublisher; 175 if (useInMemoryEvents) { 176 eventPublisher = new InMemoryEventPublisher(); 177 } else { 178 const redisConnection = RedisFactory.createConnection( 179 configService.getWorkersConfig().redisConfig, 180 ); 181 eventPublisher = new BullMQEventPublisher(redisConnection); 182 } 183 184 return { 185 ...sharedServices, 186 oauthProcessor, 187 appPasswordProcessor, 188 collectionPublisher, 189 cardPublisher, 190 cardLibraryService, 191 cardCollectionService, 192 authMiddleware, 193 eventPublisher, 194 }; 195 } 196 197 static createForWorker( 198 configService: EnvironmentConfigService, 199 repositories: Repositories, 200 ): WorkerServices { 201 const sharedServices = this.createSharedServices( 202 configService, 203 repositories, 204 ); 205 206 const useInMemoryEvents = configService.shouldUseInMemoryEvents(); 207 208 let eventPublisher: IEventPublisher; 209 let redisConnection: Redis | null = null; 210 let createEventSubscriber: (queueName: QueueName) => IEventSubscriber; 211 212 if (useInMemoryEvents) { 213 eventPublisher = new InMemoryEventPublisher(); 214 createEventSubscriber = (queueName: QueueName) => { 215 return new InMemoryEventSubscriber(); 216 }; 217 } else { 218 // Redis connection is required for BullMQ workers 219 if (!process.env.REDIS_URL) { 220 throw new Error( 221 'REDIS_URL environment variable is required for BullMQ worker services', 222 ); 223 } 224 225 const redisConfig = configService.getWorkersConfig().redisConfig; 226 redisConnection = RedisFactory.createConnection(redisConfig); 227 eventPublisher = new BullMQEventPublisher(redisConnection); 228 229 createEventSubscriber = (queueName: QueueName) => { 230 return new BullMQEventSubscriber(redisConnection!, { queueName }); 231 }; 232 } 233 234 // Create appropriate saga state store 235 const sagaStateStore = useInMemoryEvents 236 ? new InMemorySagaStateStore() 237 : new RedisSagaStateStore(redisConnection!); 238 239 return { 240 ...sharedServices, 241 redisConnection: redisConnection, 242 eventPublisher, 243 createEventSubscriber, 244 sagaStateStore, 245 }; 246 } 247 248 private static createSharedServices( 249 configService: EnvironmentConfigService, 250 repositories: Repositories, 251 ): SharedServices { 252 const useMockAuth = configService.shouldUseMockAuth(); 253 254 const nodeOauthClient = OAuthClientFactory.createClient( 255 repositories.oauthStateStore, 256 repositories.oauthSessionStore, 257 oauthConfig.baseUrl, 258 ); 259 260 // Token Service 261 const tokenService = useMockAuth 262 ? new FakeJwtTokenService(repositories.tokenRepository) 263 : new JwtTokenService( 264 repositories.tokenRepository, 265 jwtConfig.jwtSecret, 266 jwtConfig.accessTokenExpiresIn, 267 jwtConfig.refreshTokenExpiresIn, 268 ); 269 270 // User Authentication Service 271 const userAuthService = useMockAuth 272 ? new FakeUserAuthenticationService(repositories.userRepository) 273 : new UserAuthenticationService(repositories.userRepository); 274 275 // App Password Session Service (needed for ATProto Agent Service) 276 const appPasswordSessionService = useMockAuth 277 ? new FakeAppPasswordSessionService() 278 : new AppPasswordSessionService( 279 repositories.appPasswordSessionRepository, 280 ); 281 282 // ATProto Agent Service 283 const atProtoAgentService = useMockAuth 284 ? new FakeAgentService() 285 : new ATProtoAgentService(nodeOauthClient, appPasswordSessionService); 286 287 const metadataService = new IFramelyMetadataService( 288 configService.getIFramelyApiKey(), 289 ); 290 291 // Profile Service with Redis caching 292 const baseProfileService = new BlueskyProfileService(atProtoAgentService); 293 294 let profileService: IProfileService; 295 const useMockPersistence = configService.shouldUseMockPersistence(); 296 297 // caching requires persistence 298 if (useMockPersistence) { 299 profileService = baseProfileService; 300 } else { 301 // Create Redis connection for caching 302 const redisConfig = configService.getRedisConfig(); 303 const redis = RedisFactory.createConnection(redisConfig); 304 profileService = new CachedBlueskyProfileService( 305 baseProfileService, 306 redis, 307 ); 308 } 309 310 // Feed Service 311 const feedService = new FeedService(repositories.feedRepository); 312 313 // Identity Resolution Service 314 const identityResolutionService = new ATProtoIdentityResolutionService( 315 atProtoAgentService, 316 ); 317 318 // Cookie Service 319 const cookieService = new CookieService(configService); 320 321 // Create vector database and search service (shared by both web app and workers) 322 const useMockVectorDb = configService.shouldUseMockVectorDb(); 323 324 const vectorDatabase: IVectorDatabase = useMockVectorDb 325 ? InMemoryVectorDatabase.getInstance() 326 : new UpstashVectorDatabase( 327 configService.getUpstashConfig().vectorUrl, 328 configService.getUpstashConfig().vectorToken, 329 ); 330 331 const searchService = new SearchService( 332 vectorDatabase, 333 metadataService, 334 repositories.cardQueryRepository, 335 ); 336 337 return { 338 tokenService, 339 userAuthService, 340 atProtoAgentService, 341 metadataService, 342 profileService, 343 feedService, 344 nodeOauthClient, 345 identityResolutionService, 346 configService, 347 cookieService, 348 searchService, 349 }; 350 } 351}