decentralized and customizable links page on top of atproto ligo.at
atproto link-in-bio python uv

cache profile and links when updating them

+48 -30
+48 -30
src/main.py
··· 120 121 async with ClientSession() as client: 122 (profile, from_bluesky), links = await asyncio.gather( 123 - load_profile(client, pds, did, reload=True), 124 - load_links(client, pds, did, reload=True), 125 ) 126 127 return render_template( ··· 140 return redirect("/login", 303) 141 142 display_name = request.form.get("displayName") 143 - description = request.form.get("description") or "" 144 if not display_name: 145 return redirect("/editor", 303) 146 147 - await put_record( 148 user=user, 149 pds=user.pds_url, 150 repo=user.did, 151 collection=f"{SCHEMA}.actor.profile", 152 rkey="self", 153 - record={ 154 - "$type": f"{SCHEMA}.actor.profile", 155 - "displayName": display_name, 156 - "description": description, 157 - }, 158 ) 159 160 - return redirect("/editor", 303) 161 162 163 @app.post("/editor/links") ··· 183 link["subtitle"] = subtitle 184 links.append(link) 185 186 - await put_record( 187 user=user, 188 pds=user.pds_url, 189 repo=user.did, 190 collection=f"{SCHEMA}.actor.links", 191 rkey="self", 192 - record={ 193 - "$type": f"{SCHEMA}.actor.links", 194 - "links": links, 195 - }, 196 ) 197 198 - return redirect("/editor", 303) 199 200 201 @app.get("/terms") ··· 210 reload: bool = False, 211 ) -> list[dict[str, str]] | None: 212 kv = KV(app, app.logger, "links_from_did") 213 - recordstr = kv.get(did) 214 215 - if recordstr is not None and not reload: 216 - return json.loads(recordstr)["links"] 217 218 record = await get_record(client, pds, did, f"{SCHEMA}.actor.links", "self") 219 if record is None: ··· 231 reload: bool = False, 232 ) -> tuple[dict[str, str] | None, bool]: 233 kv = KV(app, app.logger, "profile_from_did") 234 - recordstr = kv.get(did) 235 236 - if recordstr is not None and not reload: 237 - return json.loads(recordstr), False 238 239 from_bluesky = False 240 - record = await get_record(client, pds, did, f"{SCHEMA}.actor.profile", "self") 241 if record is None and fallback_with_bluesky: 242 - record = await get_record(client, pds, did, "app.bsky.actor.profile", "self") 243 from_bluesky = True 244 - if record is None: 245 - return None, False 246 247 - kv.set(did, value=json.dumps(record)) 248 return record, from_bluesky 249 250 ··· 256 collection: str, 257 rkey: str, 258 record: dict[str, Any], 259 - ): 260 endpoint = f"{pds}/xrpc/com.atproto.repo.putRecord" 261 body = { 262 "repo": repo, ··· 276 user=user, 277 update_dpop_pds_nonce=update_dpop_pds_nonce, 278 ) 279 - if not response or not response.ok: 280 - app.logger.warning("PDS HTTP ERROR") 281 282 283 def _is_did_blocked(did: str) -> bool:
··· 120 121 async with ClientSession() as client: 122 (profile, from_bluesky), links = await asyncio.gather( 123 + load_profile(client, pds, did), 124 + load_links(client, pds, did), 125 ) 126 127 return render_template( ··· 140 return redirect("/login", 303) 141 142 display_name = request.form.get("displayName") 143 + description = request.form.get("description", "") 144 if not display_name: 145 return redirect("/editor", 303) 146 147 + record = { 148 + "$type": f"{SCHEMA}.actor.profile", 149 + "displayName": display_name, 150 + "description": description, 151 + } 152 + 153 + success = await put_record( 154 user=user, 155 pds=user.pds_url, 156 repo=user.did, 157 collection=f"{SCHEMA}.actor.profile", 158 rkey="self", 159 + record=record, 160 ) 161 162 + if success: 163 + kv = KV(app, app.logger, "profile_from_did") 164 + kv.set(user.did, json.dumps(record)) 165 + 166 + return redirect(url_for("page_editor"), 303) 167 168 169 @app.post("/editor/links") ··· 189 link["subtitle"] = subtitle 190 links.append(link) 191 192 + record = { 193 + "$type": f"{SCHEMA}.actor.links", 194 + "links": links, 195 + } 196 + 197 + success = await put_record( 198 user=user, 199 pds=user.pds_url, 200 repo=user.did, 201 collection=f"{SCHEMA}.actor.links", 202 rkey="self", 203 + record=record, 204 ) 205 206 + if success: 207 + kv = KV(app, app.logger, "links_from_did") 208 + kv.set(user.did, json.dumps(record)) 209 + 210 + return redirect(url_for("page_editor"), 303) 211 212 213 @app.get("/terms") ··· 222 reload: bool = False, 223 ) -> list[dict[str, str]] | None: 224 kv = KV(app, app.logger, "links_from_did") 225 + record_json = kv.get(did) 226 227 + if record_json is not None and not reload: 228 + return json.loads(record_json)["links"] 229 230 record = await get_record(client, pds, did, f"{SCHEMA}.actor.links", "self") 231 if record is None: ··· 243 reload: bool = False, 244 ) -> tuple[dict[str, str] | None, bool]: 245 kv = KV(app, app.logger, "profile_from_did") 246 + record_json = kv.get(did) 247 + 248 + if record_json is not None and not reload: 249 + return json.loads(record_json), False 250 251 + (record, bsky_record) = await asyncio.gather( 252 + get_record(client, pds, did, f"{SCHEMA}.actor.profile", "self"), 253 + get_record(client, pds, did, "app.bsky.actor.profile", "self"), 254 + ) 255 256 from_bluesky = False 257 if record is None and fallback_with_bluesky: 258 + record = bsky_record 259 from_bluesky = True 260 + 261 + if record is not None: 262 + kv.set(did, value=json.dumps(record)) 263 264 return record, from_bluesky 265 266 ··· 272 collection: str, 273 rkey: str, 274 record: dict[str, Any], 275 + ) -> bool: 276 + """Writes the record onto the users PDS. Returns bool for success.""" 277 + 278 endpoint = f"{pds}/xrpc/com.atproto.repo.putRecord" 279 body = { 280 "repo": repo, ··· 294 user=user, 295 update_dpop_pds_nonce=update_dpop_pds_nonce, 296 ) 297 + 298 + return response is not None and response.ok 299 300 301 def _is_did_blocked(did: str) -> bool: