···11use anyhow::Result;
22+use atproto_tap::TapClient;
23use axum::{
34 extract::{Path, Query},
45 response::{IntoResponse, Redirect},
···1617 pagination::{Pagination, PaginationView},
1718 },
1819 select_template,
1919- storage::identity_profile::{handle_list, handle_nuke},
2020+ storage::identity_profile::{get_all_dids, handle_list, handle_nuke},
2021};
21222223pub(crate) async fn handle_admin_handles(
···108109 Ok(Redirect::to("/admin/handles").into_response())
109110 }
110111}
112112+113113+/// Sync all known DIDs to TAP for event streaming
114114+pub(crate) async fn handle_admin_tap_sync_all(
115115+ admin_ctx: AdminRequestContext,
116116+ HxRequest(hx_request): HxRequest,
117117+) -> Result<impl IntoResponse, WebError> {
118118+ let error_template = select_template!(false, false, admin_ctx.language);
119119+120120+ // Check if TAP is enabled
121121+ if !admin_ctx.web_context.config.enable_tap {
122122+ return contextual_error!(
123123+ admin_ctx.web_context,
124124+ admin_ctx.language,
125125+ error_template,
126126+ template_context! {
127127+ message => "TAP is not enabled in the configuration."
128128+ },
129129+ "TAP is not enabled"
130130+ );
131131+ }
132132+133133+ // Get all DIDs from the database
134134+ let dids = match get_all_dids(&admin_ctx.web_context.pool).await {
135135+ Ok(dids) => dids,
136136+ Err(err) => {
137137+ return contextual_error!(
138138+ admin_ctx.web_context,
139139+ admin_ctx.language,
140140+ error_template,
141141+ template_context! {},
142142+ err
143143+ );
144144+ }
145145+ };
146146+147147+ if dids.is_empty() {
148148+ tracing::info!("No DIDs to sync to TAP");
149149+ if hx_request {
150150+ let hx_redirect = HxRedirect::from("/admin/identity_profiles");
151151+ return Ok((StatusCode::OK, hx_redirect, "").into_response());
152152+ } else {
153153+ return Ok(Redirect::to("/admin/identity_profiles").into_response());
154154+ }
155155+ }
156156+157157+ // Create TAP client and sync DIDs
158158+ let tap_client = TapClient::new(
159159+ &admin_ctx.web_context.config.tap_hostname,
160160+ admin_ctx.web_context.config.tap_password.clone(),
161161+ );
162162+163163+ // Convert Vec<String> to Vec<&str> for add_repos
164164+ let did_refs: Vec<&str> = dids.iter().map(|s| s.as_str()).collect();
165165+166166+ match tap_client.add_repos(&did_refs).await {
167167+ Ok(()) => {
168168+ tracing::info!(count = dids.len(), "Successfully synced all DIDs to TAP");
169169+ }
170170+ Err(err) => {
171171+ tracing::error!(error = %err, "Failed to sync DIDs to TAP");
172172+ return contextual_error!(
173173+ admin_ctx.web_context,
174174+ admin_ctx.language,
175175+ error_template,
176176+ template_context! {
177177+ message => format!("Failed to sync DIDs to TAP: {}", err)
178178+ },
179179+ err
180180+ );
181181+ }
182182+ }
183183+184184+ if hx_request {
185185+ let hx_redirect = HxRedirect::from("/admin/identity_profiles");
186186+ Ok((StatusCode::OK, hx_redirect, "").into_response())
187187+ } else {
188188+ Ok(Redirect::to("/admin/identity_profiles").into_response())
189189+ }
190190+}
+19
src/http/handle_oauth_aip_callback.rs
···1010use anyhow::Result;
1111use atproto_client::errors::SimpleError;
1212use atproto_identity::resolve::IdentityResolver;
1313+use atproto_tap::TapClient;
1314use axum::{
1415 extract::State,
1516 response::{IntoResponse, Redirect},
···212213 {
213214 // Log the error but don't fail the authentication flow
214215 tracing::warn!(?err, "Failed to send confirmation email to new user");
216216+ }
217217+218218+ // Register new user's DID with TAP for event streaming
219219+ if is_new_user && web_context.config.enable_tap {
220220+ let tap_client = TapClient::new(
221221+ &web_context.config.tap_hostname,
222222+ web_context.config.tap_password.clone(),
223223+ );
224224+ if let Err(err) = tap_client.add_repos(&[&document.id]).await {
225225+ // Log the error but don't fail the authentication flow
226226+ tracing::warn!(
227227+ did = %document.id,
228228+ error = %err,
229229+ "Failed to register DID with TAP"
230230+ );
231231+ } else {
232232+ tracing::info!(did = %document.id, "Registered new user DID with TAP");
233233+ }
215234 }
216235217236 // Import Bluesky profile for new users
+19
src/http/handle_oauth_callback.rs
···99 resources::oauth_authorization_server,
1010 workflow::{OAuthClient, oauth_complete},
1111};
1212+use atproto_tap::TapClient;
1213use axum::{
1314 extract::State,
1415 response::{IntoResponse, Redirect},
···235236 return contextual_error!(web_context, language, error_template, default_context, err);
236237 }
237238 };
239239+240240+ // Register new user's DID with TAP for event streaming
241241+ if is_new_user && web_context.config.enable_tap {
242242+ let tap_client = TapClient::new(
243243+ &web_context.config.tap_hostname,
244244+ web_context.config.tap_password.clone(),
245245+ );
246246+ if let Err(err) = tap_client.add_repos(&[&document.id]).await {
247247+ // Log the error but don't fail the authentication flow
248248+ tracing::warn!(
249249+ did = %document.id,
250250+ error = %err,
251251+ "Failed to register DID with TAP"
252252+ );
253253+ } else {
254254+ tracing::info!(did = %document.id, "Registered new user DID with TAP");
255255+ }
256256+ }
238257239258 // Import Bluesky profile for new users
240259 if is_new_user {
···447447 ))
448448}
449449450450+/// Get all DIDs from identity profiles
451451+pub async fn get_all_dids(pool: &StoragePool) -> Result<Vec<String>, StorageError> {
452452+ let dids = sqlx::query_scalar::<_, String>("SELECT did FROM identity_profiles ORDER BY did")
453453+ .fetch_all(pool)
454454+ .await
455455+ .map_err(StorageError::UnableToExecuteQuery)?;
456456+457457+ Ok(dids)
458458+}
459459+450460/// Get an identity profile by DID
451461pub async fn identity_profile_get(
452462 pool: &StoragePool,
+25
templates/en-us/admin_handles.html
···66 <h1 class="title">Identity Records ({{ total_count }})</h1>
77 <p class="subtitle">View all registered identities</p>
8899+ {# TAP Sync Section #}
1010+ <div class="box mb-5">
1111+ <h2 class="title is-5">
1212+ <span class="icon-text">
1313+ <span class="icon"><i class="fas fa-sync"></i></span>
1414+ <span>TAP Sync</span>
1515+ </span>
1616+ </h2>
1717+ <div class="content">
1818+ <p>
1919+ Sync all known identity DIDs to TAP (Trusted Attestation Protocol) for event streaming.
2020+ This ensures TAP is tracking repositories for all registered users.
2121+ </p>
2222+ </div>
2323+ <button class="button is-info"
2424+ hx-post="/admin/identity_profiles/tap-sync"
2525+ hx-confirm="Are you sure you want to sync all {{ total_count }} DIDs to TAP? This may take a moment."
2626+ hx-target="body"
2727+ data-loading-disable
2828+ data-loading-class="is-loading">
2929+ <span class="icon"><i class="fas fa-sync"></i></span>
3030+ <span>Sync All DIDs to TAP</span>
3131+ </button>
3232+ </div>
3333+934 <table class="table is-fullwidth is-striped">
1035 <thead>
1136 <tr>