+1
-1
client/src/components/firehose-status.tsx
+1
-1
client/src/components/firehose-status.tsx
···
1
1
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
2
2
import { Button } from '@/components/ui/button';
3
-
import { CheckCircle2, XCircle } from 'lucide-react';
3
+
import { XCircle } from 'lucide-react';
4
4
5
5
interface FirehoseStatusProps {
6
6
connected: boolean;
+1
-1
client/src/components/logs-panel.tsx
+1
-1
client/src/components/logs-panel.tsx
-8
client/src/components/osprey-status.tsx
-8
client/src/components/osprey-status.tsx
···
319
319
</Card>
320
320
);
321
321
}
322
-
323
-
function formatUptime(seconds: number): string {
324
-
if (seconds < 60) return `${seconds}s`;
325
-
if (seconds < 3600) return `${Math.floor(seconds / 60)}m`;
326
-
if (seconds < 86400)
327
-
return `${Math.floor(seconds / 3600)}h ${Math.floor((seconds % 3600) / 60)}m`;
328
-
return `${Math.floor(seconds / 86400)}d ${Math.floor((seconds % 86400) / 3600)}h`;
329
-
}
+4
-2
client/src/components/pds-fetcher-status.tsx
+4
-2
client/src/components/pds-fetcher-status.tsx
···
27
27
const fetchStats = async () => {
28
28
try {
29
29
setLoading(true);
30
-
const response = await api.get('/api/admin/pds-fetcher/stats');
31
-
setStats((response as any).stats);
30
+
const response = await api.get<{ stats: PDSFetcherStats }>(
31
+
'/api/admin/pds-fetcher/stats'
32
+
);
33
+
setStats(response.stats);
32
34
} catch (error) {
33
35
console.error('Failed to fetch PDS fetcher stats:', error);
34
36
} finally {
+4
-13
client/src/hooks/use-toast.ts
+4
-13
client/src/hooks/use-toast.ts
···
12
12
action?: ToastActionElement;
13
13
};
14
14
15
-
const actionTypes = {
16
-
ADD_TOAST: 'ADD_TOAST',
17
-
UPDATE_TOAST: 'UPDATE_TOAST',
18
-
DISMISS_TOAST: 'DISMISS_TOAST',
19
-
REMOVE_TOAST: 'REMOVE_TOAST',
20
-
} as const;
21
-
22
15
let count = 0;
23
16
24
17
function genId() {
25
18
count = (count + 1) % Number.MAX_SAFE_INTEGER;
26
19
return count.toString();
27
20
}
28
-
29
-
type ActionType = typeof actionTypes;
30
21
31
22
type Action =
32
23
| {
33
-
type: ActionType['ADD_TOAST'];
24
+
type: 'ADD_TOAST';
34
25
toast: ToasterToast;
35
26
}
36
27
| {
37
-
type: ActionType['UPDATE_TOAST'];
28
+
type: 'UPDATE_TOAST';
38
29
toast: Partial<ToasterToast>;
39
30
}
40
31
| {
41
-
type: ActionType['DISMISS_TOAST'];
32
+
type: 'DISMISS_TOAST';
42
33
toastId?: ToasterToast['id'];
43
34
}
44
35
| {
45
-
type: ActionType['REMOVE_TOAST'];
36
+
type: 'REMOVE_TOAST';
46
37
toastId?: ToasterToast['id'];
47
38
};
48
39
+14
-10
client/src/lib/api.ts
+14
-10
client/src/lib/api.ts
···
69
69
fetchCSRFToken();
70
70
71
71
// --- Main API Request Logic ---
72
-
const request = async (
72
+
const request = async <T = unknown>(
73
73
method: 'GET' | 'POST' | 'PUT' | 'DELETE',
74
74
url: string,
75
-
body?: any,
75
+
body?: unknown,
76
76
retryCount = 0
77
-
): Promise<any> => {
77
+
): Promise<T> => {
78
78
const csrf = await fetchCSRFToken();
79
79
80
80
const headers: Record<string, string> = {
···
114
114
await refreshCSRFToken();
115
115
return request(method, url, body, retryCount + 1);
116
116
}
117
-
} catch (e) {
117
+
} catch {
118
118
// If we can't parse the error, continue with normal error handling
119
119
}
120
120
}
···
124
124
queryClient.invalidateQueries({ queryKey: ['/api/auth/session'] });
125
125
}
126
126
127
-
const error: any = new Error(`HTTP error! status: ${response.status}`);
127
+
const error = new Error(
128
+
`HTTP error! status: ${response.status}`
129
+
) as Error & { data?: unknown };
128
130
try {
129
131
error.data = await response.json();
130
-
} catch (e) {
132
+
} catch {
131
133
error.data = { message: 'Could not parse error response.' };
132
134
}
133
135
throw error;
···
141
143
};
142
144
143
145
const api = {
144
-
get: <T>(url: string): Promise<T> => request('GET', url),
145
-
post: <T>(url: string, body: any): Promise<T> => request('POST', url, body),
146
-
put: <T>(url: string, body: any): Promise<T> => request('PUT', url, body),
147
-
delete: <T>(url: string): Promise<T> => request('DELETE', url),
146
+
get: <T = unknown>(url: string): Promise<T> => request<T>('GET', url),
147
+
post: <T = unknown>(url: string, body: unknown): Promise<T> =>
148
+
request<T>('POST', url, body),
149
+
put: <T = unknown>(url: string, body: unknown): Promise<T> =>
150
+
request<T>('PUT', url, body),
151
+
delete: <T = unknown>(url: string): Promise<T> => request<T>('DELETE', url),
148
152
// Expose refresh function for manual CSRF token refresh if needed
149
153
refreshCSRFToken,
150
154
};
+8
-6
client/src/lib/queryClient.ts
+8
-6
client/src/lib/queryClient.ts
···
10
10
try {
11
11
const data = await api.get<T>(url);
12
12
return data;
13
-
} catch (error: any) {
14
-
if (options?.on401 === 'returnNull' && error.response?.status === 401) {
13
+
} catch (error: unknown) {
14
+
const err = error as { response?: { status?: number } };
15
+
if (options?.on401 === 'returnNull' && err.response?.status === 401) {
15
16
return null as T;
16
17
}
17
18
// Re-throw other errors to be handled by React Query
···
26
27
refetchInterval: false,
27
28
refetchOnWindowFocus: false,
28
29
staleTime: Infinity,
29
-
retry: (failureCount, error: any) => {
30
+
retry: (failureCount, error: unknown) => {
31
+
const err = error as { response?: { status?: number } };
30
32
if (
31
-
error.response?.status === 401 ||
32
-
error.response?.status === 403 ||
33
-
error.response?.status === 404
33
+
err.response?.status === 401 ||
34
+
err.response?.status === 403 ||
35
+
err.response?.status === 404
34
36
) {
35
37
return false;
36
38
}
+20
-24
client/src/pages/admin-moderation.tsx
+20
-24
client/src/pages/admin-moderation.tsx
···
28
28
AlertCircle,
29
29
LogIn,
30
30
LogOut,
31
-
RefreshCw,
32
-
Zap,
33
31
} from 'lucide-react';
34
32
import { api } from '@/lib/api';
35
33
import { PDSFetcherStatus } from '@/components/pds-fetcher-status';
···
51
49
description: string;
52
50
}
53
51
52
+
interface MetricsData {
53
+
firehoseStatus?: {
54
+
connected: boolean;
55
+
};
56
+
eventCounts?: {
57
+
'#commit': number;
58
+
'#identity': number;
59
+
'#account': number;
60
+
};
61
+
errorRate?: number;
62
+
}
63
+
54
64
export default function AdminControlPanel() {
55
65
const { toast } = useToast();
56
66
const queryClient = useQueryClient();
···
99
109
}
100
110
// Redirect to PDS for OAuth authorization
101
111
window.location.href = data.authUrl;
102
-
} catch (error) {
112
+
} catch {
103
113
toast({
104
114
title: 'Login Failed',
105
115
description: 'Invalid authentication URL returned from server',
···
129
139
});
130
140
131
141
// Fetch firehose status
132
-
const { data: metrics } = useQuery({
142
+
const { data: metrics } = useQuery<MetricsData>({
133
143
queryKey: ['/api/metrics'],
134
144
refetchInterval: 5000,
135
145
});
···
137
147
// Update firehose stats when metrics change
138
148
useEffect(() => {
139
149
if (metrics) {
140
-
const metricsData = metrics as any;
141
-
setFirehoseConnected(metricsData.firehoseStatus?.connected || false);
150
+
setFirehoseConnected(metrics.firehoseStatus?.connected || false);
142
151
setFirehoseStats({
143
-
commits: metricsData.eventCounts?.['#commit'] || 0,
144
-
identity: metricsData.eventCounts?.['#identity'] || 0,
145
-
account: metricsData.eventCounts?.['#account'] || 0,
146
-
errorRate: metricsData.errorRate || 0,
152
+
commits: metrics.eventCounts?.['#commit'] || 0,
153
+
identity: metrics.eventCounts?.['#identity'] || 0,
154
+
account: metrics.eventCounts?.['#account'] || 0,
155
+
errorRate: metrics.errorRate || 0,
147
156
});
148
157
}
149
158
}, [metrics]);
···
156
165
title: 'Reconnect Initiated',
157
166
description: 'Attempting to reconnect to firehose...',
158
167
});
159
-
} catch (error) {
168
+
} catch {
160
169
toast({
161
170
title: 'Reconnect Failed',
162
171
description: 'Failed to reconnect to firehose',
···
245
254
const handleSearch = () => {
246
255
if (searchQuery) {
247
256
refetchLabels();
248
-
}
249
-
};
250
-
251
-
const getLabelColor = (reason: string) => {
252
-
switch (reason) {
253
-
case 'legal':
254
-
return 'destructive';
255
-
case 'safety':
256
-
return 'default';
257
-
case 'quality':
258
-
return 'secondary';
259
-
default:
260
-
return 'outline';
261
257
}
262
258
};
263
259
+12
-7
client/src/pages/dashboard.tsx
+12
-7
client/src/pages/dashboard.tsx
···
13
13
import AdminControlPanel from '@/pages/admin-moderation';
14
14
import UserPanel from '@/pages/user-panel';
15
15
import LoginPage from '@/pages/login';
16
-
import { useLocation, useSearch } from 'wouter';
17
-
import { useQuery, useQueryClient } from '@tanstack/react-query';
16
+
import { useLocation } from 'wouter';
17
+
import { useQuery } from '@tanstack/react-query';
18
18
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
19
19
import { ShieldAlert } from 'lucide-react';
20
20
import { api } from '@/lib/api';
···
49
49
}
50
50
51
51
export default function Dashboard() {
52
-
const [location, setLocation] = useLocation();
53
-
const queryClient = useQueryClient();
52
+
const [location] = useLocation();
54
53
55
54
// SECURITY: We now use HttpOnly cookies for authentication
56
55
// No need to handle tokens in URL or localStorage
···
79
78
lastUpdate: new Date().toISOString(),
80
79
});
81
80
82
-
const [events, setEvents] = useState<any[]>([]);
81
+
interface EventData {
82
+
type: string;
83
+
timestamp: string;
84
+
[key: string]: unknown;
85
+
}
86
+
87
+
const [events, setEvents] = useState<EventData[]>([]);
83
88
84
89
// Initial metrics fetch (SSE stream will update in real-time after connection)
85
90
useEffect(() => {
···
97
102
98
103
// Real-time event stream via Server-Sent Events (SSE)
99
104
useEffect(() => {
100
-
const recentEvents: any[] = [];
105
+
const recentEvents: EventData[] = [];
101
106
let eventSource: EventSource | null = null;
102
107
103
108
// Fetch initial events from API
104
109
const fetchInitialEvents = async () => {
105
110
try {
106
-
const data = await api.get<any[]>('/api/events/recent');
111
+
const data = await api.get<EventData[]>('/api/events/recent');
107
112
recentEvents.push(...data);
108
113
setEvents([...data.slice(0, 10)]);
109
114
} catch (error) {
+1
-1
client/src/pages/login.tsx
+1
-1
client/src/pages/login.tsx
···
17
17
// Sanitize user input to prevent XSS attacks
18
18
function sanitizeText(text: string): string {
19
19
return text
20
-
.replace(/[<>\"']/g, '') // Remove potentially dangerous characters
20
+
.replace(/[<>"']/g, '') // Remove potentially dangerous characters
21
21
.substring(0, 500); // Limit length to prevent abuse
22
22
}
23
23
-1
client/src/pages/user-panel.tsx
-1
client/src/pages/user-panel.tsx
+1
-1
microcosm-bridge/constellation-client/src/api-client.ts
+1
-1
microcosm-bridge/constellation-client/src/api-client.ts
+2
-2
microcosm-bridge/constellation-client/src/enricher.ts
+2
-2
microcosm-bridge/constellation-client/src/enricher.ts
···
5
5
* Includes Redis caching to minimize API calls and improve performance.
6
6
*/
7
7
8
-
import { ConstellationAPIClient, LinksCounts } from './api-client.js';
8
+
import { ConstellationAPIClient } from './api-client.js';
9
9
import Redis from 'ioredis';
10
10
11
11
interface PostStats {
···
94
94
/**
95
95
* Set value in cache
96
96
*/
97
-
private async setInCache(key: string, value: any): Promise<void> {
97
+
private async setInCache(key: string, value: unknown): Promise<void> {
98
98
if (!this.cache || !this.cacheEnabled) {
99
99
return;
100
100
}
+1
-1
microcosm-bridge/constellation-client/src/health.ts
+1
-1
microcosm-bridge/constellation-client/src/health.ts
+1
-1
osprey-bridge/firehose-to-kafka/src/adapters/base-adapter.ts
+1
-1
osprey-bridge/firehose-to-kafka/src/adapters/base-adapter.ts
+4
-4
osprey-bridge/firehose-to-kafka/src/adapters/firehose-adapter.ts
+4
-4
osprey-bridge/firehose-to-kafka/src/adapters/firehose-adapter.ts
···
5
5
6
6
// Make WebSocket available globally for @skyware/firehose in Node.js environment
7
7
if (typeof globalThis.WebSocket === 'undefined') {
8
-
(global as any).WebSocket = WebSocket;
8
+
(global as typeof WebSocket & { prototype: WebSocket }).WebSocket = WebSocket;
9
9
}
10
10
11
11
export interface FirehoseAdapterConfig {
···
39
39
const savedCursor = this.loadCursor();
40
40
41
41
// Create firehose client
42
-
const firehoseOptions: any = {
42
+
const firehoseOptions: { relay: string; cursor?: string } = {
43
43
relay: this.config.url,
44
44
};
45
45
···
64
64
data: {
65
65
repo: commit.repo,
66
66
ops: commit.ops.map((op) => {
67
-
const baseOp: any = {
67
+
const baseOp: Record<string, unknown> = {
68
68
action: op.action,
69
69
path: op.path,
70
70
};
···
113
113
console.log(`[${this.getName()}] Disconnected from firehose`);
114
114
});
115
115
116
-
this.firehose.on('error', (error: any) => {
116
+
this.firehose.on('error', (error: unknown) => {
117
117
console.error(`[${this.getName()}] Firehose error:`, error);
118
118
});
119
119
+6
-4
osprey-bridge/firehose-to-kafka/src/adapters/redis-adapter.ts
+6
-4
osprey-bridge/firehose-to-kafka/src/adapters/redis-adapter.ts
···
154
154
console.log(
155
155
`[${this.getName()}] Created consumer group: ${this.consumerGroup}`
156
156
);
157
-
} catch (error: any) {
158
-
if (error.message.includes('BUSYGROUP')) {
157
+
} catch (error: unknown) {
158
+
const err = error as Error;
159
+
if (err.message.includes('BUSYGROUP')) {
159
160
console.log(
160
161
`[${this.getName()}] Consumer group already exists: ${this.consumerGroup}`
161
162
);
···
276
277
}
277
278
278
279
return events;
279
-
} catch (error: any) {
280
+
} catch (error: unknown) {
281
+
const err = error as Error;
280
282
// Handle READONLY errors specifically
281
-
if (error.message && error.message.includes('READONLY')) {
283
+
if (err.message && err.message.includes('READONLY')) {
282
284
console.error(
283
285
`[${this.getName()}] READONLY error - Redis is configured as a read-only replica. XREADGROUP requires write access. Please connect to the master Redis instance.`
284
286
);
+7
-3
osprey-bridge/firehose-to-kafka/src/event-enricher.ts
+7
-3
osprey-bridge/firehose-to-kafka/src/event-enricher.ts
···
12
12
action: 'create' | 'update' | 'delete';
13
13
path: string;
14
14
cid?: string;
15
-
record?: any;
15
+
record?: unknown;
16
16
}>;
17
17
18
18
// Enriched metadata
···
51
51
this.enrichWithHandles = options.enrichWithHandles ?? true;
52
52
}
53
53
54
-
async enrich(event: any): Promise<EnrichedEvent> {
54
+
async enrich(event: {
55
+
type: string;
56
+
seq?: string;
57
+
data?: Record<string, unknown>;
58
+
}): Promise<EnrichedEvent> {
55
59
const enriched: EnrichedEvent = {
56
60
type: event.type,
57
61
seq: event.seq,
···
93
97
const author = result.rows[0];
94
98
95
99
// Filter based on enrichment settings
96
-
const enrichedAuthor: any = {};
100
+
const enrichedAuthor: Record<string, unknown> = {};
97
101
98
102
if (this.enrichWithHandles && author.handle) {
99
103
enrichedAuthor.handle = author.handle;
+2
-2
osprey-bridge/firehose-to-kafka/src/health.ts
+2
-2
osprey-bridge/firehose-to-kafka/src/health.ts
···
76
76
77
77
const statusCode = health.status === 'healthy' ? 200 : 503;
78
78
res.status(statusCode).json(health);
79
-
} catch (error) {
79
+
} catch {
80
80
res.status(503).json({
81
81
status: 'unhealthy',
82
82
error: 'Failed to get health status',
···
95
95
.status(503)
96
96
.json({ ready: false, timestamp: new Date().toISOString() });
97
97
}
98
-
} catch (error) {
98
+
} catch {
99
99
res.status(503).json({
100
100
ready: false,
101
101
error: 'Failed to get readiness status',
+2
-2
osprey-bridge/firehose-to-kafka/src/kafka-producer.ts
+2
-2
osprey-bridge/firehose-to-kafka/src/kafka-producer.ts
···
42
42
console.log('[KAFKA] Connected successfully');
43
43
}
44
44
45
-
async publishEvent(event: any): Promise<void> {
45
+
async publishEvent(event: Record<string, unknown>): Promise<void> {
46
46
if (!this.isConnected) {
47
47
throw new Error('Kafka producer not connected');
48
48
}
···
65
65
}
66
66
}
67
67
68
-
async publishBatch(events: any[]): Promise<void> {
68
+
async publishBatch(events: Record<string, unknown>[]): Promise<void> {
69
69
if (!this.isConnected) {
70
70
throw new Error('Kafka producer not connected');
71
71
}
+1
-1
osprey-bridge/label-effector/src/kafka-consumer.ts
+1
-1
osprey-bridge/label-effector/src/kafka-consumer.ts
+1
-1
server/db.ts
+1
-1
server/db.ts
···
95
95
96
96
// For backwards compatibility, export a pool variable (though the actual pool is internal to drizzle)
97
97
// This is used by some legacy code that checks pool status
98
-
export const pool = db as any;
98
+
export const pool = db as unknown;
99
99
100
100
// Export main db connection
101
101
export { db };
+27
-19
server/index.ts
+27
-19
server/index.ts
···
5
5
import { setupVite, serveStatic, log } from './vite';
6
6
import { logCollector } from './services/log-collector';
7
7
import { cacheService } from './services/cache';
8
-
import { spawn } from 'child_process';
9
8
10
9
const app = express();
11
10
···
67
66
const bodyBuffer = Buffer.concat(chunks);
68
67
69
68
// Replicate the 'verify' functionality to store the raw body
70
-
(req as any).rawBody = bodyBuffer;
69
+
(req as Request & { rawBody?: Buffer }).rawBody = bodyBuffer;
71
70
72
71
if (bodyBuffer.length === 0) {
73
72
req.body = {};
···
175
174
app.use((req, res, next) => {
176
175
const start = Date.now();
177
176
const path = req.path;
178
-
let capturedJsonResponse: Record<string, any> | undefined = undefined;
177
+
let capturedJsonResponse: Record<string, unknown> | undefined = undefined;
179
178
180
179
const originalResJson = res.json;
181
180
res.json = function (bodyJson, ...args) {
···
211
210
212
211
const server = await registerRoutes(app);
213
212
214
-
app.use((err: any, _req: Request, res: Response, _next: NextFunction) => {
215
-
const status = err.status || err.statusCode || 500;
216
-
const message = err.message || 'Internal Server Error';
213
+
app.use(
214
+
(
215
+
err: Error & { status?: number; statusCode?: number },
216
+
_req: Request,
217
+
res: Response,
218
+
_next: NextFunction
219
+
) => {
220
+
const status = err.status || err.statusCode || 500;
221
+
const message = err.message || 'Internal Server Error';
217
222
218
-
// Log error for debugging
219
-
console.error('[ERROR]', err);
220
-
logCollector.error('Request error', {
221
-
error: message,
222
-
status,
223
-
stack: err.stack,
224
-
});
223
+
// Log error for debugging
224
+
console.error('[ERROR]', err);
225
+
logCollector.error('Request error', {
226
+
error: message,
227
+
status,
228
+
stack: err.stack,
229
+
});
225
230
226
-
res.status(status).json({ message });
227
-
// DO NOT throw after sending response - this would crash the server
228
-
});
231
+
res.status(status).json({ message });
232
+
// DO NOT throw after sending response - this would crash the server
233
+
}
234
+
);
229
235
230
236
// importantly only setup vite in development and after
231
237
// setting up all the other routes so the catch-all route
···
273
279
});
274
280
275
281
// Initialize data pruning service (if enabled)
276
-
import('./services/data-pruning').then(({ dataPruningService }) => {
277
-
// Service auto-initializes in its constructor
278
-
});
282
+
import('./services/data-pruning').then(
283
+
({ dataPruningService: _dataPruningService }) => {
284
+
// Service auto-initializes in its constructor
285
+
}
286
+
);
279
287
280
288
// TypeScript backfill service is PERMANENTLY DISABLED
281
289
// Backfill functionality has been moved to Python (python-firehose/backfill_service.py)
+56
-40
server/routes.ts
+56
-40
server/routes.ts
···
38
38
authLimiter,
39
39
oauthLimiter,
40
40
writeLimiter,
41
-
searchLimiter,
42
41
apiLimiter,
43
42
xrpcLimiter,
44
43
adminLimiter,
···
69
68
70
69
const originalSend = res.send;
71
70
72
-
res.send = function (data: any) {
71
+
res.send = function (data: unknown) {
73
72
const duration = Date.now() - startTime;
74
73
const success = res.statusCode >= 200 && res.statusCode < 400;
75
74
metricsService.recordEndpointRequest(req.path, duration, success);
···
120
119
`);
121
120
122
121
if (stats.rows.length > 0) {
123
-
const row: any = stats.rows[0];
122
+
const row = stats.rows[0] as {
123
+
users?: string;
124
+
posts?: string;
125
+
likes?: string;
126
+
reposts?: string;
127
+
follows?: string;
128
+
blocks?: string;
129
+
};
124
130
await Promise.all([
125
131
redisQueue.incrementRecordCount('users', parseInt(row.users || '0')),
126
132
redisQueue.incrementRecordCount('posts', parseInt(row.posts || '0')),
···
199
205
const { eventProcessor } = await import('./services/event-processor');
200
206
const PARALLEL_PIPELINES = 5; // Run 5 concurrent consumer loops per worker
201
207
202
-
const processEvent = async (event: any) => {
208
+
interface RedisEvent {
209
+
type: string;
210
+
data: unknown;
211
+
}
212
+
const processEvent = async (event: RedisEvent) => {
203
213
let success = false;
204
214
try {
205
215
if (event.type === 'commit') {
···
210
220
await eventProcessor.processAccount(event.data);
211
221
}
212
222
success = true;
213
-
} catch (error: any) {
214
-
if (error?.code === '23505' || error?.code === '23503') {
223
+
} catch (error: unknown) {
224
+
const err = error as { code?: string };
225
+
if (err?.code === '23505' || err?.code === '23503') {
215
226
// Duplicate key or foreign key violation - treat as success
216
227
success = true;
217
228
} else {
···
337
348
);
338
349
res.setHeader('Pragma', 'no-cache');
339
350
res.send(didDoc);
340
-
} catch (error) {
351
+
} catch {
341
352
// If DID document doesn't exist, return a basic one based on APPVIEW_DID
342
353
const appviewDid = process.env.APPVIEW_DID;
343
354
if (!appviewDid) {
···
345
356
}
346
357
const domain = appviewDid.replace('did:web:', '');
347
358
const verificationKey = process.env.APPVIEW_ATPROTO_PUBKEY_MULTIBASE;
348
-
const basicDidDoc: any = {
359
+
const basicDidDoc: Record<string, unknown> = {
349
360
'@context': [
350
361
'https://www.w3.org/ns/did/v1',
351
362
'https://w3id.org/security/multikey/v1',
···
795
806
await storage.deleteSession(req.session.sessionId);
796
807
}
797
808
res.json({ success: true });
798
-
} catch (error) {
809
+
} catch {
799
810
res.status(500).json({ error: 'Failed to logout' });
800
811
}
801
812
}
···
820
831
const isAdmin = await adminAuthService.isAdmin(session.userDid);
821
832
822
833
res.json({ session, user, isAdmin });
823
-
} catch (error) {
834
+
} catch {
824
835
res.status(500).json({ error: 'Failed to get session' });
825
836
}
826
837
});
···
1555
1566
console.log(`[API] Deleted like ${uri} for ${session.userDid}`);
1556
1567
1557
1568
res.json({ success: true });
1558
-
} catch (error) {
1569
+
} catch {
1559
1570
res.status(400).json({ error: 'Failed to delete like' });
1560
1571
}
1561
1572
}
···
1719
1730
console.log(`[API] Deleted follow ${uri} for ${session.userDid}`);
1720
1731
1721
1732
res.json({ success: true });
1722
-
} catch (error) {
1733
+
} catch {
1723
1734
res.status(400).json({ error: 'Failed to delete follow' });
1724
1735
}
1725
1736
}
···
1747
1758
}
1748
1759
1749
1760
res.json({ settings });
1750
-
} catch (error) {
1761
+
} catch {
1751
1762
res.status(500).json({ error: 'Failed to get settings' });
1752
1763
}
1753
1764
});
···
1874
1885
});
1875
1886
1876
1887
res.json({ settings: updated });
1877
-
} catch (error) {
1888
+
} catch {
1878
1889
res.status(400).json({ error: 'Failed to unblock keyword' });
1879
1890
}
1880
1891
}
···
1960
1971
});
1961
1972
1962
1973
res.json({ settings: updated });
1963
-
} catch (error) {
1974
+
} catch {
1964
1975
res.status(400).json({ error: 'Failed to unmute user' });
1965
1976
}
1966
1977
}
···
2058
2069
2059
2070
await labelService.removeLabel(uri);
2060
2071
res.json({ success: true });
2061
-
} catch (error) {
2072
+
} catch {
2062
2073
res.status(400).json({ error: 'Failed to remove label' });
2063
2074
}
2064
2075
}
···
2068
2079
try {
2069
2080
const definitions = await labelService.getAllLabelDefinitions();
2070
2081
res.json({ definitions });
2071
-
} catch (error) {
2082
+
} catch {
2072
2083
res.status(500).json({ error: 'Failed to get label definitions' });
2073
2084
}
2074
2085
});
···
2118
2129
const labels = await labelService.queryLabels(params);
2119
2130
2120
2131
res.json({ labels });
2121
-
} catch (error) {
2132
+
} catch {
2122
2133
res.status(400).json({ error: 'Failed to query labels' });
2123
2134
}
2124
2135
});
···
2368
2379
);
2369
2380
const policy = instanceModerationService.getPublicPolicy();
2370
2381
res.json(policy);
2371
-
} catch (error) {
2382
+
} catch {
2372
2383
res.status(500).json({ error: 'Failed to retrieve instance policy' });
2373
2384
}
2374
2385
});
···
2381
2392
);
2382
2393
const stats = await instanceModerationService.getStatistics();
2383
2394
res.json(stats);
2384
-
} catch (error) {
2395
+
} catch {
2385
2396
res
2386
2397
.status(500)
2387
2398
.json({ error: 'Failed to retrieve moderation statistics' });
···
2464
2475
},
2465
2476
timestamp: new Date().toISOString(),
2466
2477
});
2467
-
} catch (error) {
2478
+
} catch {
2468
2479
res.status(500).json({ error: 'Failed to check Osprey status' });
2469
2480
}
2470
2481
});
···
2482
2493
database: metrics,
2483
2494
connectionPool: poolStatus,
2484
2495
});
2485
-
} catch (error: any) {
2496
+
} catch (error: unknown) {
2497
+
const err = error as Error;
2486
2498
res.status(500).json({
2487
2499
error: 'Health check failed',
2488
-
message: error.message,
2500
+
message: err.message,
2489
2501
});
2490
2502
}
2491
2503
});
···
3271
3283
res.status(200).json({
3272
3284
version: '0.0.1',
3273
3285
});
3274
-
} catch (error) {
3286
+
} catch {
3275
3287
res.status(503).json({
3276
3288
error: 'Service unavailable',
3277
3289
});
···
3297
3309
inviteCodeRequired: false,
3298
3310
phoneVerificationRequired: false,
3299
3311
});
3300
-
} catch (error) {
3312
+
} catch {
3301
3313
res.status(500).json({ error: 'Failed to describe server' });
3302
3314
}
3303
3315
});
···
3966
3978
const xrpcRoutes: Array<{ method: string; path: string }> = [];
3967
3979
3968
3980
// Access the Express router stack to find all XRPC routes
3969
-
app._router.stack.forEach((middleware: any) => {
3970
-
if (middleware.route) {
3971
-
const path = middleware.route.path;
3972
-
if (path.startsWith('/xrpc/')) {
3973
-
const methods = Object.keys(middleware.route.methods);
3974
-
methods.forEach((method) => {
3975
-
xrpcRoutes.push({
3976
-
method: method.toUpperCase(),
3977
-
path,
3981
+
app._router.stack.forEach(
3982
+
(middleware: {
3983
+
route?: { path: string; methods: Record<string, boolean> };
3984
+
}) => {
3985
+
if (middleware.route) {
3986
+
const path = middleware.route.path;
3987
+
if (path.startsWith('/xrpc/')) {
3988
+
const methods = Object.keys(middleware.route.methods);
3989
+
methods.forEach((method) => {
3990
+
xrpcRoutes.push({
3991
+
method: method.toUpperCase(),
3992
+
path,
3993
+
});
3978
3994
});
3979
-
});
3995
+
}
3980
3996
}
3981
3997
}
3982
-
});
3998
+
);
3983
3999
3984
4000
// Build endpoint list from discovered routes
3985
4001
const endpoints = xrpcRoutes.map((route) => {
···
4068
4084
);
4069
4085
4070
4086
// Subscribe to Redis event broadcasts (works on ALL workers)
4071
-
const eventHandler = (event: any) => {
4087
+
const eventHandler = (event: unknown) => {
4072
4088
if (res.writable) {
4073
4089
try {
4074
4090
res.write(
···
4176
4192
const stats = contentFilter.getFilterStats(recentPosts, settings || null);
4177
4193
4178
4194
res.json({ stats });
4179
-
} catch (error) {
4195
+
} catch {
4180
4196
res.status(500).json({ error: 'Failed to get filter stats' });
4181
4197
}
4182
4198
});
···
4349
4365
}
4350
4366
4351
4367
// Subscribe to firehose events for this connection
4352
-
const firehoseEventHandler = (event: any) => {
4368
+
const firehoseEventHandler = (event: unknown) => {
4353
4369
if (ws.readyState === WebSocket.OPEN) {
4354
4370
try {
4355
4371
ws.send(
···
4442
4458
});
4443
4459
4444
4460
// Listen for label events from label service and broadcast to all connected clients
4445
-
const broadcastLabelToClients = (label: any, eventId: number) => {
4461
+
const broadcastLabelToClients = (label: unknown, eventId: number) => {
4446
4462
const message = JSON.stringify({
4447
4463
seq: eventId,
4448
4464
labels: [
+29
-19
server/storage.ts
+29
-19
server/storage.ts
···
91
91
type VideoJob,
92
92
type InsertVideoJob,
93
93
type FirehoseCursor,
94
-
type InsertFirehoseCursor,
95
94
type Bookmark,
96
-
insertBookmarkSchema,
97
95
type Quote,
98
96
type InsertQuote,
99
97
type Verification,
···
111
109
type GenericRecord,
112
110
type InsertGenericRecord,
113
111
} from '@shared/schema';
114
-
import { db, pool, type DbConnection } from './db';
112
+
import { db, type DbConnection } from './db';
115
113
import { eq, desc, and, or, sql, inArray, isNull } from 'drizzle-orm';
116
114
import { encryptionService } from './services/encryption';
117
115
import { sanitizeObject } from './utils/sanitize';
···
381
379
deleteCorruptedSessions(): Promise<number>;
382
380
383
381
// OAuth state operations
384
-
saveOAuthState(state: string, stateData: any, expiresAt: Date): Promise<void>;
385
-
getOAuthState(state: string): Promise<any | undefined>;
382
+
saveOAuthState(
383
+
state: string,
384
+
stateData: unknown,
385
+
expiresAt: Date
386
+
): Promise<void>;
387
+
getOAuthState(state: string): Promise<unknown | undefined>;
386
388
deleteOAuthState(state: string): Promise<void>;
387
389
deleteExpiredOAuthStates(): Promise<void>;
388
390
···
667
669
668
670
export class DatabaseStorage implements IStorage {
669
671
private db: DbConnection;
670
-
private statsCache: { data: any; timestamp: number } | null = null;
672
+
private statsCache: { data: unknown; timestamp: number } | null = null;
671
673
private readonly STATS_CACHE_TTL = 60000;
672
674
private statsQueryInProgress = false;
673
675
private backgroundRefreshInterval: NodeJS.Timeout | null = null;
···
1062
1064
feedType?: string
1063
1065
): Promise<{ items: FeedItem[]; cursor?: string }> {
1064
1066
// Build all conditions to apply them together with AND
1065
-
const conditions: any[] = [eq(feedItems.originatorDid, actorDid)];
1067
+
import type { SQL } from 'drizzle-orm';
1068
+
const conditions: SQL[] = [eq(feedItems.originatorDid, actorDid)];
1066
1069
1067
1070
// Apply feed type filtering
1068
1071
if (feedType === 'posts_no_replies') {
···
2393
2396
? await encryptionService.decrypt(session.refreshToken)
2394
2397
: null,
2395
2398
};
2396
-
} catch (error) {
2399
+
} catch {
2397
2400
// Decryption failed (corrupted data) - delete the session
2398
2401
console.error(
2399
2402
`[STORAGE] Failed to decrypt session ${id}, deleting corrupted session`
···
2420
2423
? await encryptionService.decrypt(session.refreshToken)
2421
2424
: null,
2422
2425
});
2423
-
} catch (error) {
2426
+
} catch {
2424
2427
// Decryption failed - delete corrupted session
2425
2428
console.error(
2426
2429
`[STORAGE] Failed to decrypt session ${session.id}, deleting corrupted session`
···
2438
2441
>
2439
2442
): Promise<Session | undefined> {
2440
2443
// Encrypt tokens if provided
2441
-
const updateData: any = {};
2444
+
const updateData: Partial<
2445
+
Pick<InsertSession, 'accessToken' | 'refreshToken' | 'expiresAt'>
2446
+
> = {};
2442
2447
if (data.accessToken) {
2443
2448
updateData.accessToken = await encryptionService.encrypt(
2444
2449
data.accessToken
···
2494
2499
if (session.refreshToken) {
2495
2500
await encryptionService.decrypt(session.refreshToken);
2496
2501
}
2497
-
} catch (error) {
2502
+
} catch {
2498
2503
// Decryption failed - delete this corrupted session
2499
2504
console.log(`[STORAGE] Deleting corrupted session ${session.id}`);
2500
2505
await this.db.delete(sessions).where(eq(sessions.id, session.id));
···
2507
2512
2508
2513
async saveOAuthState(
2509
2514
state: string,
2510
-
stateData: any,
2515
+
stateData: unknown,
2511
2516
expiresAt: Date
2512
2517
): Promise<void> {
2513
2518
const { oauthStates } = await import('@shared/schema');
···
3304
3309
limit = 50,
3305
3310
cursor?: string
3306
3311
): Promise<{ starterPacks: StarterPack[]; cursor?: string }> {
3307
-
const conditions: any[] = [];
3312
+
import type { SQL } from 'drizzle-orm';
3313
+
const conditions: SQL[] = [];
3308
3314
if (cursor) {
3309
3315
conditions.push(sql`${starterPacks.indexedAt} < ${new Date(cursor)}`);
3310
3316
}
···
3327
3333
limit = 50,
3328
3334
cursor?: string
3329
3335
): Promise<{ starterPacks: StarterPack[]; cursor?: string }> {
3330
-
const conditions: any[] = [eq(starterPacks.creatorDid, creatorDid)];
3336
+
import type { SQL } from 'drizzle-orm';
3337
+
const conditions: SQL[] = [eq(starterPacks.creatorDid, creatorDid)];
3331
3338
if (cursor) {
3332
3339
conditions.push(sql`${starterPacks.indexedAt} < ${new Date(cursor)}`);
3333
3340
}
···
3350
3357
limit = 25,
3351
3358
cursor?: string
3352
3359
): Promise<{ starterPacks: StarterPack[]; cursor?: string }> {
3353
-
const conditions: any[] = [
3360
+
import type { SQL } from 'drizzle-orm';
3361
+
const conditions: SQL[] = [
3354
3362
sql`${starterPacks.name} ILIKE ${'%' + q + '%'}`,
3355
3363
];
3356
3364
if (cursor) {
···
3650
3658
timestamp: Date.now(),
3651
3659
};
3652
3660
}
3653
-
} catch (error) {
3661
+
} catch {
3654
3662
// Silent failure - cache will just be stale
3655
3663
} finally {
3656
3664
this.statsQueryInProgress = false;
···
3726
3734
const result = await Promise.race([statsPromise, timeoutPromise]);
3727
3735
3728
3736
const stats: Record<string, number> = {};
3729
-
result.rows.forEach((row: any) => {
3730
-
stats[row.relname] = Number(row.count || 0);
3731
-
});
3737
+
result.rows.forEach(
3738
+
(row: { relname: string; count: string | number }) => {
3739
+
stats[row.relname] = Number(row.count || 0);
3740
+
}
3741
+
);
3732
3742
3733
3743
const data = {
3734
3744
totalUsers: stats.users || 0,