This repository has no description
8.8 kB
317 lines
1import { ATPROTO_NSID } from 'src/shared/constants/atproto';
2
3export enum Environment {
4 LOCAL = 'local',
5 DEV = 'dev',
6 PROD = 'prod',
7}
8
9export interface EnvironmentConfig {
10 environment: Environment;
11 runtime: {
12 useMockPersistence: boolean;
13 useMockAuth: boolean;
14 useFakePublishers: boolean;
15 useMockVectorDb: boolean;
16 };
17 database: {
18 url: string;
19 };
20 auth: {
21 jwtSecret: string;
22 accessTokenExpiresIn: number;
23 refreshTokenExpiresIn: number;
24 };
25 atproto: {
26 firehoseWebsocket: string;
27 jetstreamWebsocket: string;
28 serviceEndpoint: string;
29 baseUrl: string;
30 baseCollection: string;
31 collections: {
32 card: string;
33 collection: string;
34 collectionLink: string;
35 marginBookmark: string;
36 marginCollection: string;
37 marginCollectionItem: string;
38 collectionLinkRemoval: string;
39 follow: string;
40 connection: string;
41 };
42 serviceAccount: {
43 identifier: string;
44 appPassword: string;
45 };
46 };
47 server: {
48 port: number;
49 host: string;
50 };
51 app: {
52 appUrl: string;
53 };
54 iframely: {
55 apiKey: string;
56 };
57 upstash: {
58 vectorUrl: string;
59 vectorToken: string;
60 };
61 workers: {
62 redisUrl: string;
63 redisConfig: {
64 host: string;
65 port: number;
66 password?: string;
67 maxRetriesPerRequest: number | null; // Disable automatic retries
68 };
69 };
70 sentry: {
71 dsn: string;
72 environment: string;
73 };
74}
75
76export class EnvironmentConfigService {
77 private config: EnvironmentConfig;
78
79 constructor() {
80 const environment = (process.env.NODE_ENV ||
81 Environment.LOCAL) as Environment;
82
83 this.config = {
84 environment,
85 runtime: {
86 useMockPersistence: this.determineMockPersistenceFlag(),
87 useMockAuth: process.env.USE_MOCK_AUTH === 'true',
88 useFakePublishers: process.env.USE_FAKE_PUBLISHERS === 'true',
89 useMockVectorDb: process.env.USE_MOCK_VECTOR_DB === 'true',
90 },
91 database: {
92 url:
93 process.env.DATABASE_URL ||
94 'postgres://postgres:postgres@localhost:5432/annotations',
95 },
96 auth: {
97 jwtSecret:
98 process.env.JWT_SECRET || 'default-secret-change-in-production',
99 accessTokenExpiresIn: parseInt(
100 process.env.ACCESS_TOKEN_EXPIRES_IN || '3600',
101 ),
102 refreshTokenExpiresIn: parseInt(
103 process.env.REFRESH_TOKEN_EXPIRES_IN || '2592000',
104 ),
105 },
106 atproto: {
107 firehoseWebsocket:
108 process.env.ATPROTO_FIREHOSE_WEBSOCKET ||
109 'wss://relay1.us-west.bsky.network',
110 jetstreamWebsocket:
111 process.env.ATPROTO_JETSTREAM_WEBSOCKET ||
112 'wss://jetstream2.us-west.bsky.network',
113 serviceEndpoint:
114 process.env.ATPROTO_SERVICE_ENDPOINT || 'https://bsky.social',
115 baseUrl: process.env.BASE_URL || 'http://127.0.0.1:3000',
116 baseCollection:
117 environment === Environment.PROD
118 ? ATPROTO_NSID.COSMIK.NAMESPACE
119 : `${ATPROTO_NSID.COSMIK.NAMESPACE}.${environment}`,
120 collections: {
121 card:
122 environment === Environment.PROD
123 ? ATPROTO_NSID.COSMIK.CARD
124 : `${ATPROTO_NSID.COSMIK.NAMESPACE}.${environment}.card`,
125 collection:
126 environment === Environment.PROD
127 ? ATPROTO_NSID.COSMIK.COLLECTION
128 : `${ATPROTO_NSID.COSMIK.NAMESPACE}.${environment}.collection`,
129 collectionLink:
130 environment === Environment.PROD
131 ? ATPROTO_NSID.COSMIK.COLLECTION_LINK
132 : `${ATPROTO_NSID.COSMIK.NAMESPACE}.${environment}.collectionLink`,
133 collectionLinkRemoval:
134 environment === Environment.PROD
135 ? ATPROTO_NSID.COSMIK.COLLECTION_LINK_REMOVAL
136 : `${ATPROTO_NSID.COSMIK.NAMESPACE}.${environment}.collectionLinkRemoval`,
137 follow:
138 environment === Environment.PROD
139 ? ATPROTO_NSID.COSMIK.FOLLOW
140 : `${ATPROTO_NSID.COSMIK.NAMESPACE}.${environment}.follow`,
141 connection:
142 environment === Environment.PROD
143 ? ATPROTO_NSID.COSMIK.CONNECTION
144 : `${ATPROTO_NSID.COSMIK.NAMESPACE}.${environment}.connection`,
145 // Margin collections - no environment suffix
146 marginBookmark: ATPROTO_NSID.MARGIN.BOOKMARK,
147 marginCollection: ATPROTO_NSID.MARGIN.COLLECTION,
148 marginCollectionItem: ATPROTO_NSID.MARGIN.COLLECTION_ITEM,
149 },
150 serviceAccount: {
151 identifier: process.env.BSKY_SERVICE_ACCOUNT_IDENTIFIER || '',
152 appPassword: process.env.BSKY_SERVICE_ACCOUNT_APP_PASSWORD || '',
153 },
154 },
155 server: {
156 port: parseInt(process.env.PORT || '3000'),
157 host: process.env.HOST || '127.0.0.1',
158 },
159 app: {
160 appUrl: process.env.APP_URL || 'http://127.0.0.1:4000',
161 },
162 iframely: {
163 apiKey: process.env.IFRAMELY_API_KEY || '',
164 },
165 upstash: {
166 vectorUrl: process.env.UPSTASH_VECTOR_REST_URL || '',
167 vectorToken: process.env.UPSTASH_VECTOR_REST_TOKEN || '',
168 },
169 workers: {
170 redisConfig: {
171 host: process.env.REDIS_HOST || 'localhost',
172 port: parseInt(process.env.REDIS_PORT || '6379', 10),
173 password: process.env.REDIS_PASSWORD || undefined,
174 maxRetriesPerRequest: null, // Disable automatic retries
175 },
176 redisUrl: process.env.REDIS_URL || 'redis://localhost:6379',
177 },
178 sentry: {
179 dsn: process.env.SENTRY_DSN || '',
180 environment: environment,
181 },
182 };
183
184 this.applyEnvironmentSpecificConfig();
185 }
186
187 private applyEnvironmentSpecificConfig(): void {
188 switch (this.config.environment) {
189 case Environment.DEV:
190 // Override defaults with dev-specific values
191 break;
192 case Environment.PROD:
193 // Override defaults with production-specific values
194 if (
195 this.config.auth.jwtSecret === 'default-secret-change-in-production'
196 ) {
197 throw new Error('JWT secret must be set in production environment');
198 }
199 break;
200 case Environment.LOCAL:
201 default:
202 // Local development defaults are already set
203 break;
204 }
205 }
206
207 public get(): EnvironmentConfig {
208 return this.config;
209 }
210
211 public getDatabaseConfig() {
212 return this.config.database;
213 }
214
215 public getAuthConfig() {
216 return this.config.auth;
217 }
218
219 public getAtProtoConfig() {
220 return this.config.atproto;
221 }
222
223 public getAtProtoCollections() {
224 return this.config.atproto.collections;
225 }
226
227 public getAtProtoBaseCollection() {
228 return this.config.atproto.baseCollection;
229 }
230
231 public getAtProtoServiceAccount() {
232 return this.config.atproto.serviceAccount;
233 }
234
235 public getServerConfig() {
236 return this.config.server;
237 }
238 public getAppConfig() {
239 return this.config.app;
240 }
241
242 public getIFramelyApiKey(): string {
243 return this.config.iframely.apiKey;
244 }
245
246 public getUpstashConfig() {
247 return this.config.upstash;
248 }
249
250 public getWorkersConfig() {
251 return this.config.workers;
252 }
253
254 public getRedisConfig() {
255 return this.config.workers.redisConfig;
256 }
257
258 public getSentryConfig() {
259 return this.config.sentry;
260 }
261
262 public getRuntimeConfig() {
263 return this.config.runtime;
264 }
265
266 public shouldUseMockPersistence(): boolean {
267 return this.config.runtime.useMockPersistence;
268 }
269
270 public shouldUseMockRepos(): boolean {
271 return this.config.runtime.useMockPersistence;
272 }
273
274 public shouldUseInMemoryEvents(): boolean {
275 return this.config.runtime.useMockPersistence;
276 }
277
278 public shouldUseMockAuth(): boolean {
279 return this.config.runtime.useMockAuth;
280 }
281
282 public shouldUseFakePublishers(): boolean {
283 return this.config.runtime.useFakePublishers;
284 }
285
286 public shouldUseMockVectorDb(): boolean {
287 return this.config.runtime.useMockVectorDb;
288 }
289
290 // Convenience methods for common combinations
291 public isFullyMocked(): boolean {
292 const r = this.config.runtime;
293 return r.useMockPersistence && r.useMockAuth && r.useFakePublishers;
294 }
295
296 public isMockPersistenceEnabled(): boolean {
297 return this.config.runtime.useMockPersistence;
298 }
299
300 private determineMockPersistenceFlag(): boolean {
301 // New unified flag takes precedence
302 if (process.env.USE_MOCK_PERSISTENCE !== undefined) {
303 return process.env.USE_MOCK_PERSISTENCE === 'true';
304 }
305
306 // Legacy support - if either old flag is false, persistence is disabled
307 if (
308 process.env.USE_MOCK_REPOS === 'true' ||
309 process.env.USE_IN_MEMORY_EVENTS === 'true'
310 ) {
311 return true;
312 }
313
314 // Default to false (use mock persistence) unless explicitly enabled
315 return false;
316 }
317}