A lexicon-driven AppView for ATProto. happyview.dev
backfill firehose jetstream atproto appview oauth lexicon
8
fork

Configure Feed

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

fix(event-log): prevent double filtering

Trezy 3f02b515 068f3be3

+40 -53
+14 -21
packages/oauth-client/src/client.ts
··· 153 153 } 154 154 155 155 async deleteSession(did: string): Promise<void> { 156 - const headers: Record<string, string> = { 157 - "x-client-key": this.clientKey, 158 - }; 159 - if (this.clientSecret) { 160 - headers["x-client-secret"] = this.clientSecret; 161 - } 156 + const session = await this.restoreSession(did); 157 + if (session) { 158 + const resp = await session.fetchHandler( 159 + `${this.instanceUrl}/oauth/sessions/${did}`, 160 + { method: "DELETE" }, 161 + ); 162 162 163 - const resp = await this._fetch( 164 - `${this.instanceUrl}/oauth/sessions/${did}`, 165 - { 166 - method: "DELETE", 167 - headers, 168 - }, 169 - ); 170 - 171 - if (!resp.ok && resp.status !== 404) { 172 - const body = await resp.json().catch(() => ({})); 173 - throw new ApiError( 174 - `Failed to delete session: ${resp.status} ${(body as any).message ?? resp.statusText}`, 175 - resp.status, 176 - body, 177 - ); 163 + if (!resp.ok && resp.status !== 404) { 164 + const body = await resp.json().catch(() => ({})); 165 + throw new ApiError( 166 + `Failed to delete session: ${resp.status} ${(body as any).message ?? resp.statusText}`, 167 + resp.status, 168 + body, 169 + ); 170 + } 178 171 } 179 172 180 173 await this.storage.delete(`${STORAGE_PREFIX}${did}`);
+24 -9
src/admin/events.rs
··· 57 57 if query.event_type.is_some() { 58 58 sql.push_str(" AND event_type = ?"); 59 59 } 60 - if query.category.is_some() { 61 - sql.push_str(" AND event_type LIKE ?"); 60 + if let Some(ref cat) = query.category { 61 + let cats: Vec<&str> = cat.split(',').collect(); 62 + let clauses: Vec<String> = cats 63 + .iter() 64 + .map(|_| "event_type LIKE ?".to_string()) 65 + .collect(); 66 + sql.push_str(&format!(" AND ({})", clauses.join(" OR "))); 62 67 } 63 - if query.severity.is_some() { 64 - sql.push_str(" AND severity = ?"); 68 + if let Some(ref sev) = query.severity { 69 + let count = sev.split(',').count(); 70 + let placeholders: Vec<&str> = (0..count).map(|_| "?").collect(); 71 + sql.push_str(&format!(" AND severity IN ({})", placeholders.join(","))); 65 72 } 66 73 if query.subject.is_some() { 67 - sql.push_str(" AND subject = ?"); 74 + sql.push_str(" AND subject LIKE ?"); 68 75 } 69 76 if query.cursor.is_some() { 70 77 sql.push_str(" AND created_at < ?"); ··· 92 99 q = q.bind(event_type); 93 100 } 94 101 if let Some(ref category) = query.category { 95 - q = q.bind(format!("{category}.%")); 102 + for c in category.split(',') { 103 + q = q.bind(format!("{c}.%")); 104 + } 96 105 } 97 106 if let Some(ref severity) = query.severity { 98 - q = q.bind(severity); 107 + for s in severity.split(',') { 108 + q = q.bind(s.to_string()); 109 + } 99 110 } 100 111 if let Some(ref subject) = query.subject { 101 - q = q.bind(subject); 112 + q = q.bind(format!("%{subject}%")); 102 113 } 103 114 if let Some(ref cursor) = query.cursor { 104 115 q = q.bind(cursor); ··· 123 134 }) 124 135 .collect(); 125 136 126 - let cursor = events.last().map(|e| e.created_at.to_rfc3339()); 137 + let cursor = if events.len() as i64 >= limit { 138 + events.last().map(|e| e.created_at.to_rfc3339()) 139 + } else { 140 + None 141 + }; 127 142 128 143 Ok(Json(EventsListResponse { events, cursor })) 129 144 }
+2 -23
web/src/app/dashboard/events/page.tsx
··· 6 6 type ColumnFiltersState, 7 7 type VisibilityState, 8 8 getCoreRowModel, 9 - getFilteredRowModel, 10 - getFacetedRowModel, 11 - getFacetedUniqueValues, 12 9 useReactTable, 13 10 } from "@tanstack/react-table"; 14 11 ··· 268 265 ?.value as string | undefined; 269 266 270 267 const data = await getEvents({ 271 - category: categoryFilter?.[0] || undefined, 272 - severity: severityFilter?.[0] || undefined, 268 + category: categoryFilter?.length ? categoryFilter.join(",") : undefined, 269 + severity: severityFilter?.length ? severityFilter.join(",") : undefined, 273 270 subject: subjectFilter || undefined, 274 271 cursor, 275 272 limit: 50, ··· 331 328 {row.original.subject ?? "--"} 332 329 </span> 333 330 ), 334 - filterFn: "includesString", 335 331 enableColumnFilter: true, 336 332 enableSorting: false, 337 333 meta: { ··· 347 343 <DataTableColumnHeader column={column} label="Severity" /> 348 344 ), 349 345 cell: ({ row }) => severityBadge(row.original.severity), 350 - filterFn: (row, columnId, filterValue) => { 351 - if (!Array.isArray(filterValue) || filterValue.length === 0) 352 - return true; 353 - return filterValue.includes(row.getValue(columnId)); 354 - }, 355 346 enableColumnFilter: true, 356 347 enableSorting: false, 357 348 enableHiding: false, ··· 374 365 cell: ({ row }) => ( 375 366 <span className="font-mono text-sm">{row.original.event_type}</span> 376 367 ), 377 - filterFn: (row, columnId, filterValue) => { 378 - if (!Array.isArray(filterValue) || filterValue.length === 0) 379 - return true; 380 - const eventType = row.getValue(columnId) as string; 381 - return filterValue.some( 382 - (cat: string) => 383 - eventType === cat || eventType.startsWith(cat + "."), 384 - ); 385 - }, 386 368 enableColumnFilter: true, 387 369 enableSorting: false, 388 370 meta: { ··· 444 426 onColumnFiltersChange: setColumnFilters, 445 427 onColumnVisibilityChange: setColumnVisibility, 446 428 getCoreRowModel: getCoreRowModel(), 447 - getFilteredRowModel: getFilteredRowModel(), 448 - getFacetedRowModel: getFacetedRowModel(), 449 - getFacetedUniqueValues: getFacetedUniqueValues(), 450 429 getRowId: (row) => row.id, 451 430 }); 452 431