mirror of
https://github.com/Afilmory/afilmory
synced 2026-02-01 22:48:17 +00:00
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:
@@ -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" />
|
||||
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user