Compare commits

...
1 Commits
Author SHA1 Message Date
kamilkrzywda 5e03546f40 Fix weather module DI (MeshService export + value import) and pass WEATHER_CRON through compose (#25)
CI / build + unit (push) Successful in 31s
2026-09-25 10:01:38 +02:00
6 changed files with 27 additions and 2 deletions
+6
View File
@@ -1,5 +1,11 @@
# Changelog # Changelog
## v0.12.1 — 2026-09-25 07:57
- Hotfix for the v0.12.0 bootstrap crash (`UnknownDependenciesException`): `MeshModule` now exports `MeshService`, and `WeatherService`'s `import type { MeshService }` became a value import — with `emitDecoratorMetadata` the type-only import is erased, so the constructor's DI token was `undefined` at runtime even with the export (the unit suite instantiates services directly and never booted the module graph, which is why 58 green tests still shipped a broken app).
- `docker-compose.yml`: `WEATHER_CRON` passthrough (`${WEATHER_CRON:-}`) mirroring the existing notify-var pattern — empty/unset falls back to the service default `'0 * * * *'`. No LAT/LON/CITY passthroughs (service-side defaults suffice).
- New DI smoke spec `src/weather/di.spec.ts`: compiles `WeatherModule` with Nest's `Test.createTestingModule().compile()` (no `app.init()`, so no TCP connection) and asserts `WeatherService` resolves its `MeshService` from `MeshModule` — fails if the export regresses.
## v0.12.0 — 2026-09-25 07:42 ## v0.12.0 — 2026-09-25 07:42
- New `src/weather` module: hourly cron-triggered weather report for Lublin, broadcast on the mesh (pinned send channel). `WeatherService.fetchWeather()` GETs Open-Meteo current conditions (`temperature_2m`, `apparent_temperature`, `relative_humidity_2m`, `wind_speed_10m`, `weather_code` — free API, no key) via global fetch; `formatReport()` renders one short line (e.g. `Lublin weather: 18°C (feels 17), partly cloudy, humidity 65%, wind 12 km/h`) with a WMO weather-code → description map (~19 common codes, unknown → `code <n>`). The `@Cron` handler fetches, formats and broadcasts via `MeshService.sendText(BROADCAST_ADDR, report)`; any error logs `[weather] report failed: ...` and is swallowed — the cron never crashes the app. - New `src/weather` module: hourly cron-triggered weather report for Lublin, broadcast on the mesh (pinned send channel). `WeatherService.fetchWeather()` GETs Open-Meteo current conditions (`temperature_2m`, `apparent_temperature`, `relative_humidity_2m`, `wind_speed_10m`, `weather_code` — free API, no key) via global fetch; `formatReport()` renders one short line (e.g. `Lublin weather: 18°C (feels 17), partly cloudy, humidity 65%, wind 12 km/h`) with a WMO weather-code → description map (~19 common codes, unknown → `code <n>`). The `@Cron` handler fetches, formats and broadcasts via `MeshService.sendText(BROADCAST_ADDR, report)`; any error logs `[weather] report failed: ...` and is swallowed — the cron never crashes the app.
+1
View File
@@ -8,6 +8,7 @@ services:
MESH_POLL_MS: '1000' MESH_POLL_MS: '1000'
MESH_NOTIFY_URL: ${MESH_NOTIFY_URL:-} MESH_NOTIFY_URL: ${MESH_NOTIFY_URL:-}
MESH_NOTIFY_CHANNEL: '${MESH_NOTIFY_CHANNEL:-3}' MESH_NOTIFY_CHANNEL: '${MESH_NOTIFY_CHANNEL:-3}'
WEATHER_CRON: ${WEATHER_CRON:-}
volumes: volumes:
- mesh_data:/app/data - mesh_data:/app/data
restart: unless-stopped restart: unless-stopped
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "mesh-bot", "name": "mesh-bot",
"version": "0.12.0", "version": "0.12.1",
"description": "", "description": "",
"author": "", "author": "",
"private": true, "private": true,
+1
View File
@@ -23,5 +23,6 @@ function openMessageDb(): DatabaseSync {
useFactory: () => new MeshService(openMessageDb()), useFactory: () => new MeshService(openMessageDb()),
}, },
], ],
exports: [MeshService],
}) })
export class MeshModule {} export class MeshModule {}
+17
View File
@@ -0,0 +1,17 @@
import { Test } from '@nestjs/testing';
import { describe, expect, it } from 'vitest';
import { MeshService } from '../mesh/mesh.service.js';
import { WeatherModule } from './weather.module.js';
import { WeatherService } from './weather.service.js';
describe('DI wiring (module graph)', () => {
// compile() instantiates providers but does not run lifecycle hooks, so no TCP connection is opened.
it('resolves WeatherService with MeshService provided by MeshModule', async () => {
process.env.MESH_DB_PATH = ':memory:'; // keep openMessageDb from touching the repo's ./data
const moduleRef = await Test.createTestingModule({
imports: [WeatherModule],
}).compile();
expect(moduleRef.get(WeatherService)).toBeInstanceOf(WeatherService);
expect(moduleRef.get(MeshService)).toBeInstanceOf(MeshService);
});
});
+1 -1
View File
@@ -1,7 +1,7 @@
import { Injectable, Logger } from '@nestjs/common'; import { Injectable, Logger } from '@nestjs/common';
import { Cron } from '@nestjs/schedule'; import { Cron } from '@nestjs/schedule';
import { BROADCAST_ADDR } from '../mesh/codec.js'; import { BROADCAST_ADDR } from '../mesh/codec.js';
import type { MeshService } from '../mesh/mesh.service.js'; import { MeshService } from '../mesh/mesh.service.js';
/** Open-Meteo `current` block fields we use (free API, no key). */ /** Open-Meteo `current` block fields we use (free API, no key). */
export interface CurrentWeather { export interface CurrentWeather {