This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import { db, handleRegistry } from '@/db';
|
||||
import { desc, eq, gt } from 'drizzle-orm';
|
||||
import { desc } from 'drizzle-orm';
|
||||
import { normalizeHandle } from '@/lib/federation/handles';
|
||||
|
||||
export async function GET(request: Request) {
|
||||
@@ -17,7 +17,7 @@ export async function GET(request: Request) {
|
||||
if (handleParam) {
|
||||
const cleanHandle = normalizeHandle(handleParam);
|
||||
const entry = await db.query.handleRegistry.findFirst({
|
||||
where: eq(handleRegistry.handle, cleanHandle),
|
||||
where: { handle: cleanHandle },
|
||||
});
|
||||
|
||||
if (!entry) {
|
||||
@@ -36,8 +36,8 @@ export async function GET(request: Request) {
|
||||
|
||||
const sinceDate = sinceParam ? new Date(sinceParam) : null;
|
||||
const entries = await db.query.handleRegistry.findMany({
|
||||
where: sinceDate ? gt(handleRegistry.updatedAt, sinceDate) : undefined,
|
||||
orderBy: [desc(handleRegistry.updatedAt)],
|
||||
where: sinceDate ? { updatedAt: { gt: sinceDate } } : undefined,
|
||||
orderBy: () => [desc(handleRegistry.updatedAt)],
|
||||
limit,
|
||||
});
|
||||
|
||||
|
||||
@@ -38,29 +38,6 @@ export default function AdminPage() {
|
||||
const [logoUploadError, setLogoUploadError] = useState<string | null>(null);
|
||||
const [isUploadingFavicon, setIsUploadingFavicon] = useState(false);
|
||||
const [faviconUploadError, setFaviconUploadError] = useState<string | null>(null);
|
||||
const [updateStatus, setUpdateStatus] = useState<{
|
||||
current: { version: string; commit: string | null; buildDate: string | null };
|
||||
latest: { version: string; commit: string | null; buildDate: string | null } | null;
|
||||
updateAvailable: boolean;
|
||||
updater: {
|
||||
available: boolean;
|
||||
status: string;
|
||||
message?: string;
|
||||
lastStartedAt?: string | null;
|
||||
lastFinishedAt?: string | null;
|
||||
lastExitCode?: number | null;
|
||||
lastError?: string | null;
|
||||
trigger?: 'manual' | 'auto' | null;
|
||||
config?: {
|
||||
autoUpdateEnabled: boolean;
|
||||
intervalMinutes: number;
|
||||
};
|
||||
};
|
||||
} | null>(null);
|
||||
const [loadingUpdateStatus, setLoadingUpdateStatus] = useState(false);
|
||||
const [triggeringUpdate, setTriggeringUpdate] = useState(false);
|
||||
const [savingAutoUpdate, setSavingAutoUpdate] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
fetch('/api/admin/me')
|
||||
.then((res) => res.json())
|
||||
@@ -93,42 +70,12 @@ export default function AdminPage() {
|
||||
}
|
||||
};
|
||||
|
||||
const loadUpdateStatus = async () => {
|
||||
setLoadingUpdateStatus(true);
|
||||
try {
|
||||
const res = await fetch('/api/admin/update', { cache: 'no-store' });
|
||||
if (!res.ok) {
|
||||
throw new Error('Failed to load update status');
|
||||
}
|
||||
|
||||
const data = await res.json();
|
||||
setUpdateStatus(data);
|
||||
} catch {
|
||||
setUpdateStatus(null);
|
||||
} finally {
|
||||
setLoadingUpdateStatus(false);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (isAdmin) {
|
||||
loadNodeSettings();
|
||||
loadUpdateStatus();
|
||||
}
|
||||
}, [isAdmin]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isAdmin) {
|
||||
return;
|
||||
}
|
||||
|
||||
const interval = window.setInterval(() => {
|
||||
loadUpdateStatus();
|
||||
}, 30000);
|
||||
|
||||
return () => window.clearInterval(interval);
|
||||
}, [isAdmin]);
|
||||
|
||||
const handleSaveSettings = async (override?: typeof nodeSettings) => {
|
||||
const payload = override ?? nodeSettings;
|
||||
setSavingSettings(true);
|
||||
@@ -234,33 +181,6 @@ export default function AdminPage() {
|
||||
setBannerPromptError('');
|
||||
};
|
||||
|
||||
const handleTriggerUpdate = async () => {
|
||||
setTriggeringUpdate(true);
|
||||
try {
|
||||
const res = await fetch('/api/admin/update', { method: 'POST', keepalive: true });
|
||||
const data = await res.json().catch(() => ({}));
|
||||
|
||||
if (!res.ok) {
|
||||
throw new Error(data.error || 'Failed to start update');
|
||||
}
|
||||
|
||||
showToast(data.message || 'Update started. Synapsis will restart shortly.', 'success');
|
||||
await loadUpdateStatus();
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : 'Failed to start update';
|
||||
if (message.toLowerCase().includes('fetch') || message.toLowerCase().includes('network')) {
|
||||
showToast('Update likely started. The node is restarting, so this page may disconnect briefly.', 'success');
|
||||
window.setTimeout(() => {
|
||||
window.location.reload();
|
||||
}, 5000);
|
||||
} else {
|
||||
showToast(message, 'error');
|
||||
}
|
||||
} finally {
|
||||
setTriggeringUpdate(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleLogoUpload = async (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const file = event.target.files?.[0];
|
||||
event.target.value = '';
|
||||
@@ -297,38 +217,6 @@ export default function AdminPage() {
|
||||
}
|
||||
};
|
||||
|
||||
const handleToggleAutoUpdate = async () => {
|
||||
if (!updateStatus?.updater.available || !updateStatus.updater.config) return;
|
||||
|
||||
const nextValue = !updateStatus.updater.config.autoUpdateEnabled;
|
||||
setSavingAutoUpdate(true);
|
||||
try {
|
||||
const res = await fetch('/api/admin/update', {
|
||||
method: 'PATCH',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ autoUpdateEnabled: nextValue }),
|
||||
});
|
||||
const data = await res.json().catch(() => ({}));
|
||||
|
||||
if (!res.ok) {
|
||||
throw new Error(data.error || 'Failed to update auto-update setting');
|
||||
}
|
||||
|
||||
setUpdateStatus((prev) => prev ? {
|
||||
...prev,
|
||||
updater: {
|
||||
...prev.updater,
|
||||
config: data.config,
|
||||
},
|
||||
} : prev);
|
||||
showToast(nextValue ? 'Automatic updates enabled' : 'Automatic updates disabled', 'success');
|
||||
} catch (error) {
|
||||
showToast(error instanceof Error ? error.message : 'Failed to update auto-update setting', 'error');
|
||||
} finally {
|
||||
setSavingAutoUpdate(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleFaviconUpload = async (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const file = event.target.files?.[0];
|
||||
event.target.value = '';
|
||||
@@ -703,86 +591,6 @@ export default function AdminPage() {
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div style={{
|
||||
padding: '16px',
|
||||
background: 'var(--background-secondary)',
|
||||
borderRadius: '8px',
|
||||
border: '1px solid var(--border)',
|
||||
}}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', gap: '16px', flexWrap: 'wrap' }}>
|
||||
<div>
|
||||
<h2 style={{ fontSize: '16px', fontWeight: 600, marginBottom: '6px' }}>System Update</h2>
|
||||
<p style={{ fontSize: '13px', color: 'var(--foreground-secondary)', margin: 0 }}>
|
||||
Keep this node on the latest published Synapsis build.
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
className="btn btn-primary"
|
||||
onClick={handleTriggerUpdate}
|
||||
disabled={
|
||||
triggeringUpdate ||
|
||||
loadingUpdateStatus ||
|
||||
updateStatus?.updater.available === false ||
|
||||
updateStatus?.updater.status === 'updating' ||
|
||||
!updateStatus?.updateAvailable
|
||||
}
|
||||
>
|
||||
{triggeringUpdate || updateStatus?.updater.status === 'updating' ? 'Updating...' : 'Update Now'}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{updateStatus?.updater.available && updateStatus?.updater.config && (
|
||||
<div style={{
|
||||
marginTop: '16px',
|
||||
padding: '12px',
|
||||
borderRadius: '8px',
|
||||
border: '1px solid var(--border)',
|
||||
background: 'var(--background)',
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'flex-start',
|
||||
gap: '16px',
|
||||
}}>
|
||||
<div>
|
||||
<div style={{ fontSize: '13px', fontWeight: 600, marginBottom: '4px' }}>
|
||||
Automatic updates
|
||||
</div>
|
||||
<div style={{ fontSize: '12px', color: 'var(--foreground-secondary)' }}>
|
||||
Enabled by default. This node checks for updates every {updateStatus.updater.config.intervalMinutes} minutes and installs them automatically.
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
className={`btn btn-sm ${updateStatus.updater.config.autoUpdateEnabled ? 'btn-primary' : 'btn-ghost'}`}
|
||||
onClick={handleToggleAutoUpdate}
|
||||
disabled={savingAutoUpdate}
|
||||
style={{ flexShrink: 0 }}
|
||||
>
|
||||
{savingAutoUpdate
|
||||
? 'Saving...'
|
||||
: updateStatus.updater.config.autoUpdateEnabled
|
||||
? 'Disable'
|
||||
: 'Enable'}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!updateStatus?.updater.available && (
|
||||
<div style={{
|
||||
marginTop: '16px',
|
||||
padding: '12px',
|
||||
borderRadius: '8px',
|
||||
border: '1px solid var(--border)',
|
||||
background: 'var(--background)',
|
||||
fontSize: '12px',
|
||||
color: 'var(--foreground-secondary)',
|
||||
}}>
|
||||
One-click updates are unavailable on this host. Use:
|
||||
<div style={{ marginTop: '8px', fontFamily: 'monospace', color: 'var(--foreground)' }}>
|
||||
curl -fsSL https://synapsis.social/update.sh | bash
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
@@ -51,10 +51,7 @@ export async function POST(request: Request) {
|
||||
// First get conversation IDs where user is participant1 (local user)
|
||||
// For participant2, we need to check by handle since it's stored as text (can be remote)
|
||||
const conversations = await db.query.chatConversations.findMany({
|
||||
where: or(
|
||||
eq(chatConversations.participant1Id, userId),
|
||||
eq(chatConversations.participant2Handle, user.handle)
|
||||
),
|
||||
where: { OR: [{ participant1Id: userId }, { participant2Handle: user.handle }] },
|
||||
});
|
||||
|
||||
const conversationIds = conversations.map(c => c.id);
|
||||
|
||||
@@ -48,7 +48,7 @@ export async function POST(request: Request) {
|
||||
|
||||
// Check if email is already taken by another user
|
||||
const existingUser = await db.query.users.findFirst({
|
||||
where: eq(users.email, newEmail.toLowerCase()),
|
||||
where: { email: newEmail.toLowerCase() },
|
||||
});
|
||||
|
||||
if (existingUser && existingUser.id !== user.id) {
|
||||
|
||||
@@ -156,11 +156,11 @@ export async function POST(req: NextRequest) {
|
||||
return NextResponse.json({ error: 'This account has already been migrated' }, { status: 400 });
|
||||
}
|
||||
|
||||
const nodeDomain = process.env.NEXT_PUBLIC_NODE_DOMAIN || 'localhost:3000';
|
||||
const nodeDomain = process.env.NEXT_PUBLIC_NODE_DOMAIN || 'localhost:43821';
|
||||
|
||||
// Fetch user's posts
|
||||
const userPosts = await db.query.posts.findMany({
|
||||
where: eq(posts.userId, user.id),
|
||||
where: { userId: user.id },
|
||||
with: {
|
||||
media: true,
|
||||
},
|
||||
@@ -169,19 +169,19 @@ export async function POST(req: NextRequest) {
|
||||
|
||||
// Fetch user's following list (local and remote)
|
||||
const userFollowing = await db.query.follows.findMany({
|
||||
where: eq(follows.followerId, user.id),
|
||||
where: { followerId: user.id },
|
||||
with: {
|
||||
following: true,
|
||||
},
|
||||
});
|
||||
|
||||
const userRemoteFollowing = await db.query.remoteFollows.findMany({
|
||||
where: eq(remoteFollows.followerId, user.id),
|
||||
where: { followerId: user.id },
|
||||
});
|
||||
|
||||
// Fetch DMs
|
||||
const userConversations = await db.query.chatConversations.findMany({
|
||||
where: eq(chatConversations.participant1Id, user.id),
|
||||
where: { participant1Id: user.id },
|
||||
with: {
|
||||
messages: true
|
||||
}
|
||||
@@ -189,7 +189,7 @@ export async function POST(req: NextRequest) {
|
||||
|
||||
// Fetch Bots
|
||||
const userBots = await db.query.bots.findMany({
|
||||
where: eq(bots.ownerId, user.id),
|
||||
where: { ownerId: user.id },
|
||||
with: {
|
||||
user: true,
|
||||
contentSources: true,
|
||||
|
||||
@@ -203,7 +203,7 @@ export async function POST(req: NextRequest) {
|
||||
|
||||
// Check if DID already exists on this node
|
||||
const existingDid = await db.query.users.findFirst({
|
||||
where: eq(users.did, manifest.did),
|
||||
where: { did: manifest.did },
|
||||
});
|
||||
|
||||
if (existingDid) {
|
||||
@@ -222,7 +222,7 @@ export async function POST(req: NextRequest) {
|
||||
|
||||
// Check if handle is available
|
||||
const existingHandle = await db.query.users.findFirst({
|
||||
where: eq(users.handle, handleClean),
|
||||
where: { handle: handleClean },
|
||||
});
|
||||
|
||||
if (existingHandle) {
|
||||
@@ -232,7 +232,7 @@ export async function POST(req: NextRequest) {
|
||||
}, { status: 409 });
|
||||
}
|
||||
|
||||
const nodeDomain = process.env.NEXT_PUBLIC_NODE_DOMAIN || 'localhost:3000';
|
||||
const nodeDomain = process.env.NEXT_PUBLIC_NODE_DOMAIN || 'localhost:43821';
|
||||
const oldActorUrl = `https://${manifest.sourceNode}/users/${manifest.handle}`;
|
||||
const newActorUrl = `https://${nodeDomain}/users/${handleClean}`;
|
||||
|
||||
@@ -253,7 +253,7 @@ export async function POST(req: NextRequest) {
|
||||
|
||||
// Check if this is an NSFW node and auto-enable NSFW settings
|
||||
const node = await db.query.nodes.findFirst({
|
||||
where: eq(nodes.domain, nodeDomain),
|
||||
where: { domain: nodeDomain },
|
||||
});
|
||||
|
||||
if (node?.isNsfw) {
|
||||
@@ -319,7 +319,7 @@ export async function POST(req: NextRequest) {
|
||||
} else {
|
||||
// Local follow - look up user and add to follows table
|
||||
const targetUser = await db.query.users.findFirst({
|
||||
where: eq(users.handle, follow.handle.toLowerCase()),
|
||||
where: { handle: follow.handle.toLowerCase() },
|
||||
});
|
||||
if (targetUser) {
|
||||
await db.insert(follows).values({
|
||||
|
||||
@@ -29,7 +29,7 @@ export async function POST(req: NextRequest) {
|
||||
|
||||
// Find the user on this node
|
||||
const user = await db.query.users.findFirst({
|
||||
where: eq(users.handle, oldHandle.toLowerCase()),
|
||||
where: { handle: oldHandle.toLowerCase() },
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
@@ -67,7 +67,7 @@ export async function POST(req: NextRequest) {
|
||||
|
||||
// Get all followers to notify
|
||||
const userFollowers = await db.query.follows.findMany({
|
||||
where: eq(follows.followingId, user.id),
|
||||
where: { followingId: user.id },
|
||||
with: {
|
||||
follower: true,
|
||||
},
|
||||
|
||||
@@ -9,10 +9,10 @@ export async function PATCH(req: NextRequest) {
|
||||
await requireAdmin();
|
||||
const data = await req.json();
|
||||
|
||||
const domain = process.env.NEXT_PUBLIC_NODE_DOMAIN || 'localhost:3000';
|
||||
const domain = process.env.NEXT_PUBLIC_NODE_DOMAIN || 'localhost:43821';
|
||||
|
||||
let node = await db.query.nodes.findFirst({
|
||||
where: eq(nodes.domain, domain),
|
||||
where: { domain: domain },
|
||||
});
|
||||
|
||||
// 2. Fallback: If not found, check if there is exactly ONE node in the system.
|
||||
|
||||
@@ -95,9 +95,9 @@ export async function POST(req: NextRequest) {
|
||||
const dataUrl = `data:${mimeType};base64,${base64Data}`;
|
||||
|
||||
// Get current node
|
||||
const domain = process.env.NEXT_PUBLIC_NODE_DOMAIN || 'localhost:3000';
|
||||
const domain = process.env.NEXT_PUBLIC_NODE_DOMAIN || 'localhost:43821';
|
||||
let node = await db.query.nodes.findFirst({
|
||||
where: eq(nodes.domain, domain),
|
||||
where: { domain: domain },
|
||||
});
|
||||
|
||||
// Fallback: If not found, check if there is exactly ONE node in the system
|
||||
|
||||
@@ -16,7 +16,7 @@ export async function GET() {
|
||||
await requireAdmin();
|
||||
|
||||
const nodes = await db.query.swarmNodes.findMany({
|
||||
orderBy: [desc(swarmNodes.isBlocked), desc(swarmNodes.blockedAt), desc(swarmNodes.lastSeenAt)],
|
||||
orderBy: () => [desc(swarmNodes.isBlocked), desc(swarmNodes.blockedAt), desc(swarmNodes.lastSeenAt)],
|
||||
});
|
||||
|
||||
return NextResponse.json({
|
||||
@@ -47,7 +47,7 @@ export async function PATCH(request: NextRequest) {
|
||||
const body = await request.json();
|
||||
const data = mutateNodeSchema.parse(body);
|
||||
const domain = normalizeNodeDomain(data.domain);
|
||||
const localDomain = normalizeNodeDomain(process.env.NEXT_PUBLIC_NODE_DOMAIN || 'localhost:3000');
|
||||
const localDomain = normalizeNodeDomain(process.env.NEXT_PUBLIC_NODE_DOMAIN || 'localhost:43821');
|
||||
|
||||
if (domain === localDomain) {
|
||||
return NextResponse.json({ error: 'Cannot block this node itself' }, { status: 400 });
|
||||
|
||||
@@ -24,7 +24,7 @@ export async function PATCH(request: Request, context: RouteContext) {
|
||||
const data = moderationSchema.parse(body);
|
||||
|
||||
const post = await db.query.posts.findFirst({
|
||||
where: eq(posts.id, id),
|
||||
where: { id: id },
|
||||
});
|
||||
|
||||
if (!post) {
|
||||
|
||||
@@ -15,19 +15,16 @@ export async function GET(request: Request) {
|
||||
const status = searchParams.get('status') || 'active'; // active | removed | all
|
||||
const limit = Math.min(parseInt(searchParams.get('limit') || '25'), 50);
|
||||
|
||||
const where =
|
||||
status === 'active'
|
||||
? eq(posts.isRemoved, false)
|
||||
: status === 'removed'
|
||||
? eq(posts.isRemoved, true)
|
||||
: undefined;
|
||||
const where = status === 'all'
|
||||
? undefined
|
||||
: { isRemoved: status === 'removed' };
|
||||
|
||||
const results = await db.query.posts.findMany({
|
||||
where,
|
||||
with: {
|
||||
author: true,
|
||||
},
|
||||
orderBy: [desc(posts.createdAt)],
|
||||
orderBy: () => [desc(posts.createdAt)],
|
||||
limit,
|
||||
});
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ export async function PATCH(request: Request, context: RouteContext) {
|
||||
const data = updateSchema.parse(body);
|
||||
|
||||
const report = await db.query.reports.findFirst({
|
||||
where: eq(reports.id, id),
|
||||
where: { id: id },
|
||||
});
|
||||
|
||||
if (!report) {
|
||||
|
||||
@@ -16,8 +16,8 @@ export async function GET(request: Request) {
|
||||
const limit = Math.min(parseInt(searchParams.get('limit') || '25'), 50);
|
||||
|
||||
const reportRows = await db.query.reports.findMany({
|
||||
where: status === 'all' ? undefined : eq(reports.status, status),
|
||||
orderBy: [desc(reports.createdAt)],
|
||||
where: status === 'all' ? undefined : { status: status },
|
||||
orderBy: () => [desc(reports.createdAt)],
|
||||
limit,
|
||||
with: {
|
||||
reporter: true,
|
||||
@@ -34,13 +34,13 @@ export async function GET(request: Request) {
|
||||
|
||||
const postTargetsRaw = postIds.length
|
||||
? await db.query.posts.findMany({
|
||||
where: inArray(posts.id, postIds),
|
||||
where: { id: { in: postIds } },
|
||||
with: { author: true },
|
||||
})
|
||||
: [];
|
||||
const userTargetsRaw = userIds.length
|
||||
? await db.query.users.findMany({
|
||||
where: inArray(users.id, userIds),
|
||||
where: { id: { in: userIds } },
|
||||
})
|
||||
: [];
|
||||
|
||||
|
||||
@@ -1,82 +0,0 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import { requireAdmin } from '@/lib/auth/admin';
|
||||
import { getCurrentBuildInfo, getLatestPublishedBuild, compareBuildVersions } from '@/lib/version';
|
||||
import { getHostUpdaterStatus, triggerHostUpdate, updateHostUpdaterConfig } from '@/lib/host-updater';
|
||||
|
||||
function isUpdateAvailable(currentVersion: string, latestVersion: string | null | undefined) {
|
||||
if (!latestVersion) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return compareBuildVersions(currentVersion, latestVersion) < 0;
|
||||
}
|
||||
|
||||
export async function GET() {
|
||||
try {
|
||||
await requireAdmin();
|
||||
|
||||
const [latest, updater] = await Promise.all([
|
||||
getLatestPublishedBuild(),
|
||||
getHostUpdaterStatus(),
|
||||
]);
|
||||
|
||||
const current = getCurrentBuildInfo();
|
||||
|
||||
return NextResponse.json({
|
||||
current,
|
||||
latest,
|
||||
updateAvailable: isUpdateAvailable(current.version, latest?.version),
|
||||
updater,
|
||||
});
|
||||
} catch (error) {
|
||||
if (error instanceof Error && error.message === 'Admin required') {
|
||||
return NextResponse.json({ error: 'Admin required' }, { status: 403 });
|
||||
}
|
||||
|
||||
console.error('[Admin Update] Status error:', error);
|
||||
return NextResponse.json({ error: 'Failed to get update status' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
export async function POST() {
|
||||
try {
|
||||
await requireAdmin();
|
||||
|
||||
const result = await triggerHostUpdate();
|
||||
return NextResponse.json(result, { status: 202 });
|
||||
} catch (error) {
|
||||
if (error instanceof Error && error.message === 'Admin required') {
|
||||
return NextResponse.json({ error: 'Admin required' }, { status: 403 });
|
||||
}
|
||||
|
||||
console.error('[Admin Update] Trigger error:', error);
|
||||
return NextResponse.json(
|
||||
{ error: error instanceof Error ? error.message : 'Failed to trigger update' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export async function PATCH(request: Request) {
|
||||
try {
|
||||
await requireAdmin();
|
||||
|
||||
const body = await request.json();
|
||||
if (typeof body.autoUpdateEnabled !== 'boolean') {
|
||||
return NextResponse.json({ error: 'autoUpdateEnabled must be a boolean' }, { status: 400 });
|
||||
}
|
||||
|
||||
const result = await updateHostUpdaterConfig(body.autoUpdateEnabled);
|
||||
return NextResponse.json(result);
|
||||
} catch (error) {
|
||||
if (error instanceof Error && error.message === 'Admin required') {
|
||||
return NextResponse.json({ error: 'Admin required' }, { status: 403 });
|
||||
}
|
||||
|
||||
console.error('[Admin Update] Config error:', error);
|
||||
return NextResponse.json(
|
||||
{ error: error instanceof Error ? error.message : 'Failed to update auto-update settings' },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -24,7 +24,7 @@ export async function PATCH(request: Request, context: RouteContext) {
|
||||
const data = moderationSchema.parse(body);
|
||||
|
||||
const user = await db.query.users.findFirst({
|
||||
where: eq(users.id, id),
|
||||
where: { id: id },
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
|
||||
@@ -25,7 +25,7 @@ export async function GET(req: NextRequest) {
|
||||
let existingUser = null;
|
||||
try {
|
||||
existingUser = await db.query.users.findFirst({
|
||||
where: eq(users.handle, handle),
|
||||
where: { handle: handle },
|
||||
});
|
||||
} catch (err: any) {
|
||||
// Handle fresh installs where the users table isn't created yet.
|
||||
|
||||
@@ -81,9 +81,9 @@ export async function POST(request: Request) {
|
||||
);
|
||||
|
||||
// Check if this is an NSFW node and auto-enable NSFW settings
|
||||
const nodeDomain = process.env.NEXT_PUBLIC_NODE_DOMAIN || 'localhost:3000';
|
||||
const nodeDomain = process.env.NEXT_PUBLIC_NODE_DOMAIN || 'localhost:43821';
|
||||
const node = await db.query.nodes.findFirst({
|
||||
where: eq(nodes.domain, nodeDomain),
|
||||
where: { domain: nodeDomain },
|
||||
});
|
||||
|
||||
if (node?.isNsfw) {
|
||||
|
||||
@@ -29,7 +29,7 @@ export async function GET(
|
||||
|
||||
// Verify bot exists and user owns it
|
||||
const bot = await db.query.bots.findFirst({
|
||||
where: eq(bots.id, botId),
|
||||
where: { id: botId },
|
||||
columns: { id: true, userId: true },
|
||||
});
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ export async function GET(
|
||||
|
||||
// Verify bot exists and user owns it
|
||||
const bot = await db.query.bots.findFirst({
|
||||
where: eq(bots.id, botId),
|
||||
where: { id: botId },
|
||||
columns: { id: true, userId: true },
|
||||
});
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ export async function POST(
|
||||
|
||||
// Verify bot exists and user owns it
|
||||
const bot = await db.query.bots.findFirst({
|
||||
where: eq(bots.id, botId),
|
||||
where: { id: botId },
|
||||
columns: {
|
||||
id: true,
|
||||
userId: true,
|
||||
|
||||
@@ -41,7 +41,7 @@ export async function GET(
|
||||
|
||||
// Verify bot exists and user owns it
|
||||
const bot = await db.query.bots.findFirst({
|
||||
where: eq(bots.id, botId),
|
||||
where: { id: botId },
|
||||
columns: {
|
||||
id: true,
|
||||
userId: true,
|
||||
|
||||
@@ -31,7 +31,7 @@ export async function POST(
|
||||
|
||||
// Verify bot exists
|
||||
const bot = await db.query.bots.findFirst({
|
||||
where: eq(bots.id, botId),
|
||||
where: { id: botId },
|
||||
columns: { id: true, userId: true },
|
||||
});
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ export async function POST(
|
||||
|
||||
// Verify bot exists
|
||||
const bot = await db.query.bots.findFirst({
|
||||
where: eq(bots.id, botId),
|
||||
where: { id: botId },
|
||||
columns: { id: true, userId: true },
|
||||
});
|
||||
|
||||
|
||||
@@ -70,7 +70,7 @@ export async function POST(request: Request) {
|
||||
let botAvatarUrl: string | null | undefined = data.avatarUrl;
|
||||
if (!botAvatarUrl && storageSession) {
|
||||
try {
|
||||
const nodeDomain = process.env.NEXT_PUBLIC_NODE_DOMAIN || 'localhost:3000';
|
||||
const nodeDomain = process.env.NEXT_PUBLIC_NODE_DOMAIN || 'localhost:43821';
|
||||
const botHandle = `${data.handle.toLowerCase()}@${nodeDomain}`;
|
||||
|
||||
botAvatarUrl = await generateAndUploadAvatarToUserStorage(
|
||||
|
||||
@@ -133,7 +133,7 @@ export async function POST(request: NextRequest) {
|
||||
|
||||
// 1. Resolve Sender Public Key
|
||||
let senderUser = await db.query.users.findFirst({
|
||||
where: eq(users.did, did)
|
||||
where: { did: did }
|
||||
});
|
||||
|
||||
let publicKey = senderUser?.publicKey;
|
||||
@@ -155,7 +155,7 @@ export async function POST(request: NextRequest) {
|
||||
} else {
|
||||
// Try handle registry (though we likely don't have it if we don't have the user)
|
||||
const registryEntry = await db.query.handleRegistry.findFirst({
|
||||
where: eq(handleRegistry.did, did)
|
||||
where: { did: did }
|
||||
});
|
||||
if (registryEntry) senderNodeDomain = normalizeNodeDomain(registryEntry.nodeDomain);
|
||||
}
|
||||
@@ -234,7 +234,7 @@ export async function POST(request: NextRequest) {
|
||||
|
||||
// 3. Find Local Recipient
|
||||
const recipientUser = await db.query.users.findFirst({
|
||||
where: eq(users.did, recipientDid)
|
||||
where: { did: recipientDid }
|
||||
});
|
||||
|
||||
if (!recipientUser) {
|
||||
@@ -247,10 +247,7 @@ export async function POST(request: NextRequest) {
|
||||
const computedFullSenderHandle = senderHandle.includes('@') ? senderHandle : (senderNodeDomain ? `${senderHandle}@${senderNodeDomain}` : senderHandle);
|
||||
|
||||
let conversation = await db.query.chatConversations.findFirst({
|
||||
where: and(
|
||||
eq(chatConversations.participant1Id, recipientUser.id),
|
||||
eq(chatConversations.participant2Handle, computedFullSenderHandle)
|
||||
)
|
||||
where: { AND: [{ participant1Id: recipientUser.id }, { participant2Handle: computedFullSenderHandle }] }
|
||||
});
|
||||
|
||||
if (!conversation) {
|
||||
|
||||
@@ -33,7 +33,7 @@ export async function POST(request: NextRequest) {
|
||||
|
||||
// Check if recipient is local
|
||||
const recipientUser = await db.query.users.findFirst({
|
||||
where: eq(users.did, recipientDid)
|
||||
where: { did: recipientDid }
|
||||
});
|
||||
|
||||
// Check if recipient is a local user (not remote/swarm cached)
|
||||
@@ -55,10 +55,7 @@ export async function POST(request: NextRequest) {
|
||||
} else if (recipientUser.dmPrivacy === 'following') {
|
||||
// Check if recipient follows the sender
|
||||
const isFollowingSender = await db.query.follows.findFirst({
|
||||
where: and(
|
||||
eq(follows.followerId, recipientUser.id),
|
||||
eq(follows.followingId, user.id)
|
||||
)
|
||||
where: { AND: [{ followerId: recipientUser.id }, { followingId: user.id }] }
|
||||
});
|
||||
if (!isFollowingSender) {
|
||||
return NextResponse.json({ error: 'This user only accepts messages from accounts they follow' }, { status: 403 });
|
||||
@@ -69,10 +66,7 @@ export async function POST(request: NextRequest) {
|
||||
// Ensure conversations exist for both sides
|
||||
// 1. Recipient's Inbox (Recipient -> User)
|
||||
let recipientConv = await db.query.chatConversations.findFirst({
|
||||
where: and(
|
||||
eq(chatConversations.participant1Id, recipientUser.id),
|
||||
eq(chatConversations.participant2Handle, user.handle)
|
||||
)
|
||||
where: { AND: [{ participant1Id: recipientUser.id }, { participant2Handle: user.handle }] }
|
||||
});
|
||||
|
||||
if (!recipientConv) {
|
||||
@@ -96,10 +90,7 @@ export async function POST(request: NextRequest) {
|
||||
|
||||
// 2. Sender's Sent Box (User -> Recipient)
|
||||
let senderConv = await db.query.chatConversations.findFirst({
|
||||
where: and(
|
||||
eq(chatConversations.participant1Id, user.id),
|
||||
eq(chatConversations.participant2Handle, recipientUser.handle)
|
||||
)
|
||||
where: { AND: [{ participant1Id: user.id }, { participant2Handle: recipientUser.handle }] }
|
||||
});
|
||||
|
||||
if (!senderConv) {
|
||||
@@ -151,7 +142,7 @@ export async function POST(request: NextRequest) {
|
||||
|
||||
// 1. Resolve recipient node
|
||||
const registryEntry = await db.query.handleRegistry.findFirst({
|
||||
where: eq(handleRegistry.did, recipientDid)
|
||||
where: { did: recipientDid }
|
||||
});
|
||||
|
||||
// If not in registry, try to parse from handle if it has domain
|
||||
@@ -224,10 +215,7 @@ export async function POST(request: NextRequest) {
|
||||
// 3. Store "Sent" copy locally
|
||||
// Ensure conversation exists locally
|
||||
let senderConv = await db.query.chatConversations.findFirst({
|
||||
where: and(
|
||||
eq(chatConversations.participant1Id, user.id),
|
||||
eq(chatConversations.participant2Handle, recipientHandle)
|
||||
)
|
||||
where: { AND: [{ participant1Id: user.id }, { participant2Handle: recipientHandle }] }
|
||||
});
|
||||
|
||||
if (!senderConv) {
|
||||
|
||||
@@ -12,7 +12,7 @@ export async function GET() {
|
||||
|
||||
// Get user's conversations
|
||||
const conversations = await db.query.chatConversations.findMany({
|
||||
where: eq(chatConversations.participant1Id, session.user.id),
|
||||
where: { participant1Id: session.user.id },
|
||||
});
|
||||
|
||||
if (conversations.length === 0) {
|
||||
@@ -23,10 +23,7 @@ export async function GET() {
|
||||
const conversationIds = conversations.map(c => c.id);
|
||||
|
||||
const unreadMessages = await db.query.chatMessages.findMany({
|
||||
where: and(
|
||||
inArray(chatMessages.conversationId, conversationIds),
|
||||
isNull(chatMessages.readAt)
|
||||
),
|
||||
where: { AND: [{ conversationId: { in: conversationIds } }, { readAt: { isNull: true } }] },
|
||||
});
|
||||
|
||||
// Filter out messages sent by the current user
|
||||
|
||||
@@ -2,6 +2,6 @@ import { NextResponse } from 'next/server';
|
||||
|
||||
export async function GET() {
|
||||
return NextResponse.json({
|
||||
domain: process.env.NEXT_PUBLIC_NODE_DOMAIN || process.env.NODE_DOMAIN || 'localhost:3000',
|
||||
domain: process.env.NEXT_PUBLIC_NODE_DOMAIN || process.env.NODE_DOMAIN || 'localhost:43821',
|
||||
});
|
||||
}
|
||||
|
||||
@@ -21,14 +21,14 @@ export async function GET(req: NextRequest) {
|
||||
try {
|
||||
if (!db) {
|
||||
// Redirect to default favicon
|
||||
const domain = process.env.NEXT_PUBLIC_NODE_DOMAIN || 'localhost:3000';
|
||||
const domain = process.env.NEXT_PUBLIC_NODE_DOMAIN || 'localhost:43821';
|
||||
const baseUrl = getRequestBaseUrl(req, domain);
|
||||
return NextResponse.redirect(new URL('/favicon.png', baseUrl));
|
||||
}
|
||||
|
||||
const domain = process.env.NEXT_PUBLIC_NODE_DOMAIN || 'localhost:3000';
|
||||
const domain = process.env.NEXT_PUBLIC_NODE_DOMAIN || 'localhost:43821';
|
||||
const node = await db.query.nodes.findFirst({
|
||||
where: eq(nodes.domain, domain),
|
||||
where: { domain: domain },
|
||||
columns: { faviconUrl: true },
|
||||
});
|
||||
|
||||
@@ -46,7 +46,7 @@ export async function GET(req: NextRequest) {
|
||||
return NextResponse.redirect(new URL('/favicon.png', baseUrl));
|
||||
} catch (error) {
|
||||
console.error('Favicon error:', error);
|
||||
const domain = process.env.NEXT_PUBLIC_NODE_DOMAIN || 'localhost:3000';
|
||||
const domain = process.env.NEXT_PUBLIC_NODE_DOMAIN || 'localhost:43821';
|
||||
const baseUrl = getRequestBaseUrl(req, domain);
|
||||
return NextResponse.redirect(new URL('/favicon.png', baseUrl));
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ export async function GET(request: Request) {
|
||||
const parsed = parseHandleWithDomain(handleParam);
|
||||
const lookupHandle = parsed ? parsed.handle : normalizeHandle(handleParam);
|
||||
const localEntry = await db.query.handleRegistry.findFirst({
|
||||
where: eq(handleRegistry.handle, lookupHandle),
|
||||
where: { handle: lookupHandle },
|
||||
});
|
||||
|
||||
if (localEntry) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
|
||||
/**
|
||||
* Health check endpoint for Docker and monitoring
|
||||
* Health check endpoint for systemd and external monitoring
|
||||
* Returns 200 OK when the application is running properly
|
||||
*/
|
||||
export async function GET() {
|
||||
|
||||
@@ -10,11 +10,11 @@ export async function GET() {
|
||||
return NextResponse.json({ error: 'Database not available' }, { status: 500 });
|
||||
}
|
||||
|
||||
const domain = process.env.NEXT_PUBLIC_NODE_DOMAIN || 'localhost:3000';
|
||||
const domain = process.env.NEXT_PUBLIC_NODE_DOMAIN || 'localhost:43821';
|
||||
|
||||
// 1. Try exact match
|
||||
let node = await db.query.nodes.findFirst({
|
||||
where: eq(nodes.domain, domain),
|
||||
where: { domain: domain },
|
||||
});
|
||||
|
||||
// 2. Fallback: If not found, check if there is exactly ONE node in the system
|
||||
|
||||
@@ -10,11 +10,11 @@ export async function GET() {
|
||||
return NextResponse.json({ error: 'Database not available' }, { status: 500 });
|
||||
}
|
||||
|
||||
const domain = process.env.NEXT_PUBLIC_NODE_DOMAIN || 'localhost:3000';
|
||||
const domain = process.env.NEXT_PUBLIC_NODE_DOMAIN || 'localhost:43821';
|
||||
|
||||
// 1. Try exact match
|
||||
let node = await db.query.nodes.findFirst({
|
||||
where: eq(nodes.domain, domain),
|
||||
where: { domain: domain },
|
||||
});
|
||||
|
||||
// 2. Fallback: If not found, check if there is exactly ONE node in the system
|
||||
|
||||
@@ -9,11 +9,11 @@ export async function GET() {
|
||||
try {
|
||||
if (!db) return NextResponse.json({});
|
||||
|
||||
const domain = process.env.NEXT_PUBLIC_NODE_DOMAIN || 'localhost:3000';
|
||||
const domain = process.env.NEXT_PUBLIC_NODE_DOMAIN || 'localhost:43821';
|
||||
|
||||
// 1. Try exact match
|
||||
let node = await db.query.nodes.findFirst({
|
||||
where: eq(nodes.domain, domain),
|
||||
where: { domain: domain },
|
||||
});
|
||||
|
||||
// 2. Fallback: If not found, check if there is exactly ONE node in the system.
|
||||
|
||||
@@ -45,14 +45,12 @@ export async function GET(request: Request) {
|
||||
const limit = Math.min(parseInt(searchParams.get('limit') || '30'), 50);
|
||||
const unreadOnly = searchParams.get('unread') === 'true';
|
||||
|
||||
const conditions = [eq(notifications.userId, user.id)];
|
||||
if (unreadOnly) {
|
||||
conditions.push(isNull(notifications.readAt));
|
||||
}
|
||||
|
||||
const rows = await db.query.notifications.findMany({
|
||||
where: and(...conditions),
|
||||
orderBy: [desc(notifications.createdAt)],
|
||||
where: {
|
||||
userId: user.id,
|
||||
...(unreadOnly ? { readAt: { isNull: true as const } } : {}),
|
||||
},
|
||||
orderBy: () => [desc(notifications.createdAt)],
|
||||
limit,
|
||||
});
|
||||
|
||||
|
||||
@@ -120,7 +120,7 @@ export async function POST(request: Request, context: RouteContext) {
|
||||
|
||||
const decodedId = decodedParamId;
|
||||
const postId = postIdSchema.parse(decodedId);
|
||||
const nodeDomain = process.env.NEXT_PUBLIC_NODE_DOMAIN || 'localhost:3000';
|
||||
const nodeDomain = process.env.NEXT_PUBLIC_NODE_DOMAIN || 'localhost:43821';
|
||||
|
||||
if (user.isSuspended || user.isSilenced) {
|
||||
return NextResponse.json({ error: 'Account restricted' }, { status: 403 });
|
||||
@@ -178,7 +178,7 @@ export async function POST(request: Request, context: RouteContext) {
|
||||
|
||||
// Local post - check if it exists
|
||||
const post = await db.query.posts.findFirst({
|
||||
where: eq(posts.id, postId),
|
||||
where: { id: postId },
|
||||
});
|
||||
|
||||
if (!post) {
|
||||
@@ -190,10 +190,7 @@ export async function POST(request: Request, context: RouteContext) {
|
||||
|
||||
// Check if already liked
|
||||
const existingLike = await db.query.likes.findFirst({
|
||||
where: and(
|
||||
eq(likes.userId, user.id),
|
||||
eq(likes.postId, postId)
|
||||
),
|
||||
where: { AND: [{ userId: user.id }, { postId: postId }] },
|
||||
});
|
||||
|
||||
if (existingLike) {
|
||||
@@ -213,7 +210,7 @@ export async function POST(request: Request, context: RouteContext) {
|
||||
|
||||
if (post.userId !== user.id) {
|
||||
const postAuthor = await db.query.users.findFirst({
|
||||
where: eq(users.id, post.userId),
|
||||
where: { id: post.userId },
|
||||
});
|
||||
|
||||
// Create notification with actor info stored directly
|
||||
@@ -323,7 +320,7 @@ export async function DELETE(request: Request, context: RouteContext) {
|
||||
|
||||
const decodedId = decodedParamId;
|
||||
const postId = postIdSchema.parse(decodedId);
|
||||
const nodeDomain = process.env.NEXT_PUBLIC_NODE_DOMAIN || 'localhost:3000';
|
||||
const nodeDomain = process.env.NEXT_PUBLIC_NODE_DOMAIN || 'localhost:43821';
|
||||
|
||||
if (user.isSuspended || user.isSilenced) {
|
||||
return NextResponse.json({ error: 'Account restricted' }, { status: 403 });
|
||||
@@ -368,7 +365,7 @@ export async function DELETE(request: Request, context: RouteContext) {
|
||||
|
||||
// Local post - check if it exists
|
||||
const post = await db.query.posts.findFirst({
|
||||
where: eq(posts.id, postId),
|
||||
where: { id: postId },
|
||||
});
|
||||
|
||||
if (!post) {
|
||||
@@ -380,10 +377,7 @@ export async function DELETE(request: Request, context: RouteContext) {
|
||||
|
||||
// Find the like
|
||||
const existingLike = await db.query.likes.findFirst({
|
||||
where: and(
|
||||
eq(likes.userId, user.id),
|
||||
eq(likes.postId, postId)
|
||||
),
|
||||
where: { AND: [{ userId: user.id }, { postId: postId }] },
|
||||
});
|
||||
|
||||
if (!existingLike) {
|
||||
@@ -395,7 +389,7 @@ export async function DELETE(request: Request, context: RouteContext) {
|
||||
|
||||
// Update post's like count (atomic decrement, clamped to 0)
|
||||
await db.update(posts)
|
||||
.set({ likesCount: sql`GREATEST(0, ${posts.likesCount} - 1)` })
|
||||
.set({ likesCount: sql`max(0, ${posts.likesCount} - 1)` })
|
||||
.where(eq(posts.id, postId));
|
||||
|
||||
// SWARM-FIRST: Deliver unlike to swarm node
|
||||
|
||||
@@ -90,7 +90,7 @@ export async function POST(request: Request, context: RouteContext) {
|
||||
const { id: rawId } = await context.params;
|
||||
const decodedId = decodeURIComponent(rawId);
|
||||
const postId = postIdSchema.parse(decodedId);
|
||||
const nodeDomain = process.env.NEXT_PUBLIC_NODE_DOMAIN || 'localhost:3000';
|
||||
const nodeDomain = process.env.NEXT_PUBLIC_NODE_DOMAIN || 'localhost:43821';
|
||||
|
||||
if (user.isSuspended || user.isSilenced) {
|
||||
return NextResponse.json({ error: 'Account restricted' }, { status: 403 });
|
||||
@@ -106,11 +106,7 @@ export async function POST(request: Request, context: RouteContext) {
|
||||
}
|
||||
|
||||
const existingRepost = await db.query.userSwarmReposts.findFirst({
|
||||
where: and(
|
||||
eq(userSwarmReposts.userId, user.id),
|
||||
eq(userSwarmReposts.nodeDomain, targetDomain),
|
||||
eq(userSwarmReposts.originalPostId, originalPostId),
|
||||
),
|
||||
where: { AND: [{ userId: user.id }, { nodeDomain: targetDomain }, { originalPostId: originalPostId }] },
|
||||
});
|
||||
|
||||
if (existingRepost) {
|
||||
@@ -165,7 +161,7 @@ export async function POST(request: Request, context: RouteContext) {
|
||||
|
||||
// Local post - check if it exists
|
||||
const originalPost = await db.query.posts.findFirst({
|
||||
where: eq(posts.id, postId),
|
||||
where: { id: postId },
|
||||
});
|
||||
|
||||
if (!originalPost) {
|
||||
@@ -177,11 +173,7 @@ export async function POST(request: Request, context: RouteContext) {
|
||||
|
||||
// Check if already reposted by this user
|
||||
const existingRepost = await db.query.posts.findFirst({
|
||||
where: and(
|
||||
eq(posts.userId, user.id),
|
||||
eq(posts.repostOfId, postId),
|
||||
eq(posts.isRemoved, false)
|
||||
),
|
||||
where: { AND: [{ userId: user.id }, { repostOfId: postId }, { isRemoved: false }] },
|
||||
});
|
||||
|
||||
if (existingRepost) {
|
||||
@@ -210,7 +202,7 @@ export async function POST(request: Request, context: RouteContext) {
|
||||
|
||||
if (originalPost.userId !== user.id) {
|
||||
const postAuthor = await db.query.users.findFirst({
|
||||
where: eq(users.id, originalPost.userId),
|
||||
where: { id: originalPost.userId },
|
||||
});
|
||||
|
||||
// Create notification with actor info stored directly
|
||||
@@ -300,7 +292,7 @@ export async function DELETE(request: Request, context: RouteContext) {
|
||||
const { id: rawId } = await context.params;
|
||||
const decodedId = decodeURIComponent(rawId);
|
||||
const postId = postIdSchema.parse(decodedId);
|
||||
const nodeDomain = process.env.NEXT_PUBLIC_NODE_DOMAIN || 'localhost:3000';
|
||||
const nodeDomain = process.env.NEXT_PUBLIC_NODE_DOMAIN || 'localhost:43821';
|
||||
|
||||
if (user.isSuspended || user.isSilenced) {
|
||||
return NextResponse.json({ error: 'Account restricted' }, { status: 403 });
|
||||
@@ -316,11 +308,7 @@ export async function DELETE(request: Request, context: RouteContext) {
|
||||
}
|
||||
|
||||
const existingRepost = await db.query.userSwarmReposts.findFirst({
|
||||
where: and(
|
||||
eq(userSwarmReposts.userId, user.id),
|
||||
eq(userSwarmReposts.nodeDomain, targetDomain),
|
||||
eq(userSwarmReposts.originalPostId, originalPostId),
|
||||
),
|
||||
where: { AND: [{ userId: user.id }, { nodeDomain: targetDomain }, { originalPostId: originalPostId }] },
|
||||
});
|
||||
|
||||
if (!existingRepost) {
|
||||
@@ -352,7 +340,7 @@ export async function DELETE(request: Request, context: RouteContext) {
|
||||
));
|
||||
|
||||
await db.update(users)
|
||||
.set({ postsCount: sql`GREATEST(0, ${users.postsCount} - 1)` })
|
||||
.set({ postsCount: sql`max(0, ${users.postsCount} - 1)` })
|
||||
.where(eq(users.id, user.id));
|
||||
|
||||
console.log(`[Swarm] Unrepost delivered to ${targetDomain} for post ${originalPostId}`);
|
||||
@@ -361,16 +349,12 @@ export async function DELETE(request: Request, context: RouteContext) {
|
||||
|
||||
// Local post - check if original post exists
|
||||
const originalPost = await db.query.posts.findFirst({
|
||||
where: eq(posts.id, postId),
|
||||
where: { id: postId },
|
||||
});
|
||||
|
||||
// Find the repost by this user
|
||||
const repost = await db.query.posts.findFirst({
|
||||
where: and(
|
||||
eq(posts.userId, user.id),
|
||||
eq(posts.repostOfId, postId),
|
||||
eq(posts.isRemoved, false)
|
||||
),
|
||||
where: { AND: [{ userId: user.id }, { repostOfId: postId }, { isRemoved: false }] },
|
||||
});
|
||||
|
||||
if (!repost) {
|
||||
@@ -385,13 +369,13 @@ export async function DELETE(request: Request, context: RouteContext) {
|
||||
// Update original post's repost count
|
||||
if (originalPost) {
|
||||
await db.update(posts)
|
||||
.set({ repostsCount: sql`GREATEST(0, ${posts.repostsCount} - 1)` })
|
||||
.set({ repostsCount: sql`max(0, ${posts.repostsCount} - 1)` })
|
||||
.where(eq(posts.id, postId));
|
||||
}
|
||||
|
||||
// Update user's post count
|
||||
await db.update(users)
|
||||
.set({ postsCount: sql`GREATEST(0, ${users.postsCount} - 1)` })
|
||||
.set({ postsCount: sql`max(0, ${users.postsCount} - 1)` })
|
||||
.where(eq(users.id, user.id));
|
||||
|
||||
return NextResponse.json({ success: true, reposted: false });
|
||||
|
||||
@@ -88,7 +88,7 @@ export async function GET(
|
||||
// Decode URL-encoded characters (e.g., %3A -> :)
|
||||
const id = decodeURIComponent(rawId);
|
||||
|
||||
const nodeDomain = process.env.NEXT_PUBLIC_NODE_DOMAIN || 'localhost:3000';
|
||||
const nodeDomain = process.env.NEXT_PUBLIC_NODE_DOMAIN || 'localhost:43821';
|
||||
|
||||
let mainPost: any = null;
|
||||
let replyPosts: any[] = [];
|
||||
@@ -168,7 +168,7 @@ export async function GET(
|
||||
}
|
||||
|
||||
const post = await db.query.posts.findFirst({
|
||||
where: eq(posts.id, id),
|
||||
where: { id: id },
|
||||
with: postDetailRelations,
|
||||
});
|
||||
|
||||
@@ -176,12 +176,9 @@ export async function GET(
|
||||
mainPost = post;
|
||||
|
||||
const replies = await db.query.posts.findMany({
|
||||
where: and(
|
||||
eq(posts.replyToId, id),
|
||||
eq(posts.isRemoved, false)
|
||||
),
|
||||
where: { AND: [{ replyToId: id }, { isRemoved: false }] },
|
||||
with: postDetailRelations,
|
||||
orderBy: [desc(posts.createdAt)],
|
||||
orderBy: () => [desc(posts.createdAt)],
|
||||
});
|
||||
|
||||
mainPost = {
|
||||
@@ -201,19 +198,12 @@ export async function GET(
|
||||
|
||||
if (allPostIds.length > 0) {
|
||||
const viewerLikes = await db.query.likes.findMany({
|
||||
where: and(
|
||||
eq(likes.userId, viewer.id),
|
||||
inArray(likes.postId, allPostIds)
|
||||
),
|
||||
where: { AND: [{ userId: viewer.id }, { postId: { in: allPostIds } }] },
|
||||
});
|
||||
const likedPostIds = new Set(viewerLikes.map(l => l.postId));
|
||||
|
||||
const viewerReposts = await db.query.posts.findMany({
|
||||
where: and(
|
||||
eq(posts.userId, viewer.id),
|
||||
inArray(posts.repostOfId, allPostIds),
|
||||
eq(posts.isRemoved, false)
|
||||
),
|
||||
where: { AND: [{ userId: viewer.id }, { repostOfId: { in: allPostIds } }, { isRemoved: false }] },
|
||||
});
|
||||
const repostedPostIds = new Set(viewerReposts.map(r => r.repostOfId));
|
||||
|
||||
@@ -233,7 +223,7 @@ export async function GET(
|
||||
}
|
||||
} else {
|
||||
const cached = await db.query.remotePosts.findFirst({
|
||||
where: eq(remotePosts.apId, id),
|
||||
where: { apId: id },
|
||||
});
|
||||
|
||||
if (cached) {
|
||||
@@ -296,7 +286,7 @@ export async function DELETE(
|
||||
const user = await requireAuth();
|
||||
const { id } = await params;
|
||||
|
||||
const nodeDomain = process.env.NEXT_PUBLIC_NODE_DOMAIN || 'localhost:3000';
|
||||
const nodeDomain = process.env.NEXT_PUBLIC_NODE_DOMAIN || 'localhost:43821';
|
||||
|
||||
// Handle swarm post IDs (format: swarm:domain:uuid)
|
||||
if (id.startsWith('swarm:')) {
|
||||
@@ -393,7 +383,7 @@ export async function DELETE(
|
||||
}
|
||||
|
||||
const post = await db.query.posts.findFirst({
|
||||
where: eq(posts.id, id),
|
||||
where: { id: id },
|
||||
with: {
|
||||
bot: true,
|
||||
},
|
||||
@@ -412,7 +402,7 @@ export async function DELETE(
|
||||
let isParentPostOwner = false;
|
||||
if (post.replyToId) {
|
||||
const parentPost = await db.query.posts.findFirst({
|
||||
where: eq(posts.id, post.replyToId),
|
||||
where: { id: post.replyToId },
|
||||
});
|
||||
if (parentPost && parentPost.userId === user.id) {
|
||||
isParentPostOwner = true;
|
||||
@@ -426,7 +416,7 @@ export async function DELETE(
|
||||
// 1. If it's a reply, decrement parent's repliesCount
|
||||
if (post.replyToId) {
|
||||
const parentPost = await db.query.posts.findFirst({
|
||||
where: eq(posts.id, post.replyToId),
|
||||
where: { id: post.replyToId },
|
||||
});
|
||||
if (parentPost && parentPost.repliesCount > 0) {
|
||||
await db.update(posts)
|
||||
@@ -472,7 +462,7 @@ export async function DELETE(
|
||||
|
||||
// 4. Decrement the post author's postsCount (atomic decrement, clamped to 0)
|
||||
await db.update(users)
|
||||
.set({ postsCount: sql`GREATEST(0, ${users.postsCount} - 1)` })
|
||||
.set({ postsCount: sql`max(0, ${users.postsCount} - 1)` })
|
||||
.where(eq(users.id, post.userId));
|
||||
|
||||
return NextResponse.json({ success: true });
|
||||
|
||||
@@ -85,7 +85,7 @@ describe('POST /api/posts', () => {
|
||||
};
|
||||
|
||||
// Create a mock request
|
||||
const request = new Request('http://localhost:3000/api/posts', {
|
||||
const request = new Request('http://localhost:43821/api/posts', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
@@ -122,7 +122,7 @@ describe('POST /api/posts', () => {
|
||||
signature: 'invalid-signature',
|
||||
};
|
||||
|
||||
const request = new Request('http://localhost:3000/api/posts', {
|
||||
const request = new Request('http://localhost:43821/api/posts', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
@@ -152,7 +152,7 @@ describe('POST /api/posts', () => {
|
||||
signature: 'test-signature',
|
||||
};
|
||||
|
||||
const request = new Request('http://localhost:3000/api/posts', {
|
||||
const request = new Request('http://localhost:43821/api/posts', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
@@ -182,7 +182,7 @@ describe('POST /api/posts', () => {
|
||||
signature: 'test-signature',
|
||||
};
|
||||
|
||||
const request = new Request('http://localhost:3000/api/posts', {
|
||||
const request = new Request('http://localhost:43821/api/posts', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
@@ -212,7 +212,7 @@ describe('POST /api/posts', () => {
|
||||
signature: 'test-signature',
|
||||
};
|
||||
|
||||
const request = new Request('http://localhost:3000/api/posts', {
|
||||
const request = new Request('http://localhost:43821/api/posts', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
@@ -253,7 +253,7 @@ describe('POST /api/posts', () => {
|
||||
signature: 'test-signature',
|
||||
};
|
||||
|
||||
const request = new Request('http://localhost:3000/api/posts', {
|
||||
const request = new Request('http://localhost:43821/api/posts', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
@@ -293,7 +293,7 @@ describe('POST /api/posts', () => {
|
||||
signature: 'test-signature',
|
||||
};
|
||||
|
||||
const request = new Request('http://localhost:3000/api/posts', {
|
||||
const request = new Request('http://localhost:43821/api/posts', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
|
||||
+62
-71
@@ -3,7 +3,6 @@ import { db, posts, users, media, follows, mutes, blocks, likes, remoteFollows,
|
||||
import { requireAuth } from '@/lib/auth';
|
||||
import { requireSignedAction, type SignedAction } from '@/lib/auth/verify-signature';
|
||||
import { eq, desc, and, inArray, isNull, isNotNull, or, lt, sql } from 'drizzle-orm';
|
||||
import type { SQL } from 'drizzle-orm';
|
||||
import { z } from 'zod';
|
||||
import { buildNotificationTarget } from '@/lib/notifications';
|
||||
import { serializeLinkPreviewMedia, parseLinkPreviewMediaJson } from '@/lib/media/linkPreview';
|
||||
@@ -13,12 +12,6 @@ const CURATION_WINDOW_HOURS = 72;
|
||||
const CURATION_SEED_MULTIPLIER = 5;
|
||||
const CURATION_SEED_CAP = 200;
|
||||
|
||||
const buildWhere = (...conditions: Array<SQL | undefined>) => {
|
||||
const filtered = conditions.filter(Boolean) as SQL[];
|
||||
if (filtered.length === 0) return undefined;
|
||||
return and(...filtered);
|
||||
};
|
||||
|
||||
type FeedPostWithChildren = {
|
||||
id: string;
|
||||
repostOf?: FeedPostWithChildren | null;
|
||||
@@ -90,13 +83,13 @@ async function getMixedFeedCursorDate(cursor: string | null) {
|
||||
|
||||
if (cursor.startsWith('swarm-repost:')) {
|
||||
const repostRow = await db.query.userSwarmReposts.findFirst({
|
||||
where: eq(userSwarmReposts.id, cursor.replace('swarm-repost:', '')),
|
||||
where: { id: cursor.replace('swarm-repost:', '') },
|
||||
});
|
||||
return repostRow?.repostedAt ?? null;
|
||||
}
|
||||
|
||||
const cursorPost = await db.query.posts.findFirst({
|
||||
where: eq(posts.id, cursor),
|
||||
where: { id: cursor },
|
||||
});
|
||||
return cursorPost?.createdAt ?? null;
|
||||
}
|
||||
@@ -211,7 +204,7 @@ export async function POST(request: Request) {
|
||||
return NextResponse.json({ error: 'Account restricted' }, { status: 403 });
|
||||
}
|
||||
|
||||
const nodeDomain = process.env.NEXT_PUBLIC_NODE_DOMAIN || 'localhost:3000';
|
||||
const nodeDomain = process.env.NEXT_PUBLIC_NODE_DOMAIN || 'localhost:43821';
|
||||
|
||||
// Build swarm reply fields if replying to a swarm post
|
||||
const swarmReplyFields = data.swarmReplyTo ? {
|
||||
@@ -245,11 +238,7 @@ export async function POST(request: Request) {
|
||||
let unattachedMedia: typeof media.$inferSelect[] = [];
|
||||
if (data.mediaIds?.length) {
|
||||
unattachedMedia = await db.query.media.findMany({
|
||||
where: and(
|
||||
inArray(media.id, data.mediaIds),
|
||||
eq(media.userId, user.id),
|
||||
isNull(media.postId),
|
||||
),
|
||||
where: { AND: [{ id: { in: data.mediaIds } }, { userId: user.id }, { postId: { isNull: true } }] },
|
||||
});
|
||||
}
|
||||
|
||||
@@ -329,18 +318,14 @@ export async function POST(request: Request) {
|
||||
let attachedMedia: typeof media.$inferSelect[] = [];
|
||||
if (data.mediaIds?.length) {
|
||||
attachedMedia = await db.query.media.findMany({
|
||||
where: and(
|
||||
inArray(media.id, data.mediaIds),
|
||||
eq(media.userId, user.id),
|
||||
eq(media.postId, post.id),
|
||||
),
|
||||
where: { AND: [{ id: { in: data.mediaIds } }, { userId: user.id }, { postId: post.id }] },
|
||||
});
|
||||
}
|
||||
|
||||
if (data.replyToId) {
|
||||
try {
|
||||
const parentPost = await db.query.posts.findFirst({
|
||||
where: eq(posts.id, data.replyToId),
|
||||
where: { id: data.replyToId },
|
||||
with: {
|
||||
author: true,
|
||||
},
|
||||
@@ -395,7 +380,7 @@ export async function POST(request: Request) {
|
||||
|
||||
// Find the mentioned user
|
||||
const mentionedUser = await db.query.users.findFirst({
|
||||
where: eq(users.handle, mention.handle.toLowerCase()),
|
||||
where: { handle: mention.handle.toLowerCase() },
|
||||
});
|
||||
|
||||
if (mentionedUser && mentionedUser.id !== user.id && !mentionedUser.isSuspended) {
|
||||
@@ -612,38 +597,41 @@ export async function GET(request: Request) {
|
||||
|
||||
let feedPosts;
|
||||
// Base filter excludes removed posts and replies (replies only show on detail/profile)
|
||||
const baseFilter = buildWhere(
|
||||
eq(posts.isRemoved, false),
|
||||
isNull(posts.replyToId),
|
||||
isNull(posts.swarmReplyToId)
|
||||
);
|
||||
const baseFilter = {
|
||||
isRemoved: false,
|
||||
replyToId: { isNull: true as const },
|
||||
swarmReplyToId: { isNull: true as const },
|
||||
};
|
||||
// Filter for replies only
|
||||
const repliesFilter = buildWhere(
|
||||
eq(posts.isRemoved, false),
|
||||
or(
|
||||
isNotNull(posts.replyToId),
|
||||
isNotNull(posts.swarmReplyToId)
|
||||
)
|
||||
);
|
||||
const repliesFilter = {
|
||||
isRemoved: false,
|
||||
OR: [
|
||||
{ replyToId: { isNotNull: true as const } },
|
||||
{ swarmReplyToId: { isNotNull: true as const } },
|
||||
],
|
||||
};
|
||||
|
||||
if (type === 'local') {
|
||||
// Local node posts only
|
||||
let whereCondition = baseFilter;
|
||||
let whereCondition = {
|
||||
...baseFilter,
|
||||
createdAt: undefined as { lt: Date } | undefined,
|
||||
};
|
||||
|
||||
// Apply cursor-based pagination
|
||||
if (cursor) {
|
||||
const cursorPost = await db.query.posts.findFirst({
|
||||
where: eq(posts.id, cursor),
|
||||
where: { id: cursor },
|
||||
});
|
||||
if (cursorPost) {
|
||||
whereCondition = buildWhere(baseFilter, lt(posts.createdAt, cursorPost.createdAt));
|
||||
whereCondition = { ...baseFilter, createdAt: { lt: cursorPost.createdAt } };
|
||||
}
|
||||
}
|
||||
|
||||
feedPosts = await db.query.posts.findMany({
|
||||
where: whereCondition,
|
||||
with: feedPostRelations,
|
||||
orderBy: [desc(posts.createdAt)],
|
||||
orderBy: () => [desc(posts.createdAt)],
|
||||
limit,
|
||||
});
|
||||
} else if (type === 'public') {
|
||||
@@ -651,13 +639,13 @@ export async function GET(request: Request) {
|
||||
const localPosts = await db.query.posts.findMany({
|
||||
where: baseFilter,
|
||||
with: feedPostRelations,
|
||||
orderBy: [desc(posts.createdAt)],
|
||||
orderBy: () => [desc(posts.createdAt)],
|
||||
limit: limit * 2,
|
||||
});
|
||||
|
||||
// Get all cached remote posts
|
||||
const remotePostsData = await db.query.remotePosts.findMany({
|
||||
orderBy: [desc(remotePosts.publishedAt)],
|
||||
orderBy: () => [desc(remotePosts.publishedAt)],
|
||||
limit: limit,
|
||||
});
|
||||
|
||||
@@ -669,42 +657,50 @@ export async function GET(request: Request) {
|
||||
.slice(0, limit) as any;
|
||||
} else if (type === 'user' && userId) {
|
||||
// User's posts (excluding replies)
|
||||
let whereCondition = buildWhere(baseFilter, eq(posts.userId, userId));
|
||||
let whereCondition = {
|
||||
...baseFilter,
|
||||
userId,
|
||||
createdAt: undefined as { lt: Date } | undefined,
|
||||
};
|
||||
|
||||
// Apply cursor-based pagination
|
||||
if (cursor) {
|
||||
const cursorPost = await db.query.posts.findFirst({
|
||||
where: eq(posts.id, cursor),
|
||||
where: { id: cursor },
|
||||
});
|
||||
if (cursorPost) {
|
||||
whereCondition = buildWhere(baseFilter, eq(posts.userId, userId), lt(posts.createdAt, cursorPost.createdAt));
|
||||
whereCondition = { ...baseFilter, userId, createdAt: { lt: cursorPost.createdAt } };
|
||||
}
|
||||
}
|
||||
|
||||
feedPosts = await db.query.posts.findMany({
|
||||
where: whereCondition,
|
||||
with: feedPostRelations,
|
||||
orderBy: [desc(posts.createdAt)],
|
||||
orderBy: () => [desc(posts.createdAt)],
|
||||
limit,
|
||||
});
|
||||
} else if (type === 'replies' && userId) {
|
||||
// User's replies only
|
||||
let whereCondition = buildWhere(repliesFilter, eq(posts.userId, userId));
|
||||
let whereCondition = {
|
||||
...repliesFilter,
|
||||
userId,
|
||||
createdAt: undefined as { lt: Date } | undefined,
|
||||
};
|
||||
|
||||
// Apply cursor-based pagination
|
||||
if (cursor) {
|
||||
const cursorPost = await db.query.posts.findFirst({
|
||||
where: eq(posts.id, cursor),
|
||||
where: { id: cursor },
|
||||
});
|
||||
if (cursorPost) {
|
||||
whereCondition = buildWhere(repliesFilter, eq(posts.userId, userId), lt(posts.createdAt, cursorPost.createdAt));
|
||||
whereCondition = { ...repliesFilter, userId, createdAt: { lt: cursorPost.createdAt } };
|
||||
}
|
||||
}
|
||||
|
||||
feedPosts = await db.query.posts.findMany({
|
||||
where: whereCondition,
|
||||
with: feedPostRelations,
|
||||
orderBy: [desc(posts.createdAt)],
|
||||
orderBy: () => [desc(posts.createdAt)],
|
||||
limit,
|
||||
});
|
||||
} else if (type === 'curated') {
|
||||
@@ -857,36 +853,38 @@ export async function GET(request: Request) {
|
||||
const allowedUserIds = [user.id, ...followingIds];
|
||||
|
||||
// Build where condition with cursor support
|
||||
let whereCondition = buildWhere(baseFilter, inArray(posts.userId, allowedUserIds));
|
||||
let whereCondition = {
|
||||
...baseFilter,
|
||||
userId: { in: allowedUserIds },
|
||||
createdAt: undefined as { lt: Date } | undefined,
|
||||
};
|
||||
const cursorDate = await getMixedFeedCursorDate(cursor);
|
||||
|
||||
if (cursorDate) {
|
||||
whereCondition = buildWhere(baseFilter, inArray(posts.userId, allowedUserIds), lt(posts.createdAt, cursorDate));
|
||||
whereCondition = { ...baseFilter, userId: { in: allowedUserIds }, createdAt: { lt: cursorDate } };
|
||||
}
|
||||
|
||||
// Get local posts from people the user follows + their own posts
|
||||
const localPosts = await db.query.posts.findMany({
|
||||
where: whereCondition,
|
||||
with: feedPostRelations,
|
||||
orderBy: [desc(posts.createdAt)],
|
||||
orderBy: () => [desc(posts.createdAt)],
|
||||
limit: cursor ? limit : limit * 2, // Get more on first load to account for mixing with remote
|
||||
});
|
||||
|
||||
const swarmRepostWhere = cursorDate
|
||||
? and(
|
||||
inArray(userSwarmReposts.userId, allowedUserIds),
|
||||
lt(userSwarmReposts.repostedAt, cursorDate)
|
||||
)
|
||||
: inArray(userSwarmReposts.userId, allowedUserIds);
|
||||
const swarmRepostWhere = {
|
||||
userId: { in: allowedUserIds },
|
||||
...(cursorDate ? { repostedAt: { lt: cursorDate } } : {}),
|
||||
};
|
||||
const swarmRepostRows = await db.query.userSwarmReposts.findMany({
|
||||
where: swarmRepostWhere,
|
||||
orderBy: [desc(userSwarmReposts.repostedAt)],
|
||||
orderBy: () => [desc(userSwarmReposts.repostedAt)],
|
||||
limit: cursor ? limit : limit * 2,
|
||||
});
|
||||
|
||||
const swarmRepostAuthors = swarmRepostRows.length > 0
|
||||
? await db.query.users.findMany({
|
||||
where: inArray(users.id, Array.from(new Set(swarmRepostRows.map((row) => row.userId)))),
|
||||
where: { id: { in: Array.from(new Set(swarmRepostRows.map((row) => row.userId))) } },
|
||||
})
|
||||
: [];
|
||||
const swarmRepostAuthorMap = new Map(swarmRepostAuthors.map((author) => [author.id, author]));
|
||||
@@ -902,7 +900,7 @@ export async function GET(request: Request) {
|
||||
|
||||
// Get handles of remote users we follow
|
||||
const followedRemoteUsers = await db.query.remoteFollows.findMany({
|
||||
where: eq(remoteFollows.followerId, user.id),
|
||||
where: { followerId: user.id },
|
||||
});
|
||||
|
||||
// Fetch posts LIVE from followed remote users (in parallel, with timeout)
|
||||
@@ -969,7 +967,7 @@ export async function GET(request: Request) {
|
||||
feedPosts = await db.query.posts.findMany({
|
||||
where: baseFilter,
|
||||
with: feedPostRelations,
|
||||
orderBy: [desc(posts.createdAt)],
|
||||
orderBy: () => [desc(posts.createdAt)],
|
||||
limit,
|
||||
});
|
||||
}
|
||||
@@ -982,7 +980,7 @@ export async function GET(request: Request) {
|
||||
|
||||
if (session?.user && feedPosts && feedPosts.length > 0) {
|
||||
const viewer = session.user;
|
||||
const nodeDomain = process.env.NEXT_PUBLIC_NODE_DOMAIN || 'localhost:3000';
|
||||
const nodeDomain = process.env.NEXT_PUBLIC_NODE_DOMAIN || 'localhost:43821';
|
||||
const allFeedPosts = collectNestedPosts(feedPosts as FeedPostWithChildren[]);
|
||||
|
||||
// Separate local and swarm posts
|
||||
@@ -1010,19 +1008,12 @@ export async function GET(request: Request) {
|
||||
|
||||
if (localPostIds.length > 0) {
|
||||
const viewerLikes = await db.query.likes.findMany({
|
||||
where: and(
|
||||
eq(likes.userId, viewer.id),
|
||||
inArray(likes.postId, localPostIds)
|
||||
),
|
||||
where: { AND: [{ userId: viewer.id }, { postId: { in: localPostIds } }] },
|
||||
});
|
||||
viewerLikes.forEach(l => likedPostIds.add(l.postId));
|
||||
|
||||
const viewerReposts = await db.query.posts.findMany({
|
||||
where: and(
|
||||
eq(posts.userId, viewer.id),
|
||||
inArray(posts.repostOfId, localPostIds),
|
||||
eq(posts.isRemoved, false)
|
||||
),
|
||||
where: { AND: [{ userId: viewer.id }, { repostOfId: { in: localPostIds } }, { isRemoved: false }] },
|
||||
});
|
||||
viewerReposts.forEach(r => { if (r.repostOfId) repostedPostIds.add(r.repostOfId); });
|
||||
}
|
||||
|
||||
@@ -79,7 +79,7 @@ export async function GET(request: NextRequest) {
|
||||
|
||||
const session = await getSession().catch(() => null);
|
||||
const viewer = session?.user;
|
||||
const nodeDomain = process.env.NEXT_PUBLIC_NODE_DOMAIN || 'localhost:3000';
|
||||
const nodeDomain = process.env.NEXT_PUBLIC_NODE_DOMAIN || 'localhost:43821';
|
||||
const allTimelinePosts = collectNestedSwarmPosts(timeline.posts as SwarmFeedPost[]);
|
||||
const likedPostIds = viewer
|
||||
? await getViewerSwarmLikedPostIds(
|
||||
|
||||
@@ -21,7 +21,7 @@ export async function POST(request: Request) {
|
||||
|
||||
if (data.targetType === 'post') {
|
||||
const targetPost = await db.query.posts.findFirst({
|
||||
where: eq(posts.id, data.targetId),
|
||||
where: { id: data.targetId },
|
||||
});
|
||||
if (!targetPost || targetPost.isRemoved) {
|
||||
return NextResponse.json({ error: 'Post not found' }, { status: 404 });
|
||||
@@ -30,7 +30,7 @@ export async function POST(request: Request) {
|
||||
|
||||
if (data.targetType === 'user') {
|
||||
const targetUser = await db.query.users.findFirst({
|
||||
where: eq(users.id, data.targetId),
|
||||
where: { id: data.targetId },
|
||||
});
|
||||
if (!targetUser) {
|
||||
return NextResponse.json({ error: 'User not found' }, { status: 404 });
|
||||
|
||||
+12
-22
@@ -1,6 +1,6 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import { db, users, posts, likes } from '@/db';
|
||||
import { ilike, or, desc, and, notInArray, eq, inArray } from 'drizzle-orm';
|
||||
import { like, or, desc, and, eq, inArray } from 'drizzle-orm';
|
||||
import { fetchSwarmUserProfile, isSwarmNode } from '@/lib/swarm/interactions';
|
||||
import { discoverNode } from '@/lib/swarm/discovery';
|
||||
|
||||
@@ -119,9 +119,9 @@ export async function GET(request: Request) {
|
||||
if (searchUsers.length === 0) {
|
||||
const userConditions = and(
|
||||
or(
|
||||
ilike(users.handle, searchPattern),
|
||||
ilike(users.displayName, searchPattern),
|
||||
ilike(users.bio, searchPattern)
|
||||
like(users.handle, searchPattern),
|
||||
like(users.displayName, searchPattern),
|
||||
like(users.bio, searchPattern)
|
||||
),
|
||||
eq(users.isSuspended, false),
|
||||
eq(users.isSilenced, false)
|
||||
@@ -187,17 +187,14 @@ export async function GET(request: Request) {
|
||||
|
||||
// Search posts
|
||||
if (type === 'all' || type === 'posts') {
|
||||
const postConditions = [
|
||||
ilike(posts.content, searchPattern),
|
||||
eq(posts.isRemoved, false),
|
||||
];
|
||||
if (moderatedIds.length) {
|
||||
postConditions.push(notInArray(posts.userId, moderatedIds));
|
||||
}
|
||||
const postResults = await db.query.posts.findMany({
|
||||
where: and(...postConditions),
|
||||
where: {
|
||||
content: { like: searchPattern },
|
||||
isRemoved: false,
|
||||
...(moderatedIds.length ? { userId: { notIn: moderatedIds } } : {}),
|
||||
},
|
||||
with: searchPostRelations,
|
||||
orderBy: [desc(posts.createdAt)],
|
||||
orderBy: () => [desc(posts.createdAt)],
|
||||
limit,
|
||||
});
|
||||
searchPosts = postResults;
|
||||
@@ -213,19 +210,12 @@ export async function GET(request: Request) {
|
||||
|
||||
if (postIds.length > 0) {
|
||||
const viewerLikes = await db.query.likes.findMany({
|
||||
where: and(
|
||||
eq(likes.userId, viewer.id),
|
||||
inArray(likes.postId, postIds)
|
||||
),
|
||||
where: { AND: [{ userId: viewer.id }, { postId: { in: postIds } }] },
|
||||
});
|
||||
const likedPostIds = new Set(viewerLikes.map(l => l.postId));
|
||||
|
||||
const viewerReposts = await db.query.posts.findMany({
|
||||
where: and(
|
||||
eq(posts.userId, viewer.id),
|
||||
inArray(posts.repostOfId, postIds),
|
||||
eq(posts.isRemoved, false)
|
||||
),
|
||||
where: { AND: [{ userId: viewer.id }, { repostOfId: { in: postIds } }, { isRemoved: false }] },
|
||||
});
|
||||
const repostedPostIds = new Set(viewerReposts.map(r => r.repostOfId));
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ export async function GET() {
|
||||
const currentUser = await requireAuth();
|
||||
|
||||
const blocked = await db.query.blocks.findMany({
|
||||
where: eq(blocks.userId, currentUser.id),
|
||||
where: { userId: currentUser.id },
|
||||
with: {
|
||||
blockedUser: true,
|
||||
},
|
||||
|
||||
@@ -10,7 +10,7 @@ export async function GET() {
|
||||
const currentUser = await requireAuth();
|
||||
|
||||
const muted = await db.query.mutedNodes.findMany({
|
||||
where: eq(mutedNodes.userId, currentUser.id),
|
||||
where: { userId: currentUser.id },
|
||||
orderBy: (t, { desc }) => [desc(t.createdAt)],
|
||||
});
|
||||
|
||||
@@ -43,10 +43,7 @@ export async function POST(req: NextRequest) {
|
||||
|
||||
// Check if already muted
|
||||
const existing = await db.query.mutedNodes.findFirst({
|
||||
where: and(
|
||||
eq(mutedNodes.userId, currentUser.id),
|
||||
eq(mutedNodes.nodeDomain, normalizedDomain)
|
||||
),
|
||||
where: { AND: [{ userId: currentUser.id }, { nodeDomain: normalizedDomain }] },
|
||||
});
|
||||
|
||||
if (existing) {
|
||||
|
||||
@@ -54,10 +54,7 @@ export async function DELETE(
|
||||
|
||||
// Verify the conversation belongs to this user
|
||||
const conversation = await db.query.chatConversations.findFirst({
|
||||
where: and(
|
||||
eq(chatConversations.id, id),
|
||||
eq(chatConversations.participant1Id, session.user.id)
|
||||
),
|
||||
where: { AND: [{ id: id }, { participant1Id: session.user.id }] },
|
||||
});
|
||||
|
||||
if (!conversation) {
|
||||
@@ -113,16 +110,13 @@ export async function DELETE(
|
||||
} else {
|
||||
// Local user - find and delete their conversation too
|
||||
const recipientUser = await db.query.users.findFirst({
|
||||
where: eq(users.handle, participant2Handle),
|
||||
where: { handle: participant2Handle },
|
||||
});
|
||||
|
||||
if (recipientUser) {
|
||||
// Find their conversation with us
|
||||
const recipientConversation = await db.query.chatConversations.findFirst({
|
||||
where: and(
|
||||
eq(chatConversations.participant1Id, recipientUser.id),
|
||||
eq(chatConversations.participant2Handle, session.user.handle)
|
||||
),
|
||||
where: { AND: [{ participant1Id: recipientUser.id }, { participant2Handle: session.user.handle }] },
|
||||
});
|
||||
|
||||
if (recipientConversation) {
|
||||
|
||||
@@ -22,11 +22,11 @@ export async function GET(request: NextRequest) {
|
||||
|
||||
// Get all conversations for this user
|
||||
const conversations = await db.query.chatConversations.findMany({
|
||||
where: eq(chatConversations.participant1Id, session.user.id),
|
||||
orderBy: [desc(chatConversations.lastMessageAt)],
|
||||
where: { participant1Id: session.user.id },
|
||||
orderBy: () => [desc(chatConversations.lastMessageAt)],
|
||||
with: {
|
||||
messages: {
|
||||
orderBy: [desc(chatMessages.createdAt)],
|
||||
orderBy: () => [desc(chatMessages.createdAt)],
|
||||
limit: 1,
|
||||
},
|
||||
},
|
||||
@@ -59,7 +59,7 @@ export async function GET(request: NextRequest) {
|
||||
|
||||
// Try to get cached user info
|
||||
let cachedUser = await db.query.users.findFirst({
|
||||
where: eq(users.handle, participant2Handle),
|
||||
where: { handle: participant2Handle },
|
||||
});
|
||||
|
||||
// If not found, check if it's a local user with a domain suffix
|
||||
@@ -67,7 +67,7 @@ export async function GET(request: NextRequest) {
|
||||
const [handlePart, domainPart] = participant2Handle.split('@');
|
||||
if (!domainPart || domainPart === process.env.NEXT_PUBLIC_NODE_DOMAIN) {
|
||||
cachedUser = await db.query.users.findFirst({
|
||||
where: eq(users.handle, handlePart),
|
||||
where: { handle: handlePart },
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -91,7 +91,7 @@ export async function GET(request: NextRequest) {
|
||||
|
||||
// Re-query to get the new cached user
|
||||
cachedUser = await db.query.users.findFirst({
|
||||
where: eq(users.handle, participant2Handle),
|
||||
where: { handle: participant2Handle },
|
||||
}) as any;
|
||||
}
|
||||
} catch (e) {
|
||||
|
||||
@@ -53,7 +53,7 @@ export async function POST(request: NextRequest) {
|
||||
|
||||
// Find the recipient (local user)
|
||||
const recipient = await db.query.users.findFirst({
|
||||
where: eq(users.handle, data.recipientHandle.toLowerCase()),
|
||||
where: { handle: data.recipientHandle.toLowerCase() },
|
||||
});
|
||||
|
||||
if (!recipient) {
|
||||
@@ -63,10 +63,7 @@ export async function POST(request: NextRequest) {
|
||||
// Find the conversation with the sender
|
||||
const senderFullHandle = `${data.senderHandle}@${senderNodeDomain}`;
|
||||
const conversation = await db.query.chatConversations.findFirst({
|
||||
where: and(
|
||||
eq(chatConversations.participant1Id, recipient.id),
|
||||
eq(chatConversations.participant2Handle, senderFullHandle)
|
||||
),
|
||||
where: { AND: [{ participant1Id: recipient.id }, { participant2Handle: senderFullHandle }] },
|
||||
});
|
||||
|
||||
if (!conversation) {
|
||||
|
||||
@@ -52,10 +52,7 @@ export async function GET(request: NextRequest) {
|
||||
|
||||
// Verify user has access to this conversation
|
||||
const conversation = await db.query.chatConversations.findFirst({
|
||||
where: and(
|
||||
eq(chatConversations.id, conversationId),
|
||||
eq(chatConversations.participant1Id, session.user.id)
|
||||
),
|
||||
where: { AND: [{ id: conversationId }, { participant1Id: session.user.id }] },
|
||||
});
|
||||
|
||||
if (!conversation) {
|
||||
@@ -63,15 +60,14 @@ export async function GET(request: NextRequest) {
|
||||
}
|
||||
|
||||
// Build query with cursor-based pagination
|
||||
const baseCondition = eq(chatMessages.conversationId, conversationId);
|
||||
const whereCondition = cursor
|
||||
? and(baseCondition, lt(chatMessages.createdAt, new Date(cursor)))!
|
||||
: baseCondition;
|
||||
? { conversationId, createdAt: { lt: new Date(cursor) } }
|
||||
: { conversationId };
|
||||
|
||||
// Get messages
|
||||
const messages = await db.query.chatMessages.findMany({
|
||||
where: whereCondition,
|
||||
orderBy: [desc(chatMessages.createdAt)],
|
||||
orderBy: () => [desc(chatMessages.createdAt)],
|
||||
limit,
|
||||
});
|
||||
|
||||
@@ -92,7 +88,7 @@ export async function GET(request: NextRequest) {
|
||||
|
||||
if (senderDids.size > 0) {
|
||||
const found = await db.query.users.findMany({
|
||||
where: inArray(users.did, Array.from(senderDids))
|
||||
where: { did: { in: Array.from(senderDids) } }
|
||||
});
|
||||
found.forEach(u => usersByDid[u.did] = u);
|
||||
}
|
||||
@@ -100,7 +96,7 @@ export async function GET(request: NextRequest) {
|
||||
// Also fetch local users by handle if needed
|
||||
if (senderHandles.size > 0) {
|
||||
const found = await db.query.users.findMany({
|
||||
where: inArray(users.handle, Array.from(senderHandles))
|
||||
where: { handle: { in: Array.from(senderHandles) } }
|
||||
});
|
||||
found.forEach(u => usersByHandle[u.handle] = u);
|
||||
}
|
||||
@@ -164,10 +160,7 @@ export async function PATCH(request: NextRequest) {
|
||||
|
||||
// Verify user has access to this conversation
|
||||
const conversation = await db.query.chatConversations.findFirst({
|
||||
where: and(
|
||||
eq(chatConversations.id, conversationId),
|
||||
eq(chatConversations.participant1Id, session.user.id)
|
||||
),
|
||||
where: { AND: [{ id: conversationId }, { participant1Id: session.user.id }] },
|
||||
});
|
||||
|
||||
if (!conversation) {
|
||||
|
||||
@@ -56,7 +56,7 @@ export async function POST(request: NextRequest) {
|
||||
|
||||
// Find the target user (local user being followed)
|
||||
const targetUser = await db.query.users.findFirst({
|
||||
where: eq(users.handle, data.targetHandle.toLowerCase()),
|
||||
where: { handle: data.targetHandle.toLowerCase() },
|
||||
});
|
||||
|
||||
if (!targetUser) {
|
||||
@@ -73,10 +73,7 @@ export async function POST(request: NextRequest) {
|
||||
|
||||
// Check if this follow already exists
|
||||
const existingFollow = await db.query.remoteFollowers.findFirst({
|
||||
where: and(
|
||||
eq(remoteFollowers.userId, targetUser.id),
|
||||
eq(remoteFollowers.actorUrl, actorUrl)
|
||||
),
|
||||
where: { AND: [{ userId: targetUser.id }, { actorUrl: actorUrl }] },
|
||||
});
|
||||
|
||||
if (existingFollow) {
|
||||
|
||||
@@ -52,7 +52,7 @@ export async function POST(request: NextRequest) {
|
||||
|
||||
// Find the target post
|
||||
const post = await db.query.posts.findFirst({
|
||||
where: eq(posts.id, data.postId),
|
||||
where: { id: data.postId },
|
||||
with: { author: true },
|
||||
});
|
||||
|
||||
@@ -66,11 +66,7 @@ export async function POST(request: NextRequest) {
|
||||
|
||||
// Check if already liked by this remote user
|
||||
const existingLike = await db.query.remoteLikes.findFirst({
|
||||
where: and(
|
||||
eq(remoteLikes.postId, data.postId),
|
||||
eq(remoteLikes.actorHandle, data.like.actorHandle),
|
||||
eq(remoteLikes.actorNodeDomain, data.like.actorNodeDomain)
|
||||
),
|
||||
where: { AND: [{ postId: data.postId }, { actorHandle: data.like.actorHandle }, { actorNodeDomain: data.like.actorNodeDomain }] },
|
||||
});
|
||||
|
||||
if (existingLike) {
|
||||
|
||||
@@ -54,7 +54,7 @@ export async function POST(request: NextRequest) {
|
||||
|
||||
// Find the mentioned user (local user)
|
||||
const mentionedUser = await db.query.users.findFirst({
|
||||
where: eq(users.handle, data.mentionedHandle.toLowerCase()),
|
||||
where: { handle: data.mentionedHandle.toLowerCase() },
|
||||
});
|
||||
|
||||
if (!mentionedUser) {
|
||||
|
||||
@@ -53,7 +53,7 @@ export async function POST(request: NextRequest) {
|
||||
|
||||
// Find the target post
|
||||
const post = await db.query.posts.findFirst({
|
||||
where: eq(posts.id, data.postId),
|
||||
where: { id: data.postId },
|
||||
with: { author: true },
|
||||
});
|
||||
|
||||
@@ -66,11 +66,7 @@ export async function POST(request: NextRequest) {
|
||||
}
|
||||
|
||||
const existingRepost = await db.query.remoteReposts.findFirst({
|
||||
where: and(
|
||||
eq(remoteReposts.postId, data.postId),
|
||||
eq(remoteReposts.actorHandle, data.repost.actorHandle),
|
||||
eq(remoteReposts.actorNodeDomain, data.repost.actorNodeDomain),
|
||||
),
|
||||
where: { AND: [{ postId: data.postId }, { actorHandle: data.repost.actorHandle }, { actorNodeDomain: data.repost.actorNodeDomain }] },
|
||||
});
|
||||
|
||||
if (existingRepost) {
|
||||
|
||||
@@ -49,7 +49,7 @@ export async function POST(request: NextRequest) {
|
||||
|
||||
// Find the target user
|
||||
const targetUser = await db.query.users.findFirst({
|
||||
where: eq(users.handle, data.targetHandle.toLowerCase()),
|
||||
where: { handle: data.targetHandle.toLowerCase() },
|
||||
});
|
||||
|
||||
if (!targetUser) {
|
||||
@@ -60,10 +60,7 @@ export async function POST(request: NextRequest) {
|
||||
const actorUrl = `swarm://${data.unfollow.followerNodeDomain}/${data.unfollow.followerHandle}`;
|
||||
|
||||
const existingFollow = await db.query.remoteFollowers.findFirst({
|
||||
where: and(
|
||||
eq(remoteFollowers.userId, targetUser.id),
|
||||
eq(remoteFollowers.actorUrl, actorUrl)
|
||||
),
|
||||
where: { AND: [{ userId: targetUser.id }, { actorUrl: actorUrl }] },
|
||||
});
|
||||
|
||||
if (!existingFollow) {
|
||||
@@ -78,7 +75,7 @@ export async function POST(request: NextRequest) {
|
||||
|
||||
// Update follower count
|
||||
await db.update(users)
|
||||
.set({ followersCount: sql`GREATEST(0, ${users.followersCount} - 1)` })
|
||||
.set({ followersCount: sql`max(0, ${users.followersCount} - 1)` })
|
||||
.where(eq(users.id, targetUser.id));
|
||||
|
||||
console.log(`[Swarm] Received unfollow from ${data.unfollow.followerHandle}@${data.unfollow.followerNodeDomain} for @${data.targetHandle}`);
|
||||
|
||||
@@ -49,7 +49,7 @@ export async function POST(request: NextRequest) {
|
||||
|
||||
// Find the target post
|
||||
const post = await db.query.posts.findFirst({
|
||||
where: eq(posts.id, data.postId),
|
||||
where: { id: data.postId },
|
||||
});
|
||||
|
||||
if (!post) {
|
||||
|
||||
@@ -49,7 +49,7 @@ export async function POST(request: NextRequest) {
|
||||
|
||||
// Find the target post
|
||||
const post = await db.query.posts.findFirst({
|
||||
where: eq(posts.id, data.postId),
|
||||
where: { id: data.postId },
|
||||
});
|
||||
|
||||
if (!post) {
|
||||
@@ -61,11 +61,7 @@ export async function POST(request: NextRequest) {
|
||||
}
|
||||
|
||||
const existingRepost = await db.query.remoteReposts.findFirst({
|
||||
where: and(
|
||||
eq(remoteReposts.postId, data.postId),
|
||||
eq(remoteReposts.actorHandle, data.unrepost.actorHandle),
|
||||
eq(remoteReposts.actorNodeDomain, data.unrepost.actorNodeDomain),
|
||||
),
|
||||
where: { AND: [{ postId: data.postId }, { actorHandle: data.unrepost.actorHandle }, { actorNodeDomain: data.unrepost.actorNodeDomain }] },
|
||||
});
|
||||
|
||||
if (!existingRepost) {
|
||||
@@ -77,7 +73,7 @@ export async function POST(request: NextRequest) {
|
||||
|
||||
// Decrement repost count
|
||||
await db.update(posts)
|
||||
.set({ repostsCount: sql`GREATEST(0, ${posts.repostsCount} - 1)` })
|
||||
.set({ repostsCount: sql`max(0, ${posts.repostsCount} - 1)` })
|
||||
.where(eq(posts.id, data.postId));
|
||||
|
||||
await db.delete(remoteReposts).where(eq(remoteReposts.id, existingRepost.id));
|
||||
|
||||
@@ -59,7 +59,7 @@ export async function GET(request: NextRequest, context: RouteContext) {
|
||||
|
||||
// Find the post
|
||||
const post = await db.query.posts.findFirst({
|
||||
where: eq(posts.id, postId),
|
||||
where: { id: postId },
|
||||
});
|
||||
|
||||
if (!post || post.isRemoved) {
|
||||
@@ -71,11 +71,7 @@ export async function GET(request: NextRequest, context: RouteContext) {
|
||||
// If domain is provided, check remote likes
|
||||
if (checkDomain) {
|
||||
const remoteLike = await db.query.remoteLikes.findFirst({
|
||||
where: and(
|
||||
eq(remoteLikes.postId, postId),
|
||||
eq(remoteLikes.actorHandle, checkHandle),
|
||||
eq(remoteLikes.actorNodeDomain, checkDomain)
|
||||
),
|
||||
where: { AND: [{ postId: postId }, { actorHandle: checkHandle }, { actorNodeDomain: checkDomain }] },
|
||||
});
|
||||
|
||||
return NextResponse.json({
|
||||
@@ -89,15 +85,12 @@ export async function GET(request: NextRequest, context: RouteContext) {
|
||||
|
||||
// No domain = local user
|
||||
const localUser = await db.query.users.findFirst({
|
||||
where: eq(users.handle, checkHandle),
|
||||
where: { handle: checkHandle },
|
||||
});
|
||||
|
||||
if (localUser) {
|
||||
const liked = await db.query.likes.findFirst({
|
||||
where: and(
|
||||
eq(likes.postId, postId),
|
||||
eq(likes.userId, localUser.id)
|
||||
),
|
||||
where: { AND: [{ postId: postId }, { userId: localUser.id }] },
|
||||
});
|
||||
|
||||
return NextResponse.json({
|
||||
|
||||
@@ -36,7 +36,7 @@ export async function GET(request: NextRequest, context: RouteContext) {
|
||||
|
||||
// Find the post
|
||||
const post = await db.query.posts.findFirst({
|
||||
where: eq(posts.id, postId),
|
||||
where: { id: postId },
|
||||
with: {
|
||||
author: true,
|
||||
media: true,
|
||||
@@ -45,7 +45,7 @@ export async function GET(request: NextRequest, context: RouteContext) {
|
||||
|
||||
if (!post || post.isRemoved) {
|
||||
const remoteRepost = await db.query.userSwarmReposts.findFirst({
|
||||
where: eq(userSwarmReposts.id, postId),
|
||||
where: { id: postId },
|
||||
});
|
||||
|
||||
if (!remoteRepost) {
|
||||
@@ -53,7 +53,7 @@ export async function GET(request: NextRequest, context: RouteContext) {
|
||||
}
|
||||
|
||||
const repostAuthor = await db.query.users.findFirst({
|
||||
where: eq(users.id, remoteRepost.userId),
|
||||
where: { id: remoteRepost.userId },
|
||||
});
|
||||
|
||||
return NextResponse.json({
|
||||
@@ -104,15 +104,12 @@ export async function GET(request: NextRequest, context: RouteContext) {
|
||||
|
||||
// Get replies
|
||||
const replies = await db.query.posts.findMany({
|
||||
where: and(
|
||||
eq(posts.replyToId, postId),
|
||||
eq(posts.isRemoved, false)
|
||||
),
|
||||
where: { AND: [{ replyToId: postId }, { isRemoved: false }] },
|
||||
with: {
|
||||
author: true,
|
||||
media: true,
|
||||
},
|
||||
orderBy: [desc(posts.createdAt)],
|
||||
orderBy: () => [desc(posts.createdAt)],
|
||||
limit: 50,
|
||||
});
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ const swarmReplySchema = z.object({
|
||||
|
||||
async function syncParentReplyCount(postId: string) {
|
||||
const [{ count }] = await db
|
||||
.select({ count: sql<number>`count(*)::int` })
|
||||
.select({ count: sql<number>`count(*)` })
|
||||
.from(posts)
|
||||
.where(and(
|
||||
eq(posts.replyToId, postId),
|
||||
@@ -82,10 +82,7 @@ export async function POST(request: NextRequest) {
|
||||
}
|
||||
|
||||
const parentPost = await db.query.posts.findFirst({
|
||||
where: and(
|
||||
eq(posts.id, data.postId),
|
||||
eq(posts.isRemoved, false)
|
||||
),
|
||||
where: { AND: [{ id: data.postId }, { isRemoved: false }] },
|
||||
with: {
|
||||
author: true,
|
||||
},
|
||||
@@ -107,7 +104,7 @@ export async function POST(request: NextRequest) {
|
||||
});
|
||||
|
||||
const remoteUser = await db.query.users.findFirst({
|
||||
where: eq(users.handle, remoteHandle),
|
||||
where: { handle: remoteHandle },
|
||||
});
|
||||
|
||||
if (!remoteUser) {
|
||||
@@ -116,7 +113,7 @@ export async function POST(request: NextRequest) {
|
||||
|
||||
const replyApId = `swarm:${sourceDomain}:${data.reply.id}`;
|
||||
const existingReply = await db.query.posts.findFirst({
|
||||
where: eq(posts.apId, replyApId),
|
||||
where: { apId: replyApId },
|
||||
});
|
||||
|
||||
if (existingReply) {
|
||||
@@ -205,7 +202,7 @@ export async function DELETE(request: NextRequest) {
|
||||
// Find the reply by its swarm ID
|
||||
const swarmReplyId = `swarm:${nodeDomain}:${replyId}`;
|
||||
const existingReply = await db.query.posts.findFirst({
|
||||
where: eq(posts.apId, swarmReplyId),
|
||||
where: { apId: swarmReplyId },
|
||||
});
|
||||
|
||||
if (!existingReply) {
|
||||
|
||||
@@ -128,7 +128,7 @@ export async function GET(request: NextRequest) {
|
||||
|
||||
// Get node NSFW status
|
||||
const node = await db.query.nodes.findFirst({
|
||||
where: eq(nodes.domain, nodeDomain),
|
||||
where: { domain: nodeDomain },
|
||||
});
|
||||
const nodeIsNsfw = node?.isNsfw ?? false;
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ export async function GET(request: NextRequest, context: RouteContext) {
|
||||
|
||||
// Find the user
|
||||
const user = await db.query.users.findFirst({
|
||||
where: eq(users.handle, cleanHandle),
|
||||
where: { handle: cleanHandle },
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
@@ -74,7 +74,7 @@ export async function GET(request: NextRequest, context: RouteContext) {
|
||||
|
||||
// Get remote followers
|
||||
const userRemoteFollowers = await db.query.remoteFollowers.findMany({
|
||||
where: eq(remoteFollowers.userId, user.id),
|
||||
where: { userId: user.id },
|
||||
limit,
|
||||
});
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ export async function GET(request: NextRequest, context: RouteContext) {
|
||||
|
||||
// Find the user
|
||||
const user = await db.query.users.findFirst({
|
||||
where: eq(users.handle, cleanHandle),
|
||||
where: { handle: cleanHandle },
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
@@ -73,7 +73,7 @@ export async function GET(request: NextRequest, context: RouteContext) {
|
||||
|
||||
// Get remote following
|
||||
const userRemoteFollowing = await db.query.remoteFollows.findMany({
|
||||
where: eq(remoteFollows.followerId, user.id),
|
||||
where: { followerId: user.id },
|
||||
limit,
|
||||
});
|
||||
|
||||
|
||||
@@ -188,7 +188,7 @@ export async function GET(request: NextRequest, context: RouteContext) {
|
||||
|
||||
// Find the user
|
||||
const user = await db.query.users.findFirst({
|
||||
where: eq(users.handle, cleanHandle),
|
||||
where: { handle: cleanHandle },
|
||||
with: {
|
||||
botOwner: true, // Include bot owner if this is a bot
|
||||
},
|
||||
@@ -222,20 +222,15 @@ export async function GET(request: NextRequest, context: RouteContext) {
|
||||
};
|
||||
|
||||
const localPosts = await db.query.posts.findMany({
|
||||
where: and(
|
||||
eq(posts.userId, user.id),
|
||||
eq(posts.isRemoved, false),
|
||||
isNull(posts.replyToId),
|
||||
isNull(posts.swarmReplyToId)
|
||||
),
|
||||
where: { AND: [{ userId: user.id }, { isRemoved: false }, { replyToId: { isNull: true } }, { swarmReplyToId: { isNull: true } }] },
|
||||
with: profilePostRelations,
|
||||
orderBy: [desc(posts.createdAt)],
|
||||
orderBy: () => [desc(posts.createdAt)],
|
||||
limit: limit * 2,
|
||||
});
|
||||
|
||||
const remoteRepostRows = await db.query.userSwarmReposts.findMany({
|
||||
where: eq(userSwarmReposts.userId, user.id),
|
||||
orderBy: [desc(userSwarmReposts.repostedAt)],
|
||||
where: { userId: user.id },
|
||||
orderBy: () => [desc(userSwarmReposts.repostedAt)],
|
||||
limit: limit * 2,
|
||||
});
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ export async function GET(req: NextRequest, context: RouteContext) {
|
||||
const { handle } = await context.params;
|
||||
|
||||
const targetUser = await db.query.users.findFirst({
|
||||
where: eq(users.handle, handle),
|
||||
where: { handle: handle },
|
||||
});
|
||||
|
||||
if (!targetUser) {
|
||||
@@ -21,10 +21,7 @@ export async function GET(req: NextRequest, context: RouteContext) {
|
||||
}
|
||||
|
||||
const block = await db.query.blocks.findFirst({
|
||||
where: and(
|
||||
eq(blocks.userId, currentUser.id),
|
||||
eq(blocks.blockedUserId, targetUser.id)
|
||||
),
|
||||
where: { AND: [{ userId: currentUser.id }, { blockedUserId: targetUser.id }] },
|
||||
});
|
||||
|
||||
return NextResponse.json({ blocked: !!block });
|
||||
@@ -44,7 +41,7 @@ export async function POST(req: NextRequest, context: RouteContext) {
|
||||
const { handle } = await context.params;
|
||||
|
||||
const targetUser = await db.query.users.findFirst({
|
||||
where: eq(users.handle, handle),
|
||||
where: { handle: handle },
|
||||
});
|
||||
|
||||
if (!targetUser) {
|
||||
@@ -57,10 +54,7 @@ export async function POST(req: NextRequest, context: RouteContext) {
|
||||
|
||||
// Check if already blocked
|
||||
const existing = await db.query.blocks.findFirst({
|
||||
where: and(
|
||||
eq(blocks.userId, currentUser.id),
|
||||
eq(blocks.blockedUserId, targetUser.id)
|
||||
),
|
||||
where: { AND: [{ userId: currentUser.id }, { blockedUserId: targetUser.id }] },
|
||||
});
|
||||
|
||||
if (existing) {
|
||||
@@ -104,7 +98,7 @@ export async function DELETE(req: NextRequest, context: RouteContext) {
|
||||
const { handle } = await context.params;
|
||||
|
||||
const targetUser = await db.query.users.findFirst({
|
||||
where: eq(users.handle, handle),
|
||||
where: { handle: handle },
|
||||
});
|
||||
|
||||
if (!targetUser) {
|
||||
|
||||
@@ -37,10 +37,7 @@ export async function GET(request: Request, context: RouteContext) {
|
||||
}
|
||||
const targetHandle = `${remote.handle}@${remote.domain}`;
|
||||
const existingRemoteFollow = await db.query.remoteFollows.findFirst({
|
||||
where: and(
|
||||
eq(remoteFollows.followerId, currentUser.id),
|
||||
eq(remoteFollows.targetHandle, targetHandle)
|
||||
),
|
||||
where: { AND: [{ followerId: currentUser.id }, { targetHandle: targetHandle }] },
|
||||
});
|
||||
return NextResponse.json({ following: !!existingRemoteFollow, remote: true });
|
||||
}
|
||||
@@ -50,7 +47,7 @@ export async function GET(request: Request, context: RouteContext) {
|
||||
}
|
||||
|
||||
const targetUser = await db.query.users.findFirst({
|
||||
where: eq(users.handle, cleanHandle),
|
||||
where: { handle: cleanHandle },
|
||||
});
|
||||
|
||||
if (!targetUser) {
|
||||
@@ -65,10 +62,7 @@ export async function GET(request: Request, context: RouteContext) {
|
||||
}
|
||||
|
||||
const existingFollow = await db.query.follows.findFirst({
|
||||
where: and(
|
||||
eq(follows.followerId, currentUser.id),
|
||||
eq(follows.followingId, targetUser.id)
|
||||
),
|
||||
where: { AND: [{ followerId: currentUser.id }, { followingId: targetUser.id }] },
|
||||
});
|
||||
|
||||
return NextResponse.json({ following: !!existingFollow });
|
||||
@@ -96,7 +90,7 @@ export async function POST(request: Request, context: RouteContext) {
|
||||
const { handle } = await context.params;
|
||||
const cleanHandle = handle.toLowerCase().replace(/^@/, '');
|
||||
const remote = parseRemoteHandle(handle);
|
||||
const nodeDomain = process.env.NEXT_PUBLIC_NODE_DOMAIN || 'localhost:3000';
|
||||
const nodeDomain = process.env.NEXT_PUBLIC_NODE_DOMAIN || 'localhost:43821';
|
||||
|
||||
if (currentUser.isSuspended || currentUser.isSilenced) {
|
||||
return NextResponse.json({ error: 'Account restricted' }, { status: 403 });
|
||||
@@ -107,10 +101,7 @@ export async function POST(request: Request, context: RouteContext) {
|
||||
|
||||
// Check if already following
|
||||
const existingRemoteFollow = await db.query.remoteFollows.findFirst({
|
||||
where: and(
|
||||
eq(remoteFollows.followerId, currentUser.id),
|
||||
eq(remoteFollows.targetHandle, targetHandle)
|
||||
),
|
||||
where: { AND: [{ followerId: currentUser.id }, { targetHandle: targetHandle }] },
|
||||
});
|
||||
if (existingRemoteFollow) {
|
||||
return NextResponse.json({ error: 'Already following' }, { status: 400 });
|
||||
@@ -179,7 +170,7 @@ export async function POST(request: Request, context: RouteContext) {
|
||||
|
||||
// Find target user
|
||||
const targetUser = await db.query.users.findFirst({
|
||||
where: eq(users.handle, cleanHandle),
|
||||
where: { handle: cleanHandle },
|
||||
});
|
||||
|
||||
if (!targetUser) {
|
||||
@@ -196,10 +187,7 @@ export async function POST(request: Request, context: RouteContext) {
|
||||
|
||||
// Check if already following
|
||||
const existingFollow = await db.query.follows.findFirst({
|
||||
where: and(
|
||||
eq(follows.followerId, currentUser.id),
|
||||
eq(follows.followingId, targetUser.id)
|
||||
),
|
||||
where: { AND: [{ followerId: currentUser.id }, { followingId: targetUser.id }] },
|
||||
});
|
||||
|
||||
if (existingFollow) {
|
||||
@@ -268,7 +256,7 @@ export async function DELETE(request: Request, context: RouteContext) {
|
||||
const { handle } = await context.params;
|
||||
const cleanHandle = handle.toLowerCase().replace(/^@/, '');
|
||||
const remote = parseRemoteHandle(handle);
|
||||
const nodeDomain = process.env.NEXT_PUBLIC_NODE_DOMAIN || 'localhost:3000';
|
||||
const nodeDomain = process.env.NEXT_PUBLIC_NODE_DOMAIN || 'localhost:43821';
|
||||
|
||||
if (remote) {
|
||||
if (!db) {
|
||||
@@ -276,10 +264,7 @@ export async function DELETE(request: Request, context: RouteContext) {
|
||||
}
|
||||
const targetHandle = `${remote.handle}@${remote.domain}`;
|
||||
const existingRemoteFollow = await db.query.remoteFollows.findFirst({
|
||||
where: and(
|
||||
eq(remoteFollows.followerId, currentUser.id),
|
||||
eq(remoteFollows.targetHandle, targetHandle)
|
||||
),
|
||||
where: { AND: [{ followerId: currentUser.id }, { targetHandle: targetHandle }] },
|
||||
});
|
||||
if (!existingRemoteFollow) {
|
||||
return NextResponse.json({ error: 'Not following' }, { status: 400 });
|
||||
@@ -306,7 +291,7 @@ export async function DELETE(request: Request, context: RouteContext) {
|
||||
|
||||
// Update the user's following count (atomic decrement, clamped to 0)
|
||||
await db.update(users)
|
||||
.set({ followingCount: sql`GREATEST(0, ${users.followingCount} - 1)` })
|
||||
.set({ followingCount: sql`max(0, ${users.followingCount} - 1)` })
|
||||
.where(eq(users.id, currentUser.id));
|
||||
|
||||
console.log(`[Swarm] Unfollow delivered to ${remote.domain}`);
|
||||
@@ -319,7 +304,7 @@ export async function DELETE(request: Request, context: RouteContext) {
|
||||
|
||||
// Find target user
|
||||
const targetUser = await db.query.users.findFirst({
|
||||
where: eq(users.handle, cleanHandle),
|
||||
where: { handle: cleanHandle },
|
||||
});
|
||||
|
||||
if (!targetUser) {
|
||||
@@ -331,10 +316,7 @@ export async function DELETE(request: Request, context: RouteContext) {
|
||||
|
||||
// Find existing follow
|
||||
const existingFollow = await db.query.follows.findFirst({
|
||||
where: and(
|
||||
eq(follows.followerId, currentUser.id),
|
||||
eq(follows.followingId, targetUser.id)
|
||||
),
|
||||
where: { AND: [{ followerId: currentUser.id }, { followingId: targetUser.id }] },
|
||||
});
|
||||
|
||||
if (!existingFollow) {
|
||||
@@ -346,11 +328,11 @@ export async function DELETE(request: Request, context: RouteContext) {
|
||||
|
||||
// Update counts (atomic decrements, clamped to 0)
|
||||
await db.update(users)
|
||||
.set({ followingCount: sql`GREATEST(0, ${users.followingCount} - 1)` })
|
||||
.set({ followingCount: sql`max(0, ${users.followingCount} - 1)` })
|
||||
.where(eq(users.id, currentUser.id));
|
||||
|
||||
await db.update(users)
|
||||
.set({ followersCount: sql`GREATEST(0, ${users.followersCount} - 1)` })
|
||||
.set({ followersCount: sql`max(0, ${users.followersCount} - 1)` })
|
||||
.where(eq(users.id, targetUser.id));
|
||||
|
||||
return NextResponse.json({ success: true, following: false });
|
||||
|
||||
@@ -61,7 +61,7 @@ export async function GET(request: Request, context: RouteContext) {
|
||||
|
||||
// Find the user
|
||||
const user = await db.query.users.findFirst({
|
||||
where: eq(users.handle, cleanHandle),
|
||||
where: { handle: cleanHandle },
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
@@ -94,7 +94,7 @@ export async function GET(request: Request, context: RouteContext) {
|
||||
|
||||
// Get remote followers
|
||||
const userRemoteFollowers = await db.query.remoteFollowers.findMany({
|
||||
where: eq(remoteFollowers.userId, user.id),
|
||||
where: { userId: user.id },
|
||||
limit,
|
||||
});
|
||||
|
||||
|
||||
@@ -61,7 +61,7 @@ export async function GET(request: Request, context: RouteContext) {
|
||||
|
||||
// Find the user
|
||||
const user = await db.query.users.findFirst({
|
||||
where: eq(users.handle, cleanHandle),
|
||||
where: { handle: cleanHandle },
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
@@ -94,7 +94,7 @@ export async function GET(request: Request, context: RouteContext) {
|
||||
|
||||
// Get remote following
|
||||
const userRemoteFollowing = await db.query.remoteFollows.findMany({
|
||||
where: eq(remoteFollows.followerId, user.id),
|
||||
where: { followerId: user.id },
|
||||
limit,
|
||||
});
|
||||
|
||||
|
||||
@@ -108,7 +108,7 @@ export async function GET(request: Request, context: RouteContext) {
|
||||
|
||||
// Find the user
|
||||
const user = await db.query.users.findFirst({
|
||||
where: eq(users.handle, cleanHandle),
|
||||
where: { handle: cleanHandle },
|
||||
});
|
||||
const isRemotePlaceholder = user && cleanHandle.includes('@');
|
||||
|
||||
@@ -141,13 +141,13 @@ export async function GET(request: Request, context: RouteContext) {
|
||||
|
||||
// Get user's liked posts
|
||||
const userLikes = await db.query.likes.findMany({
|
||||
where: eq(likes.userId, user.id),
|
||||
where: { userId: user.id },
|
||||
with: {
|
||||
post: {
|
||||
with: likedPostRelations,
|
||||
},
|
||||
},
|
||||
orderBy: [desc(likes.createdAt)],
|
||||
orderBy: () => [desc(likes.createdAt)],
|
||||
limit,
|
||||
});
|
||||
|
||||
@@ -156,8 +156,8 @@ export async function GET(request: Request, context: RouteContext) {
|
||||
.map(like => like.post);
|
||||
|
||||
const swarmLikedRows = await db.query.userSwarmLikes.findMany({
|
||||
where: eq(userSwarmLikes.userId, user.id),
|
||||
orderBy: [desc(userSwarmLikes.likedAt)],
|
||||
where: { userId: user.id },
|
||||
orderBy: () => [desc(userSwarmLikes.likedAt)],
|
||||
limit,
|
||||
});
|
||||
|
||||
@@ -225,19 +225,12 @@ export async function GET(request: Request, context: RouteContext) {
|
||||
|
||||
if (localPostIds.length > 0) {
|
||||
const viewerLikes = await db.query.likes.findMany({
|
||||
where: and(
|
||||
eq(likes.userId, viewer.id),
|
||||
inArray(likes.postId, localPostIds)
|
||||
),
|
||||
where: { AND: [{ userId: viewer.id }, { postId: { in: localPostIds } }] },
|
||||
});
|
||||
const likedPostIds = new Set(viewerLikes.map(l => l.postId));
|
||||
|
||||
const viewerReposts = await db.query.posts.findMany({
|
||||
where: and(
|
||||
eq(posts.userId, viewer.id),
|
||||
inArray(posts.repostOfId, localPostIds),
|
||||
eq(posts.isRemoved, false)
|
||||
),
|
||||
where: { AND: [{ userId: viewer.id }, { repostOfId: { in: localPostIds } }, { isRemoved: false }] },
|
||||
});
|
||||
const repostedPostIds = new Set(viewerReposts.map(r => r.repostOfId));
|
||||
|
||||
|
||||
@@ -151,13 +151,13 @@ async function getMixedProfileCursorDate(cursor: string | null) {
|
||||
|
||||
if (cursor.startsWith('swarm-repost:')) {
|
||||
const repostRow = await db.query.userSwarmReposts.findFirst({
|
||||
where: eq(userSwarmReposts.id, cursor.replace('swarm-repost:', '')),
|
||||
where: { id: cursor.replace('swarm-repost:', '') },
|
||||
});
|
||||
return repostRow?.repostedAt ?? null;
|
||||
}
|
||||
|
||||
const cursorPost = await db.query.posts.findFirst({
|
||||
where: eq(posts.id, cursor),
|
||||
where: { id: cursor },
|
||||
});
|
||||
return cursorPost?.createdAt ?? null;
|
||||
}
|
||||
@@ -173,7 +173,7 @@ async function populateViewerLikeState(
|
||||
const { getSession } = await import('@/lib/auth');
|
||||
const session = await getSession();
|
||||
const viewer = session?.user;
|
||||
const nodeDomain = process.env.NEXT_PUBLIC_NODE_DOMAIN || 'localhost:3000';
|
||||
const nodeDomain = process.env.NEXT_PUBLIC_NODE_DOMAIN || 'localhost:43821';
|
||||
|
||||
if (!viewer) {
|
||||
return remotePosts;
|
||||
@@ -301,7 +301,7 @@ export async function GET(request: Request, context: RouteContext) {
|
||||
|
||||
// Find the user
|
||||
const user = await db.query.users.findFirst({
|
||||
where: eq(users.handle, cleanHandle),
|
||||
where: { handle: cleanHandle },
|
||||
});
|
||||
const isRemotePlaceholder = user && cleanHandle.includes('@');
|
||||
|
||||
@@ -330,39 +330,28 @@ export async function GET(request: Request, context: RouteContext) {
|
||||
|
||||
// Get user's posts with cursor-based pagination
|
||||
const cursorDate = await getMixedProfileCursorDate(cursor);
|
||||
let whereConditions = and(
|
||||
eq(posts.userId, user.id),
|
||||
eq(posts.isRemoved, false),
|
||||
isNull(posts.replyToId),
|
||||
isNull(posts.swarmReplyToId)
|
||||
);
|
||||
|
||||
if (cursorDate) {
|
||||
whereConditions = and(
|
||||
eq(posts.userId, user.id),
|
||||
eq(posts.isRemoved, false),
|
||||
isNull(posts.replyToId),
|
||||
isNull(posts.swarmReplyToId),
|
||||
lt(posts.createdAt, cursorDate)
|
||||
);
|
||||
}
|
||||
const whereConditions = {
|
||||
userId: user.id,
|
||||
isRemoved: false,
|
||||
replyToId: { isNull: true as const },
|
||||
swarmReplyToId: { isNull: true as const },
|
||||
...(cursorDate ? { createdAt: { lt: cursorDate } } : {}),
|
||||
};
|
||||
|
||||
const localPosts = await db.query.posts.findMany({
|
||||
where: whereConditions,
|
||||
with: userPostRelations,
|
||||
orderBy: [desc(posts.createdAt)],
|
||||
orderBy: () => [desc(posts.createdAt)],
|
||||
limit: cursor ? limit : limit * 2,
|
||||
});
|
||||
|
||||
const swarmRepostWhere = cursorDate
|
||||
? and(
|
||||
eq(userSwarmReposts.userId, user.id),
|
||||
lt(userSwarmReposts.repostedAt, cursorDate)
|
||||
)
|
||||
: eq(userSwarmReposts.userId, user.id);
|
||||
const swarmRepostWhere = {
|
||||
userId: user.id,
|
||||
...(cursorDate ? { repostedAt: { lt: cursorDate } } : {}),
|
||||
};
|
||||
const swarmRepostRows = await db.query.userSwarmReposts.findMany({
|
||||
where: swarmRepostWhere,
|
||||
orderBy: [desc(userSwarmReposts.repostedAt)],
|
||||
orderBy: () => [desc(userSwarmReposts.repostedAt)],
|
||||
limit: cursor ? limit : limit * 2,
|
||||
});
|
||||
let userPosts: any[] = [
|
||||
@@ -400,19 +389,12 @@ export async function GET(request: Request, context: RouteContext) {
|
||||
|
||||
if (localPostIds.length > 0) {
|
||||
const viewerLikes = await db.query.likes.findMany({
|
||||
where: and(
|
||||
eq(likes.userId, viewer.id),
|
||||
inArray(likes.postId, localPostIds)
|
||||
),
|
||||
where: { AND: [{ userId: viewer.id }, { postId: { in: localPostIds } }] },
|
||||
});
|
||||
viewerLikes.forEach((like) => likedPostIds.add(like.postId));
|
||||
|
||||
const viewerReposts = await db.query.posts.findMany({
|
||||
where: and(
|
||||
eq(posts.userId, viewer.id),
|
||||
inArray(posts.repostOfId, localPostIds),
|
||||
eq(posts.isRemoved, false)
|
||||
),
|
||||
where: { AND: [{ userId: viewer.id }, { repostOfId: { in: localPostIds } }, { isRemoved: false }] },
|
||||
});
|
||||
viewerReposts.forEach((repost) => {
|
||||
if (repost.repostOfId) {
|
||||
@@ -422,7 +404,7 @@ export async function GET(request: Request, context: RouteContext) {
|
||||
}
|
||||
|
||||
if (swarmTargets.length > 0) {
|
||||
const nodeDomain = process.env.NEXT_PUBLIC_NODE_DOMAIN || 'localhost:3000';
|
||||
const nodeDomain = process.env.NEXT_PUBLIC_NODE_DOMAIN || 'localhost:43821';
|
||||
const likedIds = await getViewerSwarmLikedPostIds(
|
||||
swarmTargets.map((post) => ({
|
||||
id: post.id,
|
||||
|
||||
@@ -95,7 +95,7 @@ export async function GET(request: Request, context: RouteContext) {
|
||||
}
|
||||
|
||||
const user = await db.query.users.findFirst({
|
||||
where: eq(users.handle, cleanHandle),
|
||||
where: { handle: cleanHandle },
|
||||
});
|
||||
const isRemotePlaceholder = user && cleanHandle.includes('@');
|
||||
|
||||
@@ -121,30 +121,29 @@ export async function GET(request: Request, context: RouteContext) {
|
||||
return NextResponse.json({ error: 'User not found' }, { status: 404 });
|
||||
}
|
||||
|
||||
let whereConditions = and(
|
||||
eq(posts.userId, user.id),
|
||||
eq(posts.isRemoved, false),
|
||||
or(isNotNull(posts.replyToId), isNotNull(posts.swarmReplyToId)),
|
||||
);
|
||||
let cursorDate: Date | undefined;
|
||||
|
||||
if (cursor) {
|
||||
const cursorPost = await db.query.posts.findFirst({
|
||||
where: eq(posts.id, cursor),
|
||||
where: { id: cursor },
|
||||
});
|
||||
if (cursorPost) {
|
||||
whereConditions = and(
|
||||
eq(posts.userId, user.id),
|
||||
eq(posts.isRemoved, false),
|
||||
or(isNotNull(posts.replyToId), isNotNull(posts.swarmReplyToId)),
|
||||
lt(posts.createdAt, cursorPost.createdAt),
|
||||
);
|
||||
cursorDate = cursorPost.createdAt;
|
||||
}
|
||||
}
|
||||
|
||||
let replyPosts: any[] = await db.query.posts.findMany({
|
||||
where: whereConditions,
|
||||
where: {
|
||||
userId: user.id,
|
||||
isRemoved: false,
|
||||
OR: [
|
||||
{ replyToId: { isNotNull: true } },
|
||||
{ swarmReplyToId: { isNotNull: true } },
|
||||
],
|
||||
...(cursorDate ? { createdAt: { lt: cursorDate } } : {}),
|
||||
},
|
||||
with: replyRelations,
|
||||
orderBy: [desc(posts.createdAt)],
|
||||
orderBy: () => [desc(posts.createdAt)],
|
||||
limit,
|
||||
});
|
||||
|
||||
@@ -158,21 +157,14 @@ export async function GET(request: Request, context: RouteContext) {
|
||||
|
||||
const viewerLikes = postIds.length > 0
|
||||
? await db.query.likes.findMany({
|
||||
where: and(
|
||||
eq(likes.userId, viewer.id),
|
||||
inArray(likes.postId, postIds),
|
||||
),
|
||||
where: { AND: [{ userId: viewer.id }, { postId: { in: postIds } }] },
|
||||
})
|
||||
: [];
|
||||
const likedPostIds = new Set(viewerLikes.map((like) => like.postId));
|
||||
|
||||
const viewerReposts = postIds.length > 0
|
||||
? await db.query.posts.findMany({
|
||||
where: and(
|
||||
eq(posts.userId, viewer.id),
|
||||
inArray(posts.repostOfId, postIds),
|
||||
eq(posts.isRemoved, false),
|
||||
),
|
||||
where: { AND: [{ userId: viewer.id }, { repostOfId: { in: postIds } }, { isRemoved: false }] },
|
||||
})
|
||||
: [];
|
||||
const repostedPostIds = new Set(viewerReposts.map((post) => post.repostOfId));
|
||||
|
||||
@@ -32,7 +32,7 @@ export async function GET(request: Request, context: RouteContext) {
|
||||
}
|
||||
|
||||
const user = await db.query.users.findFirst({
|
||||
where: eq(users.handle, cleanHandle),
|
||||
where: { handle: cleanHandle },
|
||||
});
|
||||
|
||||
// If user exists but is a remote placeholder (handle contains @), fetch fresh data from remote
|
||||
@@ -147,10 +147,7 @@ export async function GET(request: Request, context: RouteContext) {
|
||||
canReceiveDms = true; // Can DM yourself
|
||||
} else {
|
||||
const isFollowingViewer = await db.query.follows.findFirst({
|
||||
where: and(
|
||||
eq(follows.followerId, user.id),
|
||||
eq(follows.followingId, session.user.id)
|
||||
)
|
||||
where: { AND: [{ followerId: user.id }, { followingId: session.user.id }] }
|
||||
});
|
||||
if (isFollowingViewer) {
|
||||
canReceiveDms = true;
|
||||
@@ -163,7 +160,7 @@ export async function GET(request: Request, context: RouteContext) {
|
||||
// If this is a bot, include owner info
|
||||
if (user.isBot && user.botOwnerId) {
|
||||
const owner = await db.query.users.findFirst({
|
||||
where: eq(users.id, user.botOwnerId),
|
||||
where: { id: user.botOwnerId },
|
||||
});
|
||||
if (owner) {
|
||||
userResponse.botOwner = {
|
||||
|
||||
+12
-19
@@ -1,26 +1,19 @@
|
||||
import { drizzle } from 'drizzle-orm/node-postgres';
|
||||
import { Pool } from 'pg';
|
||||
import * as schema from './schema';
|
||||
import { mkdirSync } from 'node:fs';
|
||||
import { dirname, resolve } from 'node:path';
|
||||
import { drizzle } from 'drizzle-orm/tursodatabase/database';
|
||||
import { relations } from './relations';
|
||||
|
||||
// Best Practice for VPS/Self-Hosting:
|
||||
// We use 'pg' (node-postgres) which connects via standard TCP.
|
||||
// This works for local Postgres, Docker, VPS, and managed clouds (AWS RDS, Neon, etc.).
|
||||
const configuredPath = process.env.DATABASE_PATH || './data/synapsis.db';
|
||||
const databasePath = configuredPath === ':memory:' ? configuredPath : resolve(configuredPath);
|
||||
|
||||
const connectionString = process.env.DATABASE_URL || 'postgres://placeholder:placeholder@localhost:5432/placeholder';
|
||||
if (databasePath !== ':memory:') {
|
||||
mkdirSync(dirname(databasePath), { recursive: true });
|
||||
}
|
||||
|
||||
// Create a connection pool
|
||||
const pool = new Pool({
|
||||
connectionString,
|
||||
max: 20, // Adjust based on your server capacity
|
||||
idleTimeoutMillis: 30000,
|
||||
connectionTimeoutMillis: 2000,
|
||||
});
|
||||
export const db = drizzle(databasePath, { relations });
|
||||
|
||||
// Create the Drizzle client
|
||||
export const db = drizzle(pool, { schema });
|
||||
|
||||
// Helper to check if DB is configured
|
||||
export const isDbAvailable = () => !!process.env.DATABASE_URL;
|
||||
// Embedded Turso is always available; DATABASE_PATH only changes its location.
|
||||
export const isDbAvailable = () => true;
|
||||
|
||||
// Export schema for use elsewhere
|
||||
export * from './schema';
|
||||
|
||||
@@ -0,0 +1,177 @@
|
||||
import { defineRelations } from 'drizzle-orm';
|
||||
import * as schema from './schema';
|
||||
|
||||
export const relations = defineRelations(schema, (r) => ({
|
||||
users: {
|
||||
node: r.one.nodes({ from: r.users.nodeId, to: r.nodes.id }),
|
||||
botOwner: r.one.users({
|
||||
from: r.users.botOwnerId,
|
||||
to: r.users.id,
|
||||
alias: 'ownedBots',
|
||||
}),
|
||||
ownedBotUsers: r.many.users({
|
||||
from: r.users.id,
|
||||
to: r.users.botOwnerId,
|
||||
alias: 'ownedBots',
|
||||
}),
|
||||
posts: r.many.posts({ from: r.users.id, to: r.posts.userId }),
|
||||
followersRelation: r.many.follows({
|
||||
from: r.users.id,
|
||||
to: r.follows.followingId,
|
||||
alias: 'following',
|
||||
}),
|
||||
followingRelation: r.many.follows({
|
||||
from: r.users.id,
|
||||
to: r.follows.followerId,
|
||||
alias: 'follower',
|
||||
}),
|
||||
},
|
||||
posts: {
|
||||
author: r.one.users({ from: r.posts.userId, to: r.users.id, optional: false }),
|
||||
bot: r.one.bots({ from: r.posts.botId, to: r.bots.id }),
|
||||
removedByUser: r.one.users({ from: r.posts.removedBy, to: r.users.id }),
|
||||
replyTo: r.one.posts({
|
||||
from: r.posts.replyToId,
|
||||
to: r.posts.id,
|
||||
alias: 'replies',
|
||||
}),
|
||||
replies: r.many.posts({
|
||||
from: r.posts.id,
|
||||
to: r.posts.replyToId,
|
||||
alias: 'replies',
|
||||
}),
|
||||
repostOf: r.one.posts({
|
||||
from: r.posts.repostOfId,
|
||||
to: r.posts.id,
|
||||
alias: 'reposts',
|
||||
}),
|
||||
reposts: r.many.posts({
|
||||
from: r.posts.id,
|
||||
to: r.posts.repostOfId,
|
||||
alias: 'reposts',
|
||||
}),
|
||||
likes: r.many.likes({ from: r.posts.id, to: r.likes.postId }),
|
||||
media: r.many.media({ from: r.posts.id, to: r.media.postId }),
|
||||
},
|
||||
media: {
|
||||
user: r.one.users({ from: r.media.userId, to: r.users.id, optional: false }),
|
||||
post: r.one.posts({ from: r.media.postId, to: r.posts.id }),
|
||||
},
|
||||
follows: {
|
||||
follower: r.one.users({
|
||||
from: r.follows.followerId,
|
||||
to: r.users.id,
|
||||
alias: 'follower',
|
||||
optional: false,
|
||||
}),
|
||||
following: r.one.users({
|
||||
from: r.follows.followingId,
|
||||
to: r.users.id,
|
||||
alias: 'following',
|
||||
optional: false,
|
||||
}),
|
||||
},
|
||||
likes: {
|
||||
user: r.one.users({ from: r.likes.userId, to: r.users.id, optional: false }),
|
||||
post: r.one.posts({ from: r.likes.postId, to: r.posts.id, optional: false }),
|
||||
},
|
||||
notifications: {
|
||||
recipient: r.one.users({
|
||||
from: r.notifications.userId,
|
||||
to: r.users.id,
|
||||
alias: 'recipient',
|
||||
optional: false,
|
||||
}),
|
||||
actor: r.one.users({
|
||||
from: r.notifications.actorId,
|
||||
to: r.users.id,
|
||||
alias: 'actor',
|
||||
optional: false,
|
||||
}),
|
||||
post: r.one.posts({ from: r.notifications.postId, to: r.posts.id }),
|
||||
},
|
||||
sessions: {
|
||||
user: r.one.users({ from: r.sessions.userId, to: r.users.id, optional: false }),
|
||||
},
|
||||
blocks: {
|
||||
user: r.one.users({ from: r.blocks.userId, to: r.users.id, optional: false }),
|
||||
blockedUser: r.one.users({ from: r.blocks.blockedUserId, to: r.users.id, optional: false }),
|
||||
},
|
||||
mutes: {
|
||||
user: r.one.users({ from: r.mutes.userId, to: r.users.id, optional: false }),
|
||||
mutedUser: r.one.users({ from: r.mutes.mutedUserId, to: r.users.id, optional: false }),
|
||||
},
|
||||
mutedNodes: {
|
||||
user: r.one.users({ from: r.mutedNodes.userId, to: r.users.id, optional: false }),
|
||||
},
|
||||
reports: {
|
||||
reporter: r.one.users({ from: r.reports.reporterId, to: r.users.id }),
|
||||
resolver: r.one.users({ from: r.reports.resolvedBy, to: r.users.id }),
|
||||
},
|
||||
bots: {
|
||||
user: r.one.users({
|
||||
from: r.bots.userId,
|
||||
to: r.users.id,
|
||||
alias: 'botUser',
|
||||
optional: false,
|
||||
}),
|
||||
owner: r.one.users({
|
||||
from: r.bots.ownerId,
|
||||
to: r.users.id,
|
||||
alias: 'botOwner',
|
||||
optional: false,
|
||||
}),
|
||||
contentSources: r.many.botContentSources({ from: r.bots.id, to: r.botContentSources.botId }),
|
||||
mentions: r.many.botMentions({ from: r.bots.id, to: r.botMentions.botId }),
|
||||
activityLogs: r.many.botActivityLogs({ from: r.bots.id, to: r.botActivityLogs.botId }),
|
||||
rateLimits: r.many.botRateLimits({ from: r.bots.id, to: r.botRateLimits.botId }),
|
||||
},
|
||||
botContentSources: {
|
||||
bot: r.one.bots({ from: r.botContentSources.botId, to: r.bots.id, optional: false }),
|
||||
contentItems: r.many.botContentItems({
|
||||
from: r.botContentSources.id,
|
||||
to: r.botContentItems.sourceId,
|
||||
}),
|
||||
},
|
||||
botContentItems: {
|
||||
source: r.one.botContentSources({
|
||||
from: r.botContentItems.sourceId,
|
||||
to: r.botContentSources.id,
|
||||
optional: false,
|
||||
}),
|
||||
post: r.one.posts({ from: r.botContentItems.postId, to: r.posts.id }),
|
||||
},
|
||||
botMentions: {
|
||||
bot: r.one.bots({ from: r.botMentions.botId, to: r.bots.id, optional: false }),
|
||||
post: r.one.posts({ from: r.botMentions.postId, to: r.posts.id, optional: false }),
|
||||
author: r.one.users({ from: r.botMentions.authorId, to: r.users.id, optional: false }),
|
||||
responsePost: r.one.posts({
|
||||
from: r.botMentions.responsePostId,
|
||||
to: r.posts.id,
|
||||
}),
|
||||
},
|
||||
botActivityLogs: {
|
||||
bot: r.one.bots({ from: r.botActivityLogs.botId, to: r.bots.id, optional: false }),
|
||||
},
|
||||
botRateLimits: {
|
||||
bot: r.one.bots({ from: r.botRateLimits.botId, to: r.bots.id, optional: false }),
|
||||
},
|
||||
chatConversations: {
|
||||
participant1: r.one.users({
|
||||
from: r.chatConversations.participant1Id,
|
||||
to: r.users.id,
|
||||
optional: false,
|
||||
}),
|
||||
messages: r.many.chatMessages({
|
||||
from: r.chatConversations.id,
|
||||
to: r.chatMessages.conversationId,
|
||||
}),
|
||||
},
|
||||
chatMessages: {
|
||||
conversation: r.one.chatConversations({
|
||||
from: r.chatMessages.conversationId,
|
||||
to: r.chatConversations.id,
|
||||
optional: false,
|
||||
}),
|
||||
},
|
||||
}));
|
||||
+210
-416
File diff suppressed because it is too large
Load Diff
@@ -89,7 +89,7 @@ async function loadSessionsByTokens(tokens: string[]): Promise<SessionRecord[]>
|
||||
}
|
||||
|
||||
const sessionRecords = await db.query.sessions.findMany({
|
||||
where: inArray(sessions.token, uniqueTokens),
|
||||
where: { token: { in: uniqueTokens } },
|
||||
with: {
|
||||
user: true,
|
||||
},
|
||||
@@ -296,7 +296,7 @@ export async function registerUser(
|
||||
|
||||
// Check if handle is taken
|
||||
const existingHandle = await db.query.users.findFirst({
|
||||
where: eq(users.handle, handle.toLowerCase()),
|
||||
where: { handle: handle.toLowerCase() },
|
||||
});
|
||||
|
||||
if (existingHandle) {
|
||||
@@ -305,7 +305,7 @@ export async function registerUser(
|
||||
|
||||
// Check if email is taken
|
||||
const existingEmail = await db.query.users.findFirst({
|
||||
where: eq(users.email, email.toLowerCase()),
|
||||
where: { email: email.toLowerCase() },
|
||||
});
|
||||
|
||||
if (existingEmail) {
|
||||
@@ -339,7 +339,7 @@ export async function registerUser(
|
||||
const did = generateDID(publicKey);
|
||||
const passwordHash = await hashPassword(password);
|
||||
|
||||
const nodeDomain = process.env.NEXT_PUBLIC_NODE_DOMAIN || 'localhost:3000';
|
||||
const nodeDomain = process.env.NEXT_PUBLIC_NODE_DOMAIN || 'localhost:43821';
|
||||
|
||||
// Generate avatar and upload to user's S3 storage
|
||||
const fullHandle = `${handle.toLowerCase()}@${nodeDomain}`;
|
||||
@@ -393,7 +393,7 @@ export async function authenticateUser(
|
||||
password: string
|
||||
): Promise<typeof users.$inferSelect> {
|
||||
const user = await db.query.users.findFirst({
|
||||
where: eq(users.email, email.toLowerCase()),
|
||||
where: { email: email.toLowerCase() },
|
||||
});
|
||||
|
||||
if (!user || !user.passwordHash) {
|
||||
|
||||
@@ -26,7 +26,7 @@ interface RemoteIdentity {
|
||||
export async function lookupRemoteKey(did: string): Promise<string> {
|
||||
// 1. Check Cache
|
||||
const cached = await db.query.remoteIdentityCache.findFirst({
|
||||
where: eq(remoteIdentityCache.did, did),
|
||||
where: { did: did },
|
||||
});
|
||||
|
||||
const now = new Date();
|
||||
|
||||
@@ -96,7 +96,7 @@ export async function verifyUserAction(signedAction: SignedAction): Promise<{
|
||||
|
||||
// 3. FETCH USER & KEY
|
||||
const user = await db.query.users.findFirst({
|
||||
where: eq(users.did, payload.did),
|
||||
where: { did: payload.did },
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
|
||||
@@ -120,7 +120,7 @@ export function startBackgroundTasks(origin?: string) {
|
||||
isStarted = true;
|
||||
|
||||
// Default origin for remote sync (can be overridden)
|
||||
const syncOrigin = origin || process.env.NEXT_PUBLIC_APP_URL || 'http://localhost:3000';
|
||||
const syncOrigin = origin || process.env.NEXT_PUBLIC_APP_URL || 'http://localhost:43821';
|
||||
|
||||
log('STARTUP', 'Background task scheduler starting...');
|
||||
log('STARTUP', `Bot interval: ${BOT_INTERVAL_MS / 1000}s, Gossip interval: ${GOSSIP_INTERVAL_MS / 1000}s, Remote sync interval: ${REMOTE_SYNC_INTERVAL_MS / 1000}s`);
|
||||
|
||||
@@ -95,26 +95,19 @@ export async function getLogsForBot(
|
||||
botId: string,
|
||||
options: LogQueryOptions = {}
|
||||
): Promise<ActivityLog[]> {
|
||||
const conditions = [eq(botActivityLogs.botId, botId)];
|
||||
|
||||
// Filter by action types
|
||||
if (options.actionTypes && options.actionTypes.length > 0) {
|
||||
conditions.push(inArray(botActivityLogs.action, options.actionTypes));
|
||||
}
|
||||
|
||||
// Filter by date range
|
||||
if (options.startDate) {
|
||||
conditions.push(gte(botActivityLogs.createdAt, options.startDate));
|
||||
}
|
||||
|
||||
if (options.endDate) {
|
||||
conditions.push(lte(botActivityLogs.createdAt, options.endDate));
|
||||
}
|
||||
|
||||
// Build query
|
||||
let query = db.query.botActivityLogs.findMany({
|
||||
where: and(...conditions),
|
||||
orderBy: [desc(botActivityLogs.createdAt)], // Reverse chronological
|
||||
where: {
|
||||
botId,
|
||||
...(options.actionTypes?.length ? { action: { in: options.actionTypes } } : {}),
|
||||
...(options.startDate || options.endDate ? {
|
||||
createdAt: {
|
||||
...(options.startDate ? { gte: options.startDate } : {}),
|
||||
...(options.endDate ? { lte: options.endDate } : {}),
|
||||
},
|
||||
} : {}),
|
||||
},
|
||||
orderBy: () => [desc(botActivityLogs.createdAt)], // Reverse chronological
|
||||
limit: options.limit || 100,
|
||||
offset: options.offset || 0,
|
||||
});
|
||||
@@ -146,11 +139,8 @@ export async function getErrorLogs(
|
||||
limit: number = 50
|
||||
): Promise<ActivityLog[]> {
|
||||
const logs = await db.query.botActivityLogs.findMany({
|
||||
where: and(
|
||||
eq(botActivityLogs.botId, botId),
|
||||
eq(botActivityLogs.success, false)
|
||||
),
|
||||
orderBy: [desc(botActivityLogs.createdAt)],
|
||||
where: { AND: [{ botId: botId }, { success: false }] },
|
||||
orderBy: () => [desc(botActivityLogs.createdAt)],
|
||||
limit,
|
||||
});
|
||||
|
||||
|
||||
+11
-17
@@ -114,10 +114,10 @@ export const MIN_INTEREST_SCORE = 60;
|
||||
*/
|
||||
async function hasContentSources(botId: string): Promise<boolean> {
|
||||
const bot = await db.query.bots.findFirst({
|
||||
where: eq(bots.id, botId),
|
||||
where: { id: botId },
|
||||
with: {
|
||||
contentSources: {
|
||||
where: (sources, { eq }) => eq(sources.isActive, true),
|
||||
where: { isActive: true },
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -139,10 +139,10 @@ async function getUnprocessedContentItems(
|
||||
): Promise<ContentItem[]> {
|
||||
// Get bot's content sources
|
||||
const bot = await db.query.bots.findFirst({
|
||||
where: eq(bots.id, botId),
|
||||
where: { id: botId },
|
||||
with: {
|
||||
contentSources: {
|
||||
where: (sources, { eq }) => eq(sources.isActive, true),
|
||||
where: { isActive: true },
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -155,9 +155,7 @@ async function getUnprocessedContentItems(
|
||||
|
||||
// Get unprocessed content items from these sources
|
||||
const items = await db.query.botContentItems.findMany({
|
||||
where: and(
|
||||
eq(botContentItems.isProcessed, false)
|
||||
),
|
||||
where: { AND: [{ isProcessed: false }] },
|
||||
orderBy: (items, { desc }) => [desc(items.publishedAt)],
|
||||
limit,
|
||||
});
|
||||
@@ -284,7 +282,7 @@ export async function evaluateContentForPosting(
|
||||
): Promise<AutonomousPostEvaluation> {
|
||||
// Get bot with user relation
|
||||
const dbBot = await db.query.bots.findFirst({
|
||||
where: eq(bots.id, botId),
|
||||
where: { id: botId },
|
||||
with: { user: true },
|
||||
});
|
||||
|
||||
@@ -373,7 +371,7 @@ export async function attemptAutonomousPost(
|
||||
|
||||
// Get bot with schedule config
|
||||
const dbBot = await db.query.bots.findFirst({
|
||||
where: eq(bots.id, botId),
|
||||
where: { id: botId },
|
||||
with: { user: true },
|
||||
});
|
||||
|
||||
@@ -541,11 +539,7 @@ export async function processAllAutonomousBots(): Promise<Array<{
|
||||
}>> {
|
||||
// Get all active bots with autonomous mode enabled
|
||||
const autonomousBots = await db.query.bots.findMany({
|
||||
where: and(
|
||||
eq(bots.isActive, true),
|
||||
eq(bots.isSuspended, false),
|
||||
eq(bots.autonomousMode, true)
|
||||
),
|
||||
where: { AND: [{ isActive: true }, { isSuspended: false }, { autonomousMode: true }] },
|
||||
with: { user: true },
|
||||
});
|
||||
|
||||
@@ -590,7 +584,7 @@ export async function canPostAutonomously(botId: string): Promise<{
|
||||
}> {
|
||||
// Get bot
|
||||
const bot = await db.query.bots.findFirst({
|
||||
where: eq(bots.id, botId),
|
||||
where: { id: botId },
|
||||
});
|
||||
|
||||
if (!bot) {
|
||||
@@ -686,7 +680,7 @@ export async function getAutonomousPostingStats(botId: string): Promise<{
|
||||
}> {
|
||||
// Get bot's content sources
|
||||
const bot = await db.query.bots.findFirst({
|
||||
where: eq(bots.id, botId),
|
||||
where: { id: botId },
|
||||
with: {
|
||||
contentSources: true,
|
||||
},
|
||||
@@ -706,7 +700,7 @@ export async function getAutonomousPostingStats(botId: string): Promise<{
|
||||
|
||||
// Get all content items for this bot's sources
|
||||
const allItems = await db.query.botContentItems.findMany({
|
||||
where: (items, { inArray }) => inArray(items.sourceId, sourceIds),
|
||||
where: { sourceId: { in: sourceIds } },
|
||||
});
|
||||
|
||||
const processedItems = allItems.filter(item => item.isProcessed);
|
||||
|
||||
+18
-21
@@ -396,7 +396,7 @@ export async function createBot(ownerId: string, config: BotCreateInput): Promis
|
||||
|
||||
// Check if handle is taken (in users table now)
|
||||
const existingUser = await db.query.users.findFirst({
|
||||
where: eq(users.handle, config.handle.toLowerCase()),
|
||||
where: { handle: config.handle.toLowerCase() },
|
||||
});
|
||||
|
||||
if (existingUser) {
|
||||
@@ -405,7 +405,7 @@ export async function createBot(ownerId: string, config: BotCreateInput): Promis
|
||||
|
||||
// Get owner to access their S3 storage for bot avatar
|
||||
const owner = await db.query.users.findFirst({
|
||||
where: eq(users.id, ownerId),
|
||||
where: { id: ownerId },
|
||||
});
|
||||
|
||||
if (!owner) {
|
||||
@@ -420,7 +420,7 @@ export async function createBot(ownerId: string, config: BotCreateInput): Promis
|
||||
const encryptedPrivateKey = encryptApiKey(privateKey);
|
||||
|
||||
// Generate a DID for the bot
|
||||
const nodeDomain = process.env.NEXT_PUBLIC_NODE_DOMAIN || 'localhost:3000';
|
||||
const nodeDomain = process.env.NEXT_PUBLIC_NODE_DOMAIN || 'localhost:43821';
|
||||
const botDid = `did:web:${nodeDomain}:users:${config.handle.toLowerCase()}`;
|
||||
|
||||
// Generate bot avatar using owner's S3 storage if no avatar provided
|
||||
@@ -515,7 +515,7 @@ export async function updateBot(botId: string, config: BotUpdateInput): Promise<
|
||||
|
||||
// Check if bot exists and get its user account
|
||||
const existingBot = await db.query.bots.findFirst({
|
||||
where: eq(bots.id, botId),
|
||||
where: { id: botId },
|
||||
});
|
||||
|
||||
if (!existingBot) {
|
||||
@@ -524,7 +524,7 @@ export async function updateBot(botId: string, config: BotUpdateInput): Promise<
|
||||
|
||||
// Get the bot's user account
|
||||
const botUser = await db.query.users.findFirst({
|
||||
where: eq(users.id, existingBot.userId),
|
||||
where: { id: existingBot.userId },
|
||||
});
|
||||
|
||||
if (!botUser) {
|
||||
@@ -629,7 +629,7 @@ export async function updateBot(botId: string, config: BotUpdateInput): Promise<
|
||||
|
||||
// Get updated user
|
||||
const updatedUser = await db.query.users.findFirst({
|
||||
where: eq(users.id, existingBot.userId),
|
||||
where: { id: existingBot.userId },
|
||||
});
|
||||
|
||||
return dbBotToBot(updatedBot, updatedUser!);
|
||||
@@ -647,7 +647,7 @@ export async function updateBot(botId: string, config: BotUpdateInput): Promise<
|
||||
export async function deleteBot(botId: string): Promise<void> {
|
||||
// Check if bot exists
|
||||
const existingBot = await db.query.bots.findFirst({
|
||||
where: eq(bots.id, botId),
|
||||
where: { id: botId },
|
||||
});
|
||||
|
||||
if (!existingBot) {
|
||||
@@ -671,14 +671,14 @@ export async function deleteBot(botId: string): Promise<void> {
|
||||
*/
|
||||
export async function getBotsByUser(ownerId: string): Promise<Bot[]> {
|
||||
const userBots = await db.query.bots.findMany({
|
||||
where: eq(bots.ownerId, ownerId),
|
||||
where: { ownerId: ownerId },
|
||||
orderBy: (bots, { desc }) => [desc(bots.createdAt)],
|
||||
});
|
||||
|
||||
// Get all bot user accounts
|
||||
const botUserIds = userBots.map(b => b.userId);
|
||||
const botUsers = await db.query.users.findMany({
|
||||
where: (users, { inArray }) => inArray(users.id, botUserIds),
|
||||
where: { id: { in: botUserIds } },
|
||||
});
|
||||
|
||||
const userMap = new Map(botUsers.map(u => [u.id, u]));
|
||||
@@ -702,7 +702,7 @@ export async function getBotsByUser(ownerId: string): Promise<Bot[]> {
|
||||
*/
|
||||
export async function getBotById(botId: string): Promise<Bot | null> {
|
||||
const bot = await db.query.bots.findFirst({
|
||||
where: eq(bots.id, botId),
|
||||
where: { id: botId },
|
||||
});
|
||||
|
||||
if (!bot) {
|
||||
@@ -711,7 +711,7 @@ export async function getBotById(botId: string): Promise<Bot | null> {
|
||||
|
||||
// Get the bot's user account
|
||||
const botUser = await db.query.users.findFirst({
|
||||
where: eq(users.id, bot.userId),
|
||||
where: { id: bot.userId },
|
||||
});
|
||||
|
||||
if (!botUser) {
|
||||
@@ -730,10 +730,7 @@ export async function getBotById(botId: string): Promise<Bot | null> {
|
||||
export async function getBotByHandle(handle: string): Promise<Bot | null> {
|
||||
// Find the user with this handle that is a bot
|
||||
const botUser = await db.query.users.findFirst({
|
||||
where: and(
|
||||
eq(users.handle, handle.toLowerCase()),
|
||||
eq(users.isBot, true)
|
||||
),
|
||||
where: { AND: [{ handle: handle.toLowerCase() }, { isBot: true }] },
|
||||
});
|
||||
|
||||
if (!botUser) {
|
||||
@@ -742,7 +739,7 @@ export async function getBotByHandle(handle: string): Promise<Bot | null> {
|
||||
|
||||
// Find the bot config for this user
|
||||
const bot = await db.query.bots.findFirst({
|
||||
where: eq(bots.userId, botUser.id),
|
||||
where: { userId: botUser.id },
|
||||
});
|
||||
|
||||
if (!bot) {
|
||||
@@ -789,7 +786,7 @@ export async function canUserCreateBot(ownerId: string): Promise<boolean> {
|
||||
*/
|
||||
export async function userOwnsBot(ownerId: string, botId: string): Promise<boolean> {
|
||||
const bot = await db.query.bots.findFirst({
|
||||
where: and(eq(bots.id, botId), eq(bots.ownerId, ownerId)),
|
||||
where: { AND: [{ id: botId }, { ownerId: ownerId }] },
|
||||
});
|
||||
|
||||
return bot !== undefined;
|
||||
@@ -836,7 +833,7 @@ export async function setApiKey(
|
||||
): Promise<void> {
|
||||
// Check if bot exists
|
||||
const existingBot = await db.query.bots.findFirst({
|
||||
where: eq(bots.id, botId),
|
||||
where: { id: botId },
|
||||
});
|
||||
|
||||
if (!existingBot) {
|
||||
@@ -885,7 +882,7 @@ export async function setApiKey(
|
||||
export async function removeApiKey(botId: string): Promise<void> {
|
||||
// Check if bot exists
|
||||
const existingBot = await db.query.bots.findFirst({
|
||||
where: eq(bots.id, botId),
|
||||
where: { id: botId },
|
||||
});
|
||||
|
||||
if (!existingBot) {
|
||||
@@ -930,7 +927,7 @@ export interface ApiKeyStatus {
|
||||
export async function getApiKeyStatus(botId: string): Promise<ApiKeyStatus> {
|
||||
// Get the bot with encrypted API key
|
||||
const bot = await db.query.bots.findFirst({
|
||||
where: eq(bots.id, botId),
|
||||
where: { id: botId },
|
||||
});
|
||||
|
||||
if (!bot) {
|
||||
@@ -984,7 +981,7 @@ export async function botHasApiKey(botId: string): Promise<boolean> {
|
||||
export async function getDecryptedApiKey(botId: string): Promise<string | null> {
|
||||
// Get the bot with encrypted API key
|
||||
const bot = await db.query.bots.findFirst({
|
||||
where: eq(bots.id, botId),
|
||||
where: { id: botId },
|
||||
});
|
||||
|
||||
if (!bot) {
|
||||
|
||||
@@ -562,10 +562,7 @@ export async function contentItemExists(
|
||||
externalId: string
|
||||
): Promise<boolean> {
|
||||
const existing = await db.query.botContentItems.findFirst({
|
||||
where: and(
|
||||
eq(botContentItems.sourceId, sourceId),
|
||||
eq(botContentItems.externalId, externalId)
|
||||
),
|
||||
where: { AND: [{ sourceId: sourceId }, { externalId: externalId }] },
|
||||
columns: { id: true },
|
||||
});
|
||||
|
||||
@@ -684,7 +681,7 @@ export async function recordFetchError(
|
||||
): Promise<void> {
|
||||
// Get current consecutive errors
|
||||
const source = await db.query.botContentSources.findFirst({
|
||||
where: eq(botContentSources.id, sourceId),
|
||||
where: { id: sourceId },
|
||||
columns: { consecutiveErrors: true },
|
||||
});
|
||||
|
||||
@@ -768,7 +765,7 @@ export async function fetchContent(
|
||||
case 'news_api':
|
||||
// Decrypt API key
|
||||
const dbSource = await db.query.botContentSources.findFirst({
|
||||
where: eq(botContentSources.id, sourceId),
|
||||
where: { id: sourceId },
|
||||
columns: { apiKeyEncrypted: true },
|
||||
});
|
||||
|
||||
@@ -785,7 +782,7 @@ export async function fetchContent(
|
||||
case 'brave_news':
|
||||
// Decrypt API key
|
||||
const braveDbSource = await db.query.botContentSources.findFirst({
|
||||
where: eq(botContentSources.id, sourceId),
|
||||
where: { id: sourceId },
|
||||
columns: { apiKeyEncrypted: true, sourceConfig: true },
|
||||
});
|
||||
|
||||
@@ -945,10 +942,7 @@ export async function fetchAllSourcesForBot(
|
||||
options: FetchOptions = {}
|
||||
): Promise<FetchResult[]> {
|
||||
const sources = await db.query.botContentSources.findMany({
|
||||
where: and(
|
||||
eq(botContentSources.botId, botId),
|
||||
eq(botContentSources.isActive, true)
|
||||
),
|
||||
where: { AND: [{ botId: botId }, { isActive: true }] },
|
||||
});
|
||||
|
||||
const results: FetchResult[] = [];
|
||||
@@ -994,10 +988,7 @@ export async function getUnprocessedItems(
|
||||
limit: number = 10
|
||||
): Promise<StoredContentItem[]> {
|
||||
const items = await db.query.botContentItems.findMany({
|
||||
where: and(
|
||||
eq(botContentItems.sourceId, sourceId),
|
||||
eq(botContentItems.isProcessed, false)
|
||||
),
|
||||
where: { AND: [{ sourceId: sourceId }, { isProcessed: false }] },
|
||||
orderBy: (items, { asc }) => [asc(items.publishedAt)],
|
||||
limit,
|
||||
});
|
||||
|
||||
@@ -592,7 +592,7 @@ export async function addSource(
|
||||
|
||||
// Check if bot exists
|
||||
const bot = await db.query.bots.findFirst({
|
||||
where: eq(bots.id, botId),
|
||||
where: { id: botId },
|
||||
columns: { id: true },
|
||||
});
|
||||
|
||||
@@ -646,7 +646,7 @@ export async function addSource(
|
||||
export async function removeSource(sourceId: string): Promise<void> {
|
||||
// Check if source exists
|
||||
const existingSource = await db.query.botContentSources.findFirst({
|
||||
where: eq(botContentSources.id, sourceId),
|
||||
where: { id: sourceId },
|
||||
columns: { id: true },
|
||||
});
|
||||
|
||||
@@ -666,7 +666,7 @@ export async function removeSource(sourceId: string): Promise<void> {
|
||||
*/
|
||||
export async function getSourceById(sourceId: string): Promise<ContentSource | null> {
|
||||
const source = await db.query.botContentSources.findFirst({
|
||||
where: eq(botContentSources.id, sourceId),
|
||||
where: { id: sourceId },
|
||||
});
|
||||
|
||||
if (!source) {
|
||||
@@ -686,7 +686,7 @@ export async function getSourceById(sourceId: string): Promise<ContentSource | n
|
||||
*/
|
||||
export async function getSourcesByBot(botId: string): Promise<ContentSource[]> {
|
||||
const sources = await db.query.botContentSources.findMany({
|
||||
where: eq(botContentSources.botId, botId),
|
||||
where: { botId: botId },
|
||||
orderBy: (sources, { desc }) => [desc(sources.createdAt)],
|
||||
});
|
||||
|
||||
@@ -701,10 +701,7 @@ export async function getSourcesByBot(botId: string): Promise<ContentSource[]> {
|
||||
*/
|
||||
export async function getActiveSourcesByBot(botId: string): Promise<ContentSource[]> {
|
||||
const sources = await db.query.botContentSources.findMany({
|
||||
where: and(
|
||||
eq(botContentSources.botId, botId),
|
||||
eq(botContentSources.isActive, true)
|
||||
),
|
||||
where: { AND: [{ botId: botId }, { isActive: true }] },
|
||||
orderBy: (sources, { desc }) => [desc(sources.createdAt)],
|
||||
});
|
||||
|
||||
@@ -728,7 +725,7 @@ export async function updateSource(
|
||||
): Promise<ContentSource> {
|
||||
// Check if source exists
|
||||
const existingSource = await db.query.botContentSources.findFirst({
|
||||
where: eq(botContentSources.id, sourceId),
|
||||
where: { id: sourceId },
|
||||
});
|
||||
|
||||
if (!existingSource) {
|
||||
@@ -811,10 +808,7 @@ export async function deactivateSource(sourceId: string): Promise<void> {
|
||||
*/
|
||||
export async function botOwnsSource(botId: string, sourceId: string): Promise<boolean> {
|
||||
const source = await db.query.botContentSources.findFirst({
|
||||
where: and(
|
||||
eq(botContentSources.id, sourceId),
|
||||
eq(botContentSources.botId, botId)
|
||||
),
|
||||
where: { AND: [{ id: sourceId }, { botId: botId }] },
|
||||
columns: { id: true },
|
||||
});
|
||||
|
||||
@@ -829,7 +823,7 @@ export async function botOwnsSource(botId: string, sourceId: string): Promise<bo
|
||||
*/
|
||||
export async function getSourceCountForBot(botId: string): Promise<number> {
|
||||
const sources = await db.query.botContentSources.findMany({
|
||||
where: eq(botContentSources.botId, botId),
|
||||
where: { botId: botId },
|
||||
columns: { id: true },
|
||||
});
|
||||
|
||||
@@ -850,10 +844,7 @@ export async function getSourcesByType(
|
||||
type: ContentSourceType
|
||||
): Promise<ContentSource[]> {
|
||||
const sources = await db.query.botContentSources.findMany({
|
||||
where: and(
|
||||
eq(botContentSources.botId, botId),
|
||||
eq(botContentSources.type, type)
|
||||
),
|
||||
where: { AND: [{ botId: botId }, { type: type }] },
|
||||
orderBy: (sources, { desc }) => [desc(sources.createdAt)],
|
||||
});
|
||||
|
||||
|
||||
@@ -106,7 +106,7 @@ export async function detectMentions(botId: string): Promise<MentionDetectionRes
|
||||
try {
|
||||
// Get the bot's handle
|
||||
const bot = await db.query.bots.findFirst({
|
||||
where: eq(bots.id, botId),
|
||||
where: { id: botId },
|
||||
with: {
|
||||
user: {
|
||||
columns: { handle: true },
|
||||
@@ -124,7 +124,7 @@ export async function detectMentions(botId: string): Promise<MentionDetectionRes
|
||||
|
||||
// Get existing mention post IDs to avoid duplicates
|
||||
const existingMentions = await db.query.botMentions.findMany({
|
||||
where: eq(botMentions.botId, botId),
|
||||
where: { botId: botId },
|
||||
columns: { postId: true },
|
||||
});
|
||||
|
||||
@@ -140,9 +140,7 @@ export async function detectMentions(botId: string): Promise<MentionDetectionRes
|
||||
oneDayAgo.setDate(oneDayAgo.getDate() - 1);
|
||||
|
||||
const recentPosts = await db.query.posts.findMany({
|
||||
where: and(
|
||||
eq(posts.isRemoved, false)
|
||||
),
|
||||
where: { AND: [{ isRemoved: false }] },
|
||||
with: {
|
||||
author: {
|
||||
columns: {
|
||||
@@ -152,7 +150,7 @@ export async function detectMentions(botId: string): Promise<MentionDetectionRes
|
||||
},
|
||||
},
|
||||
},
|
||||
orderBy: [desc(posts.createdAt)],
|
||||
orderBy: () => [desc(posts.createdAt)],
|
||||
limit: 1000, // Reasonable limit for scanning
|
||||
});
|
||||
|
||||
@@ -218,11 +216,8 @@ export async function detectMentions(botId: string): Promise<MentionDetectionRes
|
||||
export async function getUnprocessedMentions(botId: string): Promise<Mention[]> {
|
||||
try {
|
||||
const mentions = await db.query.botMentions.findMany({
|
||||
where: and(
|
||||
eq(botMentions.botId, botId),
|
||||
eq(botMentions.isProcessed, false)
|
||||
),
|
||||
orderBy: [asc(botMentions.createdAt)], // Chronological order (oldest first)
|
||||
where: { AND: [{ botId: botId }, { isProcessed: false }] },
|
||||
orderBy: () => [asc(botMentions.createdAt)], // Chronological order (oldest first)
|
||||
});
|
||||
|
||||
return mentions.map(m => ({
|
||||
@@ -256,8 +251,8 @@ export async function getUnprocessedMentions(botId: string): Promise<Mention[]>
|
||||
export async function getAllMentions(botId: string): Promise<Mention[]> {
|
||||
try {
|
||||
const mentions = await db.query.botMentions.findMany({
|
||||
where: eq(botMentions.botId, botId),
|
||||
orderBy: [desc(botMentions.createdAt)],
|
||||
where: { botId: botId },
|
||||
orderBy: () => [desc(botMentions.createdAt)],
|
||||
});
|
||||
|
||||
return mentions.map(m => ({
|
||||
@@ -307,7 +302,7 @@ export async function getConversationContext(
|
||||
|
||||
while (currentPostId && depth < maxDepth) {
|
||||
const post: any = await db.query.posts.findFirst({
|
||||
where: eq(posts.id, currentPostId),
|
||||
where: { id: currentPostId },
|
||||
with: {
|
||||
author: {
|
||||
columns: {
|
||||
@@ -365,7 +360,7 @@ export async function processMention(mentionId: string): Promise<MentionResponse
|
||||
try {
|
||||
// Get the mention
|
||||
const mention = await db.query.botMentions.findFirst({
|
||||
where: eq(botMentions.id, mentionId),
|
||||
where: { id: mentionId },
|
||||
});
|
||||
|
||||
if (!mention) {
|
||||
@@ -394,7 +389,7 @@ export async function processMention(mentionId: string): Promise<MentionResponse
|
||||
|
||||
// Get the bot
|
||||
const bot = await db.query.bots.findFirst({
|
||||
where: eq(bots.id, mention.botId),
|
||||
where: { id: mention.botId },
|
||||
with: {
|
||||
user: {
|
||||
columns: {
|
||||
@@ -414,7 +409,7 @@ export async function processMention(mentionId: string): Promise<MentionResponse
|
||||
|
||||
// Get the mentioning post with author info
|
||||
const mentionPost = await db.query.posts.findFirst({
|
||||
where: eq(posts.id, mention.postId),
|
||||
where: { id: mention.postId },
|
||||
with: {
|
||||
author: {
|
||||
columns: {
|
||||
@@ -607,7 +602,7 @@ export async function storeMention(data: {
|
||||
export async function getMentionById(mentionId: string): Promise<Mention | null> {
|
||||
try {
|
||||
const mention = await db.query.botMentions.findFirst({
|
||||
where: eq(botMentions.id, mentionId),
|
||||
where: { id: mentionId },
|
||||
});
|
||||
|
||||
if (!mention) {
|
||||
|
||||
@@ -335,7 +335,7 @@ export function deserializePersonalityConfig(json: string): PersonalityConfig {
|
||||
*/
|
||||
export async function getPersonalityConfig(botId: string): Promise<PersonalityConfig | null> {
|
||||
const bot = await db.query.bots.findFirst({
|
||||
where: eq(bots.id, botId),
|
||||
where: { id: botId },
|
||||
columns: {
|
||||
personalityConfig: true,
|
||||
},
|
||||
@@ -372,7 +372,7 @@ export async function updatePersonalityConfig(
|
||||
|
||||
// Check if bot exists
|
||||
const existingBot = await db.query.bots.findFirst({
|
||||
where: eq(bots.id, botId),
|
||||
where: { id: botId },
|
||||
columns: { id: true },
|
||||
});
|
||||
|
||||
|
||||
+20
-26
@@ -169,7 +169,7 @@ function getDecryptedApiKeyForBot(bot: typeof bots.$inferSelect): string {
|
||||
*/
|
||||
async function getContentItemById(contentItemId: string): Promise<ContentItem | null> {
|
||||
const item = await db.query.botContentItems.findFirst({
|
||||
where: eq(botContentItems.id, contentItemId),
|
||||
where: { id: contentItemId },
|
||||
});
|
||||
|
||||
if (!item) {
|
||||
@@ -197,10 +197,10 @@ async function getContentItemById(contentItemId: string): Promise<ContentItem |
|
||||
async function getNextUnprocessedContentItem(botId: string): Promise<ContentItem | null> {
|
||||
// Get bot's content sources
|
||||
const bot = await db.query.bots.findFirst({
|
||||
where: eq(bots.id, botId),
|
||||
where: { id: botId },
|
||||
with: {
|
||||
contentSources: {
|
||||
where: (sources, { eq }) => eq(sources.isActive, true),
|
||||
where: { isActive: true },
|
||||
},
|
||||
user: true,
|
||||
},
|
||||
@@ -214,7 +214,7 @@ async function getNextUnprocessedContentItem(botId: string): Promise<ContentItem
|
||||
|
||||
// Get ALL posts by this bot to avoid duplicates (no time limit)
|
||||
const allBotPosts = await db.query.posts.findMany({
|
||||
where: eq(posts.userId, bot.userId),
|
||||
where: { userId: bot.userId },
|
||||
columns: {
|
||||
linkPreviewUrl: true,
|
||||
},
|
||||
@@ -230,10 +230,7 @@ async function getNextUnprocessedContentItem(botId: string): Promise<ContentItem
|
||||
|
||||
// Get unprocessed content items from this bot's sources
|
||||
const items = await db.query.botContentItems.findMany({
|
||||
where: and(
|
||||
eq(botContentItems.isProcessed, false),
|
||||
inArray(botContentItems.sourceId, sourceIds)
|
||||
),
|
||||
where: { AND: [{ isProcessed: false }, { sourceId: { in: sourceIds } }] },
|
||||
orderBy: (items, { asc }) => [asc(items.publishedAt)],
|
||||
limit: 100, // Get more items to have options after filtering
|
||||
});
|
||||
@@ -278,7 +275,7 @@ async function getNextUnprocessedContentItem(botId: string): Promise<ContentItem
|
||||
async function getBotPreviousPosts(botId: string, limit: number = 40): Promise<string[]> {
|
||||
// Get bot to find its user ID
|
||||
const bot = await db.query.bots.findFirst({
|
||||
where: eq(bots.id, botId),
|
||||
where: { id: botId },
|
||||
});
|
||||
|
||||
if (!bot) {
|
||||
@@ -287,7 +284,7 @@ async function getBotPreviousPosts(botId: string, limit: number = 40): Promise<s
|
||||
|
||||
// Get the bot's recent posts
|
||||
const recentPosts = await db.query.posts.findMany({
|
||||
where: eq(posts.userId, bot.userId),
|
||||
where: { userId: bot.userId },
|
||||
orderBy: (posts, { desc }) => [desc(posts.createdAt)],
|
||||
limit,
|
||||
columns: {
|
||||
@@ -512,7 +509,7 @@ export async function selectContentForPosting(
|
||||
|
||||
// Verify the content belongs to this bot's sources
|
||||
const bot = await db.query.bots.findFirst({
|
||||
where: eq(bots.id, botId),
|
||||
where: { id: botId },
|
||||
with: {
|
||||
contentSources: true,
|
||||
user: true,
|
||||
@@ -691,7 +688,7 @@ async function resolveContentItemSourceUrl(contentItem?: ContentItem | null): Pr
|
||||
}
|
||||
|
||||
const source = await db.query.botContentSources.findFirst({
|
||||
where: eq(botContentSources.id, contentItem.sourceId),
|
||||
where: { id: contentItem.sourceId },
|
||||
columns: {
|
||||
type: true,
|
||||
subreddit: true,
|
||||
@@ -726,7 +723,7 @@ async function createPostInDatabase(
|
||||
): Promise<typeof posts.$inferSelect> {
|
||||
// Get bot config
|
||||
const bot = await db.query.bots.findFirst({
|
||||
where: eq(bots.id, botId),
|
||||
where: { id: botId },
|
||||
});
|
||||
|
||||
if (!bot) {
|
||||
@@ -738,7 +735,7 @@ async function createPostInDatabase(
|
||||
|
||||
// Get the bot's own user account (not the owner)
|
||||
const botUser = await db.query.users.findFirst({
|
||||
where: eq(users.id, bot.userId),
|
||||
where: { id: bot.userId },
|
||||
});
|
||||
|
||||
if (!botUser) {
|
||||
@@ -755,7 +752,7 @@ async function createPostInDatabase(
|
||||
linkPreview = await fetchLinkPreview(sourceUrl);
|
||||
}
|
||||
|
||||
const nodeDomain = process.env.NEXT_PUBLIC_NODE_DOMAIN || 'localhost:3000';
|
||||
const nodeDomain = process.env.NEXT_PUBLIC_NODE_DOMAIN || 'localhost:43821';
|
||||
const postUuid = crypto.randomUUID();
|
||||
|
||||
try {
|
||||
@@ -807,7 +804,7 @@ async function federatePost(
|
||||
try {
|
||||
// Get bot config
|
||||
const bot = await db.query.bots.findFirst({
|
||||
where: eq(bots.id, botId),
|
||||
where: { id: botId },
|
||||
});
|
||||
|
||||
if (!bot) {
|
||||
@@ -817,7 +814,7 @@ async function federatePost(
|
||||
|
||||
// Get the bot's own user account
|
||||
const botUser = await db.query.users.findFirst({
|
||||
where: eq(users.id, bot.userId),
|
||||
where: { id: bot.userId },
|
||||
});
|
||||
|
||||
if (!botUser) {
|
||||
@@ -825,7 +822,7 @@ async function federatePost(
|
||||
return;
|
||||
}
|
||||
|
||||
const nodeDomain = process.env.NEXT_PUBLIC_NODE_DOMAIN || 'localhost:3000';
|
||||
const nodeDomain = process.env.NEXT_PUBLIC_NODE_DOMAIN || 'localhost:43821';
|
||||
|
||||
// Use swarm delivery for bot posts
|
||||
const { deliverPostToSwarmFollowers } = await import('@/lib/swarm/interactions');
|
||||
@@ -993,7 +990,7 @@ export async function triggerPost(
|
||||
|
||||
// Get bot from database for generation
|
||||
const dbBot = await db.query.bots.findFirst({
|
||||
where: eq(bots.id, botId),
|
||||
where: { id: botId },
|
||||
with: { user: true }, // Required for generatePostContent
|
||||
});
|
||||
|
||||
@@ -1142,7 +1139,7 @@ export async function getPostingStats(botId: string): Promise<{
|
||||
}> {
|
||||
// Get bot
|
||||
const bot = await db.query.bots.findFirst({
|
||||
where: eq(bots.id, botId),
|
||||
where: { id: botId },
|
||||
with: {
|
||||
contentSources: true,
|
||||
},
|
||||
@@ -1160,7 +1157,7 @@ export async function getPostingStats(botId: string): Promise<{
|
||||
|
||||
// Get user's post count
|
||||
const user = await db.query.users.findFirst({
|
||||
where: eq(users.id, bot.userId),
|
||||
where: { id: bot.userId },
|
||||
});
|
||||
|
||||
const totalPosts = user?.postsCount || 0;
|
||||
@@ -1170,10 +1167,7 @@ export async function getPostingStats(botId: string): Promise<{
|
||||
today.setUTCHours(0, 0, 0, 0);
|
||||
|
||||
const postsToday = await db.query.posts.findMany({
|
||||
where: and(
|
||||
eq(posts.userId, bot.userId),
|
||||
// Note: This is a simplified query. In production, you'd want to use a proper date comparison
|
||||
),
|
||||
where: { AND: [{ userId: bot.userId }] },
|
||||
});
|
||||
|
||||
// Get content item stats
|
||||
@@ -1184,7 +1178,7 @@ export async function getPostingStats(botId: string): Promise<{
|
||||
|
||||
if (sourceIds.length > 0) {
|
||||
const allItems = await db.query.botContentItems.findMany({
|
||||
where: (items, { inArray }) => inArray(items.sourceId, sourceIds),
|
||||
where: { sourceId: { in: sourceIds } },
|
||||
});
|
||||
|
||||
contentItemsProcessed = allItems.filter(item => item.isProcessed).length;
|
||||
|
||||
@@ -129,11 +129,7 @@ async function getOrCreateWindow(
|
||||
): Promise<typeof botRateLimits.$inferSelect> {
|
||||
// Try to find existing window
|
||||
const existing = await db.query.botRateLimits.findFirst({
|
||||
where: and(
|
||||
eq(botRateLimits.botId, botId),
|
||||
eq(botRateLimits.windowType, windowType),
|
||||
eq(botRateLimits.windowStart, windowStart)
|
||||
),
|
||||
where: { AND: [{ botId }, { windowType }, { windowStart: { eq: windowStart } }] },
|
||||
});
|
||||
|
||||
if (existing) {
|
||||
@@ -158,11 +154,7 @@ async function getOrCreateWindow(
|
||||
async function getDailyPostCount(botId: string): Promise<number> {
|
||||
const windowStart = getDailyWindowStart();
|
||||
const window = await db.query.botRateLimits.findFirst({
|
||||
where: and(
|
||||
eq(botRateLimits.botId, botId),
|
||||
eq(botRateLimits.windowType, 'daily'),
|
||||
eq(botRateLimits.windowStart, windowStart)
|
||||
),
|
||||
where: { AND: [{ botId }, { windowType: 'daily' }, { windowStart: { eq: windowStart } }] },
|
||||
});
|
||||
|
||||
return window?.postCount ?? 0;
|
||||
@@ -174,11 +166,7 @@ async function getDailyPostCount(botId: string): Promise<number> {
|
||||
async function getHourlyReplyCount(botId: string): Promise<number> {
|
||||
const windowStart = getHourlyWindowStart();
|
||||
const window = await db.query.botRateLimits.findFirst({
|
||||
where: and(
|
||||
eq(botRateLimits.botId, botId),
|
||||
eq(botRateLimits.windowType, 'hourly'),
|
||||
eq(botRateLimits.windowStart, windowStart)
|
||||
),
|
||||
where: { AND: [{ botId }, { windowType: 'hourly' }, { windowStart: { eq: windowStart } }] },
|
||||
});
|
||||
|
||||
return window?.replyCount ?? 0;
|
||||
@@ -189,7 +177,7 @@ async function getHourlyReplyCount(botId: string): Promise<number> {
|
||||
*/
|
||||
async function getLastPostAt(botId: string): Promise<Date | null> {
|
||||
const bot = await db.query.bots.findFirst({
|
||||
where: eq(bots.id, botId),
|
||||
where: { id: botId },
|
||||
columns: { lastPostAt: true },
|
||||
});
|
||||
|
||||
@@ -368,11 +356,7 @@ export async function getPostCount(botId: string, windowHours: number): Promise<
|
||||
|
||||
// Get all daily windows that overlap with the requested time range
|
||||
const windows = await db.query.botRateLimits.findMany({
|
||||
where: and(
|
||||
eq(botRateLimits.botId, botId),
|
||||
eq(botRateLimits.windowType, 'daily'),
|
||||
gte(botRateLimits.windowStart, getDailyWindowStart(windowStart))
|
||||
),
|
||||
where: { AND: [{ botId: botId }, { windowType: 'daily' }, { windowStart: { gte: getDailyWindowStart(windowStart) } }] },
|
||||
});
|
||||
|
||||
return windows.reduce((sum, w) => sum + w.postCount, 0);
|
||||
|
||||
@@ -679,10 +679,7 @@ export function isDue(
|
||||
export async function hasUnprocessedContent(botId: string): Promise<boolean> {
|
||||
// Get all content sources for the bot
|
||||
const sources = await db.query.botContentSources.findMany({
|
||||
where: and(
|
||||
eq(botContentSources.botId, botId),
|
||||
eq(botContentSources.isActive, true)
|
||||
),
|
||||
where: { AND: [{ botId: botId }, { isActive: true }] },
|
||||
columns: { id: true },
|
||||
});
|
||||
|
||||
@@ -693,10 +690,7 @@ export async function hasUnprocessedContent(botId: string): Promise<boolean> {
|
||||
// Check if any source has unprocessed content
|
||||
for (const source of sources) {
|
||||
const unprocessedItem = await db.query.botContentItems.findFirst({
|
||||
where: and(
|
||||
eq(botContentItems.sourceId, source.id),
|
||||
eq(botContentItems.isProcessed, false)
|
||||
),
|
||||
where: { AND: [{ sourceId: source.id }, { isProcessed: false }] },
|
||||
columns: { id: true },
|
||||
});
|
||||
|
||||
@@ -724,10 +718,7 @@ export async function getNextUnprocessedContent(botId: string): Promise<{
|
||||
} | null> {
|
||||
// Get all content sources for the bot
|
||||
const sources = await db.query.botContentSources.findMany({
|
||||
where: and(
|
||||
eq(botContentSources.botId, botId),
|
||||
eq(botContentSources.isActive, true)
|
||||
),
|
||||
where: { AND: [{ botId: botId }, { isActive: true }] },
|
||||
columns: { id: true },
|
||||
});
|
||||
|
||||
@@ -740,10 +731,7 @@ export async function getNextUnprocessedContent(botId: string): Promise<{
|
||||
|
||||
for (const source of sources) {
|
||||
const item = await db.query.botContentItems.findFirst({
|
||||
where: and(
|
||||
eq(botContentItems.sourceId, source.id),
|
||||
eq(botContentItems.isProcessed, false)
|
||||
),
|
||||
where: { AND: [{ sourceId: source.id }, { isProcessed: false }] },
|
||||
orderBy: (items, { asc }) => [asc(items.publishedAt)],
|
||||
});
|
||||
|
||||
@@ -789,10 +777,7 @@ export async function processScheduledPosts(): Promise<ProcessScheduledPostsResu
|
||||
|
||||
// Get all active bots with schedules
|
||||
const activeBots = await db.query.bots.findMany({
|
||||
where: and(
|
||||
eq(bots.isActive, true),
|
||||
eq(bots.isSuspended, false)
|
||||
),
|
||||
where: { AND: [{ isActive: true }, { isSuspended: false }] },
|
||||
});
|
||||
|
||||
for (const bot of activeBots) {
|
||||
@@ -933,10 +918,7 @@ export async function processScheduledPosts(): Promise<ProcessScheduledPostsResu
|
||||
*/
|
||||
async function botHasActiveSources(botId: string): Promise<boolean> {
|
||||
const sources = await db.query.botContentSources.findMany({
|
||||
where: and(
|
||||
eq(botContentSources.botId, botId),
|
||||
eq(botContentSources.isActive, true)
|
||||
),
|
||||
where: { AND: [{ botId: botId }, { isActive: true }] },
|
||||
columns: { id: true },
|
||||
});
|
||||
|
||||
@@ -951,7 +933,7 @@ async function botHasActiveSources(botId: string): Promise<boolean> {
|
||||
*/
|
||||
export async function getBotSchedule(botId: string): Promise<ScheduleConfig | null> {
|
||||
const bot = await db.query.bots.findFirst({
|
||||
where: eq(bots.id, botId),
|
||||
where: { id: botId },
|
||||
columns: { scheduleConfig: true },
|
||||
});
|
||||
|
||||
|
||||
@@ -69,7 +69,7 @@ export async function reinstateBot(botId: string) {
|
||||
*/
|
||||
export async function isBotSuspended(botId: string): Promise<boolean> {
|
||||
const bot = await db.query.bots.findFirst({
|
||||
where: eq(bots.id, botId),
|
||||
where: { id: botId },
|
||||
columns: { isSuspended: true },
|
||||
});
|
||||
|
||||
@@ -86,7 +86,7 @@ export async function isBotSuspended(botId: string): Promise<boolean> {
|
||||
*/
|
||||
export async function ensureBotNotSuspended(botId: string): Promise<void> {
|
||||
const bot = await db.query.bots.findFirst({
|
||||
where: eq(bots.id, botId),
|
||||
where: { id: botId },
|
||||
columns: { isSuspended: true, suspensionReason: true },
|
||||
});
|
||||
|
||||
|
||||
+3
-3
@@ -9,13 +9,13 @@ export async function getRuntimeConfig() {
|
||||
.then((res) => res.json())
|
||||
.then((data) => {
|
||||
cachedConfig = {
|
||||
domain: data.domain || 'localhost:3000',
|
||||
domain: data.domain || 'localhost:43821',
|
||||
};
|
||||
return cachedConfig;
|
||||
})
|
||||
.catch(() => {
|
||||
cachedConfig = {
|
||||
domain: process.env.NEXT_PUBLIC_NODE_DOMAIN || 'localhost:3000',
|
||||
domain: process.env.NEXT_PUBLIC_NODE_DOMAIN || 'localhost:43821',
|
||||
};
|
||||
return cachedConfig;
|
||||
});
|
||||
@@ -24,7 +24,7 @@ export async function getRuntimeConfig() {
|
||||
}
|
||||
|
||||
export function getDomain(): string {
|
||||
return cachedConfig?.domain || process.env.NEXT_PUBLIC_NODE_DOMAIN || 'localhost:3000';
|
||||
return cachedConfig?.domain || process.env.NEXT_PUBLIC_NODE_DOMAIN || 'localhost:43821';
|
||||
}
|
||||
|
||||
export function clearCachedConfig() {
|
||||
|
||||
@@ -26,13 +26,13 @@ export function ConfigProvider({ children }: { children: ReactNode }) {
|
||||
.then((res) => res.json())
|
||||
.then((data) => {
|
||||
setConfig({
|
||||
domain: data.domain || 'localhost:3000',
|
||||
domain: data.domain || 'localhost:43821',
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
// Fallback to build-time value if fetch fails
|
||||
setConfig({
|
||||
domain: process.env.NEXT_PUBLIC_NODE_DOMAIN || 'localhost:3000',
|
||||
domain: process.env.NEXT_PUBLIC_NODE_DOMAIN || 'localhost:43821',
|
||||
});
|
||||
})
|
||||
.finally(() => {
|
||||
@@ -59,7 +59,7 @@ export function useDomain(): string {
|
||||
const { config, isLoading } = useRuntimeConfig();
|
||||
// Return runtime domain if loaded, otherwise fall back to build-time value
|
||||
if (isLoading || !config) {
|
||||
return process.env.NEXT_PUBLIC_NODE_DOMAIN || 'localhost:3000';
|
||||
return process.env.NEXT_PUBLIC_NODE_DOMAIN || 'localhost:43821';
|
||||
}
|
||||
return config.domain;
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ export async function upsertHandleEntries(entries: HandleEntry[]) {
|
||||
}
|
||||
|
||||
const existing = await db.query.handleRegistry.findFirst({
|
||||
where: eq(handleRegistry.handle, cleanHandle),
|
||||
where: { handle: cleanHandle },
|
||||
});
|
||||
|
||||
// If no timestamp provided, treat it as "now" but be careful
|
||||
|
||||
@@ -1,129 +0,0 @@
|
||||
import http from 'http';
|
||||
|
||||
const DEFAULT_SOCKET_PATH = '/var/run/synapsis-updater/updater.sock';
|
||||
const REQUEST_TIMEOUT_MS = 4000;
|
||||
|
||||
export interface HostUpdaterStatus {
|
||||
available: boolean;
|
||||
status: 'unavailable' | 'idle' | 'updating' | 'success' | 'error';
|
||||
message?: string;
|
||||
lastStartedAt?: string | null;
|
||||
lastFinishedAt?: string | null;
|
||||
lastExitCode?: number | null;
|
||||
lastError?: string | null;
|
||||
pid?: number | null;
|
||||
trigger?: 'manual' | 'auto' | null;
|
||||
config?: {
|
||||
autoUpdateEnabled: boolean;
|
||||
intervalMinutes: number;
|
||||
};
|
||||
}
|
||||
|
||||
function getUpdaterConfig() {
|
||||
const socketPath = process.env.HOST_UPDATER_SOCKET || DEFAULT_SOCKET_PATH;
|
||||
const token = process.env.HOST_UPDATER_TOKEN || '';
|
||||
|
||||
return {
|
||||
socketPath,
|
||||
token,
|
||||
enabled: Boolean(socketPath && token),
|
||||
};
|
||||
}
|
||||
|
||||
function requestUpdater<T>(method: 'GET' | 'POST' | 'PATCH', path: string, body?: unknown): Promise<T> {
|
||||
const { socketPath, token, enabled } = getUpdaterConfig();
|
||||
|
||||
if (!enabled) {
|
||||
return Promise.reject(new Error('Host updater is not configured'));
|
||||
}
|
||||
|
||||
const payload = body ? JSON.stringify(body) : undefined;
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const request = http.request(
|
||||
{
|
||||
method,
|
||||
socketPath,
|
||||
path,
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
Accept: 'application/json',
|
||||
...(payload
|
||||
? {
|
||||
'Content-Type': 'application/json',
|
||||
'Content-Length': Buffer.byteLength(payload),
|
||||
}
|
||||
: {}),
|
||||
},
|
||||
},
|
||||
(response) => {
|
||||
const chunks: Buffer[] = [];
|
||||
|
||||
response.on('data', (chunk) => chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)));
|
||||
response.on('end', () => {
|
||||
const text = Buffer.concat(chunks).toString('utf8');
|
||||
const data = text ? JSON.parse(text) : {};
|
||||
|
||||
if ((response.statusCode || 500) >= 400) {
|
||||
const message = data.error || `Updater request failed with ${response.statusCode}`;
|
||||
reject(new Error(message));
|
||||
return;
|
||||
}
|
||||
|
||||
resolve(data as T);
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
request.setTimeout(REQUEST_TIMEOUT_MS, () => {
|
||||
request.destroy(new Error('Host updater request timed out'));
|
||||
});
|
||||
|
||||
request.on('error', (error) => {
|
||||
reject(error);
|
||||
});
|
||||
|
||||
if (payload) {
|
||||
request.write(payload);
|
||||
}
|
||||
|
||||
request.end();
|
||||
});
|
||||
}
|
||||
|
||||
export async function getHostUpdaterStatus(): Promise<HostUpdaterStatus> {
|
||||
const config = getUpdaterConfig();
|
||||
if (!config.enabled) {
|
||||
return {
|
||||
available: false,
|
||||
status: 'unavailable',
|
||||
message: 'Host updater is not configured for this install.',
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const status = await requestUpdater<HostUpdaterStatus>('GET', '/status');
|
||||
return {
|
||||
...status,
|
||||
available: true,
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
available: false,
|
||||
status: 'unavailable',
|
||||
message: error instanceof Error ? error.message : 'Host updater is unavailable.',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export async function triggerHostUpdate() {
|
||||
return requestUpdater<{ ok: boolean; status: string; message?: string }>('POST', '/update');
|
||||
}
|
||||
|
||||
export async function updateHostUpdaterConfig(autoUpdateEnabled: boolean) {
|
||||
return requestUpdater<{ ok: boolean; config: { autoUpdateEnabled: boolean; intervalMinutes: number } }>(
|
||||
'PATCH',
|
||||
'/config',
|
||||
{ autoUpdateEnabled }
|
||||
);
|
||||
}
|
||||
@@ -22,7 +22,7 @@ function normalizeOptionalUrl(value: string | null | undefined): string | undefi
|
||||
* Build this node's announcement payload
|
||||
*/
|
||||
export async function buildAnnouncement(): Promise<SwarmAnnouncement> {
|
||||
const domain = process.env.NEXT_PUBLIC_NODE_DOMAIN || 'localhost:3000';
|
||||
const domain = process.env.NEXT_PUBLIC_NODE_DOMAIN || 'localhost:43821';
|
||||
|
||||
let name = 'Synapsis Node';
|
||||
let description: string | undefined;
|
||||
@@ -35,7 +35,7 @@ export async function buildAnnouncement(): Promise<SwarmAnnouncement> {
|
||||
if (db) {
|
||||
// Get node info
|
||||
const node = await db.query.nodes.findFirst({
|
||||
where: eq(nodes.domain, domain),
|
||||
where: { domain: domain },
|
||||
});
|
||||
|
||||
if (node) {
|
||||
|
||||
@@ -25,7 +25,7 @@ import { buildAnnouncement } from './discovery';
|
||||
* Build a gossip payload to send to another node
|
||||
*/
|
||||
export async function buildGossipPayload(since?: string): Promise<SwarmGossipPayload> {
|
||||
const ourDomain = process.env.NEXT_PUBLIC_NODE_DOMAIN || 'localhost:3000';
|
||||
const ourDomain = process.env.NEXT_PUBLIC_NODE_DOMAIN || 'localhost:43821';
|
||||
|
||||
// Get nodes to share
|
||||
let nodes: SwarmNodeInfo[];
|
||||
@@ -56,8 +56,8 @@ export async function buildGossipPayload(since?: string): Promise<SwarmGossipPay
|
||||
if (db) {
|
||||
const sinceDate = since ? new Date(since) : undefined;
|
||||
const handleEntries = await db.query.handleRegistry.findMany({
|
||||
where: sinceDate ? gt(handleRegistry.updatedAt, sinceDate) : undefined,
|
||||
orderBy: [desc(handleRegistry.updatedAt)],
|
||||
where: sinceDate ? { updatedAt: { gt: sinceDate } } : undefined,
|
||||
orderBy: () => [desc(handleRegistry.updatedAt)],
|
||||
limit: SWARM_CONFIG.maxHandlesPerGossip,
|
||||
});
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ interface IdentityCacheEntry {
|
||||
*/
|
||||
export async function getCachedIdentity(did: string): Promise<IdentityCacheEntry | null> {
|
||||
const cached = await db.query.remoteIdentityCache.findFirst({
|
||||
where: eq(remoteIdentityCache.did, did),
|
||||
where: { did: did },
|
||||
});
|
||||
|
||||
return cached || null;
|
||||
|
||||
@@ -465,7 +465,7 @@ export async function cacheSwarmUserPosts(
|
||||
|
||||
// Check if we already have this post
|
||||
const existing = await db.query.remotePosts.findFirst({
|
||||
where: eq(remotePosts.apId, apId),
|
||||
where: { apId: apId },
|
||||
});
|
||||
|
||||
if (existing) {
|
||||
@@ -673,7 +673,7 @@ export async function getSwarmFollowerDomains(userId: string): Promise<string[]>
|
||||
if (!db) return [];
|
||||
|
||||
const followers = await db.query.remoteFollowers.findMany({
|
||||
where: eq(remoteFollowers.userId, userId),
|
||||
where: { userId: userId },
|
||||
});
|
||||
|
||||
// Filter for swarm followers (actorUrl starts with swarm://)
|
||||
|
||||
@@ -17,7 +17,7 @@ export async function isNodeBlocked(domain: string | null | undefined): Promise<
|
||||
if (!normalized) return false;
|
||||
|
||||
const node = await db.query.swarmNodes.findFirst({
|
||||
where: eq(swarmNodes.domain, normalized),
|
||||
where: { domain: normalized },
|
||||
columns: {
|
||||
isBlocked: true,
|
||||
},
|
||||
@@ -30,7 +30,7 @@ export async function getBlockedNodeDomains(): Promise<Set<string>> {
|
||||
if (!db) return new Set();
|
||||
|
||||
const rows = await db.query.swarmNodes.findMany({
|
||||
where: eq(swarmNodes.isBlocked, true),
|
||||
where: { isBlocked: true },
|
||||
columns: {
|
||||
domain: true,
|
||||
},
|
||||
@@ -46,10 +46,7 @@ export async function filterBlockedDomains(domains: string[]): Promise<string[]>
|
||||
if (normalized.length === 0) return [];
|
||||
|
||||
const blocked = await db.query.swarmNodes.findMany({
|
||||
where: and(
|
||||
inArray(swarmNodes.domain, normalized),
|
||||
eq(swarmNodes.isBlocked, true),
|
||||
),
|
||||
where: { AND: [{ domain: { in: normalized } }, { isBlocked: true }] },
|
||||
columns: {
|
||||
domain: true,
|
||||
},
|
||||
@@ -66,7 +63,7 @@ export async function upsertBlockedNode(domain: string, reason?: string | null)
|
||||
if (!normalized) return null;
|
||||
|
||||
const existing = await db.query.swarmNodes.findFirst({
|
||||
where: eq(swarmNodes.domain, normalized),
|
||||
where: { domain: normalized },
|
||||
});
|
||||
|
||||
if (existing) {
|
||||
@@ -105,7 +102,7 @@ export async function unblockNode(domain: string) {
|
||||
if (!normalized) return null;
|
||||
|
||||
const existing = await db.query.swarmNodes.findFirst({
|
||||
where: eq(swarmNodes.domain, normalized),
|
||||
where: { domain: normalized },
|
||||
});
|
||||
|
||||
if (!existing) return null;
|
||||
|
||||
@@ -68,11 +68,11 @@ export async function getNodeKeypair(): Promise<{ privateKey: string; publicKey:
|
||||
throw new Error('Database not available');
|
||||
}
|
||||
|
||||
const domain = process.env.NEXT_PUBLIC_NODE_DOMAIN || 'localhost:3000';
|
||||
const domain = process.env.NEXT_PUBLIC_NODE_DOMAIN || 'localhost:43821';
|
||||
|
||||
// Try to get existing node
|
||||
let node = await db.query.nodes.findFirst({
|
||||
where: eq(nodes.domain, domain),
|
||||
where: { domain: domain },
|
||||
});
|
||||
|
||||
// If node doesn't exist, create it
|
||||
@@ -118,9 +118,9 @@ export async function getNodeKeypair(): Promise<{ privateKey: string; publicKey:
|
||||
export async function getNodePublicKey(): Promise<string | null> {
|
||||
if (!db) return null;
|
||||
|
||||
const domain = process.env.NEXT_PUBLIC_NODE_DOMAIN || 'localhost:3000';
|
||||
const domain = process.env.NEXT_PUBLIC_NODE_DOMAIN || 'localhost:43821';
|
||||
const node = await db.query.nodes.findFirst({
|
||||
where: eq(nodes.domain, domain),
|
||||
where: { domain: domain },
|
||||
});
|
||||
|
||||
if (!node?.publicKey) {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user