agkan 2.11.0 → 2.12.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ja.md +1 -1
- package/README.md +1 -1
- package/dist/board/boardRenderer.d.ts +18 -0
- package/dist/board/boardRenderer.d.ts.map +1 -0
- package/dist/board/boardRenderer.js +273 -0
- package/dist/board/boardRenderer.js.map +1 -0
- package/dist/board/boardRoutes.d.ts +23 -0
- package/dist/board/boardRoutes.d.ts.map +1 -0
- package/dist/board/boardRoutes.js +273 -0
- package/dist/board/boardRoutes.js.map +1 -0
- package/dist/board/boardScript.d.ts +2 -0
- package/dist/board/boardScript.d.ts.map +1 -0
- package/dist/board/boardScript.js +1202 -0
- package/dist/board/boardScript.js.map +1 -0
- package/dist/board/boardStyles.d.ts +2 -0
- package/dist/board/boardStyles.d.ts.map +1 -0
- package/dist/board/boardStyles.js +171 -0
- package/dist/board/boardStyles.js.map +1 -0
- package/dist/board/client/board.js +1165 -0
- package/dist/board/server.d.ts.map +1 -1
- package/dist/board/server.js +7 -1712
- package/dist/board/server.js.map +1 -1
- package/dist/db/adapters/sqlite-adapter.js +2 -0
- package/dist/db/adapters/sqlite-adapter.js.map +1 -1
- package/dist/db/connection.js +2 -2
- package/dist/db/connection.js.map +1 -1
- package/dist/services/CommentService.js +1 -0
- package/dist/services/CommentService.js.map +1 -1
- package/dist/services/MetadataService.js +1 -0
- package/dist/services/MetadataService.js.map +1 -1
- package/dist/services/TagService.js +1 -0
- package/dist/services/TagService.js.map +1 -1
- package/dist/services/TaskBlockService.js +2 -0
- package/dist/services/TaskBlockService.js.map +1 -1
- package/dist/services/TaskService.js +1 -0
- package/dist/services/TaskService.js.map +1 -1
- package/dist/services/TaskTagService.js +3 -0
- package/dist/services/TaskTagService.js.map +1 -1
- package/package.json +9 -5
package/README.ja.md
CHANGED
package/README.md
CHANGED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Task, TaskStatus } from '../models';
|
|
2
|
+
import { Tag } from '../models/Tag';
|
|
3
|
+
import { StorageProvider } from '../db/types/storage';
|
|
4
|
+
export declare const STATUSES: TaskStatus[];
|
|
5
|
+
export declare const STATUS_LABELS: Record<TaskStatus, string>;
|
|
6
|
+
export declare const STATUS_COLORS: Record<TaskStatus, string>;
|
|
7
|
+
export declare function renderCard(task: Task, tags: Tag[]): string;
|
|
8
|
+
export declare function renderColumn(status: TaskStatus, tasks: Task[], tagMap: Map<number, Tag[]>): string;
|
|
9
|
+
export declare function renderBoard(tasksByStatus: Map<TaskStatus, Task[]>, tagMap: Map<number, Tag[]>, boardTitle?: string): string;
|
|
10
|
+
export declare function sortByPriority(tasks: Task[]): Task[];
|
|
11
|
+
export declare function buildBoardCardsPayload(tasksByStatus: Map<TaskStatus, Task[]>, tagMap: Map<number, Tag[]>): {
|
|
12
|
+
status: TaskStatus;
|
|
13
|
+
html: string;
|
|
14
|
+
count: number;
|
|
15
|
+
}[];
|
|
16
|
+
export declare function buildTasksByStatus(tasks: Task[]): Map<TaskStatus, Task[]>;
|
|
17
|
+
export declare function getBoardUpdatedAt(database: StorageProvider): string | null;
|
|
18
|
+
//# sourceMappingURL=boardRenderer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"boardRenderer.d.ts","sourceRoot":"","sources":["../../src/board/boardRenderer.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,IAAI,EAAE,UAAU,EAA8B,MAAM,WAAW,CAAC;AACzE,OAAO,EAAE,GAAG,EAAE,MAAM,eAAe,CAAC;AACpC,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAGtD,eAAO,MAAM,QAAQ,EAAE,UAAU,EAA8E,CAAC;AAEhH,eAAO,MAAM,aAAa,EAAE,MAAM,CAAC,UAAU,EAAE,MAAM,CAQpD,CAAC;AAEF,eAAO,MAAM,aAAa,EAAE,MAAM,CAAC,UAAU,EAAE,MAAM,CAQpD,CAAC;AAWF,wBAAgB,UAAU,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG,MAAM,CAgB1D;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,EAAE,CAAC,GAAG,MAAM,CAkBlG;AA2FD,wBAAgB,WAAW,CACzB,aAAa,EAAE,GAAG,CAAC,UAAU,EAAE,IAAI,EAAE,CAAC,EACtC,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,EAAE,CAAC,EAC1B,UAAU,CAAC,EAAE,MAAM,GAClB,MAAM,CAuCR;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,IAAI,EAAE,GAAG,IAAI,EAAE,CAMpD;AAED,wBAAgB,sBAAsB,CACpC,aAAa,EAAE,GAAG,CAAC,UAAU,EAAE,IAAI,EAAE,CAAC,EACtC,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,EAAE,CAAC,GACzB;IAAE,MAAM,EAAE,UAAU,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,EAAE,CAMvD;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,IAAI,EAAE,GAAG,GAAG,CAAC,UAAU,EAAE,IAAI,EAAE,CAAC,CAYzE;AAED,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,eAAe,GAAG,MAAM,GAAG,IAAI,CAmB1E"}
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.STATUS_COLORS = exports.STATUS_LABELS = exports.STATUSES = void 0;
|
|
37
|
+
exports.renderCard = renderCard;
|
|
38
|
+
exports.renderColumn = renderColumn;
|
|
39
|
+
exports.renderBoard = renderBoard;
|
|
40
|
+
exports.sortByPriority = sortByPriority;
|
|
41
|
+
exports.buildBoardCardsPayload = buildBoardCardsPayload;
|
|
42
|
+
exports.buildTasksByStatus = buildTasksByStatus;
|
|
43
|
+
exports.getBoardUpdatedAt = getBoardUpdatedAt;
|
|
44
|
+
const fs = __importStar(require("fs"));
|
|
45
|
+
const path = __importStar(require("path"));
|
|
46
|
+
const models_1 = require("../models");
|
|
47
|
+
const boardStyles_1 = require("./boardStyles");
|
|
48
|
+
exports.STATUSES = ['icebox', 'backlog', 'ready', 'in_progress', 'review', 'done', 'closed'];
|
|
49
|
+
exports.STATUS_LABELS = {
|
|
50
|
+
icebox: 'Icebox',
|
|
51
|
+
backlog: 'Backlog',
|
|
52
|
+
ready: 'Ready',
|
|
53
|
+
in_progress: 'In Progress',
|
|
54
|
+
review: 'Review',
|
|
55
|
+
done: 'Done',
|
|
56
|
+
closed: 'Closed',
|
|
57
|
+
};
|
|
58
|
+
exports.STATUS_COLORS = {
|
|
59
|
+
icebox: '#6b7280',
|
|
60
|
+
backlog: '#3b82f6',
|
|
61
|
+
ready: '#8b5cf6',
|
|
62
|
+
in_progress: '#f97316',
|
|
63
|
+
review: '#eab308',
|
|
64
|
+
done: '#22c55e',
|
|
65
|
+
closed: '#374151',
|
|
66
|
+
};
|
|
67
|
+
function escapeHtml(text) {
|
|
68
|
+
return text
|
|
69
|
+
.replace(/&/g, '&')
|
|
70
|
+
.replace(/</g, '<')
|
|
71
|
+
.replace(/>/g, '>')
|
|
72
|
+
.replace(/"/g, '"')
|
|
73
|
+
.replace(/'/g, ''');
|
|
74
|
+
}
|
|
75
|
+
function renderCard(task, tags) {
|
|
76
|
+
const priority = task.priority;
|
|
77
|
+
const priorityBadge = priority
|
|
78
|
+
? `<span class="priority priority-${escapeHtml(priority)}">${escapeHtml(priority)}</span>`
|
|
79
|
+
: '';
|
|
80
|
+
const tagBadges = tags.map((t) => `<span class="tag">${escapeHtml(t.name)}</span>`).join('');
|
|
81
|
+
return `
|
|
82
|
+
<div class="card" draggable="true" data-id="${task.id}" data-status="${task.status}">
|
|
83
|
+
<div class="card-header">
|
|
84
|
+
<span class="card-id">#${task.id}</span>
|
|
85
|
+
${priorityBadge}
|
|
86
|
+
</div>
|
|
87
|
+
<div class="card-title">${escapeHtml(task.title)}</div>
|
|
88
|
+
${tagBadges ? `<div class="card-tags">${tagBadges}</div>` : ''}
|
|
89
|
+
</div>`;
|
|
90
|
+
}
|
|
91
|
+
function renderColumn(status, tasks, tagMap) {
|
|
92
|
+
const color = exports.STATUS_COLORS[status];
|
|
93
|
+
const label = exports.STATUS_LABELS[status];
|
|
94
|
+
const cards = tasks.map((t) => renderCard(t, tagMap.get(t.id) || [])).join('');
|
|
95
|
+
return `
|
|
96
|
+
<div class="column" data-status="${status}">
|
|
97
|
+
<div class="column-header" style="border-top-color:${color}">
|
|
98
|
+
<span class="column-title" style="color:${color}">${label}</span>
|
|
99
|
+
<span class="column-header-right">
|
|
100
|
+
<span class="column-count">${tasks.length}</span>
|
|
101
|
+
<button class="add-btn" data-status="${status}" title="Add task">+</button>
|
|
102
|
+
</span>
|
|
103
|
+
</div>
|
|
104
|
+
<div class="column-body" id="col-${status}">
|
|
105
|
+
${cards}
|
|
106
|
+
</div>
|
|
107
|
+
</div>`;
|
|
108
|
+
}
|
|
109
|
+
const BOARD_PRIORITY_OPTIONS = models_1.PRIORITIES.map((p) => `<option value="${p}">${p.charAt(0).toUpperCase() + p.slice(1)}</option>`).join('\n ');
|
|
110
|
+
function getAddTaskModal() {
|
|
111
|
+
return `
|
|
112
|
+
<div class="modal-overlay" id="add-modal">
|
|
113
|
+
<div class="modal">
|
|
114
|
+
<h2>Add Task</h2>
|
|
115
|
+
<label for="add-title">Title</label>
|
|
116
|
+
<input type="text" id="add-title" placeholder="Task title">
|
|
117
|
+
<label for="add-body">Description</label>
|
|
118
|
+
<textarea id="add-body" placeholder="Optional"></textarea>
|
|
119
|
+
<label for="add-priority">Priority</label>
|
|
120
|
+
<select id="add-priority">
|
|
121
|
+
<option value="">None</option>
|
|
122
|
+
${BOARD_PRIORITY_OPTIONS}
|
|
123
|
+
</select>
|
|
124
|
+
<input type="hidden" id="add-status">
|
|
125
|
+
<div class="modal-actions">
|
|
126
|
+
<button id="add-cancel">Cancel</button>
|
|
127
|
+
<button id="add-submit" class="primary">Add</button>
|
|
128
|
+
</div>
|
|
129
|
+
</div>
|
|
130
|
+
</div>`;
|
|
131
|
+
}
|
|
132
|
+
function getContextMenuAndToast() {
|
|
133
|
+
return `
|
|
134
|
+
<div class="context-menu" id="context-menu">
|
|
135
|
+
<div class="context-menu-item danger" id="ctx-delete">Delete task</div>
|
|
136
|
+
</div>
|
|
137
|
+
<div class="toast" id="toast">Failed to update task</div>`;
|
|
138
|
+
}
|
|
139
|
+
function getPurgeAndVersionModals() {
|
|
140
|
+
return `
|
|
141
|
+
<div class="modal-overlay" id="purge-confirm-modal">
|
|
142
|
+
<div class="modal">
|
|
143
|
+
<h2>Purge Tasks</h2>
|
|
144
|
+
<p style="font-size:13px;color:#64748b;margin-bottom:16px;">Delete all done/closed tasks older than 3 days. This action cannot be undone.</p>
|
|
145
|
+
<p id="purge-result" style="font-size:13px;color:#16a34a;min-height:18px;margin-bottom:8px;"></p>
|
|
146
|
+
<div class="modal-actions">
|
|
147
|
+
<button id="purge-cancel-btn">Cancel</button>
|
|
148
|
+
<button id="purge-confirm-btn" class="primary" style="background:#dc2626;border-color:#dc2626;">Purge</button>
|
|
149
|
+
</div>
|
|
150
|
+
</div>
|
|
151
|
+
</div>
|
|
152
|
+
<div class="modal-overlay" id="version-info-modal">
|
|
153
|
+
<div class="modal" style="width:320px;">
|
|
154
|
+
<h2>Version Info</h2>
|
|
155
|
+
<p id="version-info-text" style="font-size:14px;color:#1e293b;margin-bottom:16px;"></p>
|
|
156
|
+
<div class="modal-actions">
|
|
157
|
+
<button id="version-info-close">Close</button>
|
|
158
|
+
</div>
|
|
159
|
+
</div>
|
|
160
|
+
</div>`;
|
|
161
|
+
}
|
|
162
|
+
function loadClientBundle() {
|
|
163
|
+
// Try resolved path (works in compiled dist/ and in development)
|
|
164
|
+
const candidates = [
|
|
165
|
+
path.join(__dirname, 'client', 'board.js'),
|
|
166
|
+
path.join(__dirname, '..', '..', 'dist', 'board', 'client', 'board.js'),
|
|
167
|
+
];
|
|
168
|
+
for (const bundlePath of candidates) {
|
|
169
|
+
try {
|
|
170
|
+
return fs.readFileSync(bundlePath, 'utf8');
|
|
171
|
+
}
|
|
172
|
+
catch {
|
|
173
|
+
// Try next candidate
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
throw new Error(`Client bundle not found. Tried: ${candidates.join(', ')}. Run 'npm run build' to generate it.`);
|
|
177
|
+
}
|
|
178
|
+
function getBoardBodyStatic() {
|
|
179
|
+
const clientBundle = loadClientBundle();
|
|
180
|
+
const script = `
|
|
181
|
+
var statusColors = ${JSON.stringify(exports.STATUS_COLORS)};
|
|
182
|
+
var allStatuses = ${JSON.stringify(exports.STATUSES)};
|
|
183
|
+
var statusLabels = ${JSON.stringify(exports.STATUS_LABELS)};
|
|
184
|
+
var allPriorities = ${JSON.stringify(models_1.PRIORITIES)};
|
|
185
|
+
${clientBundle}`;
|
|
186
|
+
return `${getAddTaskModal()}${getContextMenuAndToast()}${getPurgeAndVersionModals()}
|
|
187
|
+
<script>${script}
|
|
188
|
+
</script>`;
|
|
189
|
+
}
|
|
190
|
+
function renderBoard(tasksByStatus, tagMap, boardTitle) {
|
|
191
|
+
const columns = exports.STATUSES.map((status) => renderColumn(status, tasksByStatus.get(status) || [], tagMap)).join('');
|
|
192
|
+
const titleHtml = boardTitle ? `<span class="board-title">${escapeHtml(boardTitle)}</span>` : '';
|
|
193
|
+
const boardBodyStatic = getBoardBodyStatic();
|
|
194
|
+
return `<!DOCTYPE html>
|
|
195
|
+
<html lang="en">
|
|
196
|
+
<head>
|
|
197
|
+
<meta charset="UTF-8">
|
|
198
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
199
|
+
<title>agkan board</title>
|
|
200
|
+
<style>${boardStyles_1.BOARD_STYLES}
|
|
201
|
+
</style>
|
|
202
|
+
</head>
|
|
203
|
+
<body>
|
|
204
|
+
<header><h1>agkan board</h1>${titleHtml}<div class="burger-menu-wrapper"><button class="burger-menu-btn" id="burger-menu-btn" title="Menu" aria-label="Menu"><span></span><span></span><span></span></button><div class="burger-menu-dropdown" id="burger-menu-dropdown"><div class="burger-menu-item danger" id="burger-purge-tasks">🗑 Purge Tasks</div><div class="burger-menu-item" id="burger-version-info">ℹ Version Info</div></div></div></header>
|
|
205
|
+
<div class="filter-bar" id="filter-bar">
|
|
206
|
+
<div class="filter-group">
|
|
207
|
+
<span class="filter-label">Priority</span>
|
|
208
|
+
<button class="filter-priority-btn" data-priority="critical">critical</button>
|
|
209
|
+
<button class="filter-priority-btn" data-priority="high">high</button>
|
|
210
|
+
<button class="filter-priority-btn" data-priority="medium">medium</button>
|
|
211
|
+
<button class="filter-priority-btn" data-priority="low">low</button>
|
|
212
|
+
</div>
|
|
213
|
+
<div class="filter-group">
|
|
214
|
+
<span class="filter-label">Tags</span>
|
|
215
|
+
<div id="filter-tags-control" style="display:flex;align-items:center;gap:4px;flex-wrap:nowrap;"></div>
|
|
216
|
+
</div>
|
|
217
|
+
<div class="filter-group">
|
|
218
|
+
<span class="filter-label">Assignee</span>
|
|
219
|
+
<input type="text" id="filter-assignee" class="filter-assignee-input" placeholder="Filter by assignee">
|
|
220
|
+
</div>
|
|
221
|
+
<button class="filter-clear-btn" id="filter-clear">Clear filters</button>
|
|
222
|
+
</div>
|
|
223
|
+
<div class="board-container">
|
|
224
|
+
<div class="board">${columns}</div>${boardBodyStatic}
|
|
225
|
+
</div>
|
|
226
|
+
</body>
|
|
227
|
+
</html>`;
|
|
228
|
+
}
|
|
229
|
+
function sortByPriority(tasks) {
|
|
230
|
+
return [...tasks].sort((a, b) => {
|
|
231
|
+
const oa = a.priority ? models_1.PRIORITY_ORDER[a.priority] : 4;
|
|
232
|
+
const ob = b.priority ? models_1.PRIORITY_ORDER[b.priority] : 4;
|
|
233
|
+
return oa - ob;
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
function buildBoardCardsPayload(tasksByStatus, tagMap) {
|
|
237
|
+
return exports.STATUSES.map((status) => {
|
|
238
|
+
const tasks = tasksByStatus.get(status) || [];
|
|
239
|
+
const html = tasks.map((t) => renderCard(t, tagMap.get(t.id) || [])).join('');
|
|
240
|
+
return { status, html, count: tasks.length };
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
function buildTasksByStatus(tasks) {
|
|
244
|
+
const tasksByStatus = new Map();
|
|
245
|
+
for (const status of exports.STATUSES) {
|
|
246
|
+
tasksByStatus.set(status, []);
|
|
247
|
+
}
|
|
248
|
+
for (const task of tasks) {
|
|
249
|
+
tasksByStatus.get(task.status)?.push(task);
|
|
250
|
+
}
|
|
251
|
+
for (const [status, statusTasks] of tasksByStatus) {
|
|
252
|
+
tasksByStatus.set(status, sortByPriority(statusTasks));
|
|
253
|
+
}
|
|
254
|
+
return tasksByStatus;
|
|
255
|
+
}
|
|
256
|
+
function getBoardUpdatedAt(database) {
|
|
257
|
+
const baseRow = database
|
|
258
|
+
.prepare(`
|
|
259
|
+
SELECT MAX(updated_at) as max_updated_at FROM (
|
|
260
|
+
SELECT updated_at FROM tasks UNION ALL SELECT updated_at FROM task_metadata
|
|
261
|
+
)
|
|
262
|
+
`)
|
|
263
|
+
.get();
|
|
264
|
+
const tagsRow = database
|
|
265
|
+
.prepare(`
|
|
266
|
+
SELECT MAX(created_at) as max_created_at, COUNT(*) as count FROM task_tags
|
|
267
|
+
`)
|
|
268
|
+
.get();
|
|
269
|
+
if (baseRow.max_updated_at === null && tagsRow.max_created_at === null)
|
|
270
|
+
return null;
|
|
271
|
+
return `${baseRow.max_updated_at}|${tagsRow.max_created_at}|${tagsRow.count}`;
|
|
272
|
+
}
|
|
273
|
+
//# sourceMappingURL=boardRenderer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"boardRenderer.js","sourceRoot":"","sources":["../../src/board/boardRenderer.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { Hono } from 'hono';
|
|
2
|
+
import { TaskService } from '../services/TaskService';
|
|
3
|
+
import { TaskTagService } from '../services/TaskTagService';
|
|
4
|
+
import { TagService } from '../services/TagService';
|
|
5
|
+
import { MetadataService } from '../services/MetadataService';
|
|
6
|
+
import { CommentService } from '../services/CommentService';
|
|
7
|
+
import { TaskBlockService } from '../services/TaskBlockService';
|
|
8
|
+
import { StorageProvider } from '../db/types/storage';
|
|
9
|
+
export type BoardServices = {
|
|
10
|
+
ts: TaskService;
|
|
11
|
+
tts: TaskTagService;
|
|
12
|
+
tags: TagService;
|
|
13
|
+
ms: MetadataService;
|
|
14
|
+
cs: CommentService;
|
|
15
|
+
tbs: TaskBlockService;
|
|
16
|
+
database: StorageProvider;
|
|
17
|
+
boardTitle?: string;
|
|
18
|
+
configDir: string;
|
|
19
|
+
};
|
|
20
|
+
export declare function registerTaskApiRoutes(app: Hono, { ts, tts, tags, ms, cs, tbs }: BoardServices): void;
|
|
21
|
+
export declare function registerConfigApiRoutes(app: Hono, configDir: string): void;
|
|
22
|
+
export declare function registerBoardRoutes(app: Hono, services: BoardServices): void;
|
|
23
|
+
//# sourceMappingURL=boardRoutes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"boardRoutes.d.ts","sourceRoot":"","sources":["../../src/board/boardRoutes.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AACtD,OAAO,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAC5D,OAAO,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AACpD,OAAO,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AAC9D,OAAO,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAC5D,OAAO,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAC;AAEhE,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAItD,MAAM,MAAM,aAAa,GAAG;IAC1B,EAAE,EAAE,WAAW,CAAC;IAChB,GAAG,EAAE,cAAc,CAAC;IACpB,IAAI,EAAE,UAAU,CAAC;IACjB,EAAE,EAAE,eAAe,CAAC;IACpB,EAAE,EAAE,cAAc,CAAC;IACnB,GAAG,EAAE,gBAAgB,CAAC;IACtB,QAAQ,EAAE,eAAe,CAAC;IAC1B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAuMF,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,IAAI,EAAE,EAAE,EAAE,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE,EAAE,EAAE,EAAE,GAAG,EAAE,EAAE,aAAa,GAAG,IAAI,CAKpG;AAED,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI,CAuB1E;AA0BD,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,IAAI,EAAE,QAAQ,EAAE,aAAa,GAAG,IAAI,CAmB5E"}
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.registerTaskApiRoutes = registerTaskApiRoutes;
|
|
4
|
+
exports.registerConfigApiRoutes = registerConfigApiRoutes;
|
|
5
|
+
exports.registerBoardRoutes = registerBoardRoutes;
|
|
6
|
+
const models_1 = require("../models");
|
|
7
|
+
const boardConfig_1 = require("./boardConfig");
|
|
8
|
+
const boardRenderer_1 = require("./boardRenderer");
|
|
9
|
+
function buildTaskUpdateInput(body) {
|
|
10
|
+
const input = {};
|
|
11
|
+
if (body.status !== undefined) {
|
|
12
|
+
if (!boardRenderer_1.STATUSES.includes(body.status)) {
|
|
13
|
+
return { input, error: 'Invalid status' };
|
|
14
|
+
}
|
|
15
|
+
input.status = body.status;
|
|
16
|
+
}
|
|
17
|
+
if (body.title !== undefined) {
|
|
18
|
+
if (!body.title.trim()) {
|
|
19
|
+
return { input, error: 'Title cannot be empty' };
|
|
20
|
+
}
|
|
21
|
+
input.title = body.title.trim();
|
|
22
|
+
}
|
|
23
|
+
if (body.body !== undefined) {
|
|
24
|
+
input.body = body.body ?? '';
|
|
25
|
+
}
|
|
26
|
+
if (body.priority !== undefined) {
|
|
27
|
+
input.priority = body.priority && (0, models_1.isPriority)(body.priority) ? body.priority : null;
|
|
28
|
+
}
|
|
29
|
+
return { input };
|
|
30
|
+
}
|
|
31
|
+
function registerTaskCrudRoutes(app, ts, tts, tbs, ms) {
|
|
32
|
+
app.get('/api/tasks', (c) => c.json({ tasks: ts.listTasks({}, 'id', 'asc') }));
|
|
33
|
+
app.post('/api/tasks', async (c) => {
|
|
34
|
+
const body = await c.req.json();
|
|
35
|
+
if (!body.title || typeof body.title !== 'string' || !body.title.trim()) {
|
|
36
|
+
return c.json({ error: 'Title is required' }, 400);
|
|
37
|
+
}
|
|
38
|
+
const status = body.status && boardRenderer_1.STATUSES.includes(body.status) ? body.status : 'backlog';
|
|
39
|
+
const priority = body.priority && (0, models_1.isPriority)(body.priority) ? body.priority : undefined;
|
|
40
|
+
return c.json(ts.createTask({ title: body.title.trim(), body: body.body || undefined, status, priority }), 201);
|
|
41
|
+
});
|
|
42
|
+
app.get('/api/tasks/:id', (c) => {
|
|
43
|
+
const id = Number(c.req.param('id'));
|
|
44
|
+
if (isNaN(id))
|
|
45
|
+
return c.json({ error: 'Invalid task id' }, 400);
|
|
46
|
+
const task = ts.getTask(id);
|
|
47
|
+
if (!task)
|
|
48
|
+
return c.json({ error: 'Task not found' }, 404);
|
|
49
|
+
const parent = task.parent_id ? ts.getTask(task.parent_id) : null;
|
|
50
|
+
const blockedByIds = tbs.getBlockerTaskIds(id);
|
|
51
|
+
const blockingIds = tbs.getBlockedTaskIds(id);
|
|
52
|
+
const blockedBy = blockedByIds.map((bid) => ts.getTask(bid)).filter(Boolean);
|
|
53
|
+
const blocking = blockingIds.map((bid) => ts.getTask(bid)).filter(Boolean);
|
|
54
|
+
return c.json({ task, tags: tts.getTagsForTask(id), metadata: ms.listMetadata(id), parent, blockedBy, blocking });
|
|
55
|
+
});
|
|
56
|
+
app.patch('/api/tasks/:id', async (c) => {
|
|
57
|
+
const id = Number(c.req.param('id'));
|
|
58
|
+
if (isNaN(id))
|
|
59
|
+
return c.json({ error: 'Invalid task id' }, 400);
|
|
60
|
+
const { input, error } = buildTaskUpdateInput(await c.req.json());
|
|
61
|
+
if (error)
|
|
62
|
+
return c.json({ error }, 400);
|
|
63
|
+
const task = ts.updateTask(id, input);
|
|
64
|
+
if (!task)
|
|
65
|
+
return c.json({ error: 'Task not found' }, 404);
|
|
66
|
+
return c.json(task);
|
|
67
|
+
});
|
|
68
|
+
app.delete('/api/tasks/:id', (c) => {
|
|
69
|
+
const id = Number(c.req.param('id'));
|
|
70
|
+
if (isNaN(id))
|
|
71
|
+
return c.json({ error: 'Invalid task id' }, 400);
|
|
72
|
+
if (!ts.getTask(id))
|
|
73
|
+
return c.json({ error: 'Task not found' }, 404);
|
|
74
|
+
ts.deleteTask(id);
|
|
75
|
+
return c.json({ success: true });
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
function registerCommentRoutes(app, cs, ts) {
|
|
79
|
+
app.get('/api/tasks/:id/comments', (c) => {
|
|
80
|
+
const id = Number(c.req.param('id'));
|
|
81
|
+
if (isNaN(id))
|
|
82
|
+
return c.json({ error: 'Invalid task id' }, 400);
|
|
83
|
+
if (!ts.getTask(id))
|
|
84
|
+
return c.json({ error: 'Task not found' }, 404);
|
|
85
|
+
return c.json({ comments: cs.listComments(id) });
|
|
86
|
+
});
|
|
87
|
+
app.post('/api/tasks/:id/comments', async (c) => {
|
|
88
|
+
const id = Number(c.req.param('id'));
|
|
89
|
+
if (isNaN(id))
|
|
90
|
+
return c.json({ error: 'Invalid task id' }, 400);
|
|
91
|
+
if (!ts.getTask(id))
|
|
92
|
+
return c.json({ error: 'Task not found' }, 404);
|
|
93
|
+
const body = await c.req.json();
|
|
94
|
+
if (!body.content || typeof body.content !== 'string') {
|
|
95
|
+
return c.json({ error: 'Content is required' }, 400);
|
|
96
|
+
}
|
|
97
|
+
try {
|
|
98
|
+
const comment = cs.addComment({ task_id: id, content: body.content, author: body.author });
|
|
99
|
+
return c.json(comment, 201);
|
|
100
|
+
}
|
|
101
|
+
catch (e) {
|
|
102
|
+
return c.json({ error: e instanceof Error ? e.message : 'Invalid input' }, 400);
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
app.get('/api/comments/:id', (c) => {
|
|
106
|
+
const id = Number(c.req.param('id'));
|
|
107
|
+
if (isNaN(id))
|
|
108
|
+
return c.json({ error: 'Invalid comment id' }, 400);
|
|
109
|
+
const comment = cs.getComment(id);
|
|
110
|
+
if (!comment)
|
|
111
|
+
return c.json({ error: 'Comment not found' }, 404);
|
|
112
|
+
return c.json(comment);
|
|
113
|
+
});
|
|
114
|
+
app.patch('/api/comments/:id', async (c) => {
|
|
115
|
+
const id = Number(c.req.param('id'));
|
|
116
|
+
if (isNaN(id))
|
|
117
|
+
return c.json({ error: 'Invalid comment id' }, 400);
|
|
118
|
+
const body = await c.req.json();
|
|
119
|
+
if (!body.content || typeof body.content !== 'string') {
|
|
120
|
+
return c.json({ error: 'Content is required' }, 400);
|
|
121
|
+
}
|
|
122
|
+
try {
|
|
123
|
+
const comment = cs.updateComment(id, body.content);
|
|
124
|
+
if (!comment)
|
|
125
|
+
return c.json({ error: 'Comment not found' }, 404);
|
|
126
|
+
return c.json(comment);
|
|
127
|
+
}
|
|
128
|
+
catch (e) {
|
|
129
|
+
return c.json({ error: e instanceof Error ? e.message : 'Invalid input' }, 400);
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
app.delete('/api/comments/:id', (c) => {
|
|
133
|
+
const id = Number(c.req.param('id'));
|
|
134
|
+
if (isNaN(id))
|
|
135
|
+
return c.json({ error: 'Invalid comment id' }, 400);
|
|
136
|
+
const deleted = cs.deleteComment(id);
|
|
137
|
+
if (!deleted)
|
|
138
|
+
return c.json({ error: 'Comment not found' }, 404);
|
|
139
|
+
return c.json({ success: true });
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
function registerTagRoutes(app, tts, tags, ts) {
|
|
143
|
+
app.post('/api/tasks/:id/tags', async (c) => {
|
|
144
|
+
const id = Number(c.req.param('id'));
|
|
145
|
+
if (isNaN(id))
|
|
146
|
+
return c.json({ error: 'Invalid task id' }, 400);
|
|
147
|
+
const body = await c.req.json();
|
|
148
|
+
if (body.tagId === undefined || body.tagId === null)
|
|
149
|
+
return c.json({ error: 'tagId is required' }, 400);
|
|
150
|
+
const tagId = Number(body.tagId);
|
|
151
|
+
if (!ts.getTask(id))
|
|
152
|
+
return c.json({ error: 'Task not found' }, 404);
|
|
153
|
+
if (!tags.getTag(tagId))
|
|
154
|
+
return c.json({ error: 'Tag not found' }, 404);
|
|
155
|
+
tts.addTagToTask({ task_id: id, tag_id: tagId });
|
|
156
|
+
return c.json({ success: true }, 201);
|
|
157
|
+
});
|
|
158
|
+
app.delete('/api/tasks/:id/tags/:tagId', (c) => {
|
|
159
|
+
const id = Number(c.req.param('id'));
|
|
160
|
+
if (isNaN(id))
|
|
161
|
+
return c.json({ error: 'Invalid task id' }, 400);
|
|
162
|
+
const tagId = Number(c.req.param('tagId'));
|
|
163
|
+
if (isNaN(tagId))
|
|
164
|
+
return c.json({ error: 'Invalid tag id' }, 400);
|
|
165
|
+
const removed = tts.removeTagFromTask(id, tagId);
|
|
166
|
+
if (!removed)
|
|
167
|
+
return c.json({ error: 'Tag not attached to task' }, 404);
|
|
168
|
+
return c.json({ success: true });
|
|
169
|
+
});
|
|
170
|
+
app.get('/api/tags', (c) => {
|
|
171
|
+
const allTags = tags.listTags();
|
|
172
|
+
return c.json({ tags: allTags });
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
function registerUtilityRoutes(app, ts) {
|
|
176
|
+
app.post('/api/tasks/purge', async (c) => {
|
|
177
|
+
const body = (await c.req.json().catch(() => ({})));
|
|
178
|
+
let beforeDate;
|
|
179
|
+
if (body.beforeDate !== undefined) {
|
|
180
|
+
const parsed = new Date(body.beforeDate);
|
|
181
|
+
if (isNaN(parsed.getTime())) {
|
|
182
|
+
return c.json({ error: 'Invalid beforeDate. Use ISO 8601 format.' }, 400);
|
|
183
|
+
}
|
|
184
|
+
beforeDate = parsed.toISOString();
|
|
185
|
+
}
|
|
186
|
+
else {
|
|
187
|
+
const d = new Date();
|
|
188
|
+
d.setDate(d.getDate() - 3);
|
|
189
|
+
beforeDate = d.toISOString();
|
|
190
|
+
}
|
|
191
|
+
const tasks = ts.purgeTasksBefore(beforeDate, ['done', 'closed'], false);
|
|
192
|
+
return c.json({
|
|
193
|
+
count: tasks.length,
|
|
194
|
+
tasks: tasks.map((t) => ({ id: t.id, title: t.title, status: t.status, updated_at: t.updated_at })),
|
|
195
|
+
});
|
|
196
|
+
});
|
|
197
|
+
app.get('/api/version', (c) => {
|
|
198
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
199
|
+
const { version } = require('../../package.json');
|
|
200
|
+
return c.json({ version });
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
function registerTaskApiRoutes(app, { ts, tts, tags, ms, cs, tbs }) {
|
|
204
|
+
registerTaskCrudRoutes(app, ts, tts, tbs, ms);
|
|
205
|
+
registerCommentRoutes(app, cs, ts);
|
|
206
|
+
registerTagRoutes(app, tts, tags, ts);
|
|
207
|
+
registerUtilityRoutes(app, ts);
|
|
208
|
+
}
|
|
209
|
+
function registerConfigApiRoutes(app, configDir) {
|
|
210
|
+
app.get('/api/config', (c) => {
|
|
211
|
+
const boardConfig = (0, boardConfig_1.readBoardConfig)(configDir);
|
|
212
|
+
return c.json({ board: boardConfig });
|
|
213
|
+
});
|
|
214
|
+
app.put('/api/config', async (c) => {
|
|
215
|
+
const body = await c.req.json();
|
|
216
|
+
const boardBody = body?.board ?? {};
|
|
217
|
+
if (boardBody.detailPaneWidth !== undefined) {
|
|
218
|
+
const width = boardBody.detailPaneWidth;
|
|
219
|
+
if (typeof width !== 'number' || !Number.isFinite(width)) {
|
|
220
|
+
return c.json({ error: 'detailPaneWidth must be a number' }, 400);
|
|
221
|
+
}
|
|
222
|
+
if (width > boardConfig_1.DETAIL_PANE_MAX_WIDTH) {
|
|
223
|
+
return c.json({ error: `detailPaneWidth must not exceed ${boardConfig_1.DETAIL_PANE_MAX_WIDTH}` }, 400);
|
|
224
|
+
}
|
|
225
|
+
(0, boardConfig_1.writeBoardConfig)(configDir, { detailPaneWidth: width });
|
|
226
|
+
}
|
|
227
|
+
return c.json({ success: true });
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
function parseBoardCardFilters(query) {
|
|
231
|
+
const filters = {};
|
|
232
|
+
if (query.tags) {
|
|
233
|
+
const tagIds = query.tags
|
|
234
|
+
.split(',')
|
|
235
|
+
.map((s) => Number(s.trim()))
|
|
236
|
+
.filter((n) => !isNaN(n) && n > 0);
|
|
237
|
+
if (tagIds.length > 0)
|
|
238
|
+
filters.tagIds = tagIds;
|
|
239
|
+
}
|
|
240
|
+
if (query.priority) {
|
|
241
|
+
const priorities = query.priority
|
|
242
|
+
.split(',')
|
|
243
|
+
.map((s) => s.trim())
|
|
244
|
+
.filter((s) => s.length > 0);
|
|
245
|
+
if (priorities.length > 0)
|
|
246
|
+
filters.priority = priorities;
|
|
247
|
+
}
|
|
248
|
+
if (query.assignee && query.assignee.trim()) {
|
|
249
|
+
filters.assignees = query.assignee.trim();
|
|
250
|
+
}
|
|
251
|
+
return filters;
|
|
252
|
+
}
|
|
253
|
+
function registerBoardRoutes(app, services) {
|
|
254
|
+
const { ts, tts, database, boardTitle, configDir } = services;
|
|
255
|
+
app.get('/', (c) => {
|
|
256
|
+
const tasksByStatus = (0, boardRenderer_1.buildTasksByStatus)(ts.listTasks({}, 'id', 'asc'));
|
|
257
|
+
return c.html((0, boardRenderer_1.renderBoard)(tasksByStatus, tts.getAllTaskTags(), boardTitle));
|
|
258
|
+
});
|
|
259
|
+
app.get('/api/board/updated-at', (c) => c.json({ updatedAt: (0, boardRenderer_1.getBoardUpdatedAt)(database) }));
|
|
260
|
+
app.get('/api/board/cards', (c) => {
|
|
261
|
+
const filters = parseBoardCardFilters({
|
|
262
|
+
tags: c.req.query('tags'),
|
|
263
|
+
priority: c.req.query('priority'),
|
|
264
|
+
assignee: c.req.query('assignee'),
|
|
265
|
+
});
|
|
266
|
+
const tasksByStatus = (0, boardRenderer_1.buildTasksByStatus)(ts.listTasks(filters, 'id', 'asc'));
|
|
267
|
+
const columns = (0, boardRenderer_1.buildBoardCardsPayload)(tasksByStatus, tts.getAllTaskTags());
|
|
268
|
+
return c.json({ columns });
|
|
269
|
+
});
|
|
270
|
+
registerTaskApiRoutes(app, services);
|
|
271
|
+
registerConfigApiRoutes(app, configDir);
|
|
272
|
+
}
|
|
273
|
+
//# sourceMappingURL=boardRoutes.js.map
|