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

knowledgepixels / nanopub-registry / 28156569757

25 Jun 2026 08:16AM UTC coverage: 31.844% (-0.08%) from 31.926%
28156569757

push

github

web-flow
Merge pull request #118 from knowledgepixels/feat/intro-nanopub-trust-state

feat(trust-state): record authorizing introNanopub on account rows

313 of 1110 branches covered (28.2%)

Branch coverage included in aggregate %.

1048 of 3164 relevant lines covered (33.12%)

5.5 hits per line

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

28.25
src/main/java/com/knowledgepixels/registry/TrustStatePage.java
1
package com.knowledgepixels.registry;
2

3
import com.mongodb.client.ClientSession;
4
import com.mongodb.client.MongoCursor;
5
import io.vertx.ext.web.RoutingContext;
6
import net.trustyuri.TrustyUriUtils;
7
import org.bson.Document;
8
import org.slf4j.Logger;
9
import org.slf4j.LoggerFactory;
10

11
import java.io.IOException;
12
import java.util.List;
13

14
import static com.knowledgepixels.registry.RegistryDB.collection;
15
import static com.knowledgepixels.registry.Utils.TYPE_HTML;
16
import static com.knowledgepixels.registry.Utils.TYPE_JSON;
17
import static com.mongodb.client.model.Projections.exclude;
18
import static com.mongodb.client.model.Sorts.descending;
19

20
/**
21
 * Serves hash-keyed trust state snapshots.
22
 *
23
 * <ul>
24
 *   <li><code>/trust-state</code> — list of retained snapshots (HTML or JSON)</li>
25
 *   <li><code>/trust-state/&lt;hash&gt;</code> — single snapshot (HTML or JSON)</li>
26
 * </ul>
27
 *
28
 * <p>Snapshot content is immutable once a hash is known. Consumers use
29
 * <code>/trust-state/&lt;hash&gt;.json</code> after detecting a hash change (via the
30
 * {@code Nanopub-Registry-Trust-State-Hash} response header) to load the corresponding
31
 * snapshot side-by-side with the previous one.
32
 */
