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 { 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(
318 vectorDatabase,
319 metadataService,
320 repositories.cardQueryRepository,
321 );
322
323 return {
324 tokenService,
325 userAuthService,
326 atProtoAgentService,
327 metadataService,
328 profileService,
329 feedService,
330 nodeOauthClient,
331 identityResolutionService,
332 configService,
333 cookieService,
334 searchService,
335 vectorDatabase,
336 };
337 }
338}