feat(swarm): Implement decentralized node discovery and content federation system

- Add swarm node registry with discovery, gossip, and timeline synchronization
- Implement database schema for swarm nodes, seeds, and sync logs with proper indexing
- Create swarm API endpoints for node discovery, gossip protocol, timeline sharing, and announcements
- Add content moderation features with NSFW flagging and muted nodes support
- Implement user settings pages for content filtering and node moderation
- Add background scheduler for automated swarm synchronization and node health checks
- Create .well-known endpoint for swarm protocol discovery
- Remove legacy bot-cron.ts in favor of integrated scheduler system
- Add user account NSFW preferences and age verification tracking
- Update database schema with swarm-related tables and user moderation fields
- Enhance post and user components to support federated content display
- Add instrumentation for monitoring swarm operations and node health
This commit is contained in:
AskIt
2026-01-25 23:01:29 +01:00
parent 765845bd89
commit e2fa572e84
42 changed files with 10829 additions and 164 deletions
+85 -8
View File
@@ -7,7 +7,7 @@ import { ArrowLeftIcon, CalendarIcon } from '@/components/Icons';
import { PostCard } from '@/components/PostCard';
import { User, Post } from '@/lib/types';
import AutoTextarea from '@/components/AutoTextarea';
import { Rocket } from 'lucide-react';
import { Rocket, MoreHorizontal } from 'lucide-react';
import { formatFullHandle } from '@/lib/utils/handle';
import { Bot } from 'lucide-react';
@@ -102,6 +102,8 @@ export default function ProfilePage() {
});
const [saveError, setSaveError] = useState<string | null>(null);
const [isSaving, setIsSaving] = useState(false);
const [isBlocked, setIsBlocked] = useState(false);
const [showMenu, setShowMenu] = useState(false);
useEffect(() => {
setIsEditing(false);
@@ -173,6 +175,7 @@ export default function ProfilePage() {
useEffect(() => {
if (!currentUser || !user || currentUser.handle === user.handle) {
setIsFollowing(false);
setIsBlocked(false);
return;
}
@@ -180,6 +183,11 @@ export default function ProfilePage() {
.then(res => res.json())
.then(data => setIsFollowing(!!data.following))
.catch(() => setIsFollowing(false));
fetch(`/api/users/${handle}/block`)
.then(res => res.json())
.then(data => setIsBlocked(!!data.blocked))
.catch(() => setIsBlocked(false));
}, [currentUser, user, handle]);
useEffect(() => {
@@ -226,6 +234,22 @@ export default function ProfilePage() {
}
};
const handleBlock = async () => {
if (!currentUser) return;
const method = isBlocked ? 'DELETE' : 'POST';
const res = await fetch(`/api/users/${handle}/block`, { method });
if (res.ok) {
setIsBlocked(!isBlocked);
if (!isBlocked) {
// If blocking, also unfollow
setIsFollowing(false);
}
setShowMenu(false);
}
};
const handleSaveProfile = async () => {
if (!isOwnProfile) return;
setIsSaving(true);
@@ -377,14 +401,67 @@ export default function ProfilePage() {
)}
</div>
<div style={{ paddingTop: '12px' }}>
<div style={{ paddingTop: '12px', display: 'flex', gap: '8px', alignItems: 'center' }}>
{!isOwnProfile && currentUser && (
<button
className={`btn ${isFollowing ? '' : 'btn-primary'}`}
onClick={handleFollow}
>
{isFollowing ? 'Following' : 'Follow'}
</button>
<>
{!isBlocked && (
<button
className={`btn ${isFollowing ? '' : 'btn-primary'}`}
onClick={handleFollow}
>
{isFollowing ? 'Following' : 'Follow'}
</button>
)}
<div style={{ position: 'relative' }}>
<button
className="btn btn-ghost"
onClick={() => setShowMenu(!showMenu)}
style={{ padding: '8px' }}
>
<MoreHorizontal size={20} />
</button>
{showMenu && (
<>
<div
style={{
position: 'fixed',
inset: 0,
zIndex: 99,
}}
onClick={() => setShowMenu(false)}
/>
<div style={{
position: 'absolute',
right: 0,
top: '100%',
marginTop: '4px',
background: 'var(--background-secondary)',
border: '1px solid var(--border)',
borderRadius: 'var(--radius-md)',
minWidth: '160px',
zIndex: 100,
overflow: 'hidden',
}}>
<button
onClick={handleBlock}
style={{
width: '100%',
padding: '12px 16px',
background: 'none',
border: 'none',
textAlign: 'left',
cursor: 'pointer',
color: isBlocked ? 'var(--foreground)' : 'var(--error)',
fontSize: '14px',
}}
>
{isBlocked ? 'Unblock user' : 'Block user'}
</button>
</div>
</>
)}
</div>
</>
)}
{isOwnProfile && (