Add bulk select and archive, improve performance

- Add database indexes on isArchived, isFavorite, createdAt columns
- Optimize article list API to exclude content/textContent fields
- Add PATCH /api/articles endpoint for bulk updates
- Implement multi-select mode with Select/Deselect all
- Add bulk archive/unarchive buttons
- Rename "All Articles" to "To Read"
- Fetch full article content only when opening for reading

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Gemini Agent
2026-01-19 13:27:13 +00:00
parent 883b0d7132
commit 911b749d3c
3 changed files with 299 additions and 81 deletions

View File

@@ -2,15 +2,37 @@ import { NextRequest, NextResponse } from "next/server";
import { db, schema } from "@/lib/db";
import { extractArticle } from "@/lib/utils/extract";
import { v4 as uuidv4 } from "uuid";
import { desc, eq } from "drizzle-orm";
import { desc, eq, inArray } from "drizzle-orm";
// GET /api/articles - List all articles
// GET /api/articles - List all articles (optimized - excludes content fields)
export async function GET(request: NextRequest) {
try {
const searchParams = request.nextUrl.searchParams;
const filter = searchParams.get("filter"); // all, favorites, archived
let query = db.select().from(schema.articles);
// Select only fields needed for list view (exclude large content/textContent)
const listFields = {
id: schema.articles.id,
url: schema.articles.url,
title: schema.articles.title,
author: schema.articles.author,
siteName: schema.articles.siteName,
excerpt: schema.articles.excerpt,
leadImage: schema.articles.leadImage,
wordCount: schema.articles.wordCount,
readingProgress: schema.articles.readingProgress,
readingTimeSeconds: schema.articles.readingTimeSeconds,
isFavorite: schema.articles.isFavorite,
isArchived: schema.articles.isArchived,
folderId: schema.articles.folderId,
tags: schema.articles.tags,
createdAt: schema.articles.createdAt,
updatedAt: schema.articles.updatedAt,
readAt: schema.articles.readAt,
finishedAt: schema.articles.finishedAt,
};
let query = db.select(listFields).from(schema.articles);
if (filter === "favorites") {
query = query.where(eq(schema.articles.isFavorite, true)) as typeof query;
@@ -33,6 +55,49 @@ export async function GET(request: NextRequest) {
}
}
// PATCH /api/articles - Bulk update articles
export async function PATCH(request: NextRequest) {
try {
const body = await request.json();
const { ids, updates } = body;
if (!ids || !Array.isArray(ids) || ids.length === 0) {
return NextResponse.json({ error: "Article IDs required" }, { status: 400 });
}
// Only allow certain fields to be bulk updated
const allowedUpdates: Partial<typeof schema.articles.$inferInsert> = {};
if (typeof updates.isArchived === "boolean") {
allowedUpdates.isArchived = updates.isArchived;
}
if (typeof updates.isFavorite === "boolean") {
allowedUpdates.isFavorite = updates.isFavorite;
}
if (updates.folderId !== undefined) {
allowedUpdates.folderId = updates.folderId;
}
if (Object.keys(allowedUpdates).length === 0) {
return NextResponse.json({ error: "No valid updates provided" }, { status: 400 });
}
allowedUpdates.updatedAt = new Date();
await db
.update(schema.articles)
.set(allowedUpdates)
.where(inArray(schema.articles.id, ids));
return NextResponse.json({ success: true, updated: ids.length });
} catch (error) {
console.error("Error bulk updating articles:", error);
return NextResponse.json(
{ error: "Failed to update articles" },
{ status: 500 }
);
}
}
// POST /api/articles - Save a new article
export async function POST(request: NextRequest) {
try {