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