Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 4 additions & 39 deletions packages/db/src/data-source.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,52 +3,17 @@ import { DataSource } from "typeorm";
import dotenv from "dotenv";

// import your entities/tables here
import { TelemetryMetadata } from "./entities/TelemetryMetadata.entity";
import { Battery } from "./entities/Battery.entity";
import { BatteryFaults } from "./entities/BatteryFaults.entity";
import { MotorDetails } from "./entities/MotorDetails.entity";
import { KeyMotor } from "./entities/KeyMotor.entity";
import { MPPT } from "./entities/MPPT.entity";
import { Contactor } from "./entities/Contactor.entity";
import { B3 } from "./entities/B3.entity";
import { MBMS } from "./entities/MBMS.entity";
import { Telemetry } from "./entities/Telemetry.entity";
import { ProximitySensors } from "./entities/ProximitySensors.entity";
import { Driver } from "./entities/Driver.entity";
import { Lap } from "./entities/Lap.entity";
import { TelemetryPacket } from "./entities/TelemetryPacket.entity";

dotenv.config({ path: ".db.env" });

export const AppDataSource = new DataSource({
// database name
database: process.env.DB_NAME || "postgres",
// entity schemas, whenever you make a table you have to add it here
entities: [
TelemetryMetadata,
Battery,
BatteryFaults,
MotorDetails,
KeyMotor,
MPPT,
Contactor,
B3,
MBMS,
Telemetry,
ProximitySensors,
Driver,
Lap,
],
// database host (if it's localhost or not)
host: process.env.DB_HOST || "localhost",
// logging: https://typeorm.io/docs/advanced-topics/logging
// you can enable logging only in development to avoid performance issues
// there are also different level of logging you can set like "all", "query", etc.
entities: [TelemetryPacket, Driver, Lap],
logging: process.env.NODE_ENV === "development",
// the migrations directory (not sure if this works yet)
migrations: [__dirname + "/migrations/*.{js,ts}"],
password: process.env.POSTGRES_PASSWORD || "postgres",
port: parseInt(process.env.DB_PORT || "5432"),
synchronize: process.env.NODE_ENV === "development", // Only in development
synchronize: process.env.NODE_ENV === "development",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Disable synchronize when using TimescaleDB hypertables.

TypeORM's synchronize: true can interfere with TimescaleDB hypertables because it doesn't understand TimescaleDB-specific features like create_hypertable(). When synchronize runs, it may recreate the TelemetryPacket table without the hypertable configuration, breaking time-series functionality even in development.

🔧 Recommended fix
- synchronize: process.env.NODE_ENV === "development",
+ synchronize: false,

Always use migrations for schema changes with TimescaleDB to ensure hypertable configuration is properly applied.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
synchronize: process.env.NODE_ENV === "development",
synchronize: false,
🤖 Prompt for AI Agents
In @packages/db/src/data-source.ts at line 20, synchronize is enabled in the
TypeORM DataSource config which can break TimescaleDB hypertables (e.g.,
TelemetryPacket); change the DataSource config to set synchronize: false (remove
the development-only conditional) and rely on migrations for schema changes,
ensuring any code that references the DataSource options (the DataSource
construction in data-source.ts and any TelemetryPacket entity setup) no longer
allows synchronize to be true.

type: "postgres",
username: process.env.POSTGRES_USERNAME || "postgres",
url: process.env.DATABASE_URL,
Comment thread
coderabbitai[bot] marked this conversation as resolved.
});
48 changes: 24 additions & 24 deletions packages/db/src/entities/B3.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,74 +19,74 @@ import { Hypertable, TimeColumn } from "@timescaledb/typeorm";
export class B3 {
@TimeColumn()
@PrimaryColumn({ type: "timestamptz" })
timestamp!: Date;
Timestamp!: Date;

@PrimaryColumn({ type: "text" })
rfid!: string;
Rfid!: string;

@Column({ type: "float" })
acceleration!: number;
Acceleration!: number;

@Column({ type: "boolean" })
b3_heartbeat!: boolean;
B3Heartbeat!: boolean;

@Column({ type: "boolean" })
brake_light_signal_status!: boolean;
BrakeLightSignalStatus!: boolean;

@Column({ type: "boolean" })
brake_switch_digital!: boolean;
BrakeSwitchDigital!: boolean;

@Column({ type: "boolean" })
daytime_running_light_signal_status!: boolean;
DaytimeRunningLightSignalStatus!: boolean;

@Column({ type: "boolean" })
forward_digital!: boolean;
ForwardDigital!: boolean;

@Column({ type: "boolean" })
handbrake_switch_digital!: boolean;
HandbrakeSwitchDigital!: boolean;

@Column({ type: "boolean" })
hazard_lights_input!: boolean;
HazardLightsInput!: boolean;

@Column({ type: "boolean" })
headlights_switch_input!: boolean;
HeadlightsSwitchInput!: boolean;

@Column({ type: "boolean" })
headlight_signal_status!: boolean;
HeadlightSignalStatus!: boolean;

@Column({ type: "boolean" })
horn_signal_status!: boolean;
HornSignalStatus!: boolean;

@Column({ type: "boolean" })
horn_switch_digital!: boolean;
HornSwitchDigital!: boolean;

@Column({ type: "boolean" })
lap_digital!: boolean;
LapDigital!: boolean;

@Column({ type: "boolean" })
left_signal_input!: boolean;
LeftSignalInput!: boolean;

@Column({ type: "boolean" })
left_signal_status!: boolean;
LeftSignalStatus!: boolean;

@Column({ type: "boolean" })
motor_reset_digital!: boolean;
MotorResetDigital!: boolean;

@Column({ type: "boolean" })
neutral_digital!: boolean;
NeutralDigital!: boolean;

@Column({ type: "boolean" })
race_mode_digital!: boolean;
RaceModeDigital!: boolean;

@Column({ type: "float" })
regen_braking!: number;
RegenBraking!: number;

@Column({ type: "boolean" })
reverse_digital!: boolean;
ReverseDigital!: boolean;

@Column({ type: "boolean" })
right_signal_input!: boolean;
RightSignalInput!: boolean;

@Column({ type: "boolean" })
right_signal_status!: boolean;
RightSignalStatus!: boolean;
}
72 changes: 36 additions & 36 deletions packages/db/src/entities/Battery.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,110 +19,110 @@ import { Hypertable, TimeColumn } from "@timescaledb/typeorm";
export class Battery {
@TimeColumn()
@PrimaryColumn({ type: "timestamptz" })
timestamp!: Date;
Timestamp!: Date;

@PrimaryColumn({ type: "text" })
rfid!: string;
Rfid!: string;

@Column({ type: "boolean" })
always_on_signal_status!: boolean;
AlwaysOnSignalStatus!: boolean;

@Column({ type: "float" })
average_cell_voltage!: number;
AverageCellVoltage!: number;

@Column({ type: "float" })
average_temperature!: number;
AverageTemperature!: number;

@Column({ type: "integer" })
bmu_alive!: number;
BmuAlive!: number;

@Column({ type: "boolean" })
charge_relay_enabled!: boolean;
ChargeRelayEnabled!: boolean;

@Column({ type: "boolean" })
charger_safety_enabled!: boolean;
ChargerSafetyEnabled!: boolean;

@Column({ type: "boolean" })
discharge_relay_enabled!: boolean;
DischargeRelayEnabled!: boolean;

@Column({ type: "float" })
fan_speed!: number;
FanSpeed!: number;

@Column({ type: "float" })
fan_voltage!: number;
FanVoltage!: number;

@Column({ type: "float" })
high_cell_voltage!: number;
HighCellVoltage!: number;

@Column({ type: "integer" })
high_cell_voltage_id!: number;
HighCellVoltageId!: number;

@Column({ type: "float" })
high_temperature!: number;
HighTemperature!: number;

@Column({ type: "integer" })
high_thermistor_id!: number;
HighThermistorId!: number;

@Column({ type: "float" })
input_12v!: number;
Input12v!: number;

@Column({ type: "float" })
internal_temperature!: number;
InternalTemperature!: number;

@Column({ type: "boolean" })
is_charging_signal_status!: boolean;
IsChargingSignalStatus!: boolean;

@Column({ type: "boolean" })
is_ready_signal_status!: boolean;
IsReadySignalStatus!: boolean;

@Column({ type: "float" })
low_cell_voltage!: number;
LowCellVoltage!: number;

@Column({ type: "integer" })
low_cell_voltage_id!: number;
LowCellVoltageId!: number;

@Column({ type: "float" })
low_temperature!: number;
LowTemperature!: number;

@Column({ type: "integer" })
low_thermistor_id!: number;
LowThermistorId!: number;

@Column({ type: "boolean" })
malfunction_indicator_active!: boolean;
MalfunctionIndicatorActive!: boolean;

@Column({ type: "float" })
maximum_cell_voltage!: number;
MaximumCellVoltage!: number;

@Column({ type: "float" })
maximum_pack_voltage!: number;
MaximumPackVoltage!: number;

@Column({ type: "float" })
minimum_cell_voltage!: number;
MinimumCellVoltage!: number;

@Column({ type: "float" })
minimum_pack_voltage!: number;
MinimumPackVoltage!: number;

@Column({ type: "boolean" })
multi_purpose_input_signal_status!: boolean;
MultiPurposeInputSignalStatus!: boolean;

@Column({ type: "float" })
pack_amphours!: number;
PackAmphours!: number;

@Column({ type: "float" })
pack_current!: number;
PackCurrent!: number;

@Column({ type: "float" })
pack_depth_of_discharge!: number;
PackDepthOfDischarge!: number;

@Column({ type: "float" })
pack_state_of_charge!: number;
PackStateOfCharge!: number;

@Column({ type: "float" })
pack_voltage!: number;
PackVoltage!: number;

@Column({ type: "integer" })
populated_cells!: number;
PopulatedCells!: number;

@Column({ type: "float" })
requested_fan_speed!: number;
RequestedFanSpeed!: number;
Comment on lines +22 to +127

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find migration files related to battery table renaming
find . -type f \( -name "*migration*" -o -name "*migrate*" \) -path "*/db/*" | head -20

Repository: UCSolarCarTeam/Helios-Telemetry

Length of output: 57


🏁 Script executed:

#!/bin/bash
# Check Battery entity for explicit column name mappings
rg '@Column' packages/db/src/entities/Battery.entity.ts -A 1 -B 0 | head -40

Repository: UCSolarCarTeam/Helios-Telemetry

Length of output: 936


🏁 Script executed:

#!/bin/bash
# Get full Battery entity file to see complete structure
wc -l packages/db/src/entities/Battery.entity.ts

Repository: UCSolarCarTeam/Helios-Telemetry

Length of output: 121


🏁 Script executed:

#!/bin/bash
# Read full Battery.entity.ts file
cat -n packages/db/src/entities/Battery.entity.ts

Repository: UCSolarCarTeam/Helios-Telemetry

Length of output: 3778


🏁 Script executed:

#!/bin/bash
# Search for migration files more broadly
find . -type f \( -name "*.ts" -o -name "*.js" \) | grep -i migrat | head -20

Repository: UCSolarCarTeam/Helios-Telemetry

Length of output: 57


🏁 Script executed:

#!/bin/bash
# Check for TypeORM migrations directory
fd -t d migration

Repository: UCSolarCarTeam/Helios-Telemetry

Length of output: 57


🏁 Script executed:

#!/bin/bash
# Search for TypeORM DataSource or Connection configuration
rg -l "DataSource|createConnection|getConnection" packages/db/src --type ts | head -10

Repository: UCSolarCarTeam/Helios-Telemetry

Length of output: 174


🏁 Script executed:

#!/bin/bash
# Search for naming strategy configuration
rg "namingStrategy|NamingStrategy" packages/db/src --type ts -B 2 -A 2

Repository: UCSolarCarTeam/Helios-Telemetry

Length of output: 57


🏁 Script executed:

#!/bin/bash
# Check package.json for TypeORM version and configuration
cat packages/db/package.json 2>/dev/null || cat package.json | grep -A 20 '"typeorm"'

Repository: UCSolarCarTeam/Helios-Telemetry

Length of output: 1367


🏁 Script executed:

#!/bin/bash
# Search for any database configuration files
find . -name "*.env*" -o -name "*config*" -o -name "*ormconfig*" | grep -v node_modules | head -20

Repository: UCSolarCarTeam/Helios-Telemetry

Length of output: 615


🏁 Script executed:

#!/bin/bash
# Read the TypeORM data source configuration
cat packages/db/src/data-source.ts

Repository: UCSolarCarTeam/Helios-Telemetry

Length of output: 787


🏁 Script executed:

#!/bin/bash
# Check for migrations directory and any migration files
find packages/db -type d -name migrations -o -name migration

Repository: UCSolarCarTeam/Helios-Telemetry

Length of output: 57


🏁 Script executed:

#!/bin/bash
# Look for any naming strategy usage or imports
rg "snake|pascal|kebab|naming" packages/db/src --type ts -i

Repository: UCSolarCarTeam/Helios-Telemetry

Length of output: 57


🏁 Script executed:

#!/bin/bash
# Check if Battery entity is imported or used anywhere else
rg "Battery" packages/db/src --type ts -l

Repository: UCSolarCarTeam/Helios-Telemetry

Length of output: 539


🏁 Script executed:

#!/bin/bash
# Check if Battery entity is referenced in any migration or services
rg "Battery" packages/ --type ts | head -20

Repository: UCSolarCarTeam/Helios-Telemetry

Length of output: 1487


🏁 Script executed:

#!/bin/bash
# List all entity files in the entities directory
ls -la packages/db/src/entities/

Repository: UCSolarCarTeam/Helios-Telemetry

Length of output: 1167


🏁 Script executed:

#!/bin/bash
# Check if there's a migrations directory
ls -la packages/db/src/migrations 2>/dev/null || echo "No migrations directory found"

Repository: UCSolarCarTeam/Helios-Telemetry

Length of output: 104


🏁 Script executed:

#!/bin/bash
# Check if Battery is used in TelemetryPacket or other entities
rg "Battery" packages/db/src/entities --type ts -B 2 -A 2

Repository: UCSolarCarTeam/Helios-Telemetry

Length of output: 10165


🏁 Script executed:

#!/bin/bash
# Check if Battery is used in repositories or services
cat packages/db/src/repositories/BatteryRepository.ts

Repository: UCSolarCarTeam/Helios-Telemetry

Length of output: 470


🏁 Script executed:

#!/bin/bash
# Check if Battery is imported or exported from index
cat packages/db/src/index.ts

Repository: UCSolarCarTeam/Helios-Telemetry

Length of output: 363


🏁 Script executed:

#!/bin/bash
# Check TypeORM version and how it handles naming strategy by default
npm list typeorm 2>/dev/null || rg '"typeorm"' packages/db/package.json

Repository: UCSolarCarTeam/Helios-Telemetry

Length of output: 137


🏁 Script executed:

#!/bin/bash
# Check if other entities also use PascalCase properties
head -50 packages/db/src/entities/MBMS.entity.ts

Repository: UCSolarCarTeam/Helios-Telemetry

Length of output: 1294


🏁 Script executed:

#!/bin/bash
# Check the BatteryFaults entity for comparison
head -50 packages/db/src/entities/BatteryFaults.entity.ts

Repository: UCSolarCarTeam/Helios-Telemetry

Length of output: 1380


🏁 Script executed:

#!/bin/bash
# Verify TypeORM's default naming strategy behavior
web_search "TypeORM 0.3 default naming strategy column names"

Repository: UCSolarCarTeam/Helios-Telemetry

Length of output: 125


🌐 Web query:

TypeORM 0.3.20 default naming strategy PascalCase to snake_case

💡 Result:

Yes — in TypeORM 0.3.x you set a namingStrategy on the DataSource. Two common approaches to convert PascalCase / camelCase → snake_case:

