File size: 4,172 Bytes
abc493d
 
 
 
b2f5bb7
 
 
 
 
 
 
 
 
a381fc9
 
 
b2f5bb7
73e0e3c
abc493d
 
 
 
 
 
 
a381fc9
abc493d
 
 
 
 
 
 
 
a381fc9
abc493d
 
 
 
 
 
 
 
 
eda9962
abc493d
 
a381fc9
 
 
 
 
 
 
 
 
 
abc493d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
73e0e3c
abc493d
 
 
 
 
 
 
 
a381fc9
abc493d
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { TypeOrmModule } from '@nestjs/typeorm';
import * as path from 'path';

// pg returns TIMESTAMP WITHOUT TIME ZONE (OID 1114) as a bare string like
// "2026-06-19 14:45:00.123456" with no timezone indicator.  Node's Date
// constructor treats that as *local* time, baking the server's TZ offset
// into the serialised JSON before the browser ever sees it.
// Force both timestamp OIDs to be parsed as UTC so the stored value is
// faithfully preserved — Python always writes utcnow() (UTC, naive).
// eslint-disable-next-line @typescript-eslint/no-require-imports
const pgTypes = require('pg').types;
pgTypes.setTypeParser(1114, (v: string) =>
  v ? new Date(v.replace(' ', 'T') + 'Z') : null,
);
pgTypes.setTypeParser(1184, (v: string) => (v ? new Date(v) : null));
import { SqliteDateSubscriber } from './common/sqlite-date.subscriber';
import { AgentRunsModule } from './agent-runs/agent-runs.module';
import { AuthModule } from './auth/auth.module';
import { AgentRun } from './common/entities/agent-run.entity';
import { AnalystReport } from './common/entities/analyst-report.entity';
import { Notification } from './common/entities/notification.entity';
import { PortfolioSnapshot } from './common/entities/portfolio-snapshot.entity';
import { Position } from './common/entities/position.entity';
import { RetrospectiveReport } from './common/entities/retrospective-report.entity';
import { Setting } from './common/entities/setting.entity';
import { Strategy } from './common/entities/strategy.entity';
import { Transaction } from './common/entities/transaction.entity';
import { User } from './common/entities/user.entity';
import { DashboardModule } from './dashboard/dashboard.module';
import { NotificationsModule } from './notifications/notifications.module';
import { PortfolioModule } from './portfolio/portfolio.module';
import { ReportsModule } from './reports/reports.module';
import { RetrospectiveReportsModule } from './retrospective-reports/retrospective-reports.module';
import { SettingsModule } from './settings/settings.module';
import { StrategiesModule } from './strategies/strategies.module';
import { TransactionsModule } from './transactions/transactions.module';
import { UsersModule } from './users/users.module';

// Resolve root .env whether running from api/ (dev) or project root (prod)
const ROOT_DIR = path.resolve(__dirname, '..', '..'); // api/dist → project root
const ENV_PATH = path.join(ROOT_DIR, '.env');

export const UI_DIST = path.join(ROOT_DIR, 'ui', 'dist');

const ENTITIES = [
  User,
  Setting,
  Strategy,
  AgentRun,
  AnalystReport,
  Position,
  Transaction,
  PortfolioSnapshot,
  Notification,
  RetrospectiveReport,
];

@Module({
  imports: [
    ConfigModule.forRoot({ isGlobal: true, envFilePath: ENV_PATH }),
    TypeOrmModule.forRootAsync({
      imports: [ConfigModule],
      inject: [ConfigService],
      useFactory: (cfg: ConfigService) => {
        const url = cfg.get<string>('DATABASE_URL');
        if (url && url.startsWith('postgresql')) {
          // Neon PostgreSQL — strip channel_binding which pg driver doesn't support
          const cleanUrl = url
            .replace('&channel_binding=require', '')
            .replace('?channel_binding=require&', '?');
          return {
            type: 'postgres' as const,
            url: cleanUrl,
            ssl: { rejectUnauthorized: false },
            entities: ENTITIES,
            synchronize: false,
          };
        }
        // Fallback: local SQLite (dev without DATABASE_URL)
        return {
          type: 'better-sqlite3' as const,
          database: path.join(ROOT_DIR, 'data', 'stockadvisor.db'),
          entities: ENTITIES,
          subscribers: [SqliteDateSubscriber],
          synchronize: false,
        };
      },
    }),
    AuthModule,
    UsersModule,
    AgentRunsModule,
    ReportsModule,
    RetrospectiveReportsModule,
    TransactionsModule,
    PortfolioModule,
    StrategiesModule,
    NotificationsModule,
    DashboardModule,
    SettingsModule,
  ],
})
export class AppModule {}