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

knowledgepixels / nanopub-registry / 25001949631

27 Apr 2026 02:47PM UTC coverage: 31.779% (-0.03%) from 31.808%
25001949631

Pull #113

github

web-flow
Merge 7b4fc273f into 377ce4430
Pull Request #113: feat: stamp foaf:name + dct:created of declaring intro on accounts

294 of 1028 branches covered (28.6%)

Branch coverage included in aggregate %.

851 of 2575 relevant lines covered (33.05%)

5.47 hits per line

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

26.98
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 org.bson.Document;
7

8
import java.io.IOException;
9
import java.util.List;
10

11
import static com.knowledgepixels.registry.RegistryDB.collection;
12
import static com.knowledgepixels.registry.Utils.*;
13
import static com.mongodb.client.model.Projections.exclude;
14
import static com.mongodb.client.model.Sorts.descending;
15

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

31
    private static final String SUPPORTED_TYPES = TYPE_JSON + "," + TYPE_HTML;
32

33
    public static void show(RoutingContext context) {
34
        TrustStatePage page;
35
        try (ClientSession s = RegistryDB.getClient().startSession()) {
9✔
36
            s.startTransaction();
6✔
37
            page = new TrustStatePage(s, context);
18✔
38
            page.show();
6✔
39
        } catch (IOException ex) {
×
40
            ex.printStackTrace();
×
41
        } finally {
42
            context.response().end();
12✔
43
        }
44
    }
3✔
45

46
    private TrustStatePage(ClientSession mongoSession, RoutingContext context) {
47
        super(mongoSession, context);
12✔
48
    }
3✔
49

50
    protected void show() throws IOException {
51
        RoutingContext c = getContext();
9✔
52
        String req = getRequestString();
9✔
53
        String ext = getExtension();
9✔
54

55
        String format;
56
        if ("json".equals(ext)) {
12✔
57
            format = TYPE_JSON;
9✔
58
        } else if (ext == null || "html".equals(ext)) {
18!
59
            format = Utils.getMimeType(c, SUPPORTED_TYPES);
×
60
        } else {
61
            c.response().setStatusCode(400).setStatusMessage("Invalid request: " + getFullRequest());
27✔
62
            return;
3✔
63
        }
64

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

71
        if ("/trust-state".equals(req) || "/trust-state/".equals(req)) {
24!
72
            showList(format);
×
73
        } else if (req.matches("/trust-state/[A-Za-z0-9_\\-]+")) {
12✔
74
            String hash = req.substring("/trust-state/".length());
15✔
75
            showDetail(hash, format);
12✔
76
        } else {
3✔
77
            c.response().setStatusCode(400).setStatusMessage("Invalid request: " + getFullRequest());
27✔
78
        }
79
    }
3✔
80

81
    private void showList(String format) {
82
        // Metadata only — the accounts array is heavy and not needed in the index.
83
        try (MongoCursor<Document> it = collection(Collection.TRUST_STATE_SNAPSHOTS.toString())
×
84
                .find(mongoSession)
×
85
                .projection(exclude("accounts"))
×
86
                .sort(descending("trustStateCounter"))
×
87
                .cursor()) {
×
88
            if (TYPE_JSON.equals(format)) {
×
89
                println("[");
×
90
                while (it.hasNext()) {
×
91
                    Document d = it.next();
×
92
                    Document out = new Document()
×
93
                            .append("trustStateHash", d.getString("_id"))
×
94
                            .append("trustStateCounter", d.get("trustStateCounter"))
×
95
                            .append("createdAt", d.get("createdAt"));
×
96
                    print(out.toJson());
×
97
                    println(it.hasNext() ? "," : "");
×
98
                }
×
99
                println("]");
×
100
            } else {
101
                printHtmlHeader("Trust State History - Nanopub Registry");
×
102
                println("<h1>Trust State History</h1>");
×
103
                println("<p><a href=\"/\">&lt; Home</a></p>");
×
104
                println("<h3>Formats</h3>");
×
105
                println("<p>");
×
106
                println("<a href=\"trust-state.json\">.json</a> |");
×
107
                println("<a href=\"trust-state.json.txt\">.json.txt</a>");
×
108
                println("</p>");
×
109
                println("<h3>Past Trust States</h3>");
×
110
                println("<ol>");
×
111
                while (it.hasNext()) {
×
112
                    Document d = it.next();
×
113
                    String hash = d.getString("_id");
×
114
                    println("<li>");
×
115
                    println("<a href=\"/trust-state/" + hash + "\"><code>" + getLabel(hash) + "</code></a>");
×
116
                    print(", counter " + d.get("trustStateCounter"));
×
117
                    Object createdAt = d.get("createdAt");
×
118
                    if (createdAt != null) {
×
119
                        print(", " + createdAt.toString().replaceFirst("\\.[^.]*$", ""));
×
120
                    }
121
                    println("</li>");
×
122
                }
×
123
                println("</ol>");
×
124
                printHtmlFooter();
×
125
            }
126
        }
127
    }
