feat: overhaul server automation, files editor, and CS2 setup workflows
This commit is contained in:
@@ -1,8 +1,9 @@
|
||||
import { Type } from '@sinclair/typebox';
|
||||
import type { FastifyInstance, FastifyRequest } from 'fastify';
|
||||
import { eq } from 'drizzle-orm';
|
||||
import { nodes } from '@source/database';
|
||||
import { and, eq, lte } from 'drizzle-orm';
|
||||
import { nodes, scheduledTasks, servers } from '@source/database';
|
||||
import { AppError } from '../../lib/errors.js';
|
||||
import { computeNextRun } from '../../lib/schedule-utils.js';
|
||||
|
||||
function extractBearerToken(authHeader?: string): string | null {
|
||||
if (!authHeader) return null;
|
||||
@@ -11,7 +12,10 @@ function extractBearerToken(authHeader?: string): string | null {
|
||||
return token;
|
||||
}
|
||||
|
||||
async function requireDaemonToken(app: FastifyInstance, request: FastifyRequest): Promise<void> {
|
||||
async function requireDaemonToken(
|
||||
app: FastifyInstance,
|
||||
request: FastifyRequest,
|
||||
): Promise<{ id: string }> {
|
||||
const token = extractBearerToken(
|
||||
typeof request.headers.authorization === 'string'
|
||||
? request.headers.authorization
|
||||
@@ -30,12 +34,44 @@ async function requireDaemonToken(app: FastifyInstance, request: FastifyRequest)
|
||||
if (!node) {
|
||||
throw AppError.unauthorized('Invalid daemon token', 'DAEMON_AUTH_INVALID');
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
export default async function internalRoutes(app: FastifyInstance) {
|
||||
app.get('/schedules/due', async (request) => {
|
||||
await requireDaemonToken(app, request);
|
||||
return { tasks: [] };
|
||||
const node = await requireDaemonToken(app, request);
|
||||
const now = new Date();
|
||||
|
||||
const dueTasks = await app.db
|
||||
.select({
|
||||
id: scheduledTasks.id,
|
||||
serverUuid: servers.uuid,
|
||||
action: scheduledTasks.action,
|
||||
payload: scheduledTasks.payload,
|
||||
scheduleType: scheduledTasks.scheduleType,
|
||||
isActive: scheduledTasks.isActive,
|
||||
nextRunAt: scheduledTasks.nextRunAt,
|
||||
})
|
||||
.from(scheduledTasks)
|
||||
.innerJoin(servers, eq(scheduledTasks.serverId, servers.id))
|
||||
.where(and(
|
||||
eq(servers.nodeId, node.id),
|
||||
eq(scheduledTasks.isActive, true),
|
||||
lte(scheduledTasks.nextRunAt, now),
|
||||
));
|
||||
|
||||
return {
|
||||
tasks: dueTasks.map((task) => ({
|
||||
id: task.id,
|
||||
server_uuid: task.serverUuid,
|
||||
action: task.action,
|
||||
payload: task.payload,
|
||||
schedule_type: task.scheduleType,
|
||||
is_active: task.isActive,
|
||||
next_run_at: task.nextRunAt?.toISOString() ?? null,
|
||||
})),
|
||||
};
|
||||
});
|
||||
|
||||
app.post(
|
||||
@@ -48,8 +84,41 @@ export default async function internalRoutes(app: FastifyInstance) {
|
||||
},
|
||||
},
|
||||
async (request) => {
|
||||
await requireDaemonToken(app, request);
|
||||
const node = await requireDaemonToken(app, request);
|
||||
const { taskId } = request.params as { taskId: string };
|
||||
|
||||
const [task] = await app.db
|
||||
.select({
|
||||
id: scheduledTasks.id,
|
||||
isActive: scheduledTasks.isActive,
|
||||
scheduleType: scheduledTasks.scheduleType,
|
||||
scheduleData: scheduledTasks.scheduleData,
|
||||
})
|
||||
.from(scheduledTasks)
|
||||
.innerJoin(servers, eq(scheduledTasks.serverId, servers.id))
|
||||
.where(and(
|
||||
eq(scheduledTasks.id, taskId),
|
||||
eq(servers.nodeId, node.id),
|
||||
));
|
||||
|
||||
if (!task) {
|
||||
throw AppError.notFound('Scheduled task not found');
|
||||
}
|
||||
|
||||
const now = new Date();
|
||||
const nextRunAt = task.isActive
|
||||
? computeNextRun(task.scheduleType, task.scheduleData as Record<string, unknown>)
|
||||
: null;
|
||||
|
||||
await app.db
|
||||
.update(scheduledTasks)
|
||||
.set({
|
||||
lastRunAt: now,
|
||||
nextRunAt,
|
||||
updatedAt: now,
|
||||
})
|
||||
.where(eq(scheduledTasks.id, taskId));
|
||||
|
||||
return { success: true, taskId };
|
||||
},
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user