feat(contents): enhance user-specific data handling and admin content management

Integrate user-specific fields (`isLiked`, `favoritesCount`) in content APIs and improve `ContentCard` through reactive updates. Add admin-only content deletion support. Refactor services and repository to enrich responses with additional data (author details, tags).
This commit is contained in:
Mathis HERRIOT
2026-01-14 21:43:44 +01:00
parent fb7ddde42e
commit d7c2a965a0
6 changed files with 152 additions and 14 deletions

View File

@@ -135,11 +135,19 @@ export class ContentsRepository {
fileSize: contents.fileSize,
views: contents.views,
usageCount: contents.usageCount,
favoritesCount: sql<number>`(SELECT count(*) FROM ${favorites} WHERE ${favorites.contentId} = ${contents.id})`.mapWith(
Number,
),
isLiked: userId
? sql<boolean>`EXISTS(SELECT 1 FROM ${favorites} WHERE ${favorites.contentId} = ${contents.id} AND ${favorites.userId} = ${userId})`
: sql<boolean>`false`,
createdAt: contents.createdAt,
updatedAt: contents.updatedAt,
author: {
id: users.uuid,
username: users.username,
displayName: users.displayName,
avatarUrl: users.avatarUrl,
},
category: {
id: categories.id,
@@ -215,7 +223,7 @@ export class ContentsRepository {
});
}
async findOne(idOrSlug: string) {
async findOne(idOrSlug: string, userId?: string) {
const [result] = await this.databaseService.db
.select({
id: contents.id,
@@ -227,11 +235,30 @@ export class ContentsRepository {
fileSize: contents.fileSize,
views: contents.views,
usageCount: contents.usageCount,
favoritesCount: sql<number>`(SELECT count(*) FROM ${favorites} WHERE ${favorites.contentId} = ${contents.id})`.mapWith(
Number,
),
isLiked: userId
? sql<boolean>`EXISTS(SELECT 1 FROM ${favorites} WHERE ${favorites.contentId} = ${contents.id} AND ${favorites.userId} = ${userId})`
: sql<boolean>`false`,
createdAt: contents.createdAt,
updatedAt: contents.updatedAt,
userId: contents.userId,
author: {
id: users.uuid,
username: users.username,
displayName: users.displayName,
avatarUrl: users.avatarUrl,
},
category: {
id: categories.id,
name: categories.name,
slug: categories.slug,
},
})
.from(contents)
.leftJoin(users, eq(contents.userId, users.uuid))
.leftJoin(categories, eq(contents.categoryId, categories.id))
.where(
and(
isNull(contents.deletedAt),
@@ -240,7 +267,20 @@ export class ContentsRepository {
)
.limit(1);
return result;
if (!result) return null;
const tagsForContent = await this.databaseService.db
.select({
name: tags.name,
})
.from(contentsToTags)
.innerJoin(tags, eq(contentsToTags.tagId, tags.id))
.where(eq(contentsToTags.contentId, result.id));
return {
...result,
tags: tagsForContent.map((t) => t.name),
};
}
async count(options: {
@@ -353,6 +393,15 @@ export class ContentsRepository {
return deleted;
}
async softDeleteAdmin(id: string) {
const [deleted] = await this.databaseService.db
.update(contents)
.set({ deletedAt: new Date() })
.where(eq(contents.id, id))
.returning();
return deleted;
}
async findBySlug(slug: string) {
const [result] = await this.databaseService.db
.select()