This repository has no description
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}