• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

knowledgepixels / nanopub-registry / 24134532645

08 Apr 2026 12:08PM UTC coverage: 32.376% (-0.4%) from 32.824%
24134532645

Pull #98

github

web-flow
Merge 03ec4a037 into 689a63b39
Pull Request #98: Revert seqNum batch allocation, use atomic counter

265 of 912 branches covered (29.06%)

Branch coverage included in aggregate %.

783 of 2325 relevant lines covered (33.68%)

5.66 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
src/main/java/com/knowledgepixels/registry/ListPage.java
1
package com.knowledgepixels.registry;
2

3
import com.google.gson.Gson;
4
import com.mongodb.client.ClientSession;
5
import com.mongodb.client.MongoCursor;
6
import io.vertx.ext.web.RoutingContext;
7
import org.bson.Document;
8
import org.bson.conversions.Bson;
9
import org.nanopub.jelly.NanopubStream;
10

11
import java.io.IOException;
12
import java.net.URLEncoder;
13
import java.util.ArrayList;
14
import java.util.List;
15

16
import static com.knowledgepixels.registry.RegistryDB.collection;
17
import static com.knowledgepixels.registry.RegistryDB.unhash;
18
import static com.knowledgepixels.registry.Utils.*;
19
import static com.mongodb.client.model.Aggregates.*;
20
import static com.mongodb.client.model.Filters.gt;
21
import static com.mongodb.client.model.Indexes.ascending;
22
import static com.mongodb.client.model.Indexes.descending;
23
import static com.mongodb.client.model.Projections.exclude;
24
import static com.mongodb.client.model.Projections.include;
25

