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 deploy: 8 name: Deploy app 9 runs-on: ubuntu-latest 10 - concurrency: deploy-group # optional: ensure only one action runs at a time 11 steps: 12 - uses: actions/checkout@v4 13 - uses: superfly/flyctl-actions/setup-flyctl@master 14 - - run: flyctl deploy --remote-only 15 env: 16 FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}
··· 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.development.toml 15 env: 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 interface EnvironmentConfig { 2 - environment: 'local' | 'dev' | 'prod'; 3 database: { 4 url: string; 5 }; ··· 42 private config: EnvironmentConfig; 43 44 constructor() { 45 - const environment = (process.env.NODE_ENV || 'local') as 46 - | 'local' 47 - | 'dev' 48 - | 'prod'; 49 50 this.config = { 51 environment, ··· 70 baseUrl: process.env.BASE_URL || 'http://127.0.0.1:3000', 71 collections: { 72 card: 73 - environment === 'prod' 74 ? 'network.cosmik.card' 75 : `network.cosmik.${environment}.card`, 76 collection: 77 - environment === 'prod' 78 ? 'network.cosmik.collection' 79 : `network.cosmik.${environment}.collection`, 80 collectionLink: 81 - environment === 'prod' 82 ? 'network.cosmik.collectionLink' 83 : `network.cosmik.${environment}.collectionLink`, 84 }, ··· 109 110 private applyEnvironmentSpecificConfig(): void { 111 switch (this.config.environment) { 112 - case 'dev': 113 // Override defaults with dev-specific values 114 break; 115 - case 'prod': 116 // Override defaults with production-specific values 117 if ( 118 this.config.auth.jwtSecret === 'default-secret-change-in-production' ··· 120 throw new Error('JWT secret must be set in production environment'); 121 } 122 break; 123 - case 'local': 124 default: 125 // Local development defaults are already set 126 break;
··· 1 + export enum Environment { 2 + LOCAL = 'local', 3 + DEV = 'dev', 4 + PROD = 'prod', 5 + } 6 + 7 export interface EnvironmentConfig { 8 + environment: Environment; 9 database: { 10 url: string; 11 }; ··· 48 private config: EnvironmentConfig; 49 50 constructor() { 51 + const environment = (process.env.NODE_ENV || 52 + Environment.LOCAL) as Environment; 53 54 this.config = { 55 environment, ··· 74 baseUrl: process.env.BASE_URL || 'http://127.0.0.1:3000', 75 collections: { 76 card: 77 + environment === Environment.PROD 78 ? 'network.cosmik.card' 79 : `network.cosmik.${environment}.card`, 80 collection: 81 + environment === Environment.PROD 82 ? 'network.cosmik.collection' 83 : `network.cosmik.${environment}.collection`, 84 collectionLink: 85 + environment === Environment.PROD 86 ? 'network.cosmik.collectionLink' 87 : `network.cosmik.${environment}.collectionLink`, 88 }, ··· 113 114 private applyEnvironmentSpecificConfig(): void { 115 switch (this.config.environment) { 116 + case Environment.DEV: 117 // Override defaults with dev-specific values 118 break; 119 + case Environment.PROD: 120 // Override defaults with production-specific values 121 if ( 122 this.config.auth.jwtSecret === 'default-secret-change-in-production' ··· 124 throw new Error('JWT secret must be set in production environment'); 125 } 126 break; 127 + case Environment.LOCAL: 128 default: 129 // Local development defaults are already set 130 break;
+7 -4
src/shared/infrastructure/http/app.ts
··· 6 import { createAtprotoRoutes } from '../../../modules/atproto/infrastructure/atprotoRoutes'; 7 import { createCardsModuleRoutes } from '../../../modules/cards/infrastructure/http/routes'; 8 import { createFeedRoutes } from '../../../modules/feeds/infrastructure/http/routes/feedRoutes'; 9 - import { EnvironmentConfigService } from '../config/EnvironmentConfigService'; 10 import { RepositoryFactory } from './factories/RepositoryFactory'; 11 import { ServiceFactory } from './factories/ServiceFactory'; 12 import { UseCaseFactory } from './factories/UseCaseFactory'; ··· 23 const appUrl = configService.getAppConfig().appUrl; 24 25 switch (environment) { 26 - case 'prod': 27 return ['https://semble.so', 'https://api.semble.so']; 28 - case 'dev': 29 return ['https://dev.semble.so', 'https://api.dev.semble.so']; 30 - case 'local': 31 default: 32 // Allow both localhost:4000 and configured appUrl for flexibility 33 return [
··· 6 import { createAtprotoRoutes } from '../../../modules/atproto/infrastructure/atprotoRoutes'; 7 import { createCardsModuleRoutes } from '../../../modules/cards/infrastructure/http/routes'; 8 import { createFeedRoutes } from '../../../modules/feeds/infrastructure/http/routes/feedRoutes'; 9 + import { 10 + EnvironmentConfigService, 11 + Environment, 12 + } from '../config/EnvironmentConfigService'; 13 import { RepositoryFactory } from './factories/RepositoryFactory'; 14 import { ServiceFactory } from './factories/ServiceFactory'; 15 import { UseCaseFactory } from './factories/UseCaseFactory'; ··· 26 const appUrl = configService.getAppConfig().appUrl; 27 28 switch (environment) { 29 + case Environment.PROD: 30 return ['https://semble.so', 'https://api.semble.so']; 31 + case Environment.DEV: 32 return ['https://dev.semble.so', 'https://api.dev.semble.so']; 33 + case Environment.LOCAL: 34 default: 35 // Allow both localhost:4000 and configured appUrl for flexibility 36 return [
+8 -5
src/shared/infrastructure/http/services/CookieService.ts
··· 1 import { Response, Request } from 'express'; 2 - import { EnvironmentConfigService } from '../../config/EnvironmentConfigService'; 3 4 export interface CookieOptions { 5 httpOnly: boolean; ··· 26 const environment = this.configService.get().environment; 27 28 switch (environment) { 29 - case 'prod': 30 return '.semble.so'; 31 - case 'dev': 32 return '.dev.semble.so'; 33 - case 'local': 34 default: 35 return undefined; // Don't set domain for localhost 36 } ··· 41 */ 42 private getBaseCookieOptions(): Omit<CookieOptions, 'maxAge'> { 43 const environment = this.configService.get().environment; 44 - const isProduction = environment === 'prod'; 45 46 return { 47 httpOnly: true,
··· 1 import { Response, Request } from 'express'; 2 + import { 3 + EnvironmentConfigService, 4 + Environment, 5 + } from '../../config/EnvironmentConfigService'; 6 7 export interface CookieOptions { 8 httpOnly: boolean; ··· 29 const environment = this.configService.get().environment; 30 31 switch (environment) { 32 + case Environment.PROD: 33 return '.semble.so'; 34 + case Environment.DEV: 35 return '.dev.semble.so'; 36 + case Environment.LOCAL: 37 default: 38 return undefined; // Don't set domain for localhost 39 } ··· 44 */ 45 private getBaseCookieOptions(): Omit<CookieOptions, 'maxAge'> { 46 const environment = this.configService.get().environment; 47 + const isProduction = environment === Environment.PROD; 48 49 return { 50 httpOnly: true,