A Minecraft Fabric mod that connects the game with ATProtocol ⛏️
1# AppView Guide: Displaying Minecraft Data on AT Protocol
2
3## Overview
4
5An **AppView** is a custom service that indexes AT Protocol records and provides rich display and query capabilities. The social-sync AppView allows players and community members to:
6
7- 📊 **View leaderboards** of top players by various statistics
8- 🏆 **Browse achievements** earned across the community
9- 👤 **See player profiles** with stats summaries
10- 🔥 **Discover trending** achievements and active players
11- 🔍 **Search** for players by username or display name
12
13## Architecture
14
15```
16AT Protocol Network
17 ↓ (Published Records)
18Firehose Subscription
19 ↓ (Real-time Events)
20AppView Service (Index & Query)
21 ↓ (HTTP Endpoints)
22Web Clients / Bluesky Custom Feeds
23```
24
25### Components
26
271. **AppViewService**: In-memory indexing and querying (uses database in production)
282. **AppViewHttpServer**: REST API for serving data to clients
293. **Firehose Subscription**: Real-time updates from AT Protocol
304. **Data Models**: Records synced from Minecraft via social-sync mod
31
32## How It Works
33
34### 1. Publishing Records
35
36When a player syncs data with the social-sync mod:
37
38```kotlin
39// Player syncs their stats
40val stats = PlayerStatsRecord(
41 player = PlayerReference("uuid", "AlicePlayer"),
42 statistics = listOf(
43 Statistic("minecraft.mined.oak_log", 1250)
44 ),
45 playtimeMinutes = 7200,
46 level = 34,
47 syncedAt = now()
48)
49
50// Record published to AT Protocol
51recordManager.createRecord(playerUuid, "com.jollywhoppers.minecraft.player.stats", stats)
52// Result: at://did:plc:alice/com.jollywhoppers.minecraft.player.stats/8l6rvp4j6d3e2c4b9
53```
54
55### 2. Indexing Records
56
57The AppView subscribes to repository events and indexes published records:
58
59```kotlin
60appViewService.indexPlayerStats(uri, statsJson)
61// Now searchable and queryable through leaderboards
62```
63
64### 3. Querying Data
65
66Clients query the AppView through HTTP endpoints:
67
68```bash
69# Get player stats
70curl https://appview.example.com/player/550e8400-e29b-41d4-a716-446655440000/stats
71
72# Get leaderboard
73curl https://appview.example.com/leaderboard/minecraft.mined.oak_log
74
75# Search players
76curl "https://appview.example.com/search?q=Alice"
77```
78
79## API Endpoints
80
81### Health Check
82
83```
84GET /health
85
86Response:
87{
88 "success": true,
89 "data": {
90 "status": "healthy",
91 "version": "1.0.0",
92 "uptime": 3600
93 }
94}
95```
96
97### Get Player Profile
98
99```
100GET /player/{uuid}
101
102Response:
103{
104 "success": true,
105 "data": {
106 "profile": {
107 "did": "did:plc:alice123",
108 "playerUuid": "550e8400-e29b-41d4-a716-446655440000",
109 "username": "AlicePlayer",
110 "displayName": "Alice",
111 "bio": "Minecraft enthusiast and builder",
112 "publicStats": true,
113 "publicSessions": true,
114 "createdAt": "2026-04-20T10:30:00Z"
115 },
116 "latestStats": {
117 "level": 34,
118 "playtimeMinutes": 7200,
119 "gamemode": "survival"
120 },
121 "statsCount": 5,
122 "achievementCount": 23
123 },
124 "timestamp": 1703004000000
125}
126```
127
128### Get Player Stats History
129
130```
131GET /player/{uuid}/stats?limit=10&offset=0
132
133Response:
134{
135 "success": true,
136 "data": [
137 {
138 "uri": "at://did:plc:alice123/.../8l6rvp4j6d3e2c4b9",
139 "playerUuid": "550e8400-e29b-41d4-a716-446655440000",
140 "username": "AlicePlayer",
141 "server": "Main SMP",
142 "level": 34,
143 "playtimeMinutes": 7200,
144 "gamemode": "survival",
145 "statistics": [
146 {"key": "minecraft.mined.oak_log", "value": 1250},
147 {"key": "minecraft.killed.zombie", "value": 425}
148 ],
149 "syncedAt": "2026-04-25T14:22:00Z"
150 }
151 ],
152 "pagination": {
153 "limit": 10,
154 "offset": 0,
155 "count": 1
156 }
157}
158```
159
160### Get Player Achievements
161
162```
163GET /player/{uuid}/achievements?limit=25&offset=0
164
165Response:
166{
167 "success": true,
168 "data": [
169 {
170 "uri": "at://did:plc:alice123/.../8l6rvp4j6d3e2c5a7",
171 "playerUuid": "550e8400-e29b-41d4-a716-446655440000",
172 "username": "AlicePlayer",
173 "server": "Main SMP",
174 "achievementId": "minecraft:adventure/kill_a_mob",
175 "achievementName": "Monster Hunter",
176 "achievementDescription": "Kill any type of monster",
177 "category": "adventure",
178 "isChallenge": false,
179 "achievedAt": "2026-04-24T15:45:00Z"
180 }
181 ],
182 "pagination": {
183 "limit": 25,
184 "offset": 0,
185 "count": 1
186 }
187}
188```
189
190### Get Leaderboard
191
192```
193GET /leaderboard/{statistic}?limit=20
194
195Example: /leaderboard/minecraft.mined.oak_log
196
197Response:
198{
199 "success": true,
200 "data": [
201 {
202 "playerUuid": "550e8400-e29b-41d4-a716-446655440000",
203 "username": "AlicePlayer",
204 "server": "Main SMP",
205 "statistic": "minecraft.mined.oak_log",
206 "value": 5250,
207 "recordedAt": "2026-04-25T14:22:00Z"
208 },
209 {
210 "playerUuid": "660e8400-e29b-41d4-a716-446655440001",
211 "username": "BobBuilder",
212 "server": "Main SMP",
213 "statistic": "minecraft.mined.oak_log",
214 "value": 4100,
215 "recordedAt": "2026-04-25T13:15:00Z"
216 }
217 ],
218 "pagination": {
219 "limit": 20,
220 "offset": 0,
221 "count": 2
222 }
223}
224```
225
226### Search Players
227
228```
229GET /search?q={query}
230
231Example: /search?q=Alice
232
233Response:
234{
235 "success": true,
236 "data": [
237 {
238 "did": "did:plc:alice123",
239 "playerUuid": "550e8400-e29b-41d4-a716-446655440000",
240 "username": "AlicePlayer",
241 "displayName": "Alice",
242 "bio": "Minecraft enthusiast and builder",
243 "publicStats": true,
244 "publicSessions": true
245 }
246 ],
247 "timestamp": 1703004000000
248}
249```
250
251### Get Trending Achievements
252
253```
254GET /trending/achievements?limit=10
255
256Response:
257{
258 "success": true,
259 "data": [
260 {
261 "achievementId": "minecraft:adventure/kill_a_mob",
262 "achievementName": "Monster Hunter",
263 "category": "adventure",
264 "timesEarned": 47,
265 "recentlyEarnedBy": ["AlicePlayer", "BobBuilder", "Charlie"]
266 },
267 {
268 "achievementId": "minecraft:story/mine_stone",
269 "achievementName": "Stone Age",
270 "category": "story",
271 "timesEarned": 89,
272 "recentlyEarnedBy": ["Diana", "Eve", "Frank"]
273 }
274 ],
275 "timestamp": 1703004000000
276}
277```
278
279### Get Stats Summary
280
281```
282GET /stats/summary/{uuid}
283
284Response:
285{
286 "success": true,
287 "data": {
288 "playerUuid": "550e8400-e29b-41d4-a716-446655440000",
289 "username": "AlicePlayer",
290 "playtimeMinutes": 7200,
291 "level": 34,
292 "gamemode": "survival",
293 "server": "Main SMP",
294 "topStatistics": [
295 {"key": "minecraft.mined.oak_log", "value": 5250},
296 {"key": "minecraft.mined.stone", "value": 4100},
297 {"key": "minecraft.killed.zombie", "value": 425}
298 ],
299 "lastSyncedAt": "2026-04-25T14:22:00Z"
300 },
301 "timestamp": 1703004000000
302}
303```
304
305## Implementation Examples
306
307### Example 1: Index a Player Profile
308
309```kotlin
310val profileRecord = json.parseToJsonElement("""
311{
312 "$type": "com.jollywhoppers.minecraft.player.profile",
313 "player": {
314 "uuid": "550e8400-e29b-41d4-a716-446655440000",
315 "username": "AlicePlayer"
316 },
317 "displayName": "Alice",
318 "bio": "Minecraft enthusiast",
319 "publicStats": true,
320 "publicSessions": true,
321 "createdAt": "2026-04-20T10:30:00Z"
322}
323""")
324
325appViewService.indexPlayerProfile(
326 uri = "at://did:plc:alice123/com.jollywhoppers.minecraft.player.profile/self",
327 record = profileRecord
328)
329```
330
331### Example 2: Query Leaderboard
332
333```kotlin
334val leaderboard = appViewService.getLeaderboard(
335 statisticKey = "minecraft.mined.oak_log",
336 limit = 10
337).getOrNull()
338
339leaderboard?.forEach { entry ->
340 println("${entry.username}: ${entry.value} blocks")
341}
342```
343
344### Example 3: Search for Players
345
346```kotlin
347val results = appViewService.searchPlayers("Alice").getOrNull()
348
349results?.forEach { player ->
350 println("${player.username} - ${player.displayName}")
351}
352```
353
354## Production Deployment
355
356### Recommended Stack
357
358- **Framework**: Ktor or Spring Boot for HTTP server
359- **Database**: PostgreSQL for record indexing and caching
360- **Cache**: Redis for leaderboard and trending queries
361- **Messaging**: AT Protocol Firehose for real-time updates
362- **Hosting**: Docker container on cloud provider
363
364### Key Considerations
365
3661. **Indexing**: Subscribe to the AT Protocol Firehose to capture published records
3672. **Databases**: Use PostgreSQL to store indexed records for fast queries
3683. **Caching**: Cache leaderboards and trending data with Redis
3694. **Pagination**: Implement cursor-based pagination for large result sets
3705. **Filtering**: Support filtering by server, time period, or category
3716. **Performance**: Add indexes on frequently queried fields
3727. **Privacy**: Respect `publicStats` and `publicSessions` flags
3738. **Registration**: Register your AppView in the AT Protocol registry
374
375### Sample Ktor Implementation
376
377```kotlin
378import io.ktor.server.application.*
379import io.ktor.server.routing.*
380import io.ktor.server.response.*
381
382fun main() {
383 embeddedServer(Netty, port = 8080) {
384 routing {
385 get("/player/{uuid}") {
386 val uuid = call.parameters["uuid"]
387 val profile = appViewService.getPlayerProfile(uuid!!)
388 call.respond(httpServer.handleGetPlayerProfile(uuid))
389 }
390
391 get("/leaderboard/{stat}") {
392 val stat = call.parameters["stat"]
393 val limit = call.parameters["limit"]?.toInt() ?: 20
394 call.respond(httpServer.handleGetLeaderboard(stat!!, limit.toString()))
395 }
396 }
397 }.start(wait = true)
398}
399```
400
401## Integration with AT Protocol Clients
402
403### Bluesky Custom Feeds
404
405AppViews can be used to create custom feeds in Bluesky:
406
407- **"Top Builders"**: Feed of recent high-value block mining achievements
408- **"Achievement Feeds"**: Achievements in specific categories
409- **"Speedrun Records"**: Fastest times for specific challenges
410- **"Server Highlights"**: Notable stats from specific servers
411
412### Web Dashboard
413
414Embed AppView data in a custom web dashboard:
415
416```html
417<div id="leaderboard">
418 <!-- JavaScript fetches from /leaderboard/minecraft.mined.oak_log -->
419</div>
420```
421
422## Data Privacy
423
424- **Public by Default**: All AT Protocol data is public
425- **Opt-in Syncing**: Players control sync consent in `/atproto sync`
426- **Respect Flags**: Check `publicStats` and `publicSessions` before displaying
427- **Anonymous Option**: Players can disable syncing entirely
428
429## Monitoring
430
431Monitor your AppView with these metrics:
432
433- **Indexing Latency**: Time from publish to index
434- **Query Performance**: Response times for popular queries
435- **Cache Hit Ratio**: Percentage of queries served from cache
436- **Record Count**: Total indexed records by type
437- **Active Players**: Number of players with data published
438
439## Troubleshooting
440
441### No Data in Queries
442
4431. Ensure Firehose subscription is active and receiving events
4442. Verify records are being published by players (check `/atproto sync` settings)
4453. Check indexing function is being called on record events
446
447### Slow Leaderboard Queries
448
4491. Add database indexes on statistic keys
4502. Implement caching layer (Redis)
4513. Consider pre-computing leaderboards periodically
452
453### Missing Records
454
4551. Verify player privacy settings allow public stats
4562. Check server subscription is capturing all record types
4573. Review error logs for indexing failures
458
459## Examples
460
461See `docs/examples/AppViewExample.kt` for complete working examples.
462
463## References
464
465- [AT Protocol AppView Documentation](https://atproto.com/specs/app-view)
466- [Lexicon Specifications](https://atproto.com/specs/lexicon)
467- [XRPC API Reference](https://atproto.com/specs/xrpc)
468- [Bluesky Custom Feeds](https://docs.bsky.app/docs/tutorials/custom-feeds)
469
470## Next Steps
471
4721. **Deploy AppView service** to a public URL
4732. **Subscribe to Firehose** for real-time record updates
4743. **Set up database** for production indexing
4754. **Register AppView** in AT Protocol registry
4765. **Publish to Bluesky** for community integration