Files
XCDesktop/dist-api/chunk-V2OWYGQG.js

400 lines
12 KiB
JavaScript

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
};