Set up backend API server with Express and Prisma
- Initialize Node.js/Express backend project structure - Configure TypeScript for backend development - Set up Prisma ORM with MySQL database schema - Implement authentication middleware with Clerk - Create photo upload endpoint with EXIF extraction - Build report CRUD endpoints (create, read, delete) - Add file upload handling with Multer - Configure CORS and security middleware (helmet) - Set up error handling and validation - Create development environment configuration Task 21 complete: Backend foundation ready for report submissions
This commit is contained in:
168
server/src/services/reportService.ts
Normal file
168
server/src/services/reportService.ts
Normal file
@@ -0,0 +1,168 @@
|
||||
import prisma from '../config/database';
|
||||
import { Prisma } from '@prisma/client';
|
||||
|
||||
export interface CreateReportData {
|
||||
userId: string;
|
||||
latitude: number;
|
||||
longitude: number;
|
||||
locationSource: 'exif' | 'gps' | 'manual';
|
||||
photoUrls: string[];
|
||||
photoTimestamp?: Date;
|
||||
litterType: string;
|
||||
quantity: string;
|
||||
description?: string;
|
||||
environmentalConcerns?: Array<{
|
||||
type: string;
|
||||
severity: string;
|
||||
description: string;
|
||||
}>;
|
||||
}
|
||||
|
||||
export class ReportService {
|
||||
async createReport(data: CreateReportData) {
|
||||
const report = await prisma.report.create({
|
||||
data: {
|
||||
userId: data.userId,
|
||||
latitude: new Prisma.Decimal(data.latitude),
|
||||
longitude: new Prisma.Decimal(data.longitude),
|
||||
locationSource: data.locationSource,
|
||||
photoUrls: data.photoUrls,
|
||||
photoTimestamp: data.photoTimestamp,
|
||||
litterType: data.litterType,
|
||||
quantity: data.quantity,
|
||||
description: data.description,
|
||||
environmentalConcerns: data.environmentalConcerns
|
||||
? {
|
||||
create: data.environmentalConcerns.map((concern) => ({
|
||||
concernType: concern.type,
|
||||
severity: concern.severity,
|
||||
description: concern.description,
|
||||
})),
|
||||
}
|
||||
: undefined,
|
||||
},
|
||||
include: {
|
||||
user: {
|
||||
select: {
|
||||
id: true,
|
||||
username: true,
|
||||
avatarUrl: true,
|
||||
},
|
||||
},
|
||||
environmentalConcerns: true,
|
||||
},
|
||||
});
|
||||
|
||||
return report;
|
||||
}
|
||||
|
||||
async getReports(filters?: {
|
||||
startDate?: Date;
|
||||
endDate?: Date;
|
||||
litterTypes?: string[];
|
||||
quantities?: string[];
|
||||
verificationStatus?: string[];
|
||||
bounds?: { north: number; south: number; east: number; west: number };
|
||||
}) {
|
||||
const where: any = {};
|
||||
|
||||
if (filters?.startDate || filters?.endDate) {
|
||||
where.createdAt = {};
|
||||
if (filters.startDate) where.createdAt.gte = filters.startDate;
|
||||
if (filters.endDate) where.createdAt.lte = filters.endDate;
|
||||
}
|
||||
|
||||
if (filters?.litterTypes?.length) {
|
||||
where.litterType = { in: filters.litterTypes };
|
||||
}
|
||||
|
||||
if (filters?.quantities?.length) {
|
||||
where.quantity = { in: filters.quantities };
|
||||
}
|
||||
|
||||
if (filters?.verificationStatus?.length) {
|
||||
where.verificationStatus = { in: filters.verificationStatus };
|
||||
}
|
||||
|
||||
if (filters?.bounds) {
|
||||
where.latitude = {
|
||||
gte: new Prisma.Decimal(filters.bounds.south),
|
||||
lte: new Prisma.Decimal(filters.bounds.north),
|
||||
};
|
||||
where.longitude = {
|
||||
gte: new Prisma.Decimal(filters.bounds.west),
|
||||
lte: new Prisma.Decimal(filters.bounds.east),
|
||||
};
|
||||
}
|
||||
|
||||
const reports = await prisma.report.findMany({
|
||||
where,
|
||||
include: {
|
||||
user: {
|
||||
select: {
|
||||
id: true,
|
||||
username: true,
|
||||
avatarUrl: true,
|
||||
},
|
||||
},
|
||||
environmentalConcerns: true,
|
||||
verifications: true,
|
||||
},
|
||||
orderBy: {
|
||||
createdAt: 'desc',
|
||||
},
|
||||
});
|
||||
|
||||
return reports;
|
||||
}
|
||||
|
||||
async getReportById(id: string) {
|
||||
const report = await prisma.report.findUnique({
|
||||
where: { id },
|
||||
include: {
|
||||
user: {
|
||||
select: {
|
||||
id: true,
|
||||
username: true,
|
||||
avatarUrl: true,
|
||||
},
|
||||
},
|
||||
environmentalConcerns: true,
|
||||
verifications: {
|
||||
include: {
|
||||
user: {
|
||||
select: {
|
||||
id: true,
|
||||
username: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
return report;
|
||||
}
|
||||
|
||||
async deleteReport(id: string, userId: string) {
|
||||
// Verify ownership
|
||||
const report = await prisma.report.findUnique({
|
||||
where: { id },
|
||||
select: { userId: true },
|
||||
});
|
||||
|
||||
if (!report) {
|
||||
throw new Error('Report not found');
|
||||
}
|
||||
|
||||
if (report.userId !== userId) {
|
||||
throw new Error('Unauthorized to delete this report');
|
||||
}
|
||||
|
||||
await prisma.report.delete({
|
||||
where: { id },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export const reportService = new ReportService();
|
||||
Reference in New Issue
Block a user