feat(sync-management): rule-based sync scheduling page + API
Phase 1 of unified sync scheduler: - New Prisma model GisSyncRule: per-UAT or per-county sync frequency rules with priority, time windows, step selection (T/C/N/E) - CRUD API: /api/eterra/sync-rules (list, create, update, delete, bulk) - Global default frequency via KeyValueStore - /sync-management page with 3 tabs: - Reguli: table with filters, add dialog (UAT search + county select) - Status: stats cards, frequency distribution, coverage overview - Judete: quick county-level frequency assignment - Monitor page: link to sync management from eTerra actions section Rule resolution: UAT-specific > county default > global default. Scheduler engine (Phase 2) will read these rules to automate syncs. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,72 @@
|
||||
/**
|
||||
* GET /api/eterra/sync-rules/scheduler — Scheduler status
|
||||
*
|
||||
* Returns current scheduler state from KeyValueStore + computed stats.
|
||||
*/
|
||||
|
||||
import { prisma } from "@/core/storage/prisma";
|
||||
import { NextResponse } from "next/server";
|
||||
|
||||
export const runtime = "nodejs";
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
export async function GET() {
|
||||
try {
|
||||
// Get scheduler state from KV (will be populated by the scheduler in Phase 2)
|
||||
const kvState = await prisma.keyValueStore.findUnique({
|
||||
where: {
|
||||
namespace_key: { namespace: "sync-management", key: "scheduler-state" },
|
||||
},
|
||||
});
|
||||
|
||||
// Compute rule stats
|
||||
const [totalRules, activeRules, dueNow, withErrors] = await Promise.all([
|
||||
prisma.gisSyncRule.count(),
|
||||
prisma.gisSyncRule.count({ where: { enabled: true } }),
|
||||
prisma.gisSyncRule.count({
|
||||
where: { enabled: true, nextDueAt: { lte: new Date() } },
|
||||
}),
|
||||
prisma.gisSyncRule.count({
|
||||
where: { lastSyncStatus: "error" },
|
||||
}),
|
||||
]);
|
||||
|
||||
// Frequency distribution
|
||||
const freqDist = await prisma.gisSyncRule.groupBy({
|
||||
by: ["frequency"],
|
||||
where: { enabled: true },
|
||||
_count: true,
|
||||
});
|
||||
|
||||
// County coverage
|
||||
const totalCounties = await prisma.gisUat.groupBy({
|
||||
by: ["county"],
|
||||
where: { county: { not: null } },
|
||||
_count: true,
|
||||
});
|
||||
|
||||
const countiesWithRules = await prisma.gisSyncRule.groupBy({
|
||||
by: ["county"],
|
||||
where: { county: { not: null } },
|
||||
_count: true,
|
||||
});
|
||||
|
||||
return NextResponse.json({
|
||||
scheduler: kvState?.value ?? { status: "not-started" },
|
||||
stats: {
|
||||
totalRules,
|
||||
activeRules,
|
||||
dueNow,
|
||||
withErrors,
|
||||
frequencyDistribution: Object.fromEntries(
|
||||
freqDist.map((f) => [f.frequency, f._count]),
|
||||
),
|
||||
totalCounties: totalCounties.length,
|
||||
countiesWithRules: countiesWithRules.length,
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
const msg = error instanceof Error ? error.message : "Eroare server";
|
||||
return NextResponse.json({ error: msg }, { status: 500 });
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user