feat: initialize ingest-api with Koa and telemetry handling
- Create ingest-api with Koa framework for handling device telemetry. - Implement telemetry ingestion with MongoDB, Redis, and PostgreSQL. - Add API endpoints for telemetry and health checks. - Set up testing with Vitest for unit and end-to-end tests. - Configure Prettier and ESLint for code formatting and linting. - Create Docker Compose setup for PostgreSQL, MongoDB, and Redis services. - Establish contracts package for shared Zod schemas and TypeScript types. - Implement React frontend with device status and greeting functionalities. - Add VS Code setup documentation for onboarding developers.
This commit is contained in:
1 parent
534fa6dc51
commit
7923ba1b8d
70 files changed
+3085
-3434
No files matched your search
@@ -0,0 +1,86 @@
|
||||
import { eq } from 'drizzle-orm';
|
||||
import { drizzle } from 'drizzle-orm/node-postgres';
|
||||
import Redis from 'ioredis';
|
||||
import pg from 'pg';
|
||||
import type { DeviceStatusPair } from 'contracts';
|
||||
import { env } from './env.js';
|
||||
import { deviceCurrentState } from './db/schema.js';
|
||||
import { emptyStatus, redisKey, toDeviceStatus } from './status.js';
|
||||
|
||||
export type DeviceStores = {
|
||||
readStatus(deviceId: string): Promise<DeviceStatusPair>;
|
||||
close(): Promise<void>;
|
||||
};
|
||||
|
||||
type RedisSnapshot = {
|
||||
lastSeen: string;
|
||||
payload: Record<string, unknown>;
|
||||
};
|
||||
|
||||
export function createDeviceStores(): DeviceStores {
|
||||
const pool = new pg.Pool({ connectionString: env.databaseUrl });
|
||||
const db = drizzle(pool);
|
||||
const redis = new Redis(env.redisUrl, { maxRetriesPerRequest: 1, lazyConnect: true });
|
||||
|
||||
return {
|
||||
async readStatus(deviceId: string): Promise<DeviceStatusPair> {
|
||||
const pair = emptyStatus(deviceId);
|
||||
|
||||
try {
|
||||
const rows = await db
|
||||
.select()
|
||||
.from(deviceCurrentState)
|
||||
.where(eq(deviceCurrentState.deviceId, deviceId))
|
||||
.limit(1);
|
||||
const row = rows[0];
|
||||
if (row) {
|
||||
pair.postgres = toDeviceStatus({
|
||||
deviceId,
|
||||
lastSeen: row.lastSeen,
|
||||
payload: row.payload,
|
||||
source: 'postgres',
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('postgres status read failed', error);
|
||||
}
|
||||
|
||||
try {
|
||||
if (redis.status === 'wait') {
|
||||
await redis.connect();
|
||||
}
|
||||
const raw = await redis.get(redisKey(deviceId));
|
||||
if (raw) {
|
||||
const snapshot = JSON.parse(raw) as RedisSnapshot;
|
||||
pair.redis = toDeviceStatus({
|
||||
deviceId,
|
||||
lastSeen: new Date(snapshot.lastSeen),
|
||||
payload: snapshot.payload ?? {},
|
||||
source: 'redis',
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('redis status read failed', error);
|
||||
}
|
||||
|
||||
return pair;
|
||||
},
|
||||
|
||||
async close() {
|
||||
await Promise.allSettled([pool.end(), redis.quit()]);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function createMemoryStores(
|
||||
seed: Partial<Record<string, DeviceStatusPair>> = {},
|
||||
): DeviceStores {
|
||||
return {
|
||||
async readStatus(deviceId: string) {
|
||||
return seed[deviceId] ?? emptyStatus(deviceId);
|
||||
},
|
||||
async close() {
|
||||
return;
|
||||
},
|
||||
};
|
||||
}
|
||||
Reference in new issue
Block a user