Cronjobs + AnthropicService Singleton Refactoring

Cronjobs:
- CronjobService (BackgroundService) prüft jede Minute auf fällige Jobs
- Cron-Expression in Server-Lokalzeit via Cronos (CronFormat.Standard)
- Cronjob-Ausführung: Prompt → Session → Agent-Antwort in Chat-History
- Optionale Benachrichtigungs-Session (notify_session_id): Zusammenfassung
  wird in separate Session geschrieben (z.B. mit Telegram)
- Telegram-Benachrichtigung mit Cronjob-Name + Ursprungs-Session
- REST API: CRUD + toggle + manuelles Ausführen (/run)
- Frontend: Dashboard-Cards, Cronjob-Formular mit Cron-Vorschau,
  Session-Dropdown, Notify-Session-Dropdown, Status-Box

Refactoring:
- AnthropicService als Singleton in DI registriert
- ChatEndpoint + TelegramService nutzen injizierte Instanz
- Redundante Dependencies (Token, ToolService, MemoryService) entfernt

Neue Dateien:
- Models/CronjobRecord.cs
- Services/CronjobService.cs
- Endpoints/CronjobEndpoint.cs
- wwwroot/js/pages/cronjob-page.js
- PLAN-Cronjob.md
This commit is contained in:
Meleki committed 2026-03-03 22:44:46 +01:00
1 parent 0120bb8f73
commit b645625b56
16 files changed
+1132 -28

No files matched your search