  1. Use the community package (quick, drop-in)
  • Install: npm i typeorm-naming-strategies
  • DataSource example:
    const AppDataSource = new DataSource({
    ...,
    namingStrategy: new SnakeNamingStrategy(),
    });
    (usage documented in the package README). [1]
  1. Implement a small custom SnakeNamingStrategy (works with 0.3.x DataSource API)
  • Example (uses TypeORM's snakeCase util and DefaultNamingStrategy):
    export class SnakeNamingStrategy extends DefaultNamingStrategy implements NamingStrategyInterface {
    tableName(className: string, customName: string) {
    return customName ? customName : snakeCase(className);
    }
    columnName(propertyName: string, customName: string, embeddedPrefixes: string[]) {
    const prefix = embeddedPrefixes.length ? embeddedPrefixes.join('') + '' : '';
    return snakeCase(prefix) + (customName ? customName : snakeCase(propertyName));
    }
    relationName(propertyName: string) { return snakeCase(propertyName); }
    joinColumnName(relationName: string, referencedColumnName: string) {
    return snakeCase(relationName + '_' + referencedColumnName);
    }
    /* implement joinTableName, joinTableColumnName, classTableInheritanceParentColumnName similarly */
    }
  • Then pass namingStrategy: new SnakeNamingStrategy() to your DataSource. (Example implementations available online.) [3]

Where to configure: DataSourceOptions includes namingStrategy — pass the instance when creating new DataSource. [2]

References:

  • typeorm-naming-strategies (SnakeNamingStrategy). [1]
  • TypeORM DataSource / DataSourceOptions (namingStrategy option). [2]
  • Example custom SnakeNamingStrategy gist. [3]

🏁 Script executed:

#!/bin/bash
# Verify that data-source.ts doesn't have a namingStrategy
grep -i "namingstrategy" packages/db/src/data-source.ts || echo "No namingStrategy found in data-source.ts"

Repository: UCSolarCarTeam/Helios-Telemetry

Length of output: 116


🏁 Script executed:

#!/bin/bash
# Check if any other entity files have explicit name mappings in @Column
rg '@Column.*name:' packages/db/src/entities --type ts -c

Repository: UCSolarCarTeam/Helios-Telemetry

Length of output: 57


Add explicit column name mappings to align properties with database schema.

TypeORM 0.3.20 does not automatically convert property names to snake_case—it uses property names as column names by default. Your Battery entity uses PascalCase properties without explicit name parameters in @Column decorators, meaning TypeORM will query for columns named Timestamp, Rfid, AlwaysOnSignalStatus, etc.

However, your @Hypertable configuration specifies timeColumnName: "timestamp" (lowercase), and your @Index(["rfid", "timestamp"]) decorator also uses lowercase. This mismatch will cause "column does not exist" errors at runtime.

Add explicit column name mappings to preserve compatibility with your database schema:

Example fix for first few columns
@Column({ name: "timestamp", type: "timestamptz" })
Timestamp!: Date;

@Column({ name: "rfid", type: "text" })
Rfid!: string;

@Column({ name: "always_on_signal_status", type: "boolean" })
AlwaysOnSignalStatus!: boolean;

@Column({ name: "average_cell_voltage", type: "float" })
AverageCellVoltage!: number;

Apply this pattern to all 37 properties in the Battery entity, or configure a global namingStrategy in your DataSource to automatically handle the conversion.

🤖 Prompt for AI Agents
In @packages/db/src/entities/Battery.entity.ts around lines 22 - 127, The
Battery entity uses PascalCase property names (e.g., Timestamp, Rfid,
AlwaysOnSignalStatus) but its @Hypertable timeColumnName ("timestamp") and
@Index(["rfid","timestamp"]) use snake_case; add explicit column name mappings
on every @Column in the Battery class (for example set name: "timestamp",
"rfid", "always_on_signal_status", etc.) so TypeORM queries the correct DB
columns; update the @Column decorators for all properties (Timestamp, Rfid,
AlwaysOnSignalStatus, AverageCellVoltage, AverageTemperature, BmuAlive,
ChargeRelayEnabled, ChargerSafetyEnabled, DischargeRelayEnabled, FanSpeed,
FanVoltage, HighCellVoltage, HighCellVoltageId, HighTemperature,
HighThermistorId, Input12v, InternalTemperature, IsChargingSignalStatus,
IsReadySignalStatus, LowCellVoltage, LowCellVoltageId, LowTemperature,
LowThermistorId, MalfunctionIndicatorActive, MaximumCellVoltage,
MaximumPackVoltage, MinimumCellVoltage, MinimumPackVoltage,
MultiPurposeInputSignalStatus, PackAmphours, PackCurrent, PackDepthOfDischarge,
PackStateOfCharge, PackVoltage, PopulatedCells, RequestedFanSpeed) to include
the correct snake_case name and appropriate DB type, or alternatively configure
a global namingStrategy in your DataSource to auto-convert PascalCase to
snake_case.

}
Loading