×
128

129
    private void showDetail(String hash, String format) {
130
        Document snapshot = collection(Collection.TRUST_STATE_SNAPSHOTS.toString())
30✔
131
                .find(mongoSession, new Document("_id", hash)).first();
12✔
132
        if (snapshot == null) {
6✔
133
            getContext().response().setStatusCode(404).setStatusMessage("Trust state snapshot not found");
24✔
134
            return;
3✔
135
        }
136

137
        if (TYPE_JSON.equals(format)) {
12!
138
            // Content is immutable by construction: the URL encodes a hash that never rewrites.
139
            getContext().response().putHeader("Cache-Control", "public, immutable, max-age=31536000");
21✔
140
            Document output = new Document()
18✔
141
                    .append("trustStateHash", snapshot.getString("_id"))
15✔
142
                    .append("trustStateCounter", snapshot.get("trustStateCounter"))
15✔
143
                    .append("createdAt", snapshot.get("createdAt"))
15✔
144
                    .append("accounts", snapshot.get("accounts"));
9✔
145
            print(output.toJson());
12✔
146
            return;
3✔
147
        }
148

149
        // HTML detail view
150
        printHtmlHeader("Trust State " + getLabel(hash) + " - Nanopub Registry");
×
151
        println("<h1>Trust State <code>" + getLabel(hash) + "</code></h1>");
×
152
        println("<p><a href=\"/trust-state\">&lt; Trust State History</a></p>");
×
153
        println("<h3>Formats</h3>");
×
154
        println("<p>");
×
155
        println("<a href=\"" + hash + ".json\">.json</a> |");
×
156
        println("<a href=\"" + hash + ".json.txt\">.json.txt</a>");
×
157
        println("</p>");
×
158
        println("<h3>Hash</h3>");
×
159
        println("<p><code>" + hash + "</code></p>");
×
160
        println("<h3>Metadata</h3>");
×
161
        println("<ul>");
×
162
        println("<li><em>trustStateCounter:</em> " + snapshot.get("trustStateCounter") + "</li>");
×
163
        Object createdAt = snapshot.get("createdAt");
×
164
        if (createdAt != null) {
×
165
            println("<li><em>createdAt:</em> " + createdAt.toString().replaceFirst("\\.[^.]*$", "") + "</li>");
×
166
        }
167
        Object accountsObj = snapshot.get("accounts");
×
168
        int accountCount = (accountsObj instanceof List) ? ((List<?>) accountsObj).size() : 0;
×
169
        println("<li><em>accountCount:</em> " + accountCount + "</li>");
×
170
        println("</ul>");
×
171
        println("<h3>Accounts</h3>");
×
172
        println("<ol>");
×
173
        if (accountsObj instanceof List) {
×
174
            for (Object entry : (List<?>) accountsObj) {
×
175
                if (!(entry instanceof Document)) continue;
×
176
                Document a = (Document) entry;
×
177
                String pubkey = a.getString("pubkey");
×
178
                String agent = a.getString("agent");
×
179
                String name = a.getString("name");
×
180
                println("<li>");
×
181
                println("<a href=\"/list/" + pubkey + "\"><code>" + getLabel(pubkey) + "</code></a>");
×
182
                if (agent != null && !agent.isBlank()) {
×
183
                    print(" by <a href=\"/agent?id=" + Utils.urlEncode(agent) + "\">" + Utils.getAgentLabel(agent) + "</a>");
×
184
                    if (name != null && !name.isBlank()) {
×
185
                        print(" (" + name + ")");
×
186
                    }
187
                }
188
                print(", status: " + a.get("status"));
×
189
                print(", depth: " + a.get("depth"));
×
190
                if (a.get("pathCount") != null) print(", pathCount: " + a.get("pathCount"));
×
191
                if (a.get("ratio") != null) print(", ratio: " + a.get("ratio"));
×
192
                if (a.get("quota") != null) print(", quota: " + a.get("quota"));
×
193
                println("</li>");
×
194
            }
×
195
        }
196
        println("</ol>");
×
197
        printHtmlFooter();
×
198
    }
×
199

200
    private static String getLabel(String s) {
201
        if (s == null) return null;
×
202
        if (s.length() < 10) return s;
×
203
        return s.substring(0, 10);
×
204
    }
205

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