Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
packages/db/src/entities/*.ts
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ yarn-error.log*

# local env files
**/.env
**/.db.env
**/.env*.local

# vercel
Expand All @@ -43,4 +44,5 @@ next-env.d.ts
amplifyconfiguration.json

#databases
packages/server/database.sqlite
packages/server/database.sqlite
packages/db/dist
8 changes: 8 additions & 0 deletions docs/AMPLIFY.md
Original file line number Diff line number Diff line change
Expand Up @@ -205,3 +205,11 @@ This table stores the [packet](./TELEMETRY.md#packet) data. The timestamp of whe
}
}
```

### Connecting through the instance through SSM

You need this IAM Role to be able to connect through SSM

```
AmazonEC2RoleforSSM
```
14 changes: 13 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,23 @@
"dev:server": "lerna run dev --scope=server --stream",
"build:client": "lerna run build --scope=client --stream",
"build:server": "lerna run build --scope=server --stream",
"build:db": "lerna run build --scope=db --stream",
"start:client": "lerna run start --scope=client --stream",
"start:server": "lerna run start --scope=server --stream",
"start:db": "lerna run start --scope=db --stream",
"lint": "lerna run lint",
"lintf": "lerna run lintf",
"test": "lerna run test"
"test": "lerna run test",
"db:up": "lerna run db:up --scope=db",
"db:down": "lerna run db:down --scope=db",
"db:logs": "lerna run db:logs --scope=db",
"db:build": "lerna run build --scope=db",
"migrate:generate": "lerna run migrate:generate --scope=db --",
"migrate:run": "lerna run migrate:run --scope=db",
"migrate:revert": "lerna run migrate:revert --scope=db",
"migrate:reset": "lerna run migrate:reset --scope=db",
"schema:sync": "lerna run schema:sync --scope=db",
"schema:drop": "lerna run schema:drop --scope=db"
},
"dependencies": {
"@mantine/dates": "^8.0.1",
Expand Down
6 changes: 6 additions & 0 deletions packages/db/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
POSTGRES_PASSWORD=postgres
POSTGRES_USER=postgres
DB_NAME=postgres
DB_HOST=localhost
DB_PORT=5432
NODE_ENV=development
8 changes: 8 additions & 0 deletions packages/db/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Node TypeORM Example

Example of using TimescaleDB with Node.js and [TypeORM](https://typeorm.io/).

```bash
docker compose down -v # take down volumes as well

