This commit is contained in:
152
src/routes/projects/[id]/+page.server.ts
Normal file
152
src/routes/projects/[id]/+page.server.ts
Normal file
@@ -0,0 +1,152 @@
|
||||
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}`);
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user