First pass
This commit is contained in:
36
src/pages/api/time-entries/[id]/delete.ts
Normal file
36
src/pages/api/time-entries/[id]/delete.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import type { APIRoute } from 'astro';
|
||||
import { db } from '../../../../db';
|
||||
import { timeEntries } from '../../../../db/schema';
|
||||
import { eq, and } from 'drizzle-orm';
|
||||
|
||||
export const POST: APIRoute = async ({ params, locals, redirect }) => {
|
||||
const user = locals.user;
|
||||
if (!user) {
|
||||
return new Response(JSON.stringify({ error: 'Unauthorized' }), { status: 401 });
|
||||
}
|
||||
|
||||
const entryId = params.id;
|
||||
if (!entryId) {
|
||||
return new Response(JSON.stringify({ error: 'Entry ID required' }), { status: 400 });
|
||||
}
|
||||
|
||||
// Verify the entry belongs to the user
|
||||
const entry = await db.select()
|
||||
.from(timeEntries)
|
||||
.where(and(
|
||||
eq(timeEntries.id, entryId),
|
||||
eq(timeEntries.userId, user.id)
|
||||
))
|
||||
.get();
|
||||
|
||||
if (!entry) {
|
||||
return new Response(JSON.stringify({ error: 'Entry not found' }), { status: 404 });
|
||||
}
|
||||
|
||||
// Delete the entry
|
||||
await db.delete(timeEntries)
|
||||
.where(eq(timeEntries.id, entryId))
|
||||
.run();
|
||||
|
||||
return redirect('/dashboard/tracker');
|
||||
};
|
||||
78
src/pages/api/time-entries/start.ts
Normal file
78
src/pages/api/time-entries/start.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
import type { APIRoute } from 'astro';
|
||||
import { db } from '../../../db';
|
||||
import { timeEntries, members, timeEntryTags, categories } from '../../../db/schema';
|
||||
import { eq, and, isNull } from 'drizzle-orm';
|
||||
import { nanoid } from 'nanoid';
|
||||
|
||||
export const POST: APIRoute = async ({ request, locals }) => {
|
||||
if (!locals.user) return new Response('Unauthorized', { status: 401 });
|
||||
|
||||
const body = await request.json();
|
||||
const description = body.description || '';
|
||||
const clientId = body.clientId;
|
||||
const categoryId = body.categoryId;
|
||||
const tags = body.tags || [];
|
||||
|
||||
if (!clientId) {
|
||||
return new Response('Client is required', { status: 400 });
|
||||
}
|
||||
|
||||
if (!categoryId) {
|
||||
return new Response('Category is required', { status: 400 });
|
||||
}
|
||||
|
||||
// Check for running entry
|
||||
const runningEntry = await db.select().from(timeEntries).where(
|
||||
and(
|
||||
eq(timeEntries.userId, locals.user.id),
|
||||
isNull(timeEntries.endTime)
|
||||
)
|
||||
).get();
|
||||
|
||||
if (runningEntry) {
|
||||
return new Response('Timer already running', { status: 400 });
|
||||
}
|
||||
|
||||
// Get default org (first one)
|
||||
const member = await db.select().from(members).where(eq(members.userId, locals.user.id)).limit(1).get();
|
||||
if (!member) {
|
||||
return new Response('No organization found', { status: 400 });
|
||||
}
|
||||
|
||||
// Verify category belongs to user's organization
|
||||
const category = await db.select().from(categories).where(
|
||||
and(
|
||||
eq(categories.id, categoryId),
|
||||
eq(categories.organizationId, member.organizationId)
|
||||
)
|
||||
).get();
|
||||
|
||||
if (!category) {
|
||||
return new Response('Invalid category', { status: 400 });
|
||||
}
|
||||
|
||||
const startTime = new Date();
|
||||
const id = nanoid();
|
||||
|
||||
await db.insert(timeEntries).values({
|
||||
id,
|
||||
userId: locals.user.id,
|
||||
organizationId: member.organizationId,
|
||||
clientId,
|
||||
categoryId,
|
||||
startTime,
|
||||
description,
|
||||
});
|
||||
|
||||
// Add tags if provided
|
||||
if (tags.length > 0) {
|
||||
await db.insert(timeEntryTags).values(
|
||||
tags.map((tagId: string) => ({
|
||||
timeEntryId: id,
|
||||
tagId,
|
||||
}))
|
||||
);
|
||||
}
|
||||
|
||||
return new Response(JSON.stringify({ id, startTime }), { status: 200 });
|
||||
};
|
||||
25
src/pages/api/time-entries/stop.ts
Normal file
25
src/pages/api/time-entries/stop.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import type { APIRoute } from 'astro';
|
||||
import { db } from '../../../db';
|
||||
import { timeEntries } from '../../../db/schema';
|
||||
import { eq, and, isNull } from 'drizzle-orm';
|
||||
|
||||
export const POST: APIRoute = async ({ locals }) => {
|
||||
if (!locals.user) return new Response('Unauthorized', { status: 401 });
|
||||
|
||||
const runningEntry = await db.select().from(timeEntries).where(
|
||||
and(
|
||||
eq(timeEntries.userId, locals.user.id),
|
||||
isNull(timeEntries.endTime)
|
||||
)
|
||||
).get();
|
||||
|
||||
if (!runningEntry) {
|
||||
return new Response('No timer running', { status: 400 });
|
||||
}
|
||||
|
||||
await db.update(timeEntries)
|
||||
.set({ endTime: new Date() })
|
||||
.where(eq(timeEntries.id, runningEntry.id));
|
||||
|
||||
return new Response(JSON.stringify({ success: true }), { status: 200 });
|
||||
};
|
||||
Reference in New Issue
Block a user