improvements
This commit is contained in:
@@ -1,11 +1,12 @@
|
||||
import { prisma } from '../prisma/client';
|
||||
import type { CreateClientInput, UpdateClientInput } from '../types';
|
||||
import { prisma } from "../prisma/client";
|
||||
import { NotFoundError } from "../errors/AppError";
|
||||
import type { CreateClientInput, UpdateClientInput } from "../types";
|
||||
|
||||
export class ClientService {
|
||||
async findAll(userId: string) {
|
||||
return prisma.client.findMany({
|
||||
where: { userId },
|
||||
orderBy: { name: 'asc' },
|
||||
orderBy: { name: "asc" },
|
||||
});
|
||||
}
|
||||
|
||||
@@ -27,9 +28,7 @@ export class ClientService {
|
||||
async update(id: string, userId: string, data: UpdateClientInput) {
|
||||
const client = await this.findById(id, userId);
|
||||
if (!client) {
|
||||
const error = new Error('Client not found') as Error & { statusCode: number };
|
||||
error.statusCode = 404;
|
||||
throw error;
|
||||
throw new NotFoundError("Client not found");
|
||||
}
|
||||
|
||||
return prisma.client.update({
|
||||
@@ -41,13 +40,11 @@ export class ClientService {
|
||||
async delete(id: string, userId: string) {
|
||||
const client = await this.findById(id, userId);
|
||||
if (!client) {
|
||||
const error = new Error('Client not found') as Error & { statusCode: number };
|
||||
error.statusCode = 404;
|
||||
throw error;
|
||||
throw new NotFoundError("Client not found");
|
||||
}
|
||||
|
||||
await prisma.client.delete({
|
||||
where: { id },
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { prisma } from '../prisma/client';
|
||||
|
||||
import type { CreateProjectInput, UpdateProjectInput } from '../types';
|
||||
import { prisma } from "../prisma/client";
|
||||
import { NotFoundError, BadRequestError } from "../errors/AppError";
|
||||
import type { CreateProjectInput, UpdateProjectInput } from "../types";
|
||||
|
||||
export class ProjectService {
|
||||
async findAll(userId: string, clientId?: string) {
|
||||
@@ -9,7 +9,7 @@ export class ProjectService {
|
||||
userId,
|
||||
...(clientId && { clientId }),
|
||||
},
|
||||
orderBy: { name: 'asc' },
|
||||
orderBy: { name: "asc" },
|
||||
include: {
|
||||
client: {
|
||||
select: {
|
||||
@@ -40,11 +40,9 @@ export class ProjectService {
|
||||
const client = await prisma.client.findFirst({
|
||||
where: { id: data.clientId, userId },
|
||||
});
|
||||
|
||||
|
||||
if (!client) {
|
||||
const error = new Error('Client not found or does not belong to user') as Error & { statusCode: number };
|
||||
error.statusCode = 400;
|
||||
throw error;
|
||||
throw new BadRequestError("Client not found or does not belong to user");
|
||||
}
|
||||
|
||||
return prisma.project.create({
|
||||
@@ -69,9 +67,7 @@ export class ProjectService {
|
||||
async update(id: string, userId: string, data: UpdateProjectInput) {
|
||||
const project = await this.findById(id, userId);
|
||||
if (!project) {
|
||||
const error = new Error('Project not found') as Error & { statusCode: number };
|
||||
error.statusCode = 404;
|
||||
throw error;
|
||||
throw new NotFoundError("Project not found");
|
||||
}
|
||||
|
||||
// If clientId is being updated, verify it belongs to the user
|
||||
@@ -79,11 +75,11 @@ export class ProjectService {
|
||||
const client = await prisma.client.findFirst({
|
||||
where: { id: data.clientId, userId },
|
||||
});
|
||||
|
||||
|
||||
if (!client) {
|
||||
const error = new Error('Client not found or does not belong to user') as Error & { statusCode: number };
|
||||
error.statusCode = 400;
|
||||
throw error;
|
||||
throw new BadRequestError(
|
||||
"Client not found or does not belong to user",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,13 +105,11 @@ export class ProjectService {
|
||||
async delete(id: string, userId: string) {
|
||||
const project = await this.findById(id, userId);
|
||||
if (!project) {
|
||||
const error = new Error('Project not found') as Error & { statusCode: number };
|
||||
error.statusCode = 404;
|
||||
throw error;
|
||||
throw new NotFoundError("Project not found");
|
||||
}
|
||||
|
||||
await prisma.project.delete({
|
||||
where: { id },
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
import { prisma } from "../prisma/client";
|
||||
import {
|
||||
NotFoundError,
|
||||
BadRequestError,
|
||||
ConflictError,
|
||||
} from "../errors/AppError";
|
||||
import type {
|
||||
CreateTimeEntryInput,
|
||||
UpdateTimeEntryInput,
|
||||
@@ -238,11 +243,7 @@ export class TimeEntryService {
|
||||
|
||||
// Validate end time is after start time
|
||||
if (endTime <= startTime) {
|
||||
const error = new Error("End time must be after start time") as Error & {
|
||||
statusCode: number;
|
||||
};
|
||||
error.statusCode = 400;
|
||||
throw error;
|
||||
throw new BadRequestError("End time must be after start time");
|
||||
}
|
||||
|
||||
// Verify the project belongs to the user
|
||||
@@ -251,11 +252,7 @@ export class TimeEntryService {
|
||||
});
|
||||
|
||||
if (!project) {
|
||||
const error = new Error("Project not found") as Error & {
|
||||
statusCode: number;
|
||||
};
|
||||
error.statusCode = 404;
|
||||
throw error;
|
||||
throw new NotFoundError("Project not found");
|
||||
}
|
||||
|
||||
// Check for overlapping entries
|
||||
@@ -265,11 +262,9 @@ export class TimeEntryService {
|
||||
endTime,
|
||||
);
|
||||
if (hasOverlap) {
|
||||
const error = new Error(
|
||||
throw new ConflictError(
|
||||
"This time entry overlaps with an existing entry",
|
||||
) as Error & { statusCode: number };
|
||||
error.statusCode = 400;
|
||||
throw error;
|
||||
);
|
||||
}
|
||||
|
||||
return prisma.timeEntry.create({
|
||||
@@ -301,11 +296,7 @@ export class TimeEntryService {
|
||||
async update(id: string, userId: string, data: UpdateTimeEntryInput) {
|
||||
const entry = await this.findById(id, userId);
|
||||
if (!entry) {
|
||||
const error = new Error("Time entry not found") as Error & {
|
||||
statusCode: number;
|
||||
};
|
||||
error.statusCode = 404;
|
||||
throw error;
|
||||
throw new NotFoundError("Time entry not found");
|
||||
}
|
||||
|
||||
const startTime = data.startTime
|
||||
@@ -315,11 +306,7 @@ export class TimeEntryService {
|
||||
|
||||
// Validate end time is after start time
|
||||
if (endTime <= startTime) {
|
||||
const error = new Error("End time must be after start time") as Error & {
|
||||
statusCode: number;
|
||||
};
|
||||
error.statusCode = 400;
|
||||
throw error;
|
||||
throw new BadRequestError("End time must be after start time");
|
||||
}
|
||||
|
||||
// If project changed, verify it belongs to the user
|
||||
@@ -329,11 +316,7 @@ export class TimeEntryService {
|
||||
});
|
||||
|
||||
if (!project) {
|
||||
const error = new Error("Project not found") as Error & {
|
||||
statusCode: number;
|
||||
};
|
||||
error.statusCode = 404;
|
||||
throw error;
|
||||
throw new NotFoundError("Project not found");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -345,11 +328,9 @@ export class TimeEntryService {
|
||||
id,
|
||||
);
|
||||
if (hasOverlap) {
|
||||
const error = new Error(
|
||||
throw new ConflictError(
|
||||
"This time entry overlaps with an existing entry",
|
||||
) as Error & { statusCode: number };
|
||||
error.statusCode = 400;
|
||||
throw error;
|
||||
);
|
||||
}
|
||||
|
||||
return prisma.timeEntry.update({
|
||||
@@ -381,11 +362,7 @@ export class TimeEntryService {
|
||||
async delete(id: string, userId: string) {
|
||||
const entry = await this.findById(id, userId);
|
||||
if (!entry) {
|
||||
const error = new Error("Time entry not found") as Error & {
|
||||
statusCode: number;
|
||||
};
|
||||
error.statusCode = 404;
|
||||
throw error;
|
||||
throw new NotFoundError("Time entry not found");
|
||||
}
|
||||
|
||||
await prisma.timeEntry.delete({
|
||||
|
||||
@@ -1,5 +1,14 @@
|
||||
import { prisma } from '../prisma/client';
|
||||
import type { StartTimerInput, UpdateTimerInput, StopTimerInput } from '../types';
|
||||
import { prisma } from "../prisma/client";
|
||||
import {
|
||||
NotFoundError,
|
||||
BadRequestError,
|
||||
ConflictError,
|
||||
} from "../errors/AppError";
|
||||
import type {
|
||||
StartTimerInput,
|
||||
UpdateTimerInput,
|
||||
StopTimerInput,
|
||||
} from "../types";
|
||||
|
||||
export class TimerService {
|
||||
async getOngoingTimer(userId: string) {
|
||||
@@ -27,9 +36,7 @@ export class TimerService {
|
||||
// Check if user already has an ongoing timer
|
||||
const existingTimer = await this.getOngoingTimer(userId);
|
||||
if (existingTimer) {
|
||||
const error = new Error('Timer is already running') as Error & { statusCode: number };
|
||||
error.statusCode = 400;
|
||||
throw error;
|
||||
throw new BadRequestError("Timer is already running");
|
||||
}
|
||||
|
||||
// If projectId provided, verify it belongs to the user
|
||||
@@ -40,9 +47,7 @@ export class TimerService {
|
||||
});
|
||||
|
||||
if (!project) {
|
||||
const error = new Error('Project not found') as Error & { statusCode: number };
|
||||
error.statusCode = 404;
|
||||
throw error;
|
||||
throw new NotFoundError("Project not found");
|
||||
}
|
||||
|
||||
projectId = data.projectId;
|
||||
@@ -75,9 +80,7 @@ export class TimerService {
|
||||
async update(userId: string, data: UpdateTimerInput) {
|
||||
const timer = await this.getOngoingTimer(userId);
|
||||
if (!timer) {
|
||||
const error = new Error('No timer is running') as Error & { statusCode: number };
|
||||
error.statusCode = 404;
|
||||
throw error;
|
||||
throw new NotFoundError("No timer is running");
|
||||
}
|
||||
|
||||
// If projectId is explicitly null, clear the project
|
||||
@@ -92,9 +95,7 @@ export class TimerService {
|
||||
});
|
||||
|
||||
if (!project) {
|
||||
const error = new Error('Project not found') as Error & { statusCode: number };
|
||||
error.statusCode = 404;
|
||||
throw error;
|
||||
throw new NotFoundError("Project not found");
|
||||
}
|
||||
|
||||
projectId = data.projectId;
|
||||
@@ -124,14 +125,12 @@ export class TimerService {
|
||||
async stop(userId: string, data?: StopTimerInput) {
|
||||
const timer = await this.getOngoingTimer(userId);
|
||||
if (!timer) {
|
||||
const error = new Error('No timer is running') as Error & { statusCode: number };
|
||||
error.statusCode = 404;
|
||||
throw error;
|
||||
throw new NotFoundError("No timer is running");
|
||||
}
|
||||
|
||||
// Determine which project to use
|
||||
let projectId = timer.projectId;
|
||||
|
||||
|
||||
// If data.projectId is provided, use it instead
|
||||
if (data?.projectId) {
|
||||
const project = await prisma.project.findFirst({
|
||||
@@ -139,9 +138,7 @@ export class TimerService {
|
||||
});
|
||||
|
||||
if (!project) {
|
||||
const error = new Error('Project not found') as Error & { statusCode: number };
|
||||
error.statusCode = 404;
|
||||
throw error;
|
||||
throw new NotFoundError("Project not found");
|
||||
}
|
||||
|
||||
projectId = data.projectId;
|
||||
@@ -149,20 +146,24 @@ export class TimerService {
|
||||
|
||||
// If no project is selected, throw error requiring project selection
|
||||
if (!projectId) {
|
||||
const error = new Error('Please select a project before stopping the timer') as Error & { statusCode: number };
|
||||
error.statusCode = 400;
|
||||
throw error;
|
||||
throw new BadRequestError(
|
||||
"Please select a project before stopping the timer",
|
||||
);
|
||||
}
|
||||
|
||||
const endTime = new Date();
|
||||
const startTime = timer.startTime;
|
||||
|
||||
// Check for overlapping entries
|
||||
const hasOverlap = await this.hasOverlappingEntries(userId, startTime, endTime);
|
||||
const hasOverlap = await this.hasOverlappingEntries(
|
||||
userId,
|
||||
startTime,
|
||||
endTime,
|
||||
);
|
||||
if (hasOverlap) {
|
||||
const error = new Error('This time entry overlaps with an existing entry') as Error & { statusCode: number };
|
||||
error.statusCode = 400;
|
||||
throw error;
|
||||
throw new ConflictError(
|
||||
"This time entry overlaps with an existing entry",
|
||||
);
|
||||
}
|
||||
|
||||
// Delete ongoing timer and create time entry in a transaction
|
||||
@@ -201,16 +202,14 @@ export class TimerService {
|
||||
private async hasOverlappingEntries(
|
||||
userId: string,
|
||||
startTime: Date,
|
||||
endTime: Date
|
||||
endTime: Date,
|
||||
): Promise<boolean> {
|
||||
const count = await prisma.timeEntry.count({
|
||||
where: {
|
||||
userId,
|
||||
OR: [
|
||||
{ startTime: { lt: endTime }, endTime: { gt: startTime } },
|
||||
],
|
||||
OR: [{ startTime: { lt: endTime }, endTime: { gt: startTime } }],
|
||||
},
|
||||
});
|
||||
return count > 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user