feat: add Agrarmonitor integration module

- New backend module (agrarmonitor) with status check and device registration
- Frontend settings tab with connection status display and registration form
- Environment variables for base URLs, credentials, cookie path and encryption key
- Docker Compose env passthrough for agrarmonitor config

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-21 21:30:46 +02:00
parent f482304061
commit 1f5dcf4a17
10 changed files with 881 additions and 4 deletions
@@ -0,0 +1,86 @@
import { Injectable, Logger } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import {
createAgrarmonitorClient,
FileCookieStore,
AesGcmCookieEncryptor,
type AgrarmonitorConnectorResult,
} from 'agrarmonitor-connector';
export interface AgrarmonitorStatusDto {
connected: boolean;
registriert: boolean | null;
freigeschaltet: boolean | null;
error?: string;
}
export interface AgrarmonitorRegisterResultDto {
success: boolean;
message: string;
}
@Injectable()
export class AgrarmonitorService {
private readonly logger = new Logger(AgrarmonitorService.name);
private client: AgrarmonitorConnectorResult | null = null;
constructor(private readonly configService: ConfigService) {}
async getClient(): Promise<AgrarmonitorConnectorResult> {
if (this.client) return this.client;
const username = this.configService.get<string>('AGRARMONITOR_USERNAME', '');
const password = this.configService.get<string>('AGRARMONITOR_PASSWORD', '');
const baseUrl = this.configService.get<string>('AGRARMONITOR_BASE_URL', 'https://admin7.agrarmonitor.de');
const apiBaseUrl = this.configService.get<string>('AGRARMONITOR_API_BASE_URL', 'https://api.agrarmonitor.de');
const apiToken = this.configService.get<string>('AGRARMONITOR_API_TOKEN');
const cookiePath = this.configService.get<string>('AGRARMONITOR_COOKIE_PATH', './data/agrarmonitor-cookies.json');
const encryptionKey = this.configService.get<string>('AGRARMONITOR_ENCRYPTION_KEY');
const encryptor = encryptionKey ? new AesGcmCookieEncryptor(encryptionKey) : undefined;
const cookieStore = new FileCookieStore(cookiePath, { encryptor, logger: this.logger });
this.client = await createAgrarmonitorClient({
baseUrl,
apiBaseUrl,
apiToken,
username,
password,
cookieStore,
autoLogin: true,
autoRetry: true,
logger: this.logger,
});
return this.client;
}
async getStatus(): Promise<AgrarmonitorStatusDto> {
try {
const client = await this.getClient();
const [registrierungStatus, freigeschaltetStatus] = await Promise.all([
client.checkRegistriert(),
client.checkFreigeschaltet(),
]);
return {
connected: true,
registriert: registrierungStatus.registriert,
freigeschaltet: freigeschaltetStatus.freigeschaltet,
};
} catch (err: any) {
this.client = null;
return {
connected: false,
registriert: null,
freigeschaltet: null,
error: err?.message ?? 'Verbindung fehlgeschlagen',
};
}
}
async registerDevice(pcName: string, agrarmonitorId: string): Promise<AgrarmonitorRegisterResultDto> {
const client = await this.getClient();
const result = await client.registerDevice({ agrarmonitorId, pcName });
return { success: result.success, message: result.message };
}
}