From 3acd366fdab06d20ae515c5a92f6c6b91e3d599a Mon Sep 17 00:00:00 2001 From: "soroush.asadi" Date: Tue, 2 Jun 2026 22:17:14 +0330 Subject: [PATCH] feat(admin): music library admin + fix CRM analytics UTC MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - /admin/music: list / upload / delete studio audio tracks (content-svc GET/POST/DELETE /v1/music) — fills the legacy music-library gap - fix: CRM analytics coerced query-bound dates to UTC (Npgsql timestamptz rejects Kind=Unspecified) — endpoint was returning 400 Co-Authored-By: Claude Opus 4.8 --- messages/en.json | 3 +- messages/fa.json | 3 +- .../Application/Services/AdminService.cs | 5 ++-- src/app/[locale]/admin/layout.tsx | 1 + src/app/[locale]/admin/music/page.tsx | 8 ++++++ src/components/admin/admin-resources.tsx | 28 +++++++++++++++++++ 6 files changed, 44 insertions(+), 4 deletions(-) create mode 100644 src/app/[locale]/admin/music/page.tsx diff --git a/messages/en.json b/messages/en.json index 47f81e2..03571a1 100644 --- a/messages/en.json +++ b/messages/en.json @@ -328,7 +328,8 @@ "marketing": "Marketing", "crm": "CRM", "ranking": "Ranking", - "stats": "Dashboard" + "stats": "Dashboard", + "music": "Music" }, "appAdminNodesPage": { "title": "Render Nodes", diff --git a/messages/fa.json b/messages/fa.json index 5acae1e..1999fe1 100644 --- a/messages/fa.json +++ b/messages/fa.json @@ -328,7 +328,8 @@ "marketing": "بازاریابی", "crm": "مدیریت مشتریان", "ranking": "رتبه‌بندی", - "stats": "داشبورد" + "stats": "داشبورد", + "music": "موسیقی" }, "appAdminNodesPage": { "title": "نودهای رندر", diff --git a/services/identity/FlatRender.IdentitySvc/Application/Services/AdminService.cs b/services/identity/FlatRender.IdentitySvc/Application/Services/AdminService.cs index 3bdc5cb..7d579cb 100644 --- a/services/identity/FlatRender.IdentitySvc/Application/Services/AdminService.cs +++ b/services/identity/FlatRender.IdentitySvc/Application/Services/AdminService.cs @@ -13,8 +13,9 @@ public class AdminService(IdentityDbContext db) public async Task GetCrmAnalyticsAsync(Guid tenantId, DateTime start, DateTime end) { - start = start.Date; - end = end.Date.AddDays(1); // inclusive of the end day + // Query-bound dates arrive Kind=Unspecified; Npgsql requires UTC for timestamptz. + start = DateTime.SpecifyKind(start.Date, DateTimeKind.Utc); + end = DateTime.SpecifyKind(end.Date.AddDays(1), DateTimeKind.Utc); // inclusive of the end day if ((end - start).TotalDays > 366) end = start.AddDays(366); var signups = await db.Users diff --git a/src/app/[locale]/admin/layout.tsx b/src/app/[locale]/admin/layout.tsx index 365ec58..98475e3 100644 --- a/src/app/[locale]/admin/layout.tsx +++ b/src/app/[locale]/admin/layout.tsx @@ -22,6 +22,7 @@ export default async function AdminLayout({ { href: "/admin/ranking", label: t("ranking") }, { href: "/admin/tags", label: t("tags") }, { href: "/admin/fonts", label: t("fonts") }, + { href: "/admin/music", label: t("music") }, { href: "/admin/blogs", label: t("blogs") }, { href: "/admin/slides", label: t("slides") }, { href: "/admin/files", label: t("media") }, diff --git a/src/app/[locale]/admin/music/page.tsx b/src/app/[locale]/admin/music/page.tsx new file mode 100644 index 0000000..f139600 --- /dev/null +++ b/src/app/[locale]/admin/music/page.tsx @@ -0,0 +1,8 @@ +"use client"; + +import { AdminResource } from "@/components/admin/AdminResource"; +import { musicConfig } from "@/components/admin/admin-resources"; + +export default function Page() { + return ; +} diff --git a/src/components/admin/admin-resources.tsx b/src/components/admin/admin-resources.tsx index f2a2678..a1da783 100644 --- a/src/components/admin/admin-resources.tsx +++ b/src/components/admin/admin-resources.tsx @@ -109,6 +109,34 @@ export const fontsConfig: ResourceConfig = { ], }; +export const musicConfig: ResourceConfig = { + title: "Music", + description: "Audio tracks available in the studio music library.", + basePath: "music", + listKey: "items", + canCreate: true, + canEdit: false, + canDelete: true, + columns: [ + { key: "name", label: "Name" }, + { key: "genre", label: "Genre" }, + { key: "mood", label: "Mood" }, + { key: "duration_sec", label: "Duration (s)" }, + { key: "is_premium", label: "Premium", render: (r) => badge(!!r.is_premium, "premium", "free") }, + ], + fields: [ + { key: "name", label: "Name", required: true }, + { key: "caption", label: "Caption" }, + { key: "keywords", label: "Keywords" }, + { key: "url", label: "Audio file", type: "file", required: true }, + { key: "duration_sec", label: "Duration (seconds)", type: "number", required: true }, + { key: "bpm", label: "BPM", type: "number" }, + { key: "genre", label: "Genre" }, + { key: "mood", label: "Mood" }, + { key: "is_premium", label: "Premium", type: "checkbox" }, + ], +}; + export const blogsConfig: ResourceConfig = { title: "Blog Posts", description: "CMS articles (also created by the AI SEO generator).",