A social knowledge tool for researchers built on ATProto
at development 302 lines 12 kB view raw
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'; 53 54// Shared services needed by both web app and workers 55export interface SharedServices { 56 tokenService: ITokenService; 57 userAuthService: IUserAuthenticationService; 58 atProtoAgentService: IAgentService; 59 metadataService: IMetadataService; 60 profileService: IProfileService; 61 feedService: FeedService; 62 nodeOauthClient: NodeOAuthClient; 63 identityResolutionService: IIdentityResolutionService; 64 configService: EnvironmentConfigService; 65} 66 67// Web app specific services (includes publishers, auth middleware) 68export interface WebAppServices extends SharedServices { 69 oauthProcessor: IOAuthProcessor; 70 appPasswordProcessor: IAppPasswordProcessor; 71 collectionPublisher: ICollectionPublisher; 72 cardPublisher: ICardPublisher; 73 cardLibraryService: CardLibraryService; 74 cardCollectionService: CardCollectionService; 75 authMiddleware: AuthMiddleware; 76 eventPublisher: IEventPublisher; 77} 78 79// Worker specific services (includes subscribers) 80export interface WorkerServices extends SharedServices { 81 redisConnection: Redis | null; 82 eventPublisher: IEventPublisher; 83 createEventSubscriber: (queueName: QueueName) => IEventSubscriber; 84 cardCollectionSaga: CardCollectionSaga; 85} 86 87// Legacy interface for backward compatibility 88export interface Services extends WebAppServices {} 89 90export class ServiceFactory { 91 static create( 92 configService: EnvironmentConfigService, 93 repositories: Repositories, 94 ): Services { 95 return this.createForWebApp(configService, repositories); 96 } 97 98 static createForWebApp( 99 configService: EnvironmentConfigService, 100 repositories: Repositories, 101 ): WebAppServices { 102 const sharedServices = this.createSharedServices( 103 configService, 104 repositories, 105 ); 106 107 const useMockAuth = process.env.USE_MOCK_AUTH === 'true'; 108 109 // App Password Session Service 110 const appPasswordSessionService = useMockAuth 111 ? new FakeAppPasswordSessionService() 112 : new AppPasswordSessionService( 113 repositories.appPasswordSessionRepository, 114 ); 115 116 // App Password Processor 117 const appPasswordProcessor = useMockAuth 118 ? new FakeAtpAppPasswordProcessor() 119 : new AtpAppPasswordProcessor(appPasswordSessionService); 120 121 // OAuth Processor 122 const oauthProcessor = useMockAuth 123 ? new FakeAtProtoOAuthProcessor(sharedServices.tokenService) 124 : new AtProtoOAuthProcessor(sharedServices.nodeOauthClient); 125 126 const useFakePublishers = process.env.USE_FAKE_PUBLISHERS === 'true'; 127 const collections = configService.getAtProtoCollections(); 128 129 const collectionPublisher = useFakePublishers 130 ? new FakeCollectionPublisher() 131 : new ATProtoCollectionPublisher( 132 sharedServices.atProtoAgentService, 133 collections.collection, 134 collections.collectionLink, 135 ); 136 137 const cardPublisher = useFakePublishers 138 ? new FakeCardPublisher() 139 : new ATProtoCardPublisher( 140 sharedServices.atProtoAgentService, 141 collections.card, 142 ); 143 144 const cardCollectionService = new CardCollectionService( 145 repositories.collectionRepository, 146 collectionPublisher, 147 ); 148 const cardLibraryService = new CardLibraryService( 149 repositories.cardRepository, 150 cardPublisher, 151 repositories.collectionRepository, 152 cardCollectionService, 153 ); 154 155 const authMiddleware = new AuthMiddleware(sharedServices.tokenService); 156 157 const useInMemoryEvents = process.env.USE_IN_MEMORY_EVENTS === 'true'; 158 159 let eventPublisher: IEventPublisher; 160 if (useInMemoryEvents) { 161 eventPublisher = new InMemoryEventPublisher(); 162 } else { 163 const redisConnection = RedisFactory.createConnection( 164 configService.getWorkersConfig().redisConfig, 165 ); 166 eventPublisher = new BullMQEventPublisher(redisConnection); 167 } 168 169 return { 170 ...sharedServices, 171 oauthProcessor, 172 appPasswordProcessor, 173 collectionPublisher, 174 cardPublisher, 175 cardLibraryService, 176 cardCollectionService, 177 authMiddleware, 178 eventPublisher, 179 }; 180 } 181 182 static createForWorker( 183 configService: EnvironmentConfigService, 184 repositories: Repositories, 185 ): WorkerServices { 186 const sharedServices = this.createSharedServices( 187 configService, 188 repositories, 189 ); 190 191 const useInMemoryEvents = process.env.USE_IN_MEMORY_EVENTS === 'true'; 192 193 let eventPublisher: IEventPublisher; 194 let redisConnection: Redis | null = null; 195 let createEventSubscriber: (queueName: QueueName) => IEventSubscriber; 196 197 if (useInMemoryEvents) { 198 eventPublisher = new InMemoryEventPublisher(); 199 createEventSubscriber = (queueName: QueueName) => { 200 return new InMemoryEventSubscriber(); 201 }; 202 } else { 203 // Redis connection is required for BullMQ workers 204 if (!process.env.REDIS_URL) { 205 throw new Error( 206 'REDIS_URL environment variable is required for BullMQ worker services', 207 ); 208 } 209 210 const redisConfig = configService.getWorkersConfig().redisConfig; 211 redisConnection = RedisFactory.createConnection(redisConfig); 212 eventPublisher = new BullMQEventPublisher(redisConnection); 213 214 createEventSubscriber = (queueName: QueueName) => { 215 return new BullMQEventSubscriber(redisConnection!, { queueName }); 216 }; 217 } 218 219 // Create saga for worker 220 const cardCollectionSaga = new CardCollectionSaga( 221 // We'll need to create this use case in the worker context 222 null as any, // Will be set properly in worker 223 ); 224 225 return { 226 ...sharedServices, 227 redisConnection: redisConnection, 228 eventPublisher, 229 createEventSubscriber, 230 cardCollectionSaga, 231 }; 232 } 233 234 private static createSharedServices( 235 configService: EnvironmentConfigService, 236 repositories: Repositories, 237 ): SharedServices { 238 const useMockAuth = process.env.USE_MOCK_AUTH === 'true'; 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(nodeOauthClient, appPasswordSessionService); 272 273 const metadataService = new IFramelyMetadataService( 274 configService.getIFramelyApiKey(), 275 ); 276 277 // Profile Service 278 const profileService = useMockAuth 279 ? new FakeBlueskyProfileService() 280 : new BlueskyProfileService(atProtoAgentService); 281 282 // Feed Service 283 const feedService = new FeedService(repositories.feedRepository); 284 285 // Identity Resolution Service 286 const identityResolutionService = new ATProtoIdentityResolutionService( 287 atProtoAgentService, 288 ); 289 290 return { 291 tokenService, 292 userAuthService, 293 atProtoAgentService, 294 metadataService, 295 profileService, 296 feedService, 297 nodeOauthClient, 298 identityResolutionService, 299 configService, 300 }; 301 } 302}