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