```
2 changes: 1 addition & 1 deletion packages/db/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ version: "3.9"

services:
db:
image: timescale/timescaledb-ha:pg17 # Image for timescale db with postgres 17
image: timescale/timescaledb:latest-pg17 # Image for timescale db with postgres 17
env_file:
- .db.env # contains the information regarding our user, password, and database
ports:
Expand Down
35 changes: 35 additions & 0 deletions packages/db/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"name": "db",
"version": "0.1.0",
"main": "./src/index.ts",
"types": "./src/index.ts",
"scripts": {
"build": "tsc",
"start": "tsx src/index.ts",
"test": "jest --runInBand",
"db:up": "docker-compose up -d",
"db:down": "docker-compose down",
"db:logs": "docker-compose logs -f timescaledb",
"migrate:generate": "typeorm-ts-node-commonjs migration:generate -d src/data-source.ts",
"migrate:run": "typeorm-ts-node-commonjs migration:run -d src/data-source.ts",
"migrate:revert": "typeorm-ts-node-commonjs migration:revert -d src/data-source.ts",
"migrate:reset": "typeorm-ts-node-commonjs migration:revert -d src/data-source.ts && typeorm-ts-node-commonjs migration:run -d src/data-source.ts",
"schema:sync": "typeorm-ts-node-commonjs schema:sync -d src/data-source.ts",
"schema:drop": "typeorm-ts-node-commonjs schema:drop -d src/data-source.ts"
},
"dependencies": {
"@timescaledb/core": "^0.0.1",
"@timescaledb/schemas": "^0.0.1",
"@timescaledb/typeorm": "^0.0.1",
"dotenv": "^16.4.5",
"express": "^4.18.2",
"pg": "^8.11.3",
"reflect-metadata": "^0.1.13",
"typeorm": "^0.3.20"
},
"devDependencies": {
"@faker-js/faker": "^9.3.0",
"@types/pg": "^8.11.0",
"typescript": "^5.5.3"
}
}
28 changes: 28 additions & 0 deletions packages/db/src/data-source.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import "reflect-metadata";
import { DataSource } from "typeorm";
import dotenv from "dotenv";

// import your entities/tables here
import { TestTable } from "./entities/TestTable.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: [TestTable],
// 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.
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
type: "postgres",
username: process.env.POSTGRES_USER || "postgres",
});
45 changes: 45 additions & 0 deletions packages/db/src/entities/TestTable.entity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import {
Entity,
PrimaryGeneratedColumn,
Column,
CreateDateColumn,
UpdateDateColumn,
} from "typeorm";
import { Hypertable, TimeColumn } from "@timescaledb/typeorm";

@Entity("test_table")
@Hypertable({
compression: {
compress: true,
compress_orderby: "timestamp DESC",
compress_segmentby: "name",
},
})
export class TestTable {
@PrimaryGeneratedColumn("uuid")
id!: string;

@TimeColumn()
timestamp!: Date;

@Column({ type: "varchar", length: 255 })
name!: string;

@Column({ type: "text", nullable: true })
description?: string;

@Column({ type: "boolean", default: true })
isActive!: boolean;

@Column({ type: "int", default: 0 })
value!: number;

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

@CreateDateColumn()
createdAt!: Date;

@UpdateDateColumn()
updatedAt!: Date;
}
22 changes: 22 additions & 0 deletions packages/db/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
export { AppDataSource } from "./data-source";

// entities
export { TestTable } from "./entities/TestTable.entity";

// repositories
export { BaseRepository } from "./repositories/BaseRepository";
export { TestTableRepository } from "./repositories/TestTableRepository";

// interfaces
export * from "./interfaces/repositories.interface";

// services
export { DatabaseService } from "./services/DatabaseService";
export {
TestTableService,
type CreateTestTableDto,
type UpdateTestTableDto,
} from "./services/TestTableService";

// types
export * from "./types";
17 changes: 17 additions & 0 deletions packages/db/src/interfaces/repositories.interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { DeepPartial, FindManyOptions, FindOneOptions } from "typeorm";
import { TestTable } from "../entities/TestTable.entity";

export interface IBaseRepository<T> {
create(entity: DeepPartial<T>): Promise<T>;
findById(id: string | number): Promise<T | null>;
findOne(options: FindOneOptions<T>): Promise<T | null>;
findMany(options?: FindManyOptions<T>): Promise<T[]>;
update(id: string | number, updates: DeepPartial<T>): Promise<T | null>;
delete(id: string | number): Promise<boolean>;
count(options?: FindManyOptions<T>): Promise<number>;
}

export interface ITestTableRepository extends IBaseRepository<TestTable> {
findByName(name: string): Promise<TestTable[]>;
findActiveRecords(): Promise<TestTable[]>;
}
53 changes: 53 additions & 0 deletions packages/db/src/repositories/BaseRepository.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import {
Repository,
FindOptionsWhere,
FindManyOptions,
DeepPartial,
} from "typeorm";
import { ObjectLiteral, FindOneOptions } from "typeorm";

// Define a base entity interface that has an id
interface BaseEntity {
id: number | string;
}

export abstract class BaseRepository<T extends ObjectLiteral & BaseEntity> {
constructor(protected repository: Repository<T>) {}

async findAll(options?: FindManyOptions<T>): Promise<T[]> {
return this.repository.find(options);
}

async findOne(options: FindOneOptions<T>): Promise<T | null> {
return this.repository.findOne(options);
}

async findById(id: number | string): Promise<T | null> {
return this.repository.findOne({
where: { id } as FindOptionsWhere<T>,
});
}

async create(data: DeepPartial<T>): Promise<T> {
const entity = this.repository.create(data);
return this.repository.save(entity);
}

async update(id: number | string, data: DeepPartial<T>): Promise<T | null> {
await this.repository.update(id, data);
return this.findById(id);
}

async delete(id: number | string): Promise<boolean> {
const result = await this.repository.delete(id);
return (result.affected ?? 0) > 0;
}

async save(entity: DeepPartial<T>): Promise<T> {
return this.repository.save(entity);
}

async count(options?: FindManyOptions<T>): Promise<number> {
return this.repository.count(options);
}
}
49 changes: 49 additions & 0 deletions packages/db/src/repositories/TestTableRepository.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { Repository, Like, FindManyOptions } from "typeorm";
import { BaseRepository } from "./BaseRepository";
import { TestTable } from "../entities/TestTable.entity";
import { ITestTableRepository } from "../interfaces/repositories.interface";

export class TestTableRepository
extends BaseRepository<TestTable>
implements ITestTableRepository
{
constructor(repository: Repository<TestTable>) {
super(repository);
}

async findMany(
options?: FindManyOptions<TestTable> | undefined,
): Promise<TestTable[]> {
return this.repository.find(options);
}

async findByName(name: string): Promise<TestTable[]> {
return await this.repository.find({
order: {
createdAt: "DESC",
},
where: {
name: Like(`%${name}%`),
},
});
}

async findActiveRecords(): Promise<TestTable[]> {
return await this.repository.find({
order: {
updatedAt: "DESC",
},
where: {
isActive: true,
},
});
}

async findByValueRange(min: number, max: number): Promise<TestTable[]> {
return await this.repository
.createQueryBuilder("test")
.where("test.value >= :min AND test.value <= :max", { max, min })
.orderBy("test.value", "ASC")
.getMany();
}
}
50 changes: 50 additions & 0 deletions packages/db/src/services/DatabaseService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { DataSource } from 'typeorm';
import { AppDataSource } from '../data-source';
import { TestTable } from '../entities/TestTable.entity';
import { TestTableRepository } from '../repositories/TestTableRepository';

export class DatabaseService {
private dataSource: DataSource;
private _testTableRepository: TestTableRepository | null = null;

constructor() {
this.dataSource = AppDataSource;
}

async initialize(): Promise<void> {
try {
if (!this.dataSource.isInitialized) {
await this.dataSource.initialize();
console.log('Database connection initialized successfully');
}
} catch (error) {
console.error('Error during database initialization:', error);
throw error;
}
}

async close(): Promise<void> {
if (this.dataSource.isInitialized) {
await this.dataSource.destroy();
console.log('Database connection closed');
}
}

get testTableRepository(): TestTableRepository {
if (!this._testTableRepository) {
const repository = this.dataSource.getRepository(TestTable);
this._testTableRepository = new TestTableRepository(repository);
}
return this._testTableRepository;
}

// Helper method to check if database is connected
get isConnected(): boolean {
return this.dataSource.isInitialized;
}

// Get the raw data source if needed for advanced operations
get rawDataSource(): DataSource {
return this.dataSource;
}
}
Loading
Loading