26
public class ListPage extends Page {
27

28
    private static final Gson gson = new Gson();
×
29

30
    public static void show(RoutingContext context) {
31
        ListPage page;
32
        try (ClientSession s = RegistryDB.getClient().startSession()) {
×
33
            // No transaction here: the nanopubs.jelly endpoint streams large result sets
34
            // that would exceed MongoDB's transaction timeout.
35
            page = new ListPage(s, context);
×
36
            page.show();
×
37
        } catch (IOException ex) {
×
38
            ex.printStackTrace();
×
39
        } finally {
40
            context.response().end();
×
41
        }
42
    }
×
43

44
    private ListPage(ClientSession mongoSession, RoutingContext context) {
45
        super(mongoSession, context);
×
46
    }
×
47

48
    protected void show() throws IOException {
49
        RoutingContext context = getContext();
×
50
        String format;
51
        String ext = getExtension();
×
52
        final String req = getRequestString();
×
53
        if ("json".equals(ext)) {
×
54
            format = TYPE_JSON;
×
55
        } else if ("jelly".equals(ext)) {
×
56
            format = TYPE_JELLY;
×
57
        } else if (ext == null || "html".equals(ext)) {
×
58
            format = Utils.getMimeType(context, SUPPORTED_TYPES_LIST);
×
59
        } else {
60
            context.response().setStatusCode(400).setStatusMessage("Invalid request: " + getFullRequest());
×
61
            return;
×
62
        }
63

64
        if (getPresentationFormat() != null) {
×
65
            setRespContentType(getPresentationFormat());
×
66
        } else {
67
            setRespContentType(format);
×
68
        }
69

70
        if (req.matches("/list/[0-9a-f]{64}/([0-9a-f]{64}|\\$)")) {
×
71
            String pubkey = req.replaceFirst("/list/([0-9a-f]{64})/([0-9a-f]{64}|\\$)", "$1");
×
72
            String type = req.replaceFirst("/list/([0-9a-f]{64})/([0-9a-f]{64}|\\$)", "$2");
×
73

74
            if (TYPE_JELLY.equals(format)) {
×
75
                // Determine start position from afterChecksums parameter (comma-separated, geometric fallback)
76
                long afterPosition = -1;
×
77
                String afterChecksums = getParam("afterChecksums", null);
×
78
                if (afterChecksums != null) {
×
79
                    for (String checksum : afterChecksums.split(",")) {
×
80
                        checksum = checksum.trim();
×
81
                        if (checksum.isEmpty()) continue;
×
82
                        Document match = collection("listEntries").find(mongoSession,
×
83
                                new Document("pubkey", pubkey).append("type", type).append("checksum", checksum)).first();
×
84
                        if (match != null) {
×
85
                            long matchPos = match.getLong("position");
×
86
                            if (matchPos > afterPosition) {
×
87
                                afterPosition = matchPos;
×
88
                            }
89
                        }
90
                    }
91
                }
92

93
                // Build pipeline with optional position filter
94
                Document matchFilter = new Document("pubkey", pubkey).append("type", type);
×
95
                if (afterPosition >= 0) {
×
96
                    matchFilter.append("position", new Document("$gt", afterPosition));
×
97
                }
98
                List<Bson> pipeline = List.of(match(matchFilter), sort(ascending("position")),
×
99
                        lookup("nanopubs", "np", "_id", "nanopub"), project(new Document("jelly", "$nanopub.jelly")), unwind("$jelly"));
×
100
                try (var result = collection("listEntries").aggregate(mongoSession, pipeline).cursor()) {
×
101
                    NanopubStream npStream = NanopubStream.fromMongoCursor(result);
×
102
                    BufferOutputStream outputStream = new BufferOutputStream();
×
103
                    npStream.writeToByteStream(outputStream);
×
104
                    context.response().write(outputStream.getBuffer());
×
105
                }
106
            } else {
×
107
                MongoCursor<Document> c = collection("listEntries").find(mongoSession, new Document("pubkey", pubkey).append("type", type)).projection(exclude("_id")).sort(ascending("position")).cursor();
×
108

109
                if (TYPE_JSON.equals(format)) {
×
110
                    println("[");
×
111
                    while (c.hasNext()) {
×
112
                        Document d = c.next();
×
113
                        // Transforming long to int, so the JSON output looks nice:
114
                        // TODO Make this scale beyond the int range
115
                        d.replace("position", d.getLong("position").intValue());
×
116
                        print(d.toJson());
×
117
                        println(c.hasNext() ? "," : "");
×
118
                    }
×
119
                    println("]");
×
120
                } else {
121
                    printHtmlHeader("List for pubkey " + getLabel(pubkey) + " / type " + getLabel(type) + " - Nanopub Registry");
×
122
                    println("<h1>List</h1>");
×
123
                    println("<p><a href=\"/list/" + pubkey + "\">&lt; Pubkey</a></p>");
×
124
                    println("<h3>Formats</h3>");
×
125
                    println("<p>");
×
126
                    println("<a href=\"/list/" + pubkey + "/" + type + ".json\">.json</a> |");
×
127
                    println("<a href=\"/list/" + pubkey + "/" + type + ".json.txt\">.json.txt</a>");
×
128
                    println("</p>");
×
129
                    println("<h3>Pubkey Hash</h3>");
×
130
                    println("<p><code>" + pubkey + "</code></p>");
×
131
                    println("<h3>Type Hash</h3>");
×
132
                    println("<p><code>" + type + "</code></p>");
×
133
                    println("<h3>Entries</h3>");
×
134
                    println("<ol>");
×
135
                    while (c.hasNext()) {
×
136
                        Document d = c.next();
×
137
                        println("<li><a href=\"/np/" + d.getString("np") + "\"><code>" + getLabel(d.getString("np")) + "</code></a></li>");
×
138
                    }
×
139
                    println("</ol>");
×
140
                    printHtmlFooter();
×
141
                }
142
            }
143
        } else if (req.matches("/list/[0-9a-f]{64}")) {
×
144
            String pubkey = req.replaceFirst("/list/([0-9a-f]{64})", "$1");
×
145
            MongoCursor<Document> c = collection("lists").find(mongoSession, new Document("pubkey", pubkey)).projection(exclude("_id")).cursor();
×
146
            if (TYPE_JSON.equals(format)) {
×
147
                println("[");
×
148
                while (c.hasNext()) {
×
149
                    print(c.next().toJson());
×
150
                    println(c.hasNext() ? "," : "");
×
151
                }
152
                println("]");
×
153
            } else {
154
                printHtmlHeader("Accounts for Pubkey " + getLabel(pubkey) + " - Nanopub Registry");
×
155
                println("<h1>Accounts for Pubkey " + getLabel(pubkey) + "</h1>");
×
156
                println("<p><a href=\"/list\">&lt; Account List</a></p>");
×
157
                println("<h3>Formats</h3>");
×
158
                println("<p>");
×
159
                println("<a href=\"/list/" + pubkey + ".json\">.json</a> |");
×
160
                println("<a href=\"/list/" + pubkey + ".json.txt\">.json.txt</a>");
×
161
                println("</p>");
×
162
                println("<h3>Pubkey Hash</h3>");
×
163
                println("<p><code>" + pubkey + "</code></p>");
×
164
                println("<h3>Entry Lists</h3>");
×
165
                println("<ol>");
×
166
                while (c.hasNext()) {
×
167
                    Document d = c.next();
×
168
                    String type = d.getString("type");
×
169
                    println("<li>");
×
170
                    println("<a href=\"/list/" + pubkey + "/" + type + "\"><code>" + getLabel(type) + "</code></a> ");
×
171
                    if (type.equals("$")) {
×
172
                        println("(all types)");
×
173
                    } else {
174
                        String typeUri = unhash(type);
×
175
                        println("(type " + (typeUri != null ? typeUri : type) + ")");
×
176
                    }
177
                    println("</li>");
×
178
                }
×
179
                println("</ol>");
×
180
                printHtmlFooter();
×
181
            }
182
        } else if (req.equals("/list")) {
×
183
            try (var c = collection(Collection.ACCOUNTS.toString()).find(mongoSession).sort(ascending("pubkey")).projection(exclude("_id")).cursor()) {
×
184
                if (TYPE_JSON.equals(format)) {
×
185
                    println("[");
×
186
                    while (c.hasNext()) {
×
187
                        print(c.next().toJson());
×
188
                        println(c.hasNext() ? "," : "");
×
189
                    }
190
                    println("]");
×
191
                } else {
192
                    printHtmlHeader("Account List - Nanopub Registry");
×
193
                    println("<h1>Account List</h1>");
×
194
                    println("<p><a href=\"/\">&lt; Home</a></p>");
×
195
                    println("<h3>Formats</h3>");
×
196
                    println("<p>");
×
197
                    println("<a href=\"list.json\">.json</a> |");
×
198
                    println("<a href=\"list.json.txt\">.json.txt</a>");
×
199
                    println("</p>");
×
200
                    println("<h3>Accounts</h3>");
×
201
                    println("<ol>");
×
202
                    while (c.hasNext()) {
×
203
                        Document d = c.next();
×
204
                        String pubkey = d.getString("pubkey");
×
205
                        if (!pubkey.equals("$")) {
×
206
                            println("<li>");
×
207
                            println("<a href=\"/list/" + pubkey + "\"><code>" + getLabel(pubkey) + "</code></a>");
×
208
                            String a = d.getString("agent");
×
209
                            if (a != null && !a.isBlank()) {
×
210
                                print(" by <a href=\"/agent?id=" + URLEncoder.encode(a, "UTF-8") + "\">" + Utils.getAgentLabel(a) + "</a>");
×
211
                            }
212
                            print(", status: " + d.get("status"));
×
213
                            print(", depth: " + d.get("depth"));
×
214
                            if (d.get("pathCount") != null) {
×
215
                                print(", pathCount: " + d.get("pathCount"));
×
216
                            }
217
                            if (d.get("ratio") != null) {
×
218
                                print(", ratio: " + df8.format(d.get("ratio")));
×
219
                            }
220
                            Document dollarList = RegistryDB.getOne(mongoSession, "lists",
×
221
                                    new Document("pubkey", pubkey).append("type", "$"));
×
222
                            if (dollarList != null && dollarList.get("maxPosition") != null) {
×
223
                                print(", count: " + (dollarList.getLong("maxPosition") + 1));
×
224
                            }
225
                            if (d.get("quota") != null) {
×
226
                                print(", quota: " + d.get("quota"));
×
227
                            }
228
                            println("");
×
229
                            println("</li>");
×
230
                        }
231
                    }
×
232
                    println("</ol>");
×
233
                    printHtmlFooter();
×
234
                }
235
            }
236
        } else if (req.equals("/agent") && context.request().getParam("id") != null) {
×
237
            String agentId = context.request().getParam("id");
×
238
            if (TYPE_JSON.equals(format)) {
×
239
                print(AgentInfo.get(mongoSession, agentId).asJson());
×
240
            } else {
241
                Document agentDoc = RegistryDB.getOne(mongoSession, Collection.AGENTS.toString(), new Document("agent", agentId));
×
242
                printHtmlHeader("Agent " + Utils.getAgentLabel(agentId) + " - Nanopub Registry");
×
243
                println("<h1>Agent " + Utils.getAgentLabel(agentId) + "</h1>");
×
244
                println("<p><a href=\"/agents\">&lt; Agent List</a></p>");
×
245
                println("<h3>Formats</h3>");
×
246
                println("<p>");
×
247
                println("<a href=\"agent.json?id=" + URLEncoder.encode(agentId, "UTF-8") + "\">.json</a> |");
×
248
                println("<a href=\"agent.json.txt?id=" + URLEncoder.encode(agentId, "UTF-8") + "\">.json.txt</a>");
×
249
                println("</p>");
×
250
                println("<h3>ID</h3>");
×
251
                println("<p><a href=\"" + agentId + "\"><code>" + agentId + "</code></a></p>");
×
252
                println("<h3>Properties</h3>");
×
253
                println("<ul>");
×
254
                println("<li>Average path count: " + agentDoc.get("avgPathCount") + "</li>");
×
255
                println("<li>Total ratio: " + agentDoc.get("totalRatio") + "</li>");
×
256
                println("</ul>");
×
257
                println("<h3>Accounts</h3>");
×
258
                println("<p>Count: " + agentDoc.get("accountCount") + "</p>");
×
259
                println("<p><a href=\"agentAccounts?id=" + URLEncoder.encode(agentId, "UTF-8") + "\">&gt; agentAccounts</a></p>");
×
260
                printHtmlFooter();
×
261
            }
262
        } else if (req.equals("/agentAccounts") && context.request().getParam("id") != null) {
×
263
            String agentId = context.request().getParam("id");
×
264
            MongoCursor<Document> c = collection(Collection.ACCOUNTS.toString()).find(mongoSession, new Document("agent", agentId)).projection(exclude("_id")).cursor();
×
265
            if (TYPE_JSON.equals(format)) {
×
266
                println("[");
×
267
                while (c.hasNext()) {
×
268
                    print(c.next().toJson());
×
269
                    println(c.hasNext() ? "," : "");
×
270
                }
271
                println("]");
×
272
            } else {
273
                printHtmlHeader("Accounts of Agent " + Utils.getAgentLabel(agentId) + " - Nanopub Registry");
×
274
                println("<h1>Accounts of Agent " + Utils.getAgentLabel(agentId) + "</h1>");
×
275
                println("<p><a href=\"/agent?id=" + URLEncoder.encode(agentId, "UTF-8") + "\">&lt; Agent</a></p>");
×
276
                println("<h3>Formats</h3>");
×
277
                println("<p>");
×
278
                println("<a href=\"agentAccounts.json?id=" + URLEncoder.encode(agentId, "UTF-8") + "\">.json</a> |");
×
279
                println("<a href=\"agentAccounts.json.txt?id=" + URLEncoder.encode(agentId, "UTF-8") + "\">.json.txt</a>");
×
280
                println("</p>");
×
281
                println("<h3>Account List</h3>");
×
282
                println("<ul>");
×
283
                while (c.hasNext()) {
×
284
                    Document d = c.next();
×
285
                    String pubkey = d.getString("pubkey");
×
286
                    //                                Object iCount = getMaxValue("listEntries", new Document("pubkey", pubkey).append("type", INTRO_TYPE_HASH), "position");
287
                    //                                Object eCount = getMaxValue("listEntries", new Document("pubkey", pubkey).append("type", ENDORSE_TYPE), "position");
288
                    //                                Object fCount = getMaxValue("listEntries", new Document("pubkey", pubkey).append("type", "$"), "position");
289
                    Document dollarList = RegistryDB.getOne(mongoSession, "lists",
×
290
                            new Document("pubkey", pubkey).append("type", "$"));
×
291
                    long npCount = (dollarList != null && dollarList.get("maxPosition") != null)
×
292
                            ? dollarList.getLong("maxPosition") + 1 : 0;
×
293
                    println("<li><a href=\"/list/" + pubkey + "\"><code>" + getLabel(pubkey) + "</code></a> (" + d.get("status") + "), " + "count " + npCount + ", " + "quota " + d.get("quota") + ", " + "ratio " + df8.format(d.get("ratio")) + ", " + "path count " + d.get("pathCount") + "</li>");
×
294
                }
×
295
                println("</ul>");
×
296
                printHtmlFooter();
×
297
            }
298
        } else if (req.equals("/agents")) {
×
299
            MongoCursor<Document> c = collection(Collection.AGENTS.toString()).find(mongoSession).sort(descending("totalRatio")).projection(exclude("_id")).cursor();
×
300
            if (TYPE_JSON.equals(format)) {
×
301
                println("[");
×
302
                while (c.hasNext()) {
×
303
                    print(c.next().toJson());
×
304
                    println(c.hasNext() ? "," : "");
×
305
                }
306
                println("]");
×
307
            } else {
308
                printHtmlHeader("Agent List - Nanopub Registry");
×
309
                println("<h1>Agent List</h1>");
×
310
                println("<p><a href=\"/\">&lt; Home</a></p>");
×
311
                println("<h3>Formats</h3>");
×
312
                println("<p>");
×
313
                println("<a href=\"agents.json\">.json</a> |");
×
314
                println("<a href=\"agents.json.txt\">.json.txt</a>");
×
315
                println("</p>");
×
316
                println("<h3>Agents</h3>");
×
317
                println("<ol>");
×
318
                while (c.hasNext()) {
×
319
                    Document d = c.next();
×
320
                    if (d.get("agent").equals("$")) continue;
×
321
                    String a = d.getString("agent");
×
322
                    int accountCount = d.getInteger("accountCount");
×
323
                    println("<li><a href=\"/agent?id=" + URLEncoder.encode(a, "UTF-8") + "\">" + Utils.getAgentLabel(a) + "</a>, " + accountCount + " account" + (accountCount == 1 ? "" : "s") + ", " + "ratio " + df8.format(d.get("totalRatio")) + ", " + "avg. path count " + df1.format(d.get("avgPathCount")) + "</li>");
×
324
                }
×
325
                println("</ol>");
×
326
                printHtmlFooter();
×
327
            }
328
        } else if (req.equals("/nanopubs")) {
×
329
            if (TYPE_JELLY.equals(format)) {
×
330
                // Return all nanopubs after counter X (-1 by default)
331
                long afterCounter;
332
                try {
333
                    afterCounter = Long.parseLong(getParam("afterCounter", "-1"));
×
334
                } catch (NumberFormatException ex) {
×
335
                    context.response().setStatusCode(400).setStatusMessage("Invalid afterCounter parameter.");
×
336
                    return;
×
337
                }
×
338
                var pipeline = collection(Collection.NANOPUBS.toString()).find(mongoSession).filter(gt("counter", afterCounter)).sort(ascending("counter"))
×
339
                        .projection(include("jelly", "counter"));
×
340

341
                try (var result = pipeline.cursor()) {
×
342
                    NanopubStream npStream = NanopubStream.fromMongoCursorWithCounter(result);
×
343
                    BufferOutputStream outputStream = new BufferOutputStream();
×
344
                    npStream.writeToByteStream(outputStream);
×
345
                    context.response().write(outputStream.getBuffer());
×
346
                }
347
            } else {
×
348
                // Return nanopubs as streamed JSON or HTML
349
                String sortParam = getParam("sort", "date");
×
350

351
                if (TYPE_JSON.equals(format)) {
×
352
                    Bson filter;
353
                    Bson sort;
354
                    if ("id".equals(sortParam)) {
×
355
                        String afterId = getParam("after", "");
×
356
                        filter = afterId.isEmpty() ? new Document() : gt("_id", afterId);
×
357
                        sort = ascending("_id");
×
358
                    } else {
×
359
                        // sort=date (default): latest first, using indexed counter field
360
                        filter = new Document();
×
361
                        sort = descending("counter");
×
362
                    }
363
                    try (MongoCursor<Document> c = collection(Collection.NANOPUBS.toString()).find(mongoSession)
×
364
                            .filter(filter).sort(sort)
×
365
                            .projection(include("_id")).cursor()) {
×
366
                        println("[");
×
367
                        boolean first = true;
×
368
                        while (c.hasNext()) {
×
369
                            if (!first) println(",");
×
370
                            first = false;
×
371
                            print(gson.toJson(c.next().getString("_id")));
×
372
                        }
373
                        println("\n]");
×
374
                    }
375
                } else {
×
376
                    printHtmlHeader("Nanopubs - Nanopub Registry");
×
377
                    println("<h1>Nanopubs</h1>");
×
378
                    println("<p><a href=\"/\">&lt; Home</a></p>");
×
379
                    println("<h3>All Nanopub IDs (JSON, latest first)</h3>");
×
380
                    println("<p>");
×
381
                    println("<a href=\"nanopubs.json\">.json</a> |");
×
382
                    println("<a href=\"nanopubs.json.txt\">.json.txt</a>");
×
383
                    println("</p>");
×
384
                    println("<h3>All Nanopub IDs (JSON, sorted by artifact code)</h3>");
×
385
                    println("<p>");
×
386
                    println("<a href=\"nanopubs.json?sort=id\">.json</a> |");
×
387
                    println("<a href=\"nanopubs.json.txt?sort=id\">.json.txt</a>");
×
388
                    println("</p>");
×
389
                    println("<h3>All Nanopubs (Jelly)</h3>");
×
390
                    println("<p><a href=\"nanopubs.jelly\">.jelly</a></p>");
×
391
                    println("<h3>Latest Nanopubs (max. 1000)</h3>");
×
392
                    println("<ol>");
×
393
                    try (MongoCursor<Document> c = collection(Collection.NANOPUBS.toString()).find(mongoSession)
×
394
                            .sort(descending("counter")).limit(1000).cursor()) {
×
395
                        while (c.hasNext()) {
×
396
                            String npId = c.next().getString("_id");
×
397
                            println("<li><a href=\"/np/" + npId + "\"><code>" + getLabel(npId) + "</code></a></li>");
×
398
                        }
×
399
                    }
400
                    println("</ol>");
×
401
                    printHtmlFooter();
×
402
                }
403
            }
×
404
        } else if (req.equals("/pubkeys")) {
×
405
            try (var c = collection("lists").distinct(mongoSession, "pubkey", String.class).cursor()) {
×
406
                if (TYPE_JSON.equals(format)) {
×
407
                    println("[");
×
408
                    while (c.hasNext()) {
×
409
                        print(gson.toJson(c.next()));
×
410
                        println(c.hasNext() ? "," : "");
×
411
                    }
412
                    println("]");
×
413
                } else {
414
                    printHtmlHeader("Pubkey List - Nanopub Registry");
×
415
                    println("<h1>Pubkey List</h1>");
×
416
                    println("<p><a href=\"/\">&lt; Home</a></p>");
×
417
                    println("<h3>Formats</h3>");
×
418
                    println("<p>");
×
419
                    println("<a href=\"pubkeys.json\">.json</a> |");
×
420
                    println("<a href=\"pubkeys.json.txt\">.json.txt</a>");
×
421
                    println("</p>");
×
422
                    println("<h3>Pubkeys</h3>");
×
423
                    println("<ol>");
×
424
                    while (c.hasNext()) {
×
425
                        String pubkey = c.next();
×
426
                        if (!pubkey.equals("$")) {
×
427
                            println("<li>");
×
428
                            println("<a href=\"/list/" + pubkey + "\"><code>" + getLabel(pubkey) + "</code></a>");
×
429
                            println("</li>");
×
430
                        }
431
                    }
×
432
                    println("</ol>");
×
433
                    printHtmlFooter();
×
434
                }
435
            }
436
        } else {
437
            context.response().setStatusCode(400).setStatusMessage("Invalid request: " + getFullRequest());
×
438
        }
439
    }
×
440

441
    private static String getLabel(Object obj) {
442
        if (obj == null) return null;
×
443
        if (obj.toString().length() < 10) return obj.toString();
×
444
        return obj.toString().substring(0, 10);
×
445
    }
446

447
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc