This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

semble / src / shared / infrastructure / config / EnvironmentConfigService.ts
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}