favorites_model.js 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144
  1. const mediaFavorites = require("../backend/media-favorites");
  2. const safeArr = (v) => (Array.isArray(v) ? v : []);
  3. const safeText = (v) => String(v || "").trim();
  4. const getFn = (obj, names) => {
  5. for (const n of names) {
  6. if (obj && typeof obj[n] === "function") return obj[n].bind(obj);
  7. }
  8. return null;
  9. };
  10. const toTs = (d) => {
  11. const t = Date.parse(String(d || ""));
  12. return Number.isFinite(t) ? t : 0;
  13. };
  14. module.exports = ({ audiosModel, bookmarksModel, documentsModel, imagesModel, videosModel }) => {
  15. const kindConfig = {
  16. audios: {
  17. base: "/audios/",
  18. getById: getFn(audiosModel, ["getAudioById", "getById"])
  19. },
  20. bookmarks: {
  21. base: "/bookmarks/",
  22. getById: getFn(bookmarksModel, ["getBookmarkById", "getById"])
  23. },
  24. documents: {
  25. base: "/documents/",
  26. getById: getFn(documentsModel, ["getDocumentById", "getById"])
  27. },
  28. images: {
  29. base: "/images/",
  30. getById: getFn(imagesModel, ["getImageById", "getById"])
  31. },
  32. videos: {
  33. base: "/videos/",
  34. getById: getFn(videosModel, ["getVideoById", "getById"])
  35. }
  36. };
  37. const kindOrder = ["audios", "bookmarks", "documents", "images", "videos"];
  38. const hydrateKind = async (kind, ids) => {
  39. const cfg = kindConfig[kind];
  40. if (!cfg?.getById) return [];
  41. const out = await Promise.all(
  42. safeArr(ids).map(async (favId) => {
  43. const id = safeText(favId);
  44. if (!id) return null;
  45. try {
  46. const obj = await cfg.getById(id);
  47. const viewId = safeText(obj?.key || obj?.id || id);
  48. return {
  49. kind,
  50. favId: id,
  51. viewHref: `${cfg.base}${encodeURIComponent(viewId)}`,
  52. title: safeText(obj?.title) || safeText(obj?.name) || safeText(obj?.category) || safeText(obj?.url) || "",
  53. description: safeText(obj?.description) || "",
  54. tags: safeArr(obj?.tags),
  55. author: safeText(obj?.author || obj?.organizer || obj?.seller || obj?.from || ""),
  56. createdAt: obj?.createdAt || null,
  57. updatedAt: obj?.updatedAt || null,
  58. url: obj?.url || null,
  59. category: obj?.category || null
  60. };
  61. } catch {
  62. return null;
  63. }
  64. })
  65. );
  66. return out.filter(Boolean);
  67. };
  68. const loadAll = async () => {
  69. const sets = await Promise.all(kindOrder.map((k) => mediaFavorites.getFavoriteSet(k)));
  70. const idsByKind = {};
  71. kindOrder.forEach((k, i) => {
  72. idsByKind[k] = Array.from(sets[i] || []);
  73. });
  74. const hydrated = await Promise.all(kindOrder.map((k) => hydrateKind(k, idsByKind[k])));
  75. const byKind = {};
  76. kindOrder.forEach((k, i) => {
  77. byKind[k] = hydrated[i] || [];
  78. });
  79. const flat = kindOrder.flatMap((k) => byKind[k]);
  80. const counts = {
  81. audios: byKind.audios.length,
  82. bookmarks: byKind.bookmarks.length,
  83. documents: byKind.documents.length,
  84. images: byKind.images.length,
  85. videos: byKind.videos.length,
  86. all: flat.length
  87. };
  88. const recentFlat = flat
  89. .slice()
  90. .sort((a, b) => (toTs(b.updatedAt) || toTs(b.createdAt)) - (toTs(a.updatedAt) || toTs(a.createdAt)));
  91. return { byKind, flat, recentFlat, counts };
  92. };
  93. return {
  94. async listAll(opts = {}) {
  95. const filter = safeText(opts.filter || "all").toLowerCase();
  96. const { byKind, recentFlat, counts } = await loadAll();
  97. if (filter === "recent") {
  98. return { items: recentFlat, counts };
  99. }
  100. if (kindOrder.includes(filter)) {
  101. const items = byKind[filter] || [];
  102. const sorted = items
  103. .slice()
  104. .sort((a, b) => (toTs(b.updatedAt) || toTs(b.createdAt)) - (toTs(a.updatedAt) || toTs(a.createdAt)));
  105. return { items: sorted, counts };
  106. }
  107. const grouped = kindOrder.flatMap((k) =>
  108. (byKind[k] || [])
  109. .slice()
  110. .sort((a, b) => (toTs(b.updatedAt) || toTs(b.createdAt)) - (toTs(a.updatedAt) || toTs(a.createdAt)))
  111. );
  112. return { items: grouped, counts };
  113. },
  114. async removeFavorite(kind, id) {
  115. const k = safeText(kind);
  116. const favId = safeText(id);
  117. if (!k || !favId) return;
  118. await mediaFavorites.removeFavorite(k, favId);
  119. }
  120. };
  121. };