33
public class TrustStatePage extends Page {
34

35
    private static final Logger logger = LoggerFactory.getLogger(TrustStatePage.class);
12✔
36

37
    private static final String SUPPORTED_TYPES = TYPE_JSON + "," + TYPE_HTML;
38

39
    public static void show(RoutingContext context) {
40
        TrustStatePage page;
41
        logger.info("Received trust-state request: {}", context.request().path());
18✔
42
        try (ClientSession s = RegistryDB.getClient().startSession()) {
9✔
43
            s.startTransaction();
6✔
44
            page = new TrustStatePage(s, context);
18✔
45
            page.show();
6✔
46
        } catch (IOException ex) {
×
47
            logger.warn("Failed to show trust-state for request {}: {} ({})", context.request().path(), ex.getMessage(), ex.getClass().getSimpleName(), ex);
×
48
        } finally {
49
            logger.debug("Ending response for trust-state request: {}", context.request().path());
18✔
50
            context.response().end();
12✔
51
        }
52
    }
3✔
53

54
    private TrustStatePage(ClientSession mongoSession, RoutingContext context) {
55
        super(mongoSession, context);
12✔
56
    }
3✔
57

58
    protected void show() throws IOException {
59
        RoutingContext c = getContext();
9✔
60
        String req = getRequestString();
9✔
61
        String ext = getExtension();
9✔
62

63
        logger.debug("Preparing trust-state response for request: {} (ext={})", getFullRequest(), ext);
18✔
64

65
        String format;
66
        if ("json".equals(ext)) {
12✔
67
            format = TYPE_JSON;
9✔
68
        } else if (ext == null || "html".equals(ext)) {
18!
69
            format = Utils.getMimeType(c, SUPPORTED_TYPES);
×
70
        } else {
71
            logger.warn("Invalid trust-state request (unsupported extension) for {}: {}", getFullRequest(), ext);
18✔
72
            c.response().setStatusCode(400).setStatusMessage("Invalid request: " + getFullRequest());
27✔
73
            return;
3✔
74
        }
75

76
        if (getPresentationFormat() != null) {
9!
77
            setRespContentType(getPresentationFormat());
×
78
            logger.debug("Overriding response content type with presentation format: {}", getPresentationFormat());
×
79
        } else {
80
            setRespContentType(format);
9✔
81
            logger.debug("Set response content type: {}", format);
12✔
82
        }
83

84
        if ("/trust-state".equals(req) || "/trust-state/".equals(req)) {
24!
85
            showList(format);
×
86
        } else if (req.matches("/trust-state/[A-Za-z0-9_\\-]+")) {
12✔
87
            String hash = req.substring("/trust-state/".length());
15✔
88
            logger.debug("Resolved snapshot hash '{}' from request path", hash);
12✔
89
            showDetail(hash, format);
12✔
90
        } else {
3✔
91
            logger.warn("Invalid trust-state request path: {}", getFullRequest());
15✔
92
            c.response().setStatusCode(400).setStatusMessage("Invalid request: " + getFullRequest());
27✔
93
        }
94
    }
3✔
95

96
    private void showList(String format) {
97
        int listed = 0;
×
98
        logger.debug("Querying trust-state snapshots collection (format={})", format);
×
99
        // Metadata only — the accounts array is heavy and not needed in the index.
100
        try (MongoCursor<Document> it = collection(Collection.TRUST_STATE_SNAPSHOTS.toString())
×
101
                .find(mongoSession)
×
102
                .projection(exclude("accounts"))
×
103
                .sort(descending("trustStateCounter"))
×
104
                .cursor()) {
×
105
            if (TYPE_JSON.equals(format)) {
×
106
                println("[");
×
107
                while (it.hasNext()) {
×
108
                    Document d = it.next();
×
109
                    Document out = new Document()
×
110
                            .append("trustStateHash", d.getString("_id"))
×
111
                            .append("trustStateCounter", d.get("trustStateCounter"))
×
112
                            .append("createdAt", d.get("createdAt"));
×
113
                    print(out.toJson());
×
114
                    println(it.hasNext() ? "," : "");
×
115
                    listed++;
×
116
                }
×
117
                println("]");
×
118
                logger.info("Listed {} trust-state snapshots (format=json) for {}", listed, getFullRequest());
×
119
            } else {
120
                printHtmlHeader("Trust State History - Nanopub Registry");
×
121
                println("<h1>Trust State History</h1>");
×
122
                println("<p><a href=\"/\">&lt; Home</a></p>");
×
123
                println("<h3>Formats</h3>");
×
124
                println("<p>");
×
125
                println("<a href=\"trust-state.json\">.json</a> |");
×
126
                println("<a href=\"trust-state.json.txt\">.json.txt</a>");
×
127
                println("</p>");
×
128
                println("<h3>Past Trust States</h3>");
×
129
                println("<ol>");
×
130
                while (it.hasNext()) {
×
131
                    Document d = it.next();
×
132
                    String hash = d.getString("_id");
×
133
                    println("<li>");
×
134
                    println("<a href=\"/trust-state/" + hash + "\"><code>" + getLabel(hash) + "</code></a>");
×
135
                    print(", counter " + d.get("trustStateCounter"));
×
136
                    Object createdAt = d.get("createdAt");
×
137
                    if (createdAt != null) {
×
138
                        print(", " + createdAt.toString().replaceFirst("\\.[^.]*$", ""));
×
139
                    }
140
                    println("</li>");
×
141
                }
×
142
                println("</ol>");
×
143
                printHtmlFooter();
×
144
                logger.info("Listed {} trust-state snapshots (format=html) for {}", listed, getFullRequest());
×
145
            }
146
        }
147
    }
×
148

149
    private void showDetail(String hash, String format) {
150
        logger.debug("Looking up trust-state snapshot '{}'", hash);
12✔
151
        Document snapshot = collection(Collection.TRUST_STATE_SNAPSHOTS.toString())
30✔
152
                .find(mongoSession, new Document("_id", hash)).first();
12✔
153
        if (snapshot == null) {
6✔
154
            logger.warn("Trust state snapshot not found: {} (request={})", hash, getFullRequest());
18✔
155
            getContext().response().setStatusCode(404).setStatusMessage("Trust state snapshot not found");
24✔
156
            return;
3✔
157
        }
158
        logger.info("Serving trust state snapshot {} (format={}) for {}", hash, format, getFullRequest());
54✔
159

160
        if (TYPE_JSON.equals(format)) {
12!
161
            // Content is immutable by construction: the URL encodes a hash that never rewrites.
162
            getContext().response().putHeader("Cache-Control", "public, immutable, max-age=31536000");
21✔
163
            logger.debug("Set immutable cache-control for snapshot {}", hash);
12✔
164
            Document output = new Document()
18✔
165
                    .append("trustStateHash", snapshot.getString("_id"))
15✔
166
                    .append("trustStateCounter", snapshot.get("trustStateCounter"))
15✔
167
                    .append("createdAt", snapshot.get("createdAt"))
15✔
168
                    .append("accounts", snapshot.get("accounts"));
9✔
169
            print(output.toJson());
12✔
170
            return;
3✔
171
        }
172

173
        // HTML detail view
174
        printHtmlHeader("Trust State " + getLabel(hash) + " - Nanopub Registry");
×
175
        println("<h1>Trust State <code>" + getLabel(hash) + "</code></h1>");
×
176
        println("<p><a href=\"/trust-state\">&lt; Trust State History</a></p>");
×
177
        println("<h3>Formats</h3>");
×
178
        println("<p>");
×
179
        println("<a href=\"" + hash + ".json\">.json</a> |");
×
180
        println("<a href=\"" + hash + ".json.txt\">.json.txt</a>");
×
181
        println("</p>");
×
182
        println("<h3>Hash</h3>");
×
183
        println("<p><code>" + hash + "</code></p>");
×
184
        println("<h3>Metadata</h3>");
×
185
        println("<ul>");
×
186
        println("<li><em>trustStateCounter:</em> " + snapshot.get("trustStateCounter") + "</li>");
×
187
        Object createdAt = snapshot.get("createdAt");
×
188
        if (createdAt != null) {
×
189
            println("<li><em>createdAt:</em> " + createdAt.toString().replaceFirst("\\.[^.]*$", "") + "</li>");
×
190
        }
191
        Object accountsObj = snapshot.get("accounts");
×
192
        int accountCount = (accountsObj instanceof List) ? ((List<?>) accountsObj).size() : 0;
×
193
        println("<li><em>accountCount:</em> " + accountCount + "</li>");
×
194
        logger.debug("Snapshot {} has {} account entries (accountsObj type: {})", hash, accountCount, accountsObj == null ? "null" : accountsObj.getClass().getSimpleName());
×
195
        println("</ul>");
×
196
        println("<h3>Accounts</h3>");
×
197
        println("<ol>");
×
198
        if (accountsObj instanceof List) {
×
199
            for (Object entry : (List<?>) accountsObj) {
×
200
                if (!(entry instanceof Document)) {
×
201
                    continue;
×
202
                }
203
                Document a = (Document) entry;
×
204
                String pubkey = a.getString("pubkey");
×
205
                String agent = a.getString("agent");
×
206
                String name = a.getString("name");
×
207
                println("<li>");
×
208
                println("<a href=\"/list/" + pubkey + "\"><code>" + getLabel(pubkey) + "</code></a>");
×
209
                if (agent != null && !agent.isBlank()) {
×
210
                    print(" by <a href=\"/agent?id=" + Utils.urlEncode(agent) + "\">" + Utils.getAgentLabel(agent) + "</a>");
×
211
                    if (name != null && !name.isBlank()) {
×
212
                        print(" (" + name + ")");
×
213
                    }
214
                }
215
                String introNanopub = a.getString("introNanopub");
×
216
                if (introNanopub != null && !introNanopub.isBlank()) {
×
217
                    // introNanopub is a full nanopub URI; link via the in-registry /np/<artifactCode>
218
                    // convention (see ListPage), not urlEncode (that escapes the scheme too).
219
                    print(" via <a href=\"/np/" + TrustyUriUtils.getArtifactCode(introNanopub) + "\">intro</a>");
×
220
                }
221
                print(", status: " + a.get("status"));
×
222
                print(", depth: " + a.get("depth"));
×
223
                if (a.get("pathCount") != null) {
×
224
                    print(", pathCount: " + a.get("pathCount"));
×
225
                }
226
                if (a.get("ratio") != null) {
×
227
                    print(", ratio: " + a.get("ratio"));
×
228
                }
229
                if (a.get("quota") != null) {
×
230
                    print(", quota: " + a.get("quota"));
×
231
                }
232
                println("</li>");
×
233
            }
×
234
        }
235
        println("</ol>");
×
236
        printHtmlFooter();
×
237
    }
×
238

239
    private static String getLabel(String s) {
240
        if (s == null) {
×
241
            return null;
×
242
        }
243
        if (s.length() < 10) {
×
244
            return s;
×
245
        }
246
        return s.substring(0, 10);
×
247
    }
248

249
}
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