feat(backend): Suche und Filter (Modul 14)
SearchModule mit GET /search?q= über Bienenstände, Völker, Königinnen, Aufgaben und Honigchargen. Kombinierbare Filter (Status, Standort, Bienenstand) in den bestehenden List-Endpunkten.
This commit is contained in:
1 parent
55b9bc2587
commit
146b56d683
15 files changed
+337
-27
No files matched your search
@@ -0,0 +1,15 @@
|
||||
import { Controller, Get, Query } from '@nestjs/common';
|
||||
import { ApiBearerAuth, ApiTags } from '@nestjs/swagger';
|
||||
import { SearchService } from './search.service';
|
||||
|
||||
@ApiTags('search')
|
||||
@ApiBearerAuth()
|
||||
@Controller('search')
|
||||
export class SearchController {
|
||||
constructor(private readonly searchService: SearchService) {}
|
||||
|
||||
@Get()
|
||||
search(@Query('q') q?: string) {
|
||||
return this.searchService.search(q ?? '');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { SearchController } from './search.controller';
|
||||
import { SearchService } from './search.service';
|
||||
|
||||
@Module({
|
||||
controllers: [SearchController],
|
||||
providers: [SearchService],
|
||||
})
|
||||
export class SearchModule {}
|
||||
@@ -0,0 +1,48 @@
|
||||
import { SearchService } from './search.service';
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
|
||||
describe('SearchService', () => {
|
||||
let service: SearchService;
|
||||
let prisma: {
|
||||
apiary: { findMany: jest.Mock };
|
||||
colony: { findMany: jest.Mock };
|
||||
queen: { findMany: jest.Mock };
|
||||
task: { findMany: jest.Mock };
|
||||
honeyBatch: { findMany: jest.Mock };
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
prisma = {
|
||||
apiary: { findMany: jest.fn().mockResolvedValue([]) },
|
||||
colony: { findMany: jest.fn().mockResolvedValue([]) },
|
||||
queen: { findMany: jest.fn().mockResolvedValue([]) },
|
||||
task: { findMany: jest.fn().mockResolvedValue([]) },
|
||||
honeyBatch: { findMany: jest.fn().mockResolvedValue([]) },
|
||||
};
|
||||
service = new SearchService(prisma as unknown as PrismaService);
|
||||
});
|
||||
|
||||
it('returns an empty array for a blank query', async () => {
|
||||
const results = await service.search(' ');
|
||||
expect(results).toEqual([]);
|
||||
expect(prisma.apiary.findMany).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('combines matches from every entity type', async () => {
|
||||
prisma.colony.findMany.mockResolvedValue([
|
||||
{ id: 'colony-1', code: 'V-001' },
|
||||
]);
|
||||
prisma.apiary.findMany.mockResolvedValue([
|
||||
{ id: 'apiary-1', name: 'Heimstand' },
|
||||
]);
|
||||
|
||||
const results = await service.search('V-001');
|
||||
|
||||
expect(results).toEqual(
|
||||
expect.arrayContaining([
|
||||
{ type: 'Colony', id: 'colony-1', label: 'V-001' },
|
||||
{ type: 'Apiary', id: 'apiary-1', label: 'Heimstand' },
|
||||
]),
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,75 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
|
||||
export interface SearchResult {
|
||||
type: 'Apiary' | 'Colony' | 'Queen' | 'Task' | 'HoneyBatch';
|
||||
id: string;
|
||||
label: string;
|
||||
}
|
||||
|
||||
const RESULT_LIMIT = 10;
|
||||
|
||||
@Injectable()
|
||||
export class SearchService {
|
||||
constructor(private readonly prisma: PrismaService) {}
|
||||
|
||||
async search(q: string): Promise<SearchResult[]> {
|
||||
const term = q.trim();
|
||||
if (term.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const [apiaries, colonies, queens, tasks, honeyBatches] = await Promise.all(
|
||||
[
|
||||
this.prisma.apiary.findMany({
|
||||
where: { name: { contains: term, mode: 'insensitive' } },
|
||||
take: RESULT_LIMIT,
|
||||
}),
|
||||
this.prisma.colony.findMany({
|
||||
where: { code: { contains: term, mode: 'insensitive' } },
|
||||
take: RESULT_LIMIT,
|
||||
}),
|
||||
this.prisma.queen.findMany({
|
||||
where: { code: { contains: term, mode: 'insensitive' } },
|
||||
take: RESULT_LIMIT,
|
||||
}),
|
||||
this.prisma.task.findMany({
|
||||
where: { title: { contains: term, mode: 'insensitive' } },
|
||||
take: RESULT_LIMIT,
|
||||
}),
|
||||
this.prisma.honeyBatch.findMany({
|
||||
where: { batchNumber: { contains: term, mode: 'insensitive' } },
|
||||
take: RESULT_LIMIT,
|
||||
}),
|
||||
],
|
||||
);
|
||||
|
||||
return [
|
||||
...apiaries.map((apiary) => ({
|
||||
type: 'Apiary' as const,
|
||||
id: apiary.id,
|
||||
label: apiary.name,
|
||||
})),
|
||||
...colonies.map((colony) => ({
|
||||
type: 'Colony' as const,
|
||||
id: colony.id,
|
||||
label: colony.code,
|
||||
})),
|
||||
...queens.map((queen) => ({
|
||||
type: 'Queen' as const,
|
||||
id: queen.id,
|
||||
label: queen.code,
|
||||
})),
|
||||
...tasks.map((task) => ({
|
||||
type: 'Task' as const,
|
||||
id: task.id,
|
||||
label: task.title,
|
||||
})),
|
||||
...honeyBatches.map((honeyBatch) => ({
|
||||
type: 'HoneyBatch' as const,
|
||||
id: honeyBatch.id,
|
||||
label: honeyBatch.batchNumber,
|
||||
})),
|
||||
];
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user