release-tracker/src/lib/actions/releases.ts

177 lines
4.7 KiB
TypeScript

'use server';
import { db } from '@/lib/db';
import {
releases,
stepTemplates,
customerSteps,
customers,
type ReleaseType,
type ReleaseStatus,
} from '@/lib/db/schema';
import { eq, and, desc } from 'drizzle-orm';
import { revalidatePath } from 'next/cache';
export type ReleaseInput = {
name: string;
type: ReleaseType;
versionNumber?: string;
releaseDate?: Date;
description?: string;
};
export async function createRelease(data: ReleaseInput) {
const [release] = await db.insert(releases).values({
...data,
releaseDate: data.releaseDate || null,
status: 'draft',
}).returning();
revalidatePath('/releases');
return release;
}
export async function updateRelease(id: number, data: Partial<ReleaseInput>) {
const [release] = await db
.update(releases)
.set({ ...data, updatedAt: new Date() })
.where(eq(releases.id, id))
.returning();
revalidatePath('/releases');
revalidatePath(`/releases/${id}`);
return release;
}
export async function activateRelease(id: number) {
const release = await db.query.releases.findFirst({
where: eq(releases.id, id),
with: { templates: true },
});
if (!release) throw new Error('Release not found');
if (release.status !== 'draft') throw new Error('Release is not in draft status');
const activeCustomers = await db.query.customers.findMany({
where: eq(customers.isActive, true),
});
// Create customer steps from templates
const customerStepsToInsert = activeCustomers.flatMap(customer =>
release.templates.map(template => ({
releaseId: id,
customerId: customer.id,
templateId: template.id,
name: template.name,
category: template.category,
type: template.type,
content: template.content,
orderIndex: template.orderIndex,
status: 'pending' as const,
isCustom: false,
isOverridden: false,
}))
);
if (customerStepsToInsert.length > 0) {
await db.insert(customerSteps).values(customerStepsToInsert);
}
await db.update(releases)
.set({ status: 'active', updatedAt: new Date() })
.where(eq(releases.id, id));
revalidatePath('/releases');
revalidatePath(`/releases/${id}`);
}
export async function archiveRelease(id: number) {
await db.update(releases)
.set({ status: 'archived', updatedAt: new Date() })
.where(eq(releases.id, id));
revalidatePath('/releases');
revalidatePath(`/releases/${id}`);
}
export async function deleteRelease(id: number) {
await db.delete(releases).where(eq(releases.id, id));
revalidatePath('/releases');
}
export async function listReleases() {
return db.query.releases.findMany({
orderBy: desc(releases.createdAt),
});
}
export async function getReleaseById(id: number) {
return db.query.releases.findFirst({
where: eq(releases.id, id),
with: {
templates: {
orderBy: [stepTemplates.category, stepTemplates.orderIndex],
},
},
});
}
export async function getActiveReleases() {
return db.query.releases.findMany({
where: eq(releases.status, 'active'),
orderBy: desc(releases.createdAt),
});
}
export async function cloneRelease(id: number, newName: string) {
const original = await db.query.releases.findFirst({
where: eq(releases.id, id),
with: { templates: true },
});
if (!original) throw new Error('Release not found');
// Create new release
const [newRelease] = await db.insert(releases).values({
name: newName,
type: original.type,
status: 'draft',
versionNumber: original.versionNumber,
description: `Cloned from: ${original.name}\n\n${original.description || ''}`,
}).returning();
// Clone templates
if (original.templates.length > 0) {
await db.insert(stepTemplates).values(
original.templates.map(t => ({
releaseId: newRelease.id,
name: t.name,
category: t.category,
type: t.type,
content: t.content,
orderIndex: t.orderIndex,
description: t.description,
}))
);
}
revalidatePath('/releases');
return newRelease;
}
export async function getReleaseStats() {
const allReleases = await db.query.releases.findMany();
const activeReleases = allReleases.filter(r => r.status === 'active');
// Count total customer steps by status
const allSteps = await db.query.customerSteps.findMany();
const pendingSteps = allSteps.filter(s => s.status === 'pending').length;
const doneSteps = allSteps.filter(s => s.status === 'done').length;
const skippedSteps = allSteps.filter(s => s.status === 'skipped').length;
return {
totalReleases: allReleases.length,
activeReleases: activeReleases.length,
pendingSteps,
doneSteps,
skippedSteps,
};
}