+1 -12
View File
@@ -1,7 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
@@ -22,7 +20,7 @@ public static class ChatEndpoint
app.MapPost("/api/chat", HandleChat);
}
private static async Task HandleChat(HttpRequest req, HttpResponse res, IHttpClientFactory httpFactory, IConfiguration config, ChatService chatService, DatabaseService db, StreamingTracker streamingTracker, MemoryService memoryService, ToolService toolService, CancellationToken cancellationToken)
private static async Task HandleChat(HttpRequest req, HttpResponse res, IConfiguration config, ChatService chatService, DatabaseService db, StreamingTracker streamingTracker, AnthropicService anthropic, CancellationToken cancellationToken)
{
var body = await JsonSerializer.DeserializeAsync<ChatRequest>(req.Body);
if (body == null || body.Messages == null || body.Messages.Count == 0)
@@ -32,14 +30,6 @@ public static class ChatEndpoint
return;
}
var token = config["AnthropicToken"];
if (string.IsNullOrEmpty(token))
{
res.StatusCode = 500;
await res.WriteAsJsonAsync(new { error = "AnthropicToken not configured" });
return;
}
var sessionId = body.SessionId;
if (string.IsNullOrEmpty(sessionId))
{
@@ -111,7 +101,6 @@ public static class ChatEndpoint
}, attachmentRecords);
}
var anthropic = new AnthropicService(httpFactory, token, toolService, db, memoryService);
streamingTracker.Start(sessionId, ctx.ChainId);
try
{
+183
View File
@@ -0,0 +1,183 @@
using System;
using System.Text.Json;
using System.Threading.Tasks;
using Gott.Models;
using Gott.Services;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
namespace Gott.Endpoints;
public static class CronjobEndpoint
{
public static void MapCronjobEndpoints(this IEndpointRouteBuilder app)
{
app.MapGet("/api/cronjobs", async (HttpContext ctx, DatabaseService db) =>
{
var userId = ctx.Items["UserId"]?.ToString();
var cronjobs = await db.GetCronjobs(userId);
return Results.Ok(cronjobs);
});
app.MapGet("/api/cronjobs/{id}", async (string id, HttpContext ctx, DatabaseService db) =>
{
var userId = ctx.Items["UserId"]?.ToString();
var cronjob = await db.GetCronjob(id);
if (cronjob == null || cronjob.UserId != userId) return Results.NotFound();
return Results.Ok(cronjob);
});
app.MapPost("/api/cronjobs", async (HttpContext ctx, DatabaseService db) =>
{
var userId = ctx.Items["UserId"]?.ToString();
var body = await JsonSerializer.DeserializeAsync<JsonElement>(ctx.Request.Body);
var name = body.TryGetProperty("name", out var n) ? n.GetString() ?? "" : "";
var sessionId = body.TryGetProperty("sessionId", out var s) ? s.GetString() ?? "" : "";
var cronExpression = body.TryGetProperty("cronExpression", out var c) ? c.GetString() ?? "" : "";
var prompt = body.TryGetProperty("prompt", out var p) ? p.GetString() ?? "" : "";
var notifySessionId = body.TryGetProperty("notifySessionId", out var ns) ? ns.GetString() ?? "" : "";
if (string.IsNullOrWhiteSpace(name) || string.IsNullOrWhiteSpace(sessionId) ||
string.IsNullOrWhiteSpace(cronExpression) || string.IsNullOrWhiteSpace(prompt))
return Results.BadRequest(new { error = "Name, Session, Cron-Expression und Prompt sind Pflichtfelder" });
// Cron-Expression validieren
var nextRun = CronjobService.CalculateNextRun(cronExpression);
if (string.IsNullOrEmpty(nextRun))
return Results.BadRequest(new { error = "Ungültige Cron-Expression" });
// Session-Zugehörigkeit prüfen
var session = await db.GetSession(sessionId);
if (session == null || session.UserId != userId)
return Results.BadRequest(new { error = "Session nicht gefunden" });
// Notify-Session prüfen (optional)
if (!string.IsNullOrEmpty(notifySessionId))
{
var notifySession = await db.GetSession(notifySessionId);
if (notifySession == null || notifySession.UserId != userId)
return Results.BadRequest(new { error = "Benachrichtigungs-Session nicht gefunden" });
}
var cronjob = new CronjobRecord
{
UserId = userId,
SessionId = sessionId,
Name = name,
CronExpression = cronExpression,
Prompt = prompt,
NotifySessionId = notifySessionId,
IsEnabled = true,
NextRunAt = nextRun
};
var created = await db.CreateCronjob(cronjob);
return Results.Ok(created);
});
app.MapPut("/api/cronjobs/{id}", async (string id, HttpContext ctx, DatabaseService db) =>
{
var userId = ctx.Items["UserId"]?.ToString();
var existing = await db.GetCronjob(id);
if (existing == null || existing.UserId != userId) return Results.NotFound();
var body = await JsonSerializer.DeserializeAsync<JsonElement>(ctx.Request.Body);
if (body.TryGetProperty("name", out var n)) existing.Name = n.GetString() ?? existing.Name;
if (body.TryGetProperty("sessionId", out var s))
{
var newSessionId = s.GetString() ?? "";
if (!string.IsNullOrEmpty(newSessionId))
{
var session = await db.GetSession(newSessionId);
if (session == null || session.UserId != userId)
return Results.BadRequest(new { error = "Session nicht gefunden" });
existing.SessionId = newSessionId;
}
}
if (body.TryGetProperty("cronExpression", out var c))
{
var expr = c.GetString() ?? "";
if (!string.IsNullOrEmpty(expr))
{
var nextRun = CronjobService.CalculateNextRun(expr);
if (string.IsNullOrEmpty(nextRun))
return Results.BadRequest(new { error = "Ungültige Cron-Expression" });
existing.CronExpression = expr;
existing.NextRunAt = nextRun;
}
}
if (body.TryGetProperty("prompt", out var p)) existing.Prompt = p.GetString() ?? existing.Prompt;
if (body.TryGetProperty("notifySessionId", out var ns))
{
var notifyId = ns.GetString() ?? "";
if (!string.IsNullOrEmpty(notifyId))
{
var notifySession = await db.GetSession(notifyId);
if (notifySession == null || notifySession.UserId != userId)
return Results.BadRequest(new { error = "Benachrichtigungs-Session nicht gefunden" });
}
existing.NotifySessionId = notifyId;
}
if (body.TryGetProperty("isEnabled", out var e))
{
existing.IsEnabled = e.GetBoolean();
if (existing.IsEnabled)
existing.NextRunAt = CronjobService.CalculateNextRun(existing.CronExpression);
}
await db.UpdateCronjob(existing);
return Results.Ok(existing);
});
app.MapDelete("/api/cronjobs/{id}", async (string id, HttpContext ctx, DatabaseService db) =>
{
var userId = ctx.Items["UserId"]?.ToString();
var existing = await db.GetCronjob(id);
if (existing == null || existing.UserId != userId) return Results.NotFound();
await db.DeleteCronjob(id, userId);
return Results.Ok();
});
app.MapPost("/api/cronjobs/{id}/toggle", async (string id, HttpContext ctx, DatabaseService db) =>
{
var userId = ctx.Items["UserId"]?.ToString();
var existing = await db.GetCronjob(id);
if (existing == null || existing.UserId != userId) return Results.NotFound();
existing.IsEnabled = !existing.IsEnabled;
if (existing.IsEnabled)
existing.NextRunAt = CronjobService.CalculateNextRun(existing.CronExpression);
await db.UpdateCronjob(existing);
return Results.Ok(existing);
});
app.MapPost("/api/cronjobs/{id}/run", async (string id, HttpContext ctx, DatabaseService db, CronjobService cronjobService) =>
{
var userId = ctx.Items["UserId"]?.ToString();
var existing = await db.GetCronjob(id);
if (existing == null || existing.UserId != userId) return Results.NotFound();
try
{
await cronjobService.ExecuteCronjob(existing);
var nextRun = CronjobService.CalculateNextRun(existing.CronExpression);
await db.UpdateCronjobRun(existing.CronjobId, "success", null, nextRun);
return Results.Ok(new { status = "success" });
}
catch (Exception ex)
{
var nextRun = CronjobService.CalculateNextRun(existing.CronExpression);
await db.UpdateCronjobRun(existing.CronjobId, "error", ex.Message, nextRun);
return Results.Ok(new { status = "error", error = ex.Message });
}
});
}
}