import { validateBody, validateQuery } from "./chunk-5EGA6GHY.js"; import { NotFoundError, resolveNotebookPath } from "./chunk-ER4KPD22.js"; import { asyncHandler, createApiModule, defineApiModule, defineEndpoints, successResponse } from "./chunk-74TMTGBG.js"; // shared/modules/todo/api.ts var TODO_ENDPOINTS = defineEndpoints({ list: { path: "/", method: "GET" }, save: { path: "/save", method: "POST" }, add: { path: "/add", method: "POST" }, toggle: { path: "/toggle", method: "POST" }, update: { path: "/update", method: "POST" }, delete: { path: "/delete", method: "DELETE" } }); // shared/modules/todo/index.ts var TODO_MODULE = defineApiModule({ id: "todo", name: "TODO", basePath: "/todo", order: 30, version: "1.0.0", endpoints: TODO_ENDPOINTS }); // api/modules/todo/service.ts import fs from "fs/promises"; import path from "path"; // api/modules/todo/parser.ts var parseTodoContent = (content) => { const lines = content.split("\n"); const result = []; let currentDate = null; let currentItems = []; let itemId = 0; for (const line of lines) { const dateMatch = line.match(/^##\s*(\d{4}-\d{2}-\d{2})/); if (dateMatch) { if (currentDate) { result.push({ date: currentDate, items: currentItems }); } currentDate = dateMatch[1]; currentItems = []; } else if (currentDate) { const todoMatch = line.match(/^- (√|○) (.*)$/); if (todoMatch) { currentItems.push({ id: `${currentDate}-${itemId++}`, content: todoMatch[2], completed: todoMatch[1] === "\u221A" }); } } } if (currentDate) { result.push({ date: currentDate, items: currentItems }); } return result; }; var generateTodoContent = (dayTodos) => { const lines = []; const sortedDays = [...dayTodos].sort((a, b) => a.date.localeCompare(b.date)); for (const day of sortedDays) { lines.push(`## ${day.date}`); for (const item of day.items) { const checkbox = item.completed ? "\u221A" : "\u25CB"; lines.push(`- ${checkbox} ${item.content}`); } lines.push(""); } return lines.join("\n").trimEnd(); }; // api/modules/todo/service.ts var TodoService = class { constructor(deps = {}) { this.deps = deps; } getTodoFilePath(year, month) { const yearStr = year.toString(); const monthStr = month.toString().padStart(2, "0"); const relPath = `TODO/${yearStr}/${yearStr}${monthStr}TODO.md`; const { fullPath } = resolveNotebookPath(relPath); return { relPath, fullPath }; } async ensureTodoFileExists(fullPath) { const dir = path.dirname(fullPath); await fs.mkdir(dir, { recursive: true }); try { await fs.access(fullPath); } catch { await fs.writeFile(fullPath, "", "utf-8"); } } async loadAndParseTodoFile(year, month) { const { fullPath } = this.getTodoFilePath(year, month); try { await fs.access(fullPath); } catch { throw new NotFoundError("TODO file not found"); } const content = await fs.readFile(fullPath, "utf-8"); return { fullPath, dayTodos: parseTodoContent(content) }; } async saveTodoFile(fullPath, dayTodos) { const content = generateTodoContent(dayTodos); await fs.writeFile(fullPath, content, "utf-8"); } async getTodo(year, month) { const { fullPath } = this.getTodoFilePath(year, month); let dayTodos = []; try { await fs.access(fullPath); const content = await fs.readFile(fullPath, "utf-8"); dayTodos = parseTodoContent(content); } catch { } const now = /* @__PURE__ */ new Date(); const todayStr = `${now.getFullYear()}-${(now.getMonth() + 1).toString().padStart(2, "0")}-${now.getDate().toString().padStart(2, "0")}`; const yesterday = new Date(now); yesterday.setDate(yesterday.getDate() - 1); const yesterdayStr = `${yesterday.getFullYear()}-${(yesterday.getMonth() + 1).toString().padStart(2, "0")}-${yesterday.getDate().toString().padStart(2, "0")}`; if (year === now.getFullYear() && month === now.getMonth() + 1) { const migrated = this.migrateIncompleteItems(dayTodos, todayStr, yesterdayStr); if (migrated) { const newContent = generateTodoContent(dayTodos); await this.ensureTodoFileExists(fullPath); await fs.writeFile(fullPath, newContent, "utf-8"); } } return { dayTodos, year, month }; } migrateIncompleteItems(dayTodos, todayStr, yesterdayStr) { let migrated = false; const yesterdayTodo = dayTodos.find((d) => d.date === yesterdayStr); if (yesterdayTodo) { const incompleteItems = yesterdayTodo.items.filter((item) => !item.completed); if (incompleteItems.length > 0) { const todayTodo = dayTodos.find((d) => d.date === todayStr); if (todayTodo) { const existingIds = new Set(todayTodo.items.map((i) => i.id)); const itemsToAdd = incompleteItems.map((item, idx) => ({ ...item, id: existingIds.has(item.id) ? `${todayStr}-migrated-${idx}` : item.id })); todayTodo.items = [...itemsToAdd, ...todayTodo.items]; } else { dayTodos.push({ date: todayStr, items: incompleteItems.map((item, idx) => ({ ...item, id: `${todayStr}-migrated-${idx}` })) }); } yesterdayTodo.items = yesterdayTodo.items.filter((item) => item.completed); if (yesterdayTodo.items.length === 0) { const index = dayTodos.findIndex((d) => d.date === yesterdayStr); if (index !== -1) { dayTodos.splice(index, 1); } } migrated = true; } } return migrated; } async saveTodo(year, month, dayTodos) { const { fullPath } = this.getTodoFilePath(year, month); await this.ensureTodoFileExists(fullPath); const content = generateTodoContent(dayTodos); await fs.writeFile(fullPath, content, "utf-8"); } async addTodo(year, month, date, todoContent) { const { fullPath } = this.getTodoFilePath(year, month); await this.ensureTodoFileExists(fullPath); let fileContent = await fs.readFile(fullPath, "utf-8"); const dayTodos = parseTodoContent(fileContent); const existingDay = dayTodos.find((d) => d.date === date); if (existingDay) { const newId = `${date}-${existingDay.items.length}`; existingDay.items.push({ id: newId, content: todoContent, completed: false }); } else { dayTodos.push({ date, items: [{ id: `${date}-0`, content: todoContent, completed: false }] }); } fileContent = generateTodoContent(dayTodos); await fs.writeFile(fullPath, fileContent, "utf-8"); return dayTodos; } async toggleTodo(year, month, date, itemIndex, completed) { const { fullPath, dayTodos } = await this.loadAndParseTodoFile(year, month); const day = dayTodos.find((d) => d.date === date); if (!day || itemIndex >= day.items.length) { throw new NotFoundError("TODO item not found"); } day.items[itemIndex].completed = completed; await this.saveTodoFile(fullPath, dayTodos); return dayTodos; } async updateTodo(year, month, date, itemIndex, newContent) { const { fullPath, dayTodos } = await this.loadAndParseTodoFile(year, month); const day = dayTodos.find((d) => d.date === date); if (!day || itemIndex >= day.items.length) { throw new NotFoundError("TODO item not found"); } day.items[itemIndex].content = newContent; await this.saveTodoFile(fullPath, dayTodos); return dayTodos; } async deleteTodo(year, month, date, itemIndex) { const { fullPath, dayTodos } = await this.loadAndParseTodoFile(year, month); const dayIndex = dayTodos.findIndex((d) => d.date === date); if (dayIndex === -1) { throw new NotFoundError("Day not found"); } const day = dayTodos[dayIndex]; if (itemIndex >= day.items.length) { throw new NotFoundError("TODO item not found"); } day.items.splice(itemIndex, 1); if (day.items.length === 0) { dayTodos.splice(dayIndex, 1); } await this.saveTodoFile(fullPath, dayTodos); return dayTodos; } }; var createTodoService = (deps) => { return new TodoService(deps); }; // api/modules/todo/routes.ts import express from "express"; // api/modules/todo/schemas.ts import { z } from "zod"; var todoItemSchema = z.object({ id: z.string(), content: z.string(), completed: z.boolean() }); var dayTodoSchema = z.object({ date: z.string(), items: z.array(todoItemSchema) }); var getTodoQuerySchema = z.object({ year: z.string().optional(), month: z.string().optional() }); var saveTodoSchema = z.object({ year: z.number().int().positive(), month: z.number().int().min(1).max(12), dayTodos: z.array(dayTodoSchema) }); var addTodoSchema = z.object({ year: z.number().int().positive(), month: z.number().int().min(1).max(12), date: z.string(), content: z.string() }); var toggleTodoSchema = z.object({ year: z.number().int().positive(), month: z.number().int().min(1).max(12), date: z.string(), itemIndex: z.number().int().nonnegative(), completed: z.boolean() }); var updateTodoSchema = z.object({ year: z.number().int().positive(), month: z.number().int().min(1).max(12), date: z.string(), itemIndex: z.number().int().nonnegative(), content: z.string() }); var deleteTodoSchema = z.object({ year: z.number().int().positive(), month: z.number().int().min(1).max(12), date: z.string(), itemIndex: z.number().int().nonnegative() }); // api/modules/todo/routes.ts var createTodoRoutes = (deps) => { const router = express.Router(); const { todoService: todoService2 } = deps; router.get( "/", validateQuery(getTodoQuerySchema), asyncHandler(async (req, res) => { const year = parseInt(req.query.year) || (/* @__PURE__ */ new Date()).getFullYear(); const month = parseInt(req.query.month) || (/* @__PURE__ */ new Date()).getMonth() + 1; const result = await todoService2.getTodo(year, month); successResponse(res, result); }) ); router.post( "/save", validateBody(saveTodoSchema), asyncHandler(async (req, res) => { const { year, month, dayTodos } = req.body; await todoService2.saveTodo(year, month, dayTodos); successResponse(res, null); }) ); router.post( "/add", validateBody(addTodoSchema), asyncHandler(async (req, res) => { const { year, month, date, content: todoContent } = req.body; const dayTodos = await todoService2.addTodo(year, month, date, todoContent); successResponse(res, { dayTodos }); }) ); router.post( "/toggle", validateBody(toggleTodoSchema), asyncHandler(async (req, res) => { const { year, month, date, itemIndex, completed } = req.body; const dayTodos = await todoService2.toggleTodo(year, month, date, itemIndex, completed); successResponse(res, { dayTodos }); }) ); router.post( "/update", validateBody(updateTodoSchema), asyncHandler(async (req, res) => { const { year, month, date, itemIndex, content: newContent } = req.body; const dayTodos = await todoService2.updateTodo(year, month, date, itemIndex, newContent); successResponse(res, { dayTodos }); }) ); router.delete( "/delete", validateBody(deleteTodoSchema), asyncHandler(async (req, res) => { const { year, month, date, itemIndex } = req.body; const dayTodos = await todoService2.deleteTodo(year, month, date, itemIndex); successResponse(res, { dayTodos }); }) ); return router; }; var todoService = new TodoService(); var routes_default = createTodoRoutes({ todoService }); // api/modules/todo/index.ts var createTodoModule = () => { return createApiModule(TODO_MODULE, { routes: (container) => { const todoService2 = container.getSync("todoService"); return createTodoRoutes({ todoService: todoService2 }); }, lifecycle: { onLoad: (container) => { container.register("todoService", () => new TodoService()); } } }); }; var todo_default = createTodoModule; export { parseTodoContent, generateTodoContent, TodoService, createTodoService, getTodoQuerySchema, saveTodoSchema, addTodoSchema, toggleTodoSchema, updateTodoSchema, deleteTodoSchema, createTodoRoutes, createTodoModule, todo_default };