added repo
This commit is contained in:
346
src/repository.ts
Normal file
346
src/repository.ts
Normal file
@@ -0,0 +1,346 @@
|
||||
import type { Database } from "bun:sqlite";
|
||||
|
||||
interface TaskFilters {
|
||||
orgId?: string | null;
|
||||
projectId?: string | null;
|
||||
status?: string | null;
|
||||
priority?: string | null;
|
||||
assigneeUserId?: string | null;
|
||||
}
|
||||
|
||||
function toCamelCaseTask(row: Record<string, unknown>) {
|
||||
return {
|
||||
id: row.id,
|
||||
orgId: row.org_id,
|
||||
projectId: row.project_id,
|
||||
assigneeUserId: row.assignee_user_id,
|
||||
title: row.title,
|
||||
description: row.description,
|
||||
status: row.status,
|
||||
priority: row.priority,
|
||||
storyPoints: row.story_points,
|
||||
dueDate: row.due_date,
|
||||
createdAt: row.created_at,
|
||||
updatedAt: row.updated_at,
|
||||
projectName: row.project_name,
|
||||
projectKey: row.project_key,
|
||||
assigneeName: row.assignee_name,
|
||||
};
|
||||
}
|
||||
|
||||
export function listOrganizations(db: Database) {
|
||||
return db
|
||||
.query(`
|
||||
SELECT
|
||||
o.id,
|
||||
o.slug,
|
||||
o.name,
|
||||
o.plan,
|
||||
o.industry,
|
||||
o.created_at AS createdAt,
|
||||
COUNT(DISTINCT p.id) AS projectCount,
|
||||
COUNT(DISTINCT t.id) AS taskCount,
|
||||
COUNT(DISTINCT CASE WHEN t.status != 'done' THEN t.id END) AS openTaskCount
|
||||
FROM organizations o
|
||||
LEFT JOIN projects p ON p.org_id = o.id
|
||||
LEFT JOIN tasks t ON t.org_id = o.id
|
||||
GROUP BY o.id
|
||||
ORDER BY o.name
|
||||
`)
|
||||
.all();
|
||||
}
|
||||
|
||||
export function getOrganizationSummary(db: Database, orgId: string) {
|
||||
const org = db
|
||||
.query(`
|
||||
SELECT
|
||||
o.id,
|
||||
o.slug,
|
||||
o.name,
|
||||
o.plan,
|
||||
o.industry,
|
||||
o.created_at AS createdAt,
|
||||
COUNT(DISTINCT u.id) AS userCount,
|
||||
COUNT(DISTINCT p.id) AS projectCount,
|
||||
COUNT(DISTINCT t.id) AS taskCount
|
||||
FROM organizations o
|
||||
LEFT JOIN users u ON u.org_id = o.id
|
||||
LEFT JOIN projects p ON p.org_id = o.id
|
||||
LEFT JOIN tasks t ON t.org_id = o.id
|
||||
WHERE o.id = ?
|
||||
GROUP BY o.id
|
||||
`)
|
||||
.get(orgId);
|
||||
|
||||
if (!org) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const statusBreakdown = db
|
||||
.query(`
|
||||
SELECT status, COUNT(*) AS count
|
||||
FROM tasks
|
||||
WHERE org_id = ?
|
||||
GROUP BY status
|
||||
ORDER BY count DESC, status ASC
|
||||
`)
|
||||
.all(orgId);
|
||||
|
||||
return {
|
||||
...org,
|
||||
statusBreakdown,
|
||||
};
|
||||
}
|
||||
|
||||
export function listUsers(db: Database, orgId?: string | null) {
|
||||
if (orgId) {
|
||||
return db
|
||||
.query(`
|
||||
SELECT
|
||||
id,
|
||||
org_id AS orgId,
|
||||
full_name AS fullName,
|
||||
email,
|
||||
role,
|
||||
timezone
|
||||
FROM users
|
||||
WHERE org_id = ?
|
||||
ORDER BY full_name
|
||||
`)
|
||||
.all(orgId);
|
||||
}
|
||||
|
||||
return db
|
||||
.query(`
|
||||
SELECT
|
||||
id,
|
||||
org_id AS orgId,
|
||||
full_name AS fullName,
|
||||
email,
|
||||
role,
|
||||
timezone
|
||||
FROM users
|
||||
ORDER BY org_id, full_name
|
||||
`)
|
||||
.all();
|
||||
}
|
||||
|
||||
export function listProjects(db: Database, orgId?: string | null) {
|
||||
const sql = `
|
||||
SELECT
|
||||
p.id,
|
||||
p.org_id AS orgId,
|
||||
p.key,
|
||||
p.name,
|
||||
p.status,
|
||||
p.owner_user_id AS ownerUserId,
|
||||
p.due_date AS dueDate,
|
||||
u.full_name AS ownerName,
|
||||
COUNT(t.id) AS taskCount
|
||||
FROM projects p
|
||||
LEFT JOIN users u ON u.id = p.owner_user_id
|
||||
LEFT JOIN tasks t ON t.project_id = p.id
|
||||
${orgId ? "WHERE p.org_id = ?" : ""}
|
||||
GROUP BY p.id
|
||||
ORDER BY p.name
|
||||
`;
|
||||
|
||||
return orgId ? db.query(sql).all(orgId) : db.query(sql).all();
|
||||
}
|
||||
|
||||
export function listTasks(db: Database, filters: TaskFilters = {}) {
|
||||
const conditions: string[] = [];
|
||||
const values: string[] = [];
|
||||
|
||||
if (filters.orgId) {
|
||||
conditions.push("t.org_id = ?");
|
||||
values.push(filters.orgId);
|
||||
}
|
||||
if (filters.projectId) {
|
||||
conditions.push("t.project_id = ?");
|
||||
values.push(filters.projectId);
|
||||
}
|
||||
if (filters.status) {
|
||||
conditions.push("t.status = ?");
|
||||
values.push(filters.status);
|
||||
}
|
||||
if (filters.priority) {
|
||||
conditions.push("t.priority = ?");
|
||||
values.push(filters.priority);
|
||||
}
|
||||
if (filters.assigneeUserId) {
|
||||
conditions.push("t.assignee_user_id = ?");
|
||||
values.push(filters.assigneeUserId);
|
||||
}
|
||||
|
||||
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
|
||||
const rows = db
|
||||
.query(`
|
||||
SELECT
|
||||
t.*,
|
||||
p.name AS project_name,
|
||||
p.key AS project_key,
|
||||
u.full_name AS assignee_name
|
||||
FROM tasks t
|
||||
JOIN projects p ON p.id = t.project_id
|
||||
LEFT JOIN users u ON u.id = t.assignee_user_id
|
||||
${whereClause}
|
||||
ORDER BY
|
||||
CASE t.priority
|
||||
WHEN 'urgent' THEN 1
|
||||
WHEN 'high' THEN 2
|
||||
WHEN 'medium' THEN 3
|
||||
ELSE 4
|
||||
END,
|
||||
t.due_date ASC,
|
||||
t.updated_at DESC
|
||||
`)
|
||||
.all(...values) as Record<string, unknown>[];
|
||||
|
||||
return rows.map(toCamelCaseTask);
|
||||
}
|
||||
|
||||
export function getTaskDetail(db: Database, taskId: string) {
|
||||
const row = db
|
||||
.query(`
|
||||
SELECT
|
||||
t.*,
|
||||
p.name AS project_name,
|
||||
p.key AS project_key,
|
||||
u.full_name AS assignee_name
|
||||
FROM tasks t
|
||||
JOIN projects p ON p.id = t.project_id
|
||||
LEFT JOIN users u ON u.id = t.assignee_user_id
|
||||
WHERE t.id = ?
|
||||
`)
|
||||
.get(taskId) as Record<string, unknown> | null;
|
||||
|
||||
if (!row) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const labels = db
|
||||
.query(`
|
||||
SELECT l.id, l.name, l.color
|
||||
FROM task_labels tl
|
||||
JOIN labels l ON l.id = tl.label_id
|
||||
WHERE tl.task_id = ?
|
||||
ORDER BY l.name
|
||||
`)
|
||||
.all(taskId);
|
||||
|
||||
const comments = db
|
||||
.query(`
|
||||
SELECT
|
||||
c.id,
|
||||
c.body,
|
||||
c.created_at AS createdAt,
|
||||
c.author_user_id AS authorUserId,
|
||||
u.full_name AS authorName
|
||||
FROM comments c
|
||||
JOIN users u ON u.id = c.author_user_id
|
||||
WHERE c.task_id = ?
|
||||
ORDER BY c.created_at ASC
|
||||
`)
|
||||
.all(taskId);
|
||||
|
||||
return {
|
||||
...toCamelCaseTask(row),
|
||||
labels,
|
||||
comments,
|
||||
};
|
||||
}
|
||||
|
||||
export interface CreateTaskInput {
|
||||
orgId: string;
|
||||
projectId: string;
|
||||
assigneeUserId?: string | null;
|
||||
title: string;
|
||||
description?: string;
|
||||
status?: string;
|
||||
priority?: string;
|
||||
storyPoints?: number | null;
|
||||
dueDate?: string | null;
|
||||
}
|
||||
|
||||
export function createTask(db: Database, input: CreateTaskInput) {
|
||||
const now = new Date().toISOString();
|
||||
const id = `task_${Math.random().toString(36).slice(2, 10)}`;
|
||||
|
||||
db.query(`
|
||||
INSERT INTO tasks (
|
||||
id, org_id, project_id, assignee_user_id, title, description, status, priority, story_points, due_date, created_at, updated_at
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`).run(
|
||||
id,
|
||||
input.orgId,
|
||||
input.projectId,
|
||||
input.assigneeUserId ?? null,
|
||||
input.title,
|
||||
input.description ?? "",
|
||||
input.status ?? "todo",
|
||||
input.priority ?? "medium",
|
||||
input.storyPoints ?? null,
|
||||
input.dueDate ?? null,
|
||||
now,
|
||||
now,
|
||||
);
|
||||
|
||||
return getTaskDetail(db, id);
|
||||
}
|
||||
|
||||
export interface UpdateTaskInput {
|
||||
title?: string;
|
||||
description?: string;
|
||||
status?: string;
|
||||
priority?: string;
|
||||
assigneeUserId?: string | null;
|
||||
storyPoints?: number | null;
|
||||
dueDate?: string | null;
|
||||
}
|
||||
|
||||
export function updateTask(db: Database, taskId: string, input: UpdateTaskInput) {
|
||||
const existing = db.query("SELECT id FROM tasks WHERE id = ?").get(taskId);
|
||||
if (!existing) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const fields: string[] = [];
|
||||
const values: Array<string | number | null> = [];
|
||||
|
||||
if (input.title !== undefined) {
|
||||
fields.push("title = ?");
|
||||
values.push(input.title);
|
||||
}
|
||||
if (input.description !== undefined) {
|
||||
fields.push("description = ?");
|
||||
values.push(input.description);
|
||||
}
|
||||
if (input.status !== undefined) {
|
||||
fields.push("status = ?");
|
||||
values.push(input.status);
|
||||
}
|
||||
if (input.priority !== undefined) {
|
||||
fields.push("priority = ?");
|
||||
values.push(input.priority);
|
||||
}
|
||||
if (input.assigneeUserId !== undefined) {
|
||||
fields.push("assignee_user_id = ?");
|
||||
values.push(input.assigneeUserId);
|
||||
}
|
||||
if (input.storyPoints !== undefined) {
|
||||
fields.push("story_points = ?");
|
||||
values.push(input.storyPoints);
|
||||
}
|
||||
if (input.dueDate !== undefined) {
|
||||
fields.push("due_date = ?");
|
||||
values.push(input.dueDate);
|
||||
}
|
||||
|
||||
fields.push("updated_at = ?");
|
||||
values.push(new Date().toISOString());
|
||||
values.push(taskId);
|
||||
|
||||
db.query(`UPDATE tasks SET ${fields.join(", ")} WHERE id = ?`).run(...values);
|
||||
return getTaskDetail(db, taskId);
|
||||
}
|
||||
Reference in New Issue
Block a user