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

knowledgepixels / nanopub-registry / 30893238389

04 Aug 2026 08:44AM UTC coverage: 83.415% (+51.1%) from 32.285%
30893238389

Pull #123

github

web-flow
Merge 84fad06c1 into e5ed6d462
Pull Request #123: Add unit tests for existing functionality

865 of 1112 branches covered (77.79%)

Branch coverage included in aggregate %.

2706 of 3169 relevant lines covered (85.39%)

12.96 hits per line

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

86.69
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
import org.slf4j.Logger;
11
import org.slf4j.LoggerFactory;
12

13
import java.io.IOException;
14
import java.net.URLEncoder;
15
import java.util.List;
16

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

27
public class ListPage extends Page {
28

29
    private static final Gson gson = new Gson();
12✔
30
    private static final Logger logger = LoggerFactory.getLogger(ListPage.class);
12✔
31

32
    public static void show(RoutingContext context) {
33
        ListPage page;
34
        logger.info("Received list request: {}", context.request().path());
18✔
35
        try (ClientSession s = RegistryDB.getClient().startSession()) {
9✔
36
            // No transaction here: the nanopubs.jelly endpoint streams large result sets
37
            // that would exceed MongoDB's transaction timeout.
38
            page = new ListPage(s, context);
18✔
39
            page.show();
6✔
40
        } catch (IOException ex) {
×
41
            logger.warn("Failed to show list for request {}: {} ({})", context.request().path(), ex.getMessage(), ex.getClass().getSimpleName(), ex);
×
42
        } finally {
43
            logger.debug("Ending response for list request: {}", context.request().path());
18✔
44
            context.response().end();
12✔
45
        }
46
    }
3✔
47

48
    private ListPage(ClientSession mongoSession, RoutingContext context) {
49
        super(mongoSession, context);
12✔
50
    }
3✔
51

52
    protected void show() throws IOException {
53
        RoutingContext context = getContext();
9✔
54
        String format;
55
        String ext = getExtension();
9✔
56
        final String req = getRequestString();
9✔
57

58
        logger.debug("Preparing list response for request: {} (ext={})", getFullRequest(), ext);
18✔
59

60
        if ("json".equals(ext)) {
12✔
61
            format = TYPE_JSON;
9✔
62
        } else if ("jelly".equals(ext)) {
12✔
63
            format = TYPE_JELLY;
9✔
64
        } else if (ext == null || "html".equals(ext)) {
18!
65
            format = Utils.getMimeType(context, SUPPORTED_TYPES_LIST);
15✔
66
        } else {
67
            logger.warn("Invalid list request (unsupported extension) for {}: {}", getFullRequest(), ext);
18✔
68
            context.response().setStatusCode(400).setStatusMessage("Invalid request: " + getFullRequest());
27✔
69
            return;
3✔
70
        }
71

72
        if (getPresentationFormat() != null) {
9✔
73
            setRespContentType(getPresentationFormat());
12✔
74
            logger.debug("Overriding response content type with presentation format: {}", getPresentationFormat());
18✔
75
        } else {
76
            setRespContentType(format);
9✔
77
            logger.debug("Set response content type: {}", format);
12✔
78
        }
79

80
        if (req.matches("/list/[0-9a-f]{64}/([0-9a-f]{64}|\\$)")) {
12✔
81
            String pubkey = req.replaceFirst("/list/([0-9a-f]{64})/([0-9a-f]{64}|\\$)", "$1");
15✔
82
            String type = req.replaceFirst("/list/([0-9a-f]{64})/([0-9a-f]{64}|\\$)", "$2");
15✔
83

84
            logger.info("Serving list for pubkey={} type={} format={}", getLabel(pubkey), getLabel(type), format);
57✔
85

86
            if (TYPE_JELLY.equals(format)) {
12!
87
                // Determine start position from afterChecksums parameter (comma-separated, geometric fallback)
88
                long afterPosition = -1;
×
89
                String afterChecksums = getParam("afterChecksums", null);
×
90
                if (afterChecksums != null) {
×
91
                    for (String checksum : afterChecksums.split(",")) {
×
92
                        checksum = checksum.trim();
×
93
                        if (checksum.isEmpty()) {
×
94
                            continue;
×
95
                        }
96
                        Document match = collection("listEntries").find(mongoSession,
×
97
                                new Document("pubkey", pubkey).append("type", type).append("checksum", checksum)).first();
×
98
                        if (match != null) {
×
99
                            long matchPos = match.getLong("position");
×
100
                            if (matchPos > afterPosition) {
×
101
                                afterPosition = matchPos;
×
102
                            }
103
                        }
104
                    }
105
                }
106

107
                // Build pipeline with optional position filter
108
                Document matchFilter = new Document("pubkey", pubkey).append("type", type);
×
109
                if (afterPosition >= 0) {
×
110
                    matchFilter.append("position", new Document("$gt", afterPosition));
×
111
                }
112
                List<Bson> pipeline = List.of(match(matchFilter), sort(ascending("position")),
×
113
                        lookup("nanopubs", "np", "_id", "nanopub"), project(new Document("jelly", "$nanopub.jelly")), unwind("$jelly"));
×
114
                try (var result = collection("listEntries").aggregate(mongoSession, pipeline).cursor()) {
×
115
                    logger.info("Streaming Jelly nanopubs for pubkey={} type={} afterPosition={}", getLabel(pubkey), getLabel(type), afterPosition);
×
116
                    NanopubStream npStream = NanopubStream.fromMongoCursor(result);
×
117
                    BufferOutputStream outputStream = new BufferOutputStream();
×
118
                    npStream.writeToByteStream(outputStream);
×
119
                    context.response().write(outputStream.getBuffer());
×
120
                    logger.info("Finished streaming Jelly nanopubs for pubkey={} type={}", getLabel(pubkey), getLabel(type));
×
121
                }
122
            } else {
×
123
                try (MongoCursor<Document> c = collection("listEntries").find(mongoSession, new Document("pubkey", pubkey).append("type", type)).projection(exclude("_id")).sort(ascending("position")).cursor()) {
93✔
124

125
                    if (TYPE_JSON.equals(format)) {
12✔
126
                        int count = 0;
6✔
127
                        println("[");
9✔
128
                        while (c.hasNext()) {
9✔
129
                            Document d = c.next();
12✔
130
                            // Transforming long to int, so the JSON output looks nice:
131
                            // TODO Make this scale beyond the int range
132
                            d.replace("position", d.getLong("position").intValue());
27✔
133
                            print(d.toJson());
12✔
134
                            println(c.hasNext() ? "," : "");
24✔
135
                            count++;
3✔
136
                        }
3✔
137
                        println("]");
9✔
138
                        logger.info("Served {} list entries for pubkey={} type={} (format=json)", count, getLabel(pubkey), getLabel(type));
60✔
139
                    } else {
3✔
140
                        int listed = 0;
6✔
141
                        printHtmlHeader("List for pubkey " + getLabel(pubkey) + " / type " + getLabel(type) + " - Nanopub Registry");
21✔
142
                        println("<h1>List</h1>");
9✔
143
                        println("<p><a href=\"/list/" + pubkey + "\">&lt; Pubkey</a></p>");
12✔
144
                        println("<h3>Formats</h3>");
9✔
145
                        println("<p>");
9✔
146
                        println("<a href=\"/list/" + pubkey + "/" + type + ".json\">.json</a> |");
15✔
147
                        println("<a href=\"/list/" + pubkey + "/" + type + ".json.txt\">.json.txt</a>");
15✔
148
                        println("</p>");
9✔
149
                        println("<h3>Pubkey Hash</h3>");
9✔
150
                        println("<p><code>" + pubkey + "</code></p>");
12✔
151
                        println("<h3>Type Hash</h3>");
9✔
152
                        println("<p><code>" + type + "</code></p>");
12✔
153
                        println("<h3>Entries</h3>");
9✔
154
                        println("<ol>");
9✔
155
                        while (c.hasNext()) {
9✔
156
                            Document d = c.next();
12✔
157
                            println("<li><a href=\"/np/" + d.getString("np") + "\"><code>" + getLabel(d.getString("np")) + "</code></a></li>");
30✔
158
                            listed++;
3✔
159
                        }
3✔
160
                        println("</ol>");
9✔
161
                        printHtmlFooter();
6✔
162
                        logger.info("Listed {} entries for pubkey={} type={} (format=html)", listed, getLabel(pubkey), getLabel(type));
60✔
163
                    }
164
                }
165
            }
166
        } else if (req.matches("/list/[0-9a-f]{64}")) {
15✔
167
            String pubkey = req.replaceFirst("/list/([0-9a-f]{64})", "$1");
15✔
168
            try (MongoCursor<Document> c = collection("lists").find(mongoSession, new Document("pubkey", pubkey)).projection(exclude("_id")).cursor()) {
60✔
169
                if (TYPE_JSON.equals(format)) {
12✔
170
                    int count = 0;
6✔
171
                    println("[");
9✔
172
                    while (c.hasNext()) {
9✔
173
                        print(c.next().toJson());
18✔
174
                        println(c.hasNext() ? "," : "");
24✔
175
                        count++;
6✔
176
                    }
177
                    println("]");
9✔
178
                    logger.info("Served {} account documents for pubkey={} (format=json)", count, getLabel(pubkey));
21✔
179
                } else {
3✔
180
                    int listed = 0;
6✔
181
                    printHtmlHeader("Accounts for Pubkey " + getLabel(pubkey) + " - Nanopub Registry");
15✔
182
                    println("<h1>Accounts for Pubkey " + getLabel(pubkey) + "</h1>");
15✔
183
                    println("<p><a href=\"/list\">&lt; Current Trust State</a></p>");
9✔
184
                    println("<h3>Formats</h3>");
9✔
185
                    println("<p>");
9✔
186
                    println("<a href=\"/list/" + pubkey + ".json\">.json</a> |");
12✔
187
                    println("<a href=\"/list/" + pubkey + ".json.txt\">.json.txt</a>");
12✔
188
                    println("</p>");
9✔
189
                    println("<h3>Pubkey Hash</h3>");
9✔
190
                    println("<p><code>" + pubkey + "</code></p>");
12✔
191
                    println("<h3>Entry Lists</h3>");
9✔
192
                    println("<ol>");
9✔
193
                    while (c.hasNext()) {
9✔
194
                        Document d = c.next();
12✔
195
                        String type = d.getString("type");
12✔
196
                        println("<li>");
9✔
197
                        println("<a href=\"/list/" + pubkey + "/" + type + "\"><code>" + getLabel(type) + "</code></a> ");
21✔
198
                        if (type.equals("$")) {
12✔
199
                            println("(all types)");
12✔
200
                        } else {
201
                            String typeUri = unhash(type);
9✔
202
                            println("(type " + (typeUri != null ? typeUri : type) + ")");
24✔
203
                        }
204
                        println("</li>");
9✔
205
                        listed++;
3✔
206
                    }
3✔
207
                    println("</ol>");
9✔
208
                    printHtmlFooter();
6✔
209
                    logger.info("Listed {} entry lists for pubkey={} (format=html)", listed, getLabel(pubkey));
21✔
210
                }
211
            }
212
        } else if (req.equals("/list")) {
15✔
213
            try (var c = collection(Collection.ACCOUNTS.toString()).find(mongoSession).sort(ascending("pubkey")).projection(exclude("_id")).cursor()) {
75✔
214
                if (TYPE_JSON.equals(format)) {
12✔
215
                    int count = 0;
6✔
216
                    println("[");
9✔
217
                    while (c.hasNext()) {
9✔
218
                        print(c.next().toJson());
18✔
219
                        println(c.hasNext() ? "," : "");
24✔
220
                        count++;
6✔
221
                    }
222
                    println("]");
9✔
223
                    logger.info("Served {} accounts (format=json) for {}", count, getFullRequest());
21✔
224
                } else {
3✔
225
                    int listed = 0;
6✔
226
                    printHtmlHeader("Current Trust State - Nanopub Registry");
9✔
227
                    println("<h1>Current Trust State</h1>");
9✔
228
                    println("<p><a href=\"/\">&lt; Home</a></p>");
9✔
229
                    println("<h3>Formats</h3>");
9✔
230
                    println("<p>");
9✔
231
                    println("<a href=\"list.json\">.json</a> |");
9✔
232
                    println("<a href=\"list.json.txt\">.json.txt</a>");
9✔
233
                    println("</p>");
9✔
234
                    println("<h3>Accounts</h3>");
9✔
235
                    println("<ol>");
9✔
236
                    while (c.hasNext()) {
9✔
237
                        Document d = c.next();
12✔
238
                        String pubkey = d.getString("pubkey");
12✔
239
                        if (!pubkey.equals("$")) {
12✔
240
                            println("<li>");
9✔
241
                            println("<a href=\"/list/" + pubkey + "\"><code>" + getLabel(pubkey) + "</code></a>");
18✔
242
                            String a = d.getString("agent");
12✔
243
                            if (a != null && !a.isBlank()) {
15!
244
                                print(" by <a href=\"/agent?id=" + URLEncoder.encode(a, "UTF-8") + "\">" + Utils.getAgentLabel(a) + "</a>");
24✔
245
                                String name = d.getString("name");
12✔
246
                                if (name != null && !name.isBlank()) {
15!
247
                                    print(" (" + name + ")");
12✔
248
                                }
249
                            }
250
                            print(", status: " + d.get("status"));
21✔
251
                            print(", depth: " + d.get("depth"));
21✔
252
                            if (d.get("pathCount") != null) {
12!
253
                                print(", pathCount: " + d.get("pathCount"));
21✔
254
                            }
255
                            if (d.get("ratio") != null) {
12!
256
                                print(", ratio: " + df8.format(d.get("ratio")));
24✔
257
                            }
258
                            Document dollarList = RegistryDB.getOne(mongoSession, "lists",
36✔
259
                                    new Document("pubkey", pubkey).append("type", "$"));
3✔
260
                            if (dollarList != null && dollarList.get("maxPosition") != null) {
18!
261
                                print(", count: " + (dollarList.getLong("maxPosition") + 1));
27✔
262
                            }
263
                            if (d.get("quota") != null) {
12!
264
                                print(", quota: " + d.get("quota"));
21✔
265
                            }
266
                            println("");
9✔
267
                            println("</li>");
9✔
268
                            listed++;
3✔
269
                        }
270
                    }
3✔
271
                    println("</ol>");
9✔
272
                    printHtmlFooter();
6✔
273
                    logger.info("Listed {} accounts (format=html) for {}", listed, getFullRequest());
21✔
274
                }
275
            }
276
        } else if (req.equals("/agent") && context.request().getParam("id") != null) {
27✔
277
            String agentId = context.request().getParam("id");
15✔
278
            logger.info("Serving agent detail for id={} format={}", Utils.getAgentLabel(agentId), format);
18✔
279
            if (TYPE_JSON.equals(format)) {
12✔
280
                print(AgentInfo.get(mongoSession, agentId).asJson());
24✔
281
            } else {
282
                Document agentDoc = RegistryDB.getOne(mongoSession, Collection.AGENTS.toString(), new Document("agent", agentId));
33✔
283
                String agentName = (agentDoc != null) ? agentDoc.getString("name") : null;
21!
284
                String headingSuffix = (agentName != null && !agentName.isBlank()) ? " (" + agentName + ")" : "";
27!
285
                printHtmlHeader("Agent " + Utils.getAgentLabel(agentId) + headingSuffix + " - Nanopub Registry");
18✔
286
                println("<h1>Agent " + Utils.getAgentLabel(agentId) + headingSuffix + "</h1>");
18✔
287
                println("<p><a href=\"/agents\">&lt; Agent List</a></p>");
9✔
288
                println("<h3>Formats</h3>");
9✔
289
                println("<p>");
9✔
290
                println("<a href=\"agent.json?id=" + URLEncoder.encode(agentId, "UTF-8") + "\">.json</a> |");
18✔
291
                println("<a href=\"agent.json.txt?id=" + URLEncoder.encode(agentId, "UTF-8") + "\">.json.txt</a>");
18✔
292
                println("</p>");
9✔
293
                println("<h3>ID</h3>");
9✔
294
                println("<p><a href=\"" + agentId + "\"><code>" + agentId + "</code></a></p>");
15✔
295
                println("<h3>Properties</h3>");
9✔
296
                println("<ul>");
9✔
297
                if (agentName != null && !agentName.isBlank()) {
15!
298
                    println("<li>Name: " + agentName + "</li>");
12✔
299
                }
300
                println("<li>Average path count: " + agentDoc.get("avgPathCount") + "</li>");
21✔
301
                println("<li>Total ratio: " + agentDoc.get("totalRatio") + "</li>");
21✔
302
                println("</ul>");
9✔
303
                println("<h3>Accounts</h3>");
9✔
304
                println("<p>Count: " + agentDoc.get("accountCount") + "</p>");
21✔
305
                println("<p><a href=\"agentAccounts?id=" + URLEncoder.encode(agentId, "UTF-8") + "\">&gt; agentAccounts</a></p>");
18✔
306
                printHtmlFooter();
6✔
307
            }
308
        } else if (req.equals("/agentAccounts") && context.request().getParam("id") != null) {
30!
309
            String agentId = context.request().getParam("id");
15✔
310
            logger.info("Serving agent accounts for id={} format={}", Utils.getAgentLabel(agentId), format);
18✔
311
            try (MongoCursor<Document> c = collection(Collection.ACCOUNTS.toString()).find(mongoSession, new Document("agent", agentId)).projection(exclude("_id")).cursor()) {
63✔
312
                if (TYPE_JSON.equals(format)) {
12✔
313
                    int count = 0;
6✔
314
                    println("[");
9✔
315
                    while (c.hasNext()) {
9✔
316
                        print(c.next().toJson());
18✔
317
                        println(c.hasNext() ? "," : "");
18!
318
                        count++;
6✔
319
                    }
320
                    println("]");
9✔
321
                    logger.info("Served {} agent accounts for id={} (format=json)", count, Utils.getAgentLabel(agentId));
21✔
322
                } else {
3✔
323
                    Document agentDoc = RegistryDB.getOne(mongoSession, Collection.AGENTS.toString(), new Document("agent", agentId));
33✔
324
                    String agentName = (agentDoc != null) ? agentDoc.getString("name") : null;
24✔
325
                    String headingSuffix = (agentName != null && !agentName.isBlank()) ? " (" + agentName + ")" : "";
30!
326
                    printHtmlHeader("Accounts of Agent " + Utils.getAgentLabel(agentId) + headingSuffix + " - Nanopub Registry");
18✔
327
                    println("<h1>Accounts of Agent " + Utils.getAgentLabel(agentId) + headingSuffix + "</h1>");
18✔
328
                    println("<p><a href=\"/agent?id=" + URLEncoder.encode(agentId, "UTF-8") + "\">&lt; Agent</a></p>");
18✔
329
                    println("<h3>Formats</h3>");
9✔
330
                    println("<p>");
9✔
331
                    println("<a href=\"agentAccounts.json?id=" + URLEncoder.encode(agentId, "UTF-8") + "\">.json</a> |");
18✔
332
                    println("<a href=\"agentAccounts.json.txt?id=" + URLEncoder.encode(agentId, "UTF-8") + "\">.json.txt</a>");
18✔
333
                    println("</p>");
9✔
334
                    println("<h3>Account List</h3>");
9✔
335
                    println("<ul>");
9✔
336
                    int listed = 0;
6✔
337
                    while (c.hasNext()) {
9✔
338
                        Document d = c.next();
12✔
339
                        String pubkey = d.getString("pubkey");
12✔
340
                        //                                Object iCount = getMaxValue("listEntries", new Document("pubkey", pubkey).append("type", INTRO_TYPE_HASH), "position");
341
                        //                                Object eCount = getMaxValue("listEntries", new Document("pubkey", pubkey).append("type", ENDORSE_TYPE), "position");
342
                        //                                Object fCount = getMaxValue("listEntries", new Document("pubkey", pubkey).append("type", "$"), "position");
343
                        Document dollarList = RegistryDB.getOne(mongoSession, "lists",
36✔
344
                                new Document("pubkey", pubkey).append("type", "$"));
3✔
345
                        long npCount = (dollarList != null && dollarList.get("maxPosition") != null)
18!
346
                                ? dollarList.getLong("maxPosition") + 1 : 0;
27✔
347
                        String accountName = d.getString("name");
12✔
348
                        String nameSuffix = (accountName != null && !accountName.isBlank()) ? " (" + accountName + ")" : "";
30!
349
                        println("<li><a href=\"/list/" + pubkey + "\"><code>" + getLabel(pubkey) + "</code></a>" + nameSuffix + " (" + d.get("status") + "), " + "count " + npCount + ", " + "quota " + d.get("quota") + ", " + "ratio " + df8.format(d.get("ratio")) + ", " + "path count " + d.get("pathCount") + "</li>");
75✔
350
                        listed++;
3✔
351
                    }
3✔
352
                    println("</ul>");
9✔
353
                    printHtmlFooter();
6✔
354
                    logger.info("Listed {} accounts for agent id={} (format=html)", listed, Utils.getAgentLabel(agentId));
21✔
355
                }
356
            }
357
        } else if (req.equals("/agents")) {
15✔
358
            try (MongoCursor<Document> c = collection(Collection.AGENTS.toString()).find(mongoSession).sort(descending("totalRatio")).projection(exclude("_id")).cursor()) {
75✔
359
                if (TYPE_JSON.equals(format)) {
12✔
360
                    int count = 0;
6✔
361
                    println("[");
9✔
362
                    while (c.hasNext()) {
9✔
363
                        print(c.next().toJson());
18✔
364
                        println(c.hasNext() ? "," : "");
18!
365
                        count++;
6✔
366
                    }
367
                    println("]");
9✔
368
                    logger.info("Served {} agents (format=json)", count);
15✔
369
                } else {
3✔
370
                    int listed = 0;
6✔
371
                    printHtmlHeader("Agent List - Nanopub Registry");
9✔
372
                    println("<h1>Agent List</h1>");
9✔
373
                    println("<p><a href=\"/\">&lt; Home</a></p>");
9✔
374
                    println("<h3>Formats</h3>");
9✔
375
                    println("<p>");
9✔
376
                    println("<a href=\"agents.json\">.json</a> |");
9✔
377
                    println("<a href=\"agents.json.txt\">.json.txt</a>");
9✔
378
                    println("</p>");
9✔
379
                    println("<h3>Agents</h3>");
9✔
380
                    println("<ol>");
9✔
381
                    while (c.hasNext()) {
9✔
382
                        Document d = c.next();
12✔
383
                        if (d.get("agent").equals("$")) {
18✔
384
                            continue;
3✔
385
                        }
386
                        String a = d.getString("agent");
12✔
387
                        int accountCount = d.getInteger("accountCount");
15✔
388
                        String name = d.getString("name");
12✔
389
                        String nameSuffix = (name != null && !name.isBlank()) ? " (" + name + ")" : "";
30!
390
                        println("<li><a href=\"/agent?id=" + URLEncoder.encode(a, "UTF-8") + "\">" + Utils.getAgentLabel(a) + "</a>" + nameSuffix + ", " + accountCount + " account" + (accountCount == 1 ? "" : "s") + ", " + "ratio " + df8.format(d.get("totalRatio")) + ", " + "avg. path count " + df1.format(d.get("avgPathCount")) + "</li>");
78✔
391
                        listed++;
3✔
392
                    }
3✔
393
                    println("</ol>");
9✔
394
                    printHtmlFooter();
6✔
395
                    logger.info("Listed {} agents (format=html)", listed);
15✔
396
                }
397
            }
398
        } else if (req.equals("/nanopubs")) {
12✔
399
            if (TYPE_JELLY.equals(format)) {
12✔
400
                // Return all nanopubs after counter X (-1 by default)
401
                long afterCounter;
402
                try {
403
                    afterCounter = Long.parseLong(getParam("afterCounter", "-1"));
×
404
                } catch (NumberFormatException ex) {
3✔
405
                    logger.warn("Invalid afterCounter parameter for {}: {}", getFullRequest(), getParam("afterCounter", ""), ex);
63✔
406
                    context.response().setStatusCode(400).setStatusMessage("Invalid afterCounter parameter.");
21✔
407
                    return;
3✔
408
                }
×
409
                logger.info("Streaming nanopubs.jelly afterCounter={}", afterCounter);
×
410
                var pipeline = collection(Collection.NANOPUBS.toString()).find(mongoSession).filter(gt("counter", afterCounter)).sort(ascending("counter"))
×
411
                        .projection(include("jelly", "counter"));
×
412

413
                try (var result = pipeline.cursor()) {
×
414
                    NanopubStream npStream = NanopubStream.fromMongoCursorWithCounter(result);
×
415
                    BufferOutputStream outputStream = new BufferOutputStream();
×
416
                    npStream.writeToByteStream(outputStream);
×
417
                    context.response().write(outputStream.getBuffer());
×
418
                }
419
                logger.info("Finished streaming nanopubs.jelly for {}", getFullRequest());
×
420
            } else {
×
421
                // Return nanopubs as streamed JSON or HTML
422
                String sortParam = getParam("sort", "date");
15✔
423

424
                if (TYPE_JSON.equals(format)) {
12✔
425
                    Bson filter;
426
                    Bson sort;
427
                    if ("id".equals(sortParam)) {
12✔
428
                        String afterId = getParam("after", "");
15✔
429
                        filter = afterId.isEmpty() ? new Document() : gt("_id", afterId);
21!
430
                        sort = ascending("_id");
24✔
431
                    } else {
3✔
432
                        // sort=date (default): latest first, using indexed counter field
433
                        filter = new Document();
12✔
434
                        sort = descending("counter");
24✔
435
                    }
436
                    int count = 0;
6✔
437
                    try (MongoCursor<Document> c = collection(Collection.NANOPUBS.toString()).find(mongoSession)
21✔
438
                            .filter(filter).sort(sort)
27✔
439
                            .projection(include("_id")).cursor()) {
12✔
440
                        println("[");
9✔
441
                        boolean first = true;
6✔
442
                        while (c.hasNext()) {
9✔
443
                            if (!first) {
6✔
444
                                println(",");
9✔
445
                            }
446
                            first = false;
6✔
447
                            print(gson.toJson(c.next().getString("_id")));
27✔
448
                            count++;
6✔
449
                        }
450
                        println("\n]");
9✔
451
                    }
452
                    logger.info("Served {} nanopub ids (format=json, sort={})", count, sortParam);
18✔
453
                } else {
3✔
454
                    printHtmlHeader("Nanopubs - Nanopub Registry");
9✔
455
                    println("<h1>Nanopubs</h1>");
9✔
456
                    println("<p><a href=\"/\">&lt; Home</a></p>");
9✔
457
                    println("<h3>All Nanopub IDs (JSON, latest first)</h3>");
9✔
458
                    println("<p>");
9✔
459
                    println("<a href=\"nanopubs.json\">.json</a> |");
9✔
460
                    println("<a href=\"nanopubs.json.txt\">.json.txt</a>");
9✔
461
                    println("</p>");
9✔
462
                    println("<h3>All Nanopub IDs (JSON, sorted by artifact code)</h3>");
9✔
463
                    println("<p>");
9✔
464
                    println("<a href=\"nanopubs.json?sort=id\">.json</a> |");
9✔
465
                    println("<a href=\"nanopubs.json.txt?sort=id\">.json.txt</a>");
9✔
466
                    println("</p>");
9✔
467
                    println("<h3>All Nanopubs (Jelly)</h3>");
9✔
468
                    println("<p><a href=\"nanopubs.jelly\">.jelly</a></p>");
9✔
469
                    println("<h3>Latest Nanopubs (max. 1000)</h3>");
9✔
470
                    println("<ol>");
9✔
471
                    int listed = 0;
6✔
472
                    try (MongoCursor<Document> c = collection(Collection.NANOPUBS.toString()).find(mongoSession)
36✔
473
                            .sort(descending("counter")).limit(1000).cursor()) {
18✔
474
                        while (c.hasNext()) {
9✔
475
                            String npId = c.next().getString("_id");
18✔
476
                            println("<li><a href=\"/np/" + npId + "\"><code>" + getLabel(npId) + "</code></a></li>");
18✔
477
                            listed++;
3✔
478
                        }
3✔
479
                    }
480
                    println("</ol>");
9✔
481
                    printHtmlFooter();
6✔
482
                    logger.info("Listed {} latest nanopubs (format=html)", listed);
15✔
483
                }
484
            }
3✔
485
        } else if (req.equals("/pubkeys")) {
12✔
486
            try (var c = collection("lists").distinct(mongoSession, "pubkey", String.class).cursor()) {
30✔
487
                if (TYPE_JSON.equals(format)) {
12✔
488
                    int count = 0;
6✔
489
                    println("[");
9✔
490
                    while (c.hasNext()) {
9✔
491
                        print(gson.toJson(c.next()));
18✔
492
                        println(c.hasNext() ? "," : "");
24✔
493
                        count++;
6✔
494
                    }
495
                    println("]");
9✔
496
                    logger.info("Served {} pubkeys (format=json)", count);
15✔
497
                } else {
3✔
498
                    int listed = 0;
6✔
499
                    printHtmlHeader("Pubkey List - Nanopub Registry");
9✔
500
                    println("<h1>Pubkey List</h1>");
9✔
501
                    println("<p><a href=\"/\">&lt; Home</a></p>");
9✔
502
                    println("<h3>Formats</h3>");
9✔
503
                    println("<p>");
9✔
504
                    println("<a href=\"pubkeys.json\">.json</a> |");
9✔
505
                    println("<a href=\"pubkeys.json.txt\">.json.txt</a>");
9✔
506
                    println("</p>");
9✔
507
                    println("<h3>Pubkeys</h3>");
9✔
508
                    println("<ol>");
9✔
509
                    while (c.hasNext()) {
9✔
510
                        String pubkey = c.next();
12✔
511
                        if (!pubkey.equals("$")) {
12✔
512
                            println("<li>");
9✔
513
                            println("<a href=\"/list/" + pubkey + "\"><code>" + getLabel(pubkey) + "</code></a>");
18✔
514
                            println("</li>");
9✔
515
                            listed++;
3✔
516
                        }
517
                    }
3✔
518
                    println("</ol>");
9✔
519
                    printHtmlFooter();
6✔
520
                    logger.info("Listed {} pubkeys (format=html)", listed);
15✔
521
                }
522
            }
523
        } else {
524
            logger.warn("Invalid list request path: {}", getFullRequest());
15✔
525
            context.response().setStatusCode(400).setStatusMessage("Invalid request: " + getFullRequest());
27✔
526
        }
527
    }
3✔
528

529
    private static String getLabel(Object obj) {
530
        if (obj == null) {
6!
531
            return null;
×
532
        }
533
        if (obj.toString().length() < 10) {
15✔
534
            return obj.toString();
9✔
535
        }
536
        return obj.toString().substring(0, 10);
18✔
537
    }
538

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

© 2026 Coveralls, Inc