feat: enhance GalleryShowcase with photo count and tags

- Added photo count and tags display to the GalleryShowcase component, improving the information presented to users.
- Updated the FeaturedGalleriesService to fetch photo counts and popular tags for each tenant, ensuring accurate data representation.

Signed-off-by: Innei <tukon479@gmail.com>
This commit is contained in:
Innei
2025-11-24 21:08:46 +08:00
parent 5a98b43544
commit 471c1b11ae
2 changed files with 86 additions and 1 deletions

View File

@@ -18,6 +18,8 @@ interface FeaturedGallery {
domain: string | null
description: string | null
author: FeaturedGalleryAuthor | null
photoCount: number
tags: string[]
createdAt: string
}
@@ -206,6 +208,38 @@ export const GalleryShowcase = () => {
</p>
)}
{/* Photo Count & Tags */}
<div className="mb-4 space-y-2">
{gallery.photoCount > 0 && (
<div className="flex items-center gap-2 text-xs text-white/60">
<i className="i-lucide-image size-3.5" />
<span>
{gallery.photoCount}{' '}
<span>
{gallery.photoCount === 1 ? 'photo' : 'photos'}
</span>
</span>
</div>
)}
{gallery.tags.length > 0 && (
<div className="flex flex-wrap gap-1.5">
{gallery.tags.slice(0, 4).map((tag) => (
<span
key={tag}
className="rounded-full border border-white/10 bg-white/5 px-2 py-0.5 text-xs text-white/70"
>
{tag}
</span>
))}
{gallery.tags.length > 4 && (
<span className="rounded-full border border-white/10 bg-white/5 px-2 py-0.5 text-xs text-white/50">
+{gallery.tags.length - 4}
</span>
)}
</div>
)}
</div>
{/* Divider */}
<div className="mb-4 h-px w-full bg-linear-to-r from-transparent via-white/30 to-transparent opacity-50" />

View File

@@ -1,4 +1,4 @@
import { authUsers, settings, tenantDomains } from '@afilmory/db'
import { authUsers, photoAssets, settings, tenantDomains } from '@afilmory/db'
import { DbAccessor } from 'core/database/database.provider'
import { normalizeDate } from 'core/helpers/normalize.helper'
import { and, asc, eq, inArray, sql } from 'drizzle-orm'
@@ -60,6 +60,48 @@ export class FeaturedGalleriesService {
.from(tenantDomains)
.where(and(inArray(tenantDomains.tenantId, tenantIds), eq(tenantDomains.status, 'verified')))
// Fetch photo counts for all tenants (only synced/conflict photos)
const photoCounts = await db
.select({
tenantId: photoAssets.tenantId,
count: sql<number>`count(*)::int`,
})
.from(photoAssets)
.where(and(inArray(photoAssets.tenantId, tenantIds), inArray(photoAssets.syncStatus, ['synced', 'conflict'])))
.groupBy(photoAssets.tenantId)
// Fetch popular tags for all tenants
// This query extracts tags from manifest JSONB and counts them per tenant
// Process tags per tenant to ensure proper SQL parameterization
const tagMap = new Map<string, string[]>()
for (const tenantId of tenantIds) {
const tagsResult = await db.execute<{ tag: string | null; count: number | null }>(sql`
select tag, count(*)::int as count
from (
select nullif(trim(jsonb_array_elements_text(${photoAssets.manifest}->'data'->'tags')), '') as tag
from ${photoAssets}
where ${photoAssets.tenantId} = ${tenantId}
and ${photoAssets.syncStatus} in ('synced', 'conflict')
) as tag_items
where tag is not null and tag != ''
group by tag
order by count desc
limit 5
`)
const tags = tagsResult.rows
.map((row) => {
const tag = row.tag?.trim()
return tag && tag.length > 0 ? tag : null
})
.filter((tag): tag is string => tag !== null)
if (tags.length > 0) {
tagMap.set(tenantId, tags)
}
}
// Build maps for quick lookup
const settingsMap = new Map<string, Map<string, string | null>>()
for (const setting of siteSettings) {
@@ -87,12 +129,19 @@ export class FeaturedGalleriesService {
}
}
const photoCountMap = new Map<string, number>()
for (const count of photoCounts) {
photoCountMap.set(count.tenantId, Number(count.count ?? 0))
}
// Build response
const featuredGalleries = validTenants.map((aggregate) => {
const { tenant } = aggregate
const tenantSettings = settingsMap.get(tenant.id) ?? new Map()
const author = authorMap.get(tenant.id)
const domain = domainMap.get(tenant.id)
const photoCount = photoCountMap.get(tenant.id) ?? 0
const tags = tagMap.get(tenant.id) ?? []
return {
id: tenant.id,
@@ -106,6 +155,8 @@ export class FeaturedGalleriesService {
avatar: author.avatar,
}
: null,
photoCount,
tags,
createdAt: normalizeDate(tenant.createdAt) ?? tenant.createdAt,
}
})