import { error, redirect } from '@sveltejs/kit'; import type { Actions, PageServerLoad } from './$types'; import { db } from '$lib/server/db'; import { githubInstallation, githubRepositoryLink, issue, project, task } from '$lib/server/db/schema'; import { desc, eq, and, ne } from 'drizzle-orm'; import { publish } from '$lib/server/events'; import { listGitHubRepositoryIssues } from '$lib/server/github-app'; export const load: PageServerLoad = async (event) => { if (!event.locals.user) throw redirect(302, '/auth/login'); const projectId = Number(event.params.id); if (!Number.isFinite(projectId)) throw error(404, 'Project not found'); const [record] = await db .select() .from(project) .where(and(eq(project.id, projectId), eq(project.ownerId, event.locals.user.id))) .limit(1); if (!record) throw error(404, 'Project not found'); const tasks = await db .select() .from(task) .where(eq(task.projectId, projectId)) .orderBy(desc(task.updatedAt)); const completedTasks = tasks.filter((item) => item.status === 'done').length; const progress = tasks.length ? Math.round((completedTasks / tasks.length) * 100) : 0; if (record.progress !== progress) { await db.update(project).set({ progress }).where(eq(project.id, projectId)); } const issues = await db .select() .from(issue) .where(eq(issue.projectId, projectId)) .orderBy(desc(issue.updatedAt)); const repositoryLinks = await db .select() .from(githubRepositoryLink) .where(eq(githubRepositoryLink.projectId, projectId)); return { project: record, tasks, issues, repositoryLinks }; }; export const actions: Actions = { createTask: async (event) => { if (!event.locals.user) throw redirect(302, '/auth/login'); const projectId = Number(event.params.id); if (!Number.isFinite(projectId)) throw error(404, 'Project not found'); const formData = await event.request.formData(); const title = formData.get('title')?.toString().trim() ?? ''; const description = formData.get('description')?.toString().trim() ?? ''; if (!title) return { error: 'Task title is required' }; const [record] = await db .select() .from(project) .where(and(eq(project.id, projectId), eq(project.ownerId, event.locals.user.id))) .limit(1); if (!record) throw error(404, 'Project not found'); await db.insert(task).values({ projectId, title, description }); await db.update(project).set({ updatedAt: new Date() }).where(eq(project.id, projectId)); publish(`task-created:${projectId}`); throw redirect(303, `/projects/${projectId}`); }, updateTaskStatus: async (event) => { if (!event.locals.user) throw redirect(302, '/auth/login'); const projectId = Number(event.params.id); if (!Number.isFinite(projectId)) throw error(404, 'Project not found'); const formData = await event.request.formData(); const taskId = Number(formData.get('taskId')); const status = formData.get('status')?.toString(); if (!Number.isFinite(taskId) || !status) throw error(400, 'Invalid task update'); await db .update(task) .set({ status: status as 'todo' | 'doing' | 'blocked' | 'done' }) .where(and(eq(task.id, taskId), eq(task.projectId, projectId))); await db.update(project).set({ updatedAt: new Date() }).where(eq(project.id, projectId)); publish(`task-updated:${projectId}`); throw redirect(303, `/projects/${projectId}`); }, syncIssues: async (event) => { if (!event.locals.user) throw redirect(302, '/auth/login'); const projectId = Number(event.params.id); if (!Number.isFinite(projectId)) throw error(404, 'Project not found'); const [record] = await db .select() .from(project) .where(and(eq(project.id, projectId), eq(project.ownerId, event.locals.user.id))) .limit(1); if (!record) throw error(404, 'Project not found'); const [linkedRepository] = await db .select({ link: githubRepositoryLink, githubInstallationId: githubInstallation.installationId }) .from(githubRepositoryLink) .innerJoin(githubInstallation, eq(githubRepositoryLink.installationId, githubInstallation.id)) .where(eq(githubRepositoryLink.projectId, projectId)) .limit(1); if (!linkedRepository) throw error(400, 'Link a repository first'); const githubIssues = await listGitHubRepositoryIssues({ installationId: linkedRepository.githubInstallationId, owner: linkedRepository.link.owner, repo: linkedRepository.link.repo }); await db.delete(issue).where(and(eq(issue.projectId, projectId), eq(issue.provider, 'github'))); for (const item of githubIssues.filter((entry) => !entry.pull_request)) { await db .insert(issue) .values({ projectId, provider: 'github', externalId: String(item.id), url: item.html_url, title: item.title, state: item.state, labels: JSON.stringify(item.labels.map((label) => label.name)) }) ; } publish(`issues-synced:${projectId}`); throw redirect(303, `/projects/${projectId}`); } };