first init

This commit is contained in:
Power BI Dev
2026-08-17 10:52:09 +07:00
commit 88c7c653c2
9281 changed files with 2156575 additions and 0 deletions
+169
View File
@@ -0,0 +1,169 @@
import { prisma } from '../prisma.js';
import { generateSecureKey, hashKey, verifyKeyHash } from '../utils/crypto.js';
export class BoardService {
/**
* Create a new Kanban board with admin and member keys and standard agile columns with WIP limits.
*/
static async createBoard(title = 'Agile Kanban Board') {
const adminKey = generateSecureKey(32);
const memberKey = generateSecureKey(32);
const adminKeyHash = hashKey(adminKey);
const memberKeyHash = hashKey(memberKey);
const board = await prisma.board.create({
data: {
title: title.trim() || 'Agile Kanban Board',
adminKeyHash,
memberKeyHash,
autoMoveStale: false,
columns: {
create: [
{ title: '📋 Backlog', order: 0, maxWipLimit: 0, autoStaleHours: 0 },
{ title: '📝 To Do', order: 1, maxWipLimit: 0, autoStaleHours: 0 },
{ title: '⚡ In Progress', order: 2, maxWipLimit: 3, autoStaleHours: 24 }, // Standard 3-card WIP limit & 24h stale
{ title: '🔍 Review', order: 3, maxWipLimit: 4, autoStaleHours: 24 },
{ title: '✅ Done', order: 4, maxWipLimit: 0, autoStaleHours: 0 },
],
},
},
});
return {
id: board.id,
title: board.title,
adminKey,
memberKey,
adminUrlFragment: `/b/${board.id}#admin=${adminKey}`,
memberUrlFragment: `/b/${board.id}#member=${memberKey}`,
createdAt: board.createdAt.toISOString(),
};
}
/**
* Authenticate a key against a board ID. Returns the role ('admin' | 'member') or null if invalid.
*/
static async authenticateKey(boardId, key) {
if (!boardId || !key)
return null;
const board = await prisma.board.findUnique({
where: { id: boardId },
});
if (!board)
return null;
if (verifyKeyHash(key, board.adminKeyHash)) {
return { role: 'admin', board };
}
if (verifyKeyHash(key, board.memberKeyHash)) {
return { role: 'member', board };
}
return null;
}
/**
* Fetch full board data with columns and tickets ordered properly.
*/
static async getBoardDetails(boardId, role) {
const board = await prisma.board.findUnique({
where: { id: boardId },
include: {
columns: {
orderBy: { order: 'asc' },
include: {
tickets: {
orderBy: { order: 'asc' },
},
},
},
},
});
if (!board)
return null;
const columns = board.columns.map((col) => ({
id: col.id,
boardId: col.boardId,
title: col.title,
order: col.order,
maxWipLimit: col.maxWipLimit || 0,
autoStaleHours: col.autoStaleHours || 0,
createdAt: col.createdAt.toISOString(),
updatedAt: col.updatedAt.toISOString(),
tickets: col.tickets.map((t) => {
let parsedTags = [];
let parsedSubtasks = [];
try {
parsedTags = JSON.parse(t.tags || '[]');
}
catch {
parsedTags = [];
}
try {
parsedSubtasks = JSON.parse(t.subtasks || '[]');
}
catch {
parsedSubtasks = [];
}
return {
id: t.id,
columnId: t.columnId,
title: t.title,
description: t.description,
order: t.order,
priority: t.priority,
tags: parsedTags,
subtasks: parsedSubtasks,
dueDate: t.dueDate || null,
isBlocked: t.isBlocked || false,
blockedReason: t.blockedReason || null,
assignee: t.assignee,
movedAt: t.movedAt ? t.movedAt.toISOString() : t.createdAt.toISOString(),
createdAt: t.createdAt.toISOString(),
updatedAt: t.updatedAt.toISOString(),
};
}),
}));
return {
id: board.id,
title: board.title,
role,
autoMoveStale: board.autoMoveStale || false,
columns,
createdAt: board.createdAt.toISOString(),
updatedAt: board.updatedAt.toISOString(),
};
}
/**
* Regenerate member key (Key Rotation). Only superadmin can perform this.
*/
static async rotateMemberKey(boardId) {
const newMemberKey = generateSecureKey(32);
const newMemberKeyHash = hashKey(newMemberKey);
await prisma.board.update({
where: { id: boardId },
data: {
memberKeyHash: newMemberKeyHash,
},
});
return {
newMemberKey,
memberUrlFragment: `/b/${boardId}#member=${newMemberKey}`,
};
}
/**
* Delete a board. Only superadmin can perform this.
*/
static async deleteBoard(boardId) {
await prisma.board.delete({
where: { id: boardId },
});
}
/**
* Update board title & settings.
*/
static async updateBoard(boardId, updates) {
const data = {};
if (updates.title !== undefined)
data.title = updates.title.trim() || 'Agile Kanban Board';
if (updates.autoMoveStale !== undefined)
data.autoMoveStale = updates.autoMoveStale;
await prisma.board.update({
where: { id: boardId },
data,
});
}
}