A social knowledge tool for researchers built on ATProto

Merge branch 'development' into fix/merging-types-semble-page

Changed files
+114 -63
.github
src
shared
infrastructure
+16
.github/workflows/fly-prod-deploy.yml
··· 1 + name: Fly Prod Deploy 2 + on: 3 + push: 4 + branches: 5 + - main 6 + jobs: 7 + deploy: 8 + name: Deploy app 9 + runs-on: ubuntu-latest 10 + concurrency: deploy-group 11 + steps: 12 + - uses: actions/checkout@v4 13 + - uses: superfly/flyctl-actions/setup-flyctl@master 14 + - run: flyctl deploy --remote-only -c fly.production.toml 15 + env: 16 + FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}
+2 -2
.github/workflows/fly.yml .github/workflows/fly-dev-deploy.yml
··· 7 7 deploy: 8 8 name: Deploy app 9 9 runs-on: ubuntu-latest 10 - concurrency: deploy-group # optional: ensure only one action runs at a time 10 + concurrency: deploy-group 11 11 steps: 12 12 - uses: actions/checkout@v4 13 13 - uses: superfly/flyctl-actions/setup-flyctl@master 14 - - run: flyctl deploy --remote-only 14 + - run: flyctl deploy --remote-only -c fly.development.toml 15 15 env: 16 16 FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}
+33
fly.development.toml
··· 1 + app = 'semble-development' 2 + primary_region = 'yyz' 3 + 4 + [build] 5 + 6 + [processes] 7 + web = "npm start" 8 + feed-worker = "npm run worker:feeds" 9 + 10 + [http_service] 11 + internal_port = 3000 12 + force_https = true 13 + auto_stop_machines = 'stop' 14 + auto_start_machines = true 15 + min_machines_running = 0 16 + processes = ['web'] 17 + 18 + [[vm]] 19 + memory = '512mb' 20 + cpu_kind = 'shared' 21 + cpus = 1 22 + 23 + [[vm]] 24 + processes = ['feed-worker'] 25 + memory = '256mb' 26 + cpu_kind = 'shared' 27 + cpus = 1 28 + 29 + [env] 30 + NODE_ENV="dev" 31 + BASE_URL="https://api.dev.semble.so" 32 + HOST="0.0.0.0" 33 + APP_URL="https://dev.semble.so"
+33
fly.production.toml
··· 1 + app = 'semble-production' 2 + primary_region = 'yyz' 3 + 4 + [build] 5 + 6 + [processes] 7 + web = "npm start" 8 + feed-worker = "npm run worker:feeds" 9 + 10 + [http_service] 11 + internal_port = 3000 12 + force_https = true 13 + auto_stop_machines = 'stop' 14 + auto_start_machines = true 15 + min_machines_running = 1 16 + processes = ['web'] 17 + 18 + [[vm]] 19 + memory = '1gb' 20 + cpu_kind = 'shared' 21 + cpus = 1 22 + 23 + [[vm]] 24 + processes = ['feed-worker'] 25 + memory = '512mb' 26 + cpu_kind = 'shared' 27 + cpus = 1 28 + 29 + [env] 30 + NODE_ENV="prod" 31 + BASE_URL="https://api.semble.so" 32 + HOST="0.0.0.0" 33 + APP_URL="https://semble.so"
-41
fly.toml
··· 1 - # fly.toml app configuration file generated for annos on 2025-05-13T18:45:49-07:00 2 - # 3 - # See https://fly.io/docs/reference/configuration/ for information about how to use this file. 4 - # 5 - 6 - app = 'annos' 7 - primary_region = 'yyz' 8 - 9 - [build] 10 - 11 - # Define different process types 12 - [processes] 13 - web = "npm start" 14 - feed-worker = "npm run worker:feeds" 15 - 16 - [http_service] 17 - internal_port = 3000 18 - force_https = true 19 - auto_stop_machines = 'stop' 20 - auto_start_machines = true 21 - min_machines_running = 1 22 - processes = ['web'] # Only web processes handle HTTP traffic 23 - 24 - # Default VM configuration for all processes 25 - [[vm]] 26 - memory = '1gb' 27 - cpu_kind = 'shared' 28 - cpus = 1 29 - 30 - # Override VM settings for worker processes 31 - [[vm]] 32 - processes = ['feed-worker'] 33 - memory = '512mb' # Workers typically need less memory 34 - cpu_kind = 'shared' 35 - cpus = 1 36 - 37 - [env] 38 - NODE_ENV="dev" 39 - BASE_URL="https://api.dev.semble.so" 40 - HOST="0.0.0.0" 41 - APP_URL="https://dev.semble.so"
+15 -11
src/shared/infrastructure/config/EnvironmentConfigService.ts
··· 1 + export enum Environment { 2 + LOCAL = 'local', 3 + DEV = 'dev', 4 + PROD = 'prod', 5 + } 6 + 1 7 export interface EnvironmentConfig { 2 - environment: 'local' | 'dev' | 'prod'; 8 + environment: Environment; 3 9 database: { 4 10 url: string; 5 11 }; ··· 42 48 private config: EnvironmentConfig; 43 49 44 50 constructor() { 45 - const environment = (process.env.NODE_ENV || 'local') as 46 - | 'local' 47 - | 'dev' 48 - | 'prod'; 51 + const environment = (process.env.NODE_ENV || 52 + Environment.LOCAL) as Environment; 49 53 50 54 this.config = { 51 55 environment, ··· 70 74 baseUrl: process.env.BASE_URL || 'http://127.0.0.1:3000', 71 75 collections: { 72 76 card: 73 - environment === 'prod' 77 + environment === Environment.PROD 74 78 ? 'network.cosmik.card' 75 79 : `network.cosmik.${environment}.card`, 76 80 collection: 77 - environment === 'prod' 81 + environment === Environment.PROD 78 82 ? 'network.cosmik.collection' 79 83 : `network.cosmik.${environment}.collection`, 80 84 collectionLink: 81 - environment === 'prod' 85 + environment === Environment.PROD 82 86 ? 'network.cosmik.collectionLink' 83 87 : `network.cosmik.${environment}.collectionLink`, 84 88 }, ··· 109 113 110 114 private applyEnvironmentSpecificConfig(): void { 111 115 switch (this.config.environment) { 112 - case 'dev': 116 + case Environment.DEV: 113 117 // Override defaults with dev-specific values 114 118 break; 115 - case 'prod': 119 + case Environment.PROD: 116 120 // Override defaults with production-specific values 117 121 if ( 118 122 this.config.auth.jwtSecret === 'default-secret-change-in-production' ··· 120 124 throw new Error('JWT secret must be set in production environment'); 121 125 } 122 126 break; 123 - case 'local': 127 + case Environment.LOCAL: 124 128 default: 125 129 // Local development defaults are already set 126 130 break;
+7 -4
src/shared/infrastructure/http/app.ts
··· 6 6 import { createAtprotoRoutes } from '../../../modules/atproto/infrastructure/atprotoRoutes'; 7 7 import { createCardsModuleRoutes } from '../../../modules/cards/infrastructure/http/routes'; 8 8 import { createFeedRoutes } from '../../../modules/feeds/infrastructure/http/routes/feedRoutes'; 9 - import { EnvironmentConfigService } from '../config/EnvironmentConfigService'; 9 + import { 10 + EnvironmentConfigService, 11 + Environment, 12 + } from '../config/EnvironmentConfigService'; 10 13 import { RepositoryFactory } from './factories/RepositoryFactory'; 11 14 import { ServiceFactory } from './factories/ServiceFactory'; 12 15 import { UseCaseFactory } from './factories/UseCaseFactory'; ··· 23 26 const appUrl = configService.getAppConfig().appUrl; 24 27 25 28 switch (environment) { 26 - case 'prod': 29 + case Environment.PROD: 27 30 return ['https://semble.so', 'https://api.semble.so']; 28 - case 'dev': 31 + case Environment.DEV: 29 32 return ['https://dev.semble.so', 'https://api.dev.semble.so']; 30 - case 'local': 33 + case Environment.LOCAL: 31 34 default: 32 35 // Allow both localhost:4000 and configured appUrl for flexibility 33 36 return [
+8 -5
src/shared/infrastructure/http/services/CookieService.ts
··· 1 1 import { Response, Request } from 'express'; 2 - import { EnvironmentConfigService } from '../../config/EnvironmentConfigService'; 2 + import { 3 + EnvironmentConfigService, 4 + Environment, 5 + } from '../../config/EnvironmentConfigService'; 3 6 4 7 export interface CookieOptions { 5 8 httpOnly: boolean; ··· 26 29 const environment = this.configService.get().environment; 27 30 28 31 switch (environment) { 29 - case 'prod': 32 + case Environment.PROD: 30 33 return '.semble.so'; 31 - case 'dev': 34 + case Environment.DEV: 32 35 return '.dev.semble.so'; 33 - case 'local': 36 + case Environment.LOCAL: 34 37 default: 35 38 return undefined; // Don't set domain for localhost 36 39 } ··· 41 44 */ 42 45 private getBaseCookieOptions(): Omit<CookieOptions, 'maxAge'> { 43 46 const environment = this.configService.get().environment; 44 - const isProduction = environment === 'prod'; 47 + const isProduction = environment === Environment.PROD; 45 48 46 49 return { 47 50 httpOnly: true,