Testing implementation for private data in ATProto with ATPKeyserver and ATCute tools
2
fork

Configure Feed

Select the types of activity you want to include in your feed.

getRelationship method

+147 -1
+43
lexicons/app/wafrn/graph/getRelationship.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "app.wafrn.graph.getRelationship", 4 + "defs": { 5 + "main": { 6 + "type": "query", 7 + "description": "Get the follow relationship between two users (bidirectional)", 8 + "parameters": { 9 + "type": "params", 10 + "required": ["actor", "viewer"], 11 + "properties": { 12 + "actor": { 13 + "type": "string", 14 + "format": "did", 15 + "description": "DID of the actor (typically the profile being viewed)" 16 + }, 17 + "viewer": { 18 + "type": "string", 19 + "format": "did", 20 + "description": "DID of the viewer (typically the current user)" 21 + } 22 + } 23 + }, 24 + "output": { 25 + "encoding": "application/json", 26 + "schema": { 27 + "type": "object", 28 + "required": ["following", "followedBy"], 29 + "properties": { 30 + "following": { 31 + "type": "boolean", 32 + "description": "True if viewer follows actor" 33 + }, 34 + "followedBy": { 35 + "type": "boolean", 36 + "description": "True if actor follows viewer" 37 + } 38 + } 39 + } 40 + } 41 + } 42 + } 43 + }
+1
packages/lexicon/index.ts
··· 11 11 export * as AppWafrnGraphFollow from "./types/app/wafrn/graph/follow.js"; 12 12 export * as AppWafrnGraphGetFollowers from "./types/app/wafrn/graph/getFollowers.js"; 13 13 export * as AppWafrnGraphGetFollows from "./types/app/wafrn/graph/getFollows.js"; 14 + export * as AppWafrnGraphGetRelationship from "./types/app/wafrn/graph/getRelationship.js"; 14 15 export * as ComAtprotoRepoStrongRef from "./types/com/atproto/repo/strongRef.js";
+44
packages/lexicon/types/app/wafrn/graph/getRelationship.ts
··· 1 + import type {} from "@atcute/lexicons"; 2 + import * as v from "@atcute/lexicons/validations"; 3 + import type {} from "@atcute/lexicons/ambient"; 4 + 5 + const _mainSchema = /*#__PURE__*/ v.query("app.wafrn.graph.getRelationship", { 6 + params: /*#__PURE__*/ v.object({ 7 + /** 8 + * DID of the actor (typically the profile being viewed) 9 + */ 10 + actor: /*#__PURE__*/ v.didString(), 11 + /** 12 + * DID of the viewer (typically the current user) 13 + */ 14 + viewer: /*#__PURE__*/ v.didString(), 15 + }), 16 + output: { 17 + type: "lex", 18 + schema: /*#__PURE__*/ v.object({ 19 + /** 20 + * True if actor follows viewer 21 + */ 22 + followedBy: /*#__PURE__*/ v.boolean(), 23 + /** 24 + * True if viewer follows actor 25 + */ 26 + following: /*#__PURE__*/ v.boolean(), 27 + }), 28 + }, 29 + }); 30 + 31 + type main$schematype = typeof _mainSchema; 32 + 33 + export interface mainSchema extends main$schematype {} 34 + 35 + export const mainSchema = _mainSchema as mainSchema; 36 + 37 + export interface $params extends v.InferInput<mainSchema["params"]> {} 38 + export interface $output extends v.InferXRPCBodyInput<mainSchema["output"]> {} 39 + 40 + declare module "@atcute/lexicons/ambient" { 41 + interface XRPCQueries { 42 + "app.wafrn.graph.getRelationship": mainSchema; 43 + } 44 + }
+41
packages/server/CLAUDE.md
··· 228 228 229 229 **Design rationale**: Post feeds are the hottest query path in the application. By making counts optional and defaulting to `false`, we optimize the most common case while supporting full profile views when needed. 230 230 231 + **Checking User Relationships**: 232 + 233 + The `app.wafrn.graph.getRelationship` endpoint checks the bidirectional follow relationship between two users: 234 + 235 + ```typescript 236 + // Check relationship between viewer and profile 237 + const relationship = await serverClient.get('app.wafrn.graph.getRelationship', { 238 + params: { 239 + actor: profileUserDid, // Profile being viewed 240 + viewer: currentUserDid // Current user viewing 241 + } 242 + }) 243 + 244 + // Display appropriate UI based on relationship 245 + if (relationship.following && relationship.followedBy) { 246 + // Mutual follows - show "Following" with mutual badge 247 + return <button className="btn">Following · Mutual</button> 248 + } else if (relationship.following) { 249 + // You follow them - show "Following" 250 + return <button className="btn">Following</button> 251 + } else if (relationship.followedBy) { 252 + // They follow you - show "Follow Back" 253 + return <button className="btn btn-primary">Follow Back</button> 254 + } else { 255 + // No relationship - show "Follow" 256 + return <button className="btn btn-primary">Follow</button> 257 + } 258 + ``` 259 + 260 + **Response fields**: 261 + - `following` - True if viewer follows actor 262 + - `followedBy` - True if actor follows viewer 263 + 264 + **Common UI patterns**: 265 + - No relationship: "Follow" button 266 + - Viewer follows actor: "Following" button 267 + - Actor follows viewer: "Follow Back" button (encourages reciprocation) 268 + - Mutual follows: "Following" button with "Mutual" badge 269 + 270 + **Performance**: Uses `Promise.all` to check both directions in parallel (efficient). 271 + 231 272 ### Path Aliases 232 273 233 274 The server uses the `@api/*` path alias for clean internal imports:
+18 -1
packages/server/src/lib/xrpcServer.ts
··· 9 9 AppWafrnGraphDeleteFollow, 10 10 AppWafrnGraphGetFollowers, 11 11 AppWafrnGraphGetFollows, 12 + AppWafrnGraphGetRelationship, 12 13 AppWafrnActorGetProfiles 13 14 } from '@watproto/lexicon' 14 15 import { didDocResolver } from './idResolver' ··· 20 21 deleteFollow, 21 22 getFollowCountsBatch, 22 23 getFollowers, 23 - getFollowing 24 + getFollowing, 25 + isFollowing 24 26 } from '@api/lib/follow' 25 27 import { getWafrnProfiles } from '@api/lib/profile' 26 28 ··· 321 323 createdAt: new Date(f.created_at).toISOString(), 322 324 uri: f.uri as ResourceUri 323 325 })) 326 + }) 327 + } 328 + }) 329 + 330 + xrpcServer.addQuery(AppWafrnGraphGetRelationship.mainSchema, { 331 + async handler({ params }) { 332 + // Check both directions in parallel 333 + const [following, followedBy] = await Promise.all([ 334 + isFollowing(params.viewer, params.actor), // viewer -> actor 335 + isFollowing(params.actor, params.viewer) // actor -> viewer 336 + ]) 337 + 338 + return json({ 339 + following, // Does viewer follow actor? 340 + followedBy // Does actor follow viewer? 324 341 }) 325 342 } 326 343 })