From da5f18bf923ee4d96705eda2b6d86f4f71a1b2b3 Mon Sep 17 00:00:00 2001 From: Mathis HERRIOT <197931332+0x485254@users.noreply.github.com> Date: Thu, 8 Jan 2026 15:27:11 +0100 Subject: [PATCH] feat: implement TagsModule with service, controller, and endpoints Added TagsModule to manage tags, including a TagsService for querying and sorting by popularity or recency. Created TagsController with endpoint to retrieve paginated and searchable tag data. Integrated with database and relevant schemas. --- backend/src/tags/tags.controller.ts | 23 +++++++++++++ backend/src/tags/tags.module.ts | 12 +++++++ backend/src/tags/tags.service.ts | 53 +++++++++++++++++++++++++++++ 3 files changed, 88 insertions(+) create mode 100644 backend/src/tags/tags.controller.ts create mode 100644 backend/src/tags/tags.module.ts create mode 100644 backend/src/tags/tags.service.ts diff --git a/backend/src/tags/tags.controller.ts b/backend/src/tags/tags.controller.ts new file mode 100644 index 0000000..95bdd56 --- /dev/null +++ b/backend/src/tags/tags.controller.ts @@ -0,0 +1,23 @@ +import { + Controller, + DefaultValuePipe, + Get, + ParseIntPipe, + Query, +} from "@nestjs/common"; +import { TagsService } from "./tags.service"; + +@Controller("tags") +export class TagsController { + constructor(private readonly tagsService: TagsService) {} + + @Get() + findAll( + @Query("limit", new DefaultValuePipe(10), ParseIntPipe) limit: number, + @Query("offset", new DefaultValuePipe(0), ParseIntPipe) offset: number, + @Query("query") query?: string, + @Query("sort") sort?: "popular" | "recent", + ) { + return this.tagsService.findAll({ limit, offset, query, sortBy: sort }); + } +} diff --git a/backend/src/tags/tags.module.ts b/backend/src/tags/tags.module.ts new file mode 100644 index 0000000..7b3438a --- /dev/null +++ b/backend/src/tags/tags.module.ts @@ -0,0 +1,12 @@ +import { Module } from "@nestjs/common"; +import { DatabaseModule } from "../database/database.module"; +import { TagsController } from "./tags.controller"; +import { TagsService } from "./tags.service"; + +@Module({ + imports: [DatabaseModule], + controllers: [TagsController], + providers: [TagsService], + exports: [TagsService], +}) +export class TagsModule {} diff --git a/backend/src/tags/tags.service.ts b/backend/src/tags/tags.service.ts new file mode 100644 index 0000000..cbb4d09 --- /dev/null +++ b/backend/src/tags/tags.service.ts @@ -0,0 +1,53 @@ +import { Injectable } from "@nestjs/common"; +import { desc, eq, ilike, sql } from "drizzle-orm"; +import { DatabaseService } from "../database/database.service"; +import { contentsToTags, tags } from "../database/schemas"; + +@Injectable() +export class TagsService { + constructor(private readonly databaseService: DatabaseService) {} + + async findAll(options: { + limit: number; + offset: number; + query?: string; + sortBy?: "popular" | "recent"; + }) { + const { limit, offset, query, sortBy } = options; + + let whereClause = sql`1=1`; + if (query) { + whereClause = ilike(tags.name, `%${query}%`); + } + + // Pour la popularité, on compte le nombre d'associations dans contentsToTags + if (sortBy === "popular") { + const data = await this.databaseService.db + .select({ + id: tags.id, + name: tags.name, + slug: tags.slug, + count: sql`count(${contentsToTags.contentId})`.as("usage_count"), + }) + .from(tags) + .leftJoin(contentsToTags, eq(tags.id, contentsToTags.tagId)) + .where(whereClause) + .groupBy(tags.id) + .orderBy(desc(sql`usage_count`)) + .limit(limit) + .offset(offset); + + return data; + } + + const data = await this.databaseService.db + .select() + .from(tags) + .where(whereClause) + .orderBy(sortBy === "recent" ? desc(tags.createdAt) : desc(tags.name)) + .limit(limit) + .offset(offset); + + return data; + } +}