← Directory
schedules
Cron and ISO date scheduling for node versions
v1.0.0 by TreeOS Site 0 downloads published 3d ago
treeos ext install schedules

Manifest

Provides

  • routes
  • 2 CLI commands
  • 1 energy actions

Requires

  • models: Node

Optional

  • services: energy

CLI Commands

CommandMethodDescription
schedule <date>POSTSet schedule on current node
calendarGETShow scheduled nodes for current tree

Source Code

1import Node from "../../db/models/node.js";
2import { logContribution } from "../../db/utils.js";
3import { useEnergy } from "../../core/tree/energy.js";
4
5async function updateSchedule({
6  nodeId,
7  versionIndex,
8  newSchedule,
9  reeffectTime,
10  userId,
11  wasAi = false,
12  aiChatId = null,
13  sessionId = null,
14}) {
15  if (!nodeId || versionIndex === undefined || reeffectTime === undefined) {
16    const error = new Error(
17      "nodeId, versionIndex, and reeffectTime are required.",
18    );
19    error.status = 400;
20    throw error;
21  }
22
23  if (reeffectTime > 1000000) {
24    const error = new Error("reeffect time must be below 1,000,000 hrs");
25    error.status = 400;
26    throw error;
27  }
28
29  const node = await Node.findById(nodeId);
30  if (!node) {
31    const error = new Error("Node not found.");
32    error.status = 404;
33    throw error;
34  }
35  if (node.isSystem) throw new Error("Cannot modify system nodes");
36
37  if (versionIndex < 0 || versionIndex >= node.versions.length) {
38    const error = new Error("Invalid version index.");
39    error.status = 400;
40    throw error;
41  }
42
43  let formattedDate = null;
44
45  if (newSchedule !== undefined && newSchedule !== "" && newSchedule !== null) {
46    formattedDate = new Date(newSchedule);
47    if (isNaN(formattedDate)) {
48      const error = new Error("Invalid schedule date.");
49      error.status = 400;
50      throw error;
51    }
52  }
53  const { energyUsed } = await useEnergy({
54    userId,
55    action: "editSchedule",
56  });
57  node.versions[versionIndex].schedule = formattedDate;
58
59  node.versions[versionIndex].schedule = formattedDate;
60
61  node.versions[versionIndex].reeffectTime = reeffectTime;
62
63  await node.save();
64
65  const scheduleEdited = { date: formattedDate, reeffectTime };
66
67  await logContribution({
68    userId,
69    nodeId,
70    wasAi,
71    aiChatId,
72    sessionId,
73    action: "editSchedule",
74    nodeVersion: versionIndex,
75    scheduleEdited,
76    energyUsed,
77  });
78
79  return {
80    message: "Schedule and re-effect time updated successfully.",
81    node,
82  };
83}
84
85async function getCalendar({ rootNodeId, startDate, endDate }) {
86  if (!rootNodeId) {
87    const error = new Error("rootNodeId is required");
88    error.status = 400;
89    throw error;
90  }
91
92  const start = startDate ? new Date(startDate) : null;
93  const end = endDate ? new Date(endDate) : null;
94
95  if (start && isNaN(start)) {
96    const error = new Error("Invalid startDate");
97    error.status = 400;
98    throw error;
99  }
100
101  if (end && isNaN(end)) {
102    const error = new Error("Invalid endDate");
103    error.status = 400;
104    throw error;
105  }
106
107  const results = [];
108  const visited = new Set();
109
110  async function walk(nodeId) {
111    if (!nodeId || visited.has(nodeId)) return;
112    visited.add(nodeId);
113
114    const node = await Node.findById(nodeId)
115      .select("name prestige versions children")
116      .lean();
117
118    if (!node) return;
119
120    const versionIndex = node.prestige;
121    const version = node.versions?.[versionIndex];
122
123    if (version?.schedule) {
124      const scheduleDate = new Date(version.schedule);
125
126      const inRange =
127        (!start || scheduleDate >= start) && (!end || scheduleDate <= end);
128
129      if (inRange) {
130        results.push({
131          nodeId: node._id.toString(),
132          name: node.name ?? "(Untitled)",
133          versionIndex,
134          schedule: scheduleDate,
135          reeffectTime: version.reeffectTime ?? null,
136        });
137      }
138    }
139
140    if (Array.isArray(node.children)) {
141      for (const childId of node.children) {
142        await walk(childId);
143      }
144    }
145  }
146
147  await walk(rootNodeId);
148
149  return results;
150}
151
152export { updateSchedule, getCalendar };
153
1import router from "./routes.js";
2
3export async function init(core) {
4  return { router };
5}
6
1export default {
2  name: "schedules",
3  version: "1.0.0",
4  description: "Cron and ISO date scheduling for node versions",
5
6  needs: {
7    models: ["Node"],
8  },
9
10  optional: {
11    services: ["energy"],
12  },
13
14  provides: {
15    models: {},
16    routes: "./routes.js",
17    tools: false,
18    jobs: false,
19    orchestrator: false,
20    energyActions: {
21      editSchedule: { cost: 1 },
22    },
23    sessionTypes: {},
24    cli: [
25      { command: "schedule <date>", description: "Set schedule on current node", method: "POST", endpoint: "/node/:nodeId/editSchedule" },
26      { command: "calendar", description: "Show scheduled nodes for current tree", method: "GET", endpoint: "/root/:rootId/calendar" },
27    ],
28  },
29};
30
1import express from "express";
2import authenticate from "../../middleware/authenticate.js";
3import { updateSchedule } from "./core.js";
4import Node from "../../db/models/node.js";
5
6const router = express.Router();
7
8async function useLatest(req, res, next) {
9  try {
10    const node = await Node.findById(req.params.nodeId).select("prestige").lean();
11    if (!node) return res.status(404).json({ error: "Node not found" });
12    req.params.version = String(node.prestige);
13    next();
14  } catch (err) {
15    res.status(500).json({ error: err.message });
16  }
17}
18
19const editScheduleHandler = async (req, res) => {
20  try {
21    const { nodeId, version } = req.params;
22    const userId = req.userId;
23
24    const newSchedule = req.body?.newSchedule || req.query?.newSchedule;
25    const reeffectTime = req.body?.reeffectTime ?? req.query?.reeffectTime;
26
27    if (reeffectTime === undefined) {
28      return res.status(400).json({
29        error: "reeffectTime is required",
30      });
31    }
32
33    const result = await updateSchedule({
34      nodeId,
35      versionIndex: Number(version),
36      newSchedule,
37      reeffectTime: Number(reeffectTime),
38      userId,
39    });
40
41    if ("html" in req.query) {
42      return res.redirect(
43        `/api/v1/node/${nodeId}/${version}?token=${req.query.token ?? ""}&html`,
44      );
45    }
46
47    res.json({ success: true, ...result });
48  } catch (err) {
49    console.error("editSchedule error:", err);
50    res.status(err.status || 400).json({ error: err.message });
51  }
52};
53
54router.post("/node/:nodeId/editSchedule", authenticate, useLatest, editScheduleHandler);
55router.post("/node/:nodeId/:version/editSchedule", authenticate, editScheduleHandler);
56
57export default router;
58