mirror of
https://github.com/juherr/kill-the-news.git
synced 2026-06-20 22:03:48 +00:00
debbfc623e
Capture each attachment's Content-ID at ingestion (postal-mime and mailparser paths) and rewrite cid: image refs to the stored /files URL in processEmailContent, shared by the entry view and RSS/Atom feeds. Bodyless HTML fragments are now serialized so sanitization and the cid rewrite apply to them too. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
141 lines
4.1 KiB
TypeScript
141 lines
4.1 KiB
TypeScript
// Global environment interface for Cloudflare Workers
|
|
export interface Env {
|
|
EMAIL_STORAGE: KVNamespace;
|
|
ADMIN_PASSWORD: string;
|
|
DOMAIN: string;
|
|
EMAIL_DOMAIN?: string;
|
|
ATTACHMENT_BUCKET?: R2Bucket;
|
|
ATTACHMENTS_ENABLED?: string; // "false" disables attachments even when R2 is bound
|
|
FEED_MAX_SIZE_BYTES?: string;
|
|
PROXY_TRUSTED_IPS?: string;
|
|
PROXY_AUTH_SECRET?: string;
|
|
FEED_TTL_HOURS?: string;
|
|
}
|
|
|
|
// Stored attachment metadata (bytes live in R2, keyed by id)
|
|
export interface AttachmentData {
|
|
id: string;
|
|
filename: string;
|
|
contentType: string;
|
|
size: number;
|
|
contentId?: string; // Normalized Content-ID (no <>) used to resolve inline cid: refs
|
|
}
|
|
|
|
// Email interface for stored emails
|
|
export interface EmailData {
|
|
subject: string;
|
|
from: string;
|
|
content: string;
|
|
receivedAt: number;
|
|
headers: Record<string, string>;
|
|
attachments?: AttachmentData[];
|
|
}
|
|
|
|
// Feed configuration interface
|
|
export interface FeedConfig {
|
|
title: string;
|
|
description?: string;
|
|
allowed_senders?: string[];
|
|
blocked_senders?: string[];
|
|
language: string;
|
|
author?: string;
|
|
created_at: number;
|
|
updated_at?: number;
|
|
expires_at?: number; // Unix timestamp ms — present when a TTL is configured
|
|
}
|
|
|
|
// Feed metadata interface
|
|
export interface FeedMetadata {
|
|
emails: EmailMetadata[];
|
|
iconDomain?: string; // Most recent sender's domain, used to resolve the feed icon
|
|
// RFC 8058 one-click unsubscribe URLs, keyed by sender so each newsletter on
|
|
// the feed keeps its own (latest) link; fired when the feed is deleted.
|
|
unsubscribe?: Record<string, string>;
|
|
}
|
|
|
|
// Email metadata interface (summary info for listing)
|
|
export interface EmailMetadata {
|
|
key: string;
|
|
subject: string;
|
|
receivedAt: number;
|
|
size?: number;
|
|
attachmentIds?: string[];
|
|
}
|
|
|
|
// Feed list interface
|
|
export interface FeedList {
|
|
feeds: FeedListItem[];
|
|
}
|
|
|
|
// Feed summary interface (for the global feed list)
|
|
export interface FeedListItem {
|
|
id: string;
|
|
title: string;
|
|
description?: string;
|
|
expires_at?: number; // Cached from FeedConfig to avoid per-feed KV reads
|
|
}
|
|
|
|
// Cumulative monitoring counters (persisted as a KV singleton)
|
|
export interface Counters {
|
|
feeds_created: number;
|
|
feeds_deleted: number;
|
|
emails_received: number;
|
|
emails_rejected: number;
|
|
unsubscribes_sent: number;
|
|
last_email_at?: string; // ISO 8601
|
|
last_feed_created_at?: string; // ISO 8601
|
|
first_seen?: string; // ISO 8601 — first time counters were written (instance start)
|
|
// Storage usage snapshot, refreshed by the hourly cron (overwritten, not incremented).
|
|
attachments_bytes?: number; // Total R2 bytes used by attachments
|
|
attachments_count?: number; // Number of R2 objects
|
|
kv_bytes_estimated?: number; // Estimated KV bytes (sum of stored email sizes)
|
|
storage_scanned_at?: string; // ISO 8601 — last storage scan
|
|
}
|
|
|
|
// Monitoring API response: persisted counters + live-computed values
|
|
export interface StatsResponse extends Counters {
|
|
active_feeds: number;
|
|
websub_subscriptions_active: number;
|
|
attachments_enabled: boolean;
|
|
}
|
|
|
|
// WebSub (PubSubHubbub) subscription configuration
|
|
export interface WebSubSubscription {
|
|
callbackUrl: string;
|
|
secret?: string;
|
|
expiresAt: number; // Unix timestamp ms
|
|
format?: "rss" | "atom";
|
|
}
|
|
|
|
// Declare KVNamespace for TypeScript
|
|
declare global {
|
|
// This is not an ideal solution but works for our example
|
|
interface KVNamespace {
|
|
get(key: string, options?: { type: "text" }): Promise<string | null>;
|
|
get(key: string, options: { type: "json" }): Promise<unknown | null>;
|
|
get(
|
|
key: string,
|
|
options: { type: "arrayBuffer" },
|
|
): Promise<ArrayBuffer | null>;
|
|
get(
|
|
key: string,
|
|
options: { type: "stream" },
|
|
): Promise<ReadableStream | null>;
|
|
put(
|
|
key: string,
|
|
value: string | ArrayBuffer | ReadableStream | FormData,
|
|
options?: { expirationTtl?: number; expiration?: number },
|
|
): Promise<void>;
|
|
delete(key: string): Promise<void>;
|
|
list(options?: {
|
|
prefix?: string;
|
|
limit?: number;
|
|
cursor?: string;
|
|
}): Promise<{
|
|
keys: { name: string; expiration?: number }[];
|
|
list_complete: boolean;
|
|
cursor?: string;
|
|
}>;
|
|
}
|
|
}
|