tangled
alpha
login
or
join now
margin.at
/
margin
87
fork
atom
Write on the margins of the internet. Powered by the AT Protocol.
margin.at
extension
web
atproto
comments
87
fork
atom
overview
issues
4
pulls
1
pipelines
fix collectionItem count on collection list
scanash.com
1 month ago
13af900b
0c101253
+65
-10
6 changed files
expand all
collapse all
unified
split
backend
internal
api
collections.go
hydration.go
db
queries_collections.go
web
src
views
collections
CollectionDetail.tsx
Collections.tsx
core
Settings.tsx
+10
backend/internal/api/collections.go
···
228
228
profiles := fetchProfilesForDIDs(s.db, []string{authorDID})
229
229
creator := profiles[authorDID]
230
230
231
231
+
collectionURIs := make([]string, len(collections))
232
232
+
for i, c := range collections {
233
233
+
collectionURIs[i] = c.URI
234
234
+
}
235
235
+
itemCounts, _ := s.db.GetCollectionItemCounts(collectionURIs)
236
236
+
231
237
apiCollections := make([]APICollection, len(collections))
232
238
for i, c := range collections {
233
239
icon := ""
···
246
252
Creator: creator,
247
253
CreatedAt: c.CreatedAt,
248
254
IndexedAt: c.IndexedAt,
255
255
+
ItemsCount: itemCounts[c.URI],
249
256
}
250
257
}
251
258
···
472
479
profiles := fetchProfilesForDIDs(s.db, []string{collection.AuthorDID})
473
480
creator := profiles[collection.AuthorDID]
474
481
482
482
+
itemCounts, _ := s.db.GetCollectionItemCounts([]string{collection.URI})
483
483
+
475
484
icon := ""
476
485
if collection.Icon != nil {
477
486
icon = *collection.Icon
···
489
498
Creator: creator,
490
499
CreatedAt: collection.CreatedAt,
491
500
IndexedAt: collection.IndexedAt,
501
501
+
ItemsCount: itemCounts[collection.URI],
492
502
}
493
503
494
504
w.Header().Set("Content-Type", "application/json")
+1
backend/internal/api/hydration.go
···
141
141
Creator Author `json:"creator"`
142
142
CreatedAt time.Time `json:"createdAt"`
143
143
IndexedAt time.Time `json:"indexedAt"`
144
144
+
ItemsCount int `json:"itemCount"`
144
145
}
145
146
146
147
type APICollectionItem struct {
+35
backend/internal/db/queries_collections.go
···
223
223
return uris, nil
224
224
}
225
225
226
226
+
func (db *DB) GetCollectionItemCounts(uris []string) (map[string]int, error) {
227
227
+
if len(uris) == 0 {
228
228
+
return map[string]int{}, nil
229
229
+
}
230
230
+
231
231
+
query := db.Rebind(`
232
232
+
SELECT collection_uri, COUNT(*)
233
233
+
FROM collection_items
234
234
+
WHERE collection_uri IN (` + buildPlaceholders(len(uris)) + `)
235
235
+
GROUP BY collection_uri
236
236
+
`)
237
237
+
238
238
+
args := make([]interface{}, len(uris))
239
239
+
for i, uri := range uris {
240
240
+
args[i] = uri
241
241
+
}
242
242
+
243
243
+
rows, err := db.Query(query, args...)
244
244
+
if err != nil {
245
245
+
return nil, err
246
246
+
}
247
247
+
defer rows.Close()
248
248
+
249
249
+
counts := make(map[string]int)
250
250
+
for rows.Next() {
251
251
+
var uri string
252
252
+
var count int
253
253
+
if err := rows.Scan(&uri, &count); err != nil {
254
254
+
return nil, err
255
255
+
}
256
256
+
counts[uri] = count
257
257
+
}
258
258
+
return counts, nil
259
259
+
}
260
260
+
226
261
func (db *DB) GetCollectionsByURIs(uris []string) ([]Collection, error) {
227
262
if len(uris) == 0 {
228
263
return []Collection{}, nil
+8
-1
web/src/views/collections/CollectionDetail.tsx
···
1
1
import React, { useEffect, useState } from "react";
2
2
+
import { Link } from "react-router-dom";
2
3
import {
3
4
getCollection,
4
5
getCollectionItems,
···
143
144
{items.length} items
144
145
</span>
145
146
<span>
146
146
-
by {collection.creator.displayName || collection.creator.handle}
147
147
+
by{" "}
148
148
+
<Link
149
149
+
to={`/profile/${collection.creator.did}`}
150
150
+
className="hover:text-primary-600 dark:hover:text-primary-400 hover:underline transition-colors"
151
151
+
>
152
152
+
{collection.creator.displayName || collection.creator.handle}
153
153
+
</Link>
147
154
</span>
148
155
</div>
149
156
</div>
+1
-1
web/src/views/collections/Collections.tsx
···
140
140
href={`/${collection.creator?.handle || user?.handle}/collection/${(collection.uri || "").split("/").pop()}`}
141
141
className="group card p-4 hover:ring-primary-300 dark:hover:ring-primary-600 transition-all flex items-center gap-4"
142
142
>
143
143
-
<div className="p-2.5 bg-primary-50 dark:bg-primary-900/30 text-primary-600 dark:text-primary-400 rounded-xl">
143
143
+
<div className="w-10 h-10 flex items-center justify-center shrink-0 bg-primary-50 dark:bg-primary-900/30 text-primary-600 dark:text-primary-400 rounded-xl">
144
144
<CollectionIcon icon={collection.icon} size={20} />
145
145
</div>
146
146
<div className="flex-1 min-w-0">
+10
-8
web/src/views/core/Settings.tsx
···
176
176
<button
177
177
key={opt.value}
178
178
onClick={() => setTheme(opt.value)}
179
179
-
className={`flex-1 flex flex-col items-center gap-2 p-4 rounded-xl border-2 transition-all ${theme === opt.value
179
179
+
className={`flex-1 flex flex-col items-center gap-2 p-4 rounded-xl border-2 transition-all ${
180
180
+
theme === opt.value
180
181
? "border-primary-500 bg-primary-50 dark:bg-primary-900/20"
181
182
: "border-surface-200 dark:border-surface-700 hover:border-surface-300 dark:hover:border-surface-600"
182
182
-
}`}
183
183
+
}`}
183
184
>
184
185
<opt.icon
185
186
size={24}
···
574
575
label: string;
575
576
icon: typeof Eye;
576
577
}[] = [
577
577
-
{ value: "warn", label: "Warn", icon: EyeOff },
578
578
-
{ value: "hide", label: "Hide", icon: XCircle },
579
579
-
{ value: "ignore", label: "Ignore", icon: Eye },
580
580
-
];
578
578
+
{ value: "warn", label: "Warn", icon: EyeOff },
579
579
+
{ value: "hide", label: "Hide", icon: XCircle },
580
580
+
{ value: "ignore", label: "Ignore", icon: Eye },
581
581
+
];
581
582
return (
582
583
<div
583
584
key={label}
···
597
598
opt.value,
598
599
)
599
600
}
600
600
-
className={`px-2.5 py-1 text-xs font-medium rounded-lg transition-all flex items-center gap-1 ${current === opt.value
601
601
+
className={`px-2.5 py-1 text-xs font-medium rounded-lg transition-all flex items-center gap-1 ${
602
602
+
current === opt.value
601
603
? opt.value === "hide"
602
604
? "bg-red-100 dark:bg-red-900/30 text-red-700 dark:text-red-400"
603
605
: opt.value === "warn"
604
606
? "bg-amber-100 dark:bg-amber-900/30 text-amber-700 dark:text-amber-400"
605
607
: "bg-green-100 dark:bg-green-900/30 text-green-700 dark:text-green-400"
606
608
: "text-surface-400 dark:text-surface-500 hover:bg-surface-200 dark:hover:bg-surface-700"
607
607
-
}`}
609
609
+
}`}
608
610
>
609
611
<opt.icon size={12} />
610
612
{opt.label}