← back to kyle94024__Science-Simplified

Function bodies 169 total

All specs Real LLM only Function bodies
middleware function · javascript · L15-L74 (60 LOC)
middleware.js
export async function middleware(req) {
  const { pathname } = req.nextUrl;

  // 1) Public pages
  if (
    pathname === "/" ||
    pathname.startsWith("/login") ||
    pathname.startsWith("/signup")
  ) {
    return NextResponse.next();
  }

  // 2) Must be authenticated (for everything else)
  const token = req.cookies.get("auth")?.value;

  if (!token) {
    // APIs → JSON
    if (pathname.startsWith("/api")) {
      return NextResponse.json({ message: "Not authenticated" }, { status: 401 });
    }

    // Pages → redirect
    return NextResponse.redirect(new URL("/login", req.url));
  }

  let decoded;
  try {
    decoded = verify(token, process.env.JWT_SECRET || "your-secret-key");
    req.user = decoded;
  } catch {
    if (pathname.startsWith("/api")) {
      return NextResponse.json({ message: "Invalid token" }, { status: 401 });
    }
    return NextResponse.redirect(new URL("/login", req.url));
  }

  /** -----------------------------------------------------
   * 3) Admin-ON
generateShortTitle function · javascript · L3-L23 (21 LOC)
scripts/generateShortTitles.js
function generateShortTitle(summary) {
  if (!summary) return null;

  // Simple, predictable cleanup
  const stopWords = new Set([
    "this", "clinical", "trial", "study", "focused",
    "investigating", "evaluating", "designed", "aims",
    "to", "the", "of", "for", "and", "is"
  ]);

  const words = summary
    .replace(/[^\w\s]/g, "")
    .toLowerCase()
    .split(" ")
    .filter(w => w.length > 2 && !stopWords.has(w));

  const title = words.slice(0, 4).join(" ");
  return title
    ? title.replace(/\b\w/g, c => c.toUpperCase())
    : null;
}
run function · javascript · L25-L48 (24 LOC)
scripts/generateShortTitles.js
async function run() {
  const trials = await sql`
    SELECT id, ai_summary
    FROM clinical_trials
    WHERE short_title IS NULL
      AND ai_summary IS NOT NULL
    LIMIT 100
  `;

  for (const trial of trials) {
    const shortTitle = generateShortTitle(trial.ai_summary);

    if (!shortTitle) continue;

    await sql`
      UPDATE clinical_trials
      SET short_title = ${shortTitle}
      WHERE id = ${trial.id}
    `;
  }

  console.log("✅ Short titles generated");
  process.exit(0);
}
AdminTrialEditPage function · javascript · L36-L187 (152 LOC)
src/app/(admin)/admin/clinical-trials/[nctId]/page.jsx
export default function AdminTrialEditPage() {
  const { nctId } = useParams();
  const searchParams = useSearchParams();
  const tenant = searchParams.get("tenant");

  const [trial, setTrial] = useState(null);
  const [original, setOriginal] = useState(null);
  const [saving, setSaving] = useState(false);

  /*LOAD TRIAL*/
  useEffect(() => {
    if (!tenant) return;

    fetch(`/api/admin/clinical-trials/${nctId}?tenant=${tenant}`, {
      cache: "no-store",
    })
      .then(async (res) => {
        const text = await res.text();
        if (!res.ok) throw new Error(text || "Failed to load trial");
        return JSON.parse(text);
      })
      .then((data) => {
        setTrial(data.trial);
        setOriginal(data.trial); // ← store original values
      })
      .catch((err) => {
        console.error(err);
        alert("Failed to load trial");
      });
  }, [nctId, tenant]);

  if (!trial) return <p className="page-loading">Loading…</p>;

  /*SAVE*/
  async function save() 
save function · javascript · L70-L100 (31 LOC)
src/app/(admin)/admin/clinical-trials/[nctId]/page.jsx
  async function save() {
    setSaving(true);

    const payload = {};

    payload.short_title_manual = trial.short_title_manual ?? null;
    payload.ai_summary_manual = trial.ai_summary_manual ?? null;

    QUESTION_FIELDS.forEach(({ key }) => {
      payload[key] = trial[key] ?? null;
    });

    const res = await fetch(
      `/api/admin/clinical-trials/${nctId}?tenant=${tenant}`,
      {
        method: "PATCH",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify(payload),
      }
    );

    if (!res.ok) {
      const err = await res.json();
      setSaving(false);
      alert(`Save failed: ${err.error}`);
      return;
    }

    setSaving(false);
    alert("Saved");
  }
resetField function · javascript · L103-L108 (6 LOC)
src/app/(admin)/admin/clinical-trials/[nctId]/page.jsx
  function resetField(manualKey) {
    setTrial((prev) => ({
      ...prev,
      [manualKey]: null,
    }));
  }
AdminTrialsPage function · javascript · L13-L161 (149 LOC)
src/app/(admin)/admin/clinical-trials/page.jsx
export default function AdminTrialsPage() {
    const [tenant, setTenant] = useState("HS");
    const [trials, setTrials] = useState([]);
    const [loading, setLoading] = useState(false);
    const [searchQuery, setSearchQuery] = useState("");

    useEffect(() => {
        let cancelled = false;

        async function load() {
            setLoading(true);
            const res = await fetch(`/api/admin/clinical-trials?tenant=${tenant}`, {
                cache: "no-store",
            });
            const data = await res.json();

            if (!cancelled) {
                setTrials(data.trials || []);
                setLoading(false);
            }
        }

        load();
        return () => {
            cancelled = true;
        };
    }, [tenant]);

    const filteredTrials = useMemo(() => {
        if (!searchQuery) return trials;
        const query = searchQuery.toLowerCase();
        return trials.filter(
            (trial) =>
                trial.short_title?.to
Open data scored by Repobility · https://repobility.com
load function · javascript · L22-L33 (12 LOC)
src/app/(admin)/admin/clinical-trials/page.jsx
        async function load() {
            setLoading(true);
            const res = await fetch(`/api/admin/clinical-trials?tenant=${tenant}`, {
                cache: "no-store",
            });
            const data = await res.json();

            if (!cancelled) {
                setTrials(data.trials || []);
                setLoading(false);
            }
        }
SyncPage function · javascript · L7-L174 (168 LOC)
src/app/(admin)/admin/sync/page.jsx
export default function SyncPage() {
  const [progress, setProgress] = useState(null);
  const [isRunning, setIsRunning] = useState(false);
  const [log, setLog] = useState([]);
  const [status, setStatus] = useState('');
  const eventSourceRef = useRef(null);

  const startSync = () => {
    setIsRunning(true);
    setLog([]);
    setProgress(null);
    setStatus('Connecting...');

    const eventSource = new EventSource('/api/clinical-trials/sync');
    eventSourceRef.current = eventSource;

    eventSource.onmessage = (event) => {
      const data = JSON.parse(event.data);

      if (data.type === 'status') {
        setStatus(data.message);
        setLog((prev) => [...prev, { type: 'info', message: data.message, time: new Date() }]);
      } else if (data.type === 'progress') {
        setProgress(data);
        if (data.action === 'processed') {
          setLog((prev) => [
            ...prev,
            { type: 'success', message: `✅ Processed: ${data.nctId}`, time: new Date()
ArticleItem function · javascript · L366-L419 (54 LOC)
src/app/(admin)/assign-articles/page.jsx
function ArticleItem({ article, isSelected, onSelect, onUnassign }) {
    return (
        <div
            className={`p-4 rounded-lg border transition-all ${
                isSelected
                    ? "border-[#4cb19f] bg-[rgba(76,177,159,0.05)]"
                    : "border-gray-200 hover:border-gray-300"
            }`}
        >
            <div className="flex gap-4">
                <Checkbox
                    checked={isSelected}
                    onCheckedChange={() => onSelect(article.id)}
                    className="mt-1 w-5 h-5"
                />
                <img
                    src={article.image_url || "/default-article-image.png"}
                    alt=""
                    className="w-16 h-16 object-cover rounded-lg flex-shrink-0"
                />
                <div className="flex-1 min-w-0">
                    <label
                        htmlFor={`article-${article.id}`}
                        className="text-[1.5rem] font-medium te
EditorItem function · javascript · L421-L459 (39 LOC)
src/app/(admin)/assign-articles/page.jsx
function EditorItem({ editor, isSelected, onSelect }) {
    return (
        <div
            onClick={() => onSelect(editor.id)}
            className={`p-4 rounded-lg border cursor-pointer transition-all ${
                isSelected
                    ? "border-[#4cb19f] bg-[rgba(76,177,159,0.05)]"
                    : "border-gray-200 hover:border-gray-300"
            }`}
        >
            <div className="flex items-center gap-3">
                <Checkbox
                    checked={isSelected}
                    onCheckedChange={() => onSelect(editor.id)}
                    className="w-5 h-5"
                />
                {editor.image ? (
                    <Image
                        src={editor.image}
                        alt=""
                        width={40}
                        height={40}
                        className="rounded-full"
                    />
                ) : (
                    <FallbackAuthorImage authorName={editor.name
ArticleCard function · javascript · L276-L323 (48 LOC)
src/app/(admin)/featured/page.jsx
function ArticleCard({ article, isFeatured, onToggle, isToggling }) {
    return (
        <div className="admin-card p-5 flex gap-5 items-center">
            {/* Thumbnail */}
            <img
                src={article.image_url || "/default-article-image.png"}
                alt=""
                className="w-20 h-20 object-cover rounded-lg bg-gray-100 flex-shrink-0"
            />

            {/* Content */}
            <div className="flex-1 min-w-0">
                <div className="flex items-start gap-4 mb-1">
                    <h3 className="text-[1.5rem] font-semibold text-gray-900 line-clamp-1">
                        {article.title}
                    </h3>
                </div>
                {article.authors && (
                    <p className="text-[1.3rem] text-gray-500 line-clamp-1">
                        {article.authors}
                    </p>
                )}
            </div>

            {/* Featured Toggle Button */}
            <button
      
NavItem function · javascript · L49-L69 (21 LOC)
src/app/(admin)/layout.js
function NavItem({ item, isActive }) {
    const Icon = item.icon;

    return (
        <Link
            href={item.href}
            className={`
                flex items-center gap-3 px-4 py-3 rounded-lg text-[1.4rem] font-medium
                transition-all duration-150
                ${
                    isActive
                        ? "bg-[rgba(76,177,159,0.1)] text-[#4cb19f]"
                        : "text-gray-600 hover:bg-gray-100 hover:text-gray-900"
                }
            `}
        >
            <Icon size={18} strokeWidth={isActive ? 2 : 1.5} />
            <span>{item.label}</span>
        </Link>
    );
}
NavGroup function · javascript · L71-L106 (36 LOC)
src/app/(admin)/layout.js
function NavGroup({ group, pathname, isCollapsed, onToggle }) {
    const hasActiveItem = group.items.some((item) =>
        pathname.startsWith(item.href)
    );

    return (
        <div className="mb-2">
            <button
                onClick={onToggle}
                className="
                    w-full flex items-center justify-between px-4 py-2
                    text-[1.2rem] font-semibold text-gray-400 uppercase tracking-wider
                    hover:text-gray-600 transition-colors
                "
            >
                <span>{group.label}</span>
                {isCollapsed ? (
                    <ChevronRight size={14} />
                ) : (
                    <ChevronDown size={14} />
                )}
            </button>
            {!isCollapsed && (
                <div className="mt-1 space-y-1">
                    {group.items.map((item) => (
                        <NavItem
                            key={item.href}
                       
AdminLayout function · javascript · L108-L222 (115 LOC)
src/app/(admin)/layout.js
export default function AdminLayout({ children }) {
    const pathname = usePathname();
    const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
    const [collapsedGroups, setCollapsedGroups] = useState({});

    const toggleGroup = (label) => {
        setCollapsedGroups((prev) => ({
            ...prev,
            [label]: !prev[label],
        }));
    };

    const SidebarContent = () => (
        <>
            {/* Logo/Title */}
            <div className="p-6 border-b border-gray-100">
                <Link href="/" className="flex items-center gap-3">
                    <div className="w-10 h-10 bg-[#4cb19f] rounded-lg flex items-center justify-center">
                        <LayoutDashboard size={20} className="text-white" />
                    </div>
                    <div>
                        <h1 className="text-[1.6rem] font-bold text-gray-900">
                            Admin Panel
                        </h1>
                        <p className="te
Repobility's GitHub App fixes findings like these · https://github.com/apps/repobility-bot
fetchLinks function · javascript · L28-L34 (7 LOC)
src/app/(admin)/magic-links/page.jsx
    async function fetchLinks() {
        setFetching(true);
        const res = await fetch(`/api/magic-link/list`);
        const data = await res.json();
        setLinks(data.links || []);
        setFetching(false);
    }
generateLink function · javascript · L57-L81 (25 LOC)
src/app/(admin)/magic-links/page.jsx
    async function generateLink() {
        if (!email) {
            toast.error("Please enter an email address");
            return;
        }
        setLoading(true);
        const res = await fetch("/api/magic-link/create", {
            method: "POST",
            headers: { "Content-Type": "application/json" },
            body: JSON.stringify({ email }),
        });

        const data = await res.json();
        setLoading(false);

        if (data.success) {
            setNewMagicUrl(data.magicUrl);
            await navigator.clipboard.writeText(data.magicUrl);
            toast.success("Magic link created & copied to clipboard!");
            setEmail("");
            fetchLinks();
        } else {
            toast.error(data.message || "Failed to create link");
        }
    }
copyToClipboard function · javascript · L83-L88 (6 LOC)
src/app/(admin)/magic-links/page.jsx
    async function copyToClipboard(url) {
        await navigator.clipboard.writeText(url);
        setCopied(true);
        toast.success("Link copied to clipboard!");
        setTimeout(() => setCopied(false), 2000);
    }
deleteLink function · javascript · L90-L97 (8 LOC)
src/app/(admin)/magic-links/page.jsx
    async function deleteLink(id) {
        await fetch(`/api/magic-link/delete?id=${id}`, {
            method: "DELETE",
        });
        toast.success("Magic link deleted");
        setDeleteConfirm({ open: false, id: null });
        fetchLinks();
    }
MagicLinkStatusPage function · javascript · L6-L53 (48 LOC)
src/app/(admin)/magic-links/status/page.js
export default function MagicLinkStatusPage() {
    const params = useSearchParams();
    const status = params.get("status");
    const message = params.get("message");

    const [count, setCount] = useState(3);

    useEffect(() => {
        if (status === "success") {
            const interval = setInterval(() => {
                setCount((c) => c - 1);
            }, 1000);

            setTimeout(() => {
                window.location.href = "/";
            }, 3000);

            return () => clearInterval(interval);
        }
    }, [status]);

    return (
        <div style={{ padding: 40, textAlign: "center" }}>
            {status === "loading" && <h2>Verifying your login…</h2>}

            {status === "success" && (
                <>
                    <h2>Login successful!</h2>
                    <p>Redirecting in {count}...</p>
                </>
            )}

            {status === "error" && (
                <>
                    <h2>Invalid Link</h2>
    
ArticleCard function · javascript · L183-L238 (56 LOC)
src/app/(admin)/pending-articles/page.jsx
function ArticleCard({ article }) {
    const hasEditors = article.assigned_editors?.length > 0;

    return (
        <Link
            href={`/pending-articles/${article.id}`}
            className="admin-card admin-card-interactive block"
        >
            <div className="p-5 flex gap-5">
                {/* Thumbnail */}
                <div className="flex-shrink-0">
                    <img
                        src={article.image_url || "/default-article-image.png"}
                        alt=""
                        className="w-24 h-24 object-cover rounded-lg bg-gray-100"
                    />
                </div>

                {/* Content */}
                <div className="flex-1 min-w-0">
                    <div className="flex items-start justify-between gap-4 mb-2">
                        <h3 className="text-[1.6rem] font-semibold text-gray-900 line-clamp-2">
                            {article.title}
                        </h3>
                       
GET function · javascript · L5-L39 (35 LOC)
src/app/api/admin/clinical-trials/[nctId]/route.js
export async function GET(req, { params }) {
  try {
    const nctId = params.nctId;

    const { searchParams } = new URL(req.url);
    const tenant = searchParams.get("tenant");

    if (!tenant) {
      return NextResponse.json({ error: "Missing tenant" }, { status: 400 });
    }

    const result = await sql`
      SELECT *
      FROM clinical_trials
      WHERE nct_id = ${nctId}
        AND tenant = ${tenant}
      LIMIT 1
    `;

    if (result.length === 0) {
      return NextResponse.json({ error: "Trial not found" }, { status: 404 });
    }

    return NextResponse.json({
      success: true,
      trial: result[0],
    });
  } catch (err) {
    console.error("ADMIN TRIAL GET ERROR:", err);
    return NextResponse.json(
      { error: "Failed to load trial" },
      { status: 500 }
    );
  }
}
PATCH function · javascript · L43-L87 (45 LOC)
src/app/api/admin/clinical-trials/[nctId]/route.js
export async function PATCH(req, { params }) {
  try {
    const nctId = params.nctId;

    const { searchParams } = new URL(req.url);
    const tenant = searchParams.get("tenant");

    if (!tenant) {
      return NextResponse.json({ error: "Missing tenant" }, { status: 400 });
    }

    const body = await req.json();

   
    if (!body || Object.keys(body).length === 0) {
      return NextResponse.json(
        { error: "No fields provided" },
        { status: 400 }
      );
    }

    await sql`
      UPDATE clinical_trials
      SET
        short_title_manual        = ${body.short_title_manual ?? null},
        ai_summary_manual         = ${body.ai_summary_manual ?? null},
        ai_purpose_manual         = ${body.ai_purpose_manual ?? null},
        ai_treatments_manual      = ${body.ai_treatments_manual ?? null},
        ai_design_manual          = ${body.ai_design_manual ?? null},
        ai_eligibility_manual     = ${body.ai_eligibility_manual ?? null},
        ai_participati
Want fix-PRs on findings? Install Repobility's GitHub App · github.com/apps/repobility-bot
GET function · javascript · L4-L28 (25 LOC)
src/app/api/admin/clinical-trials/route.js
export async function GET(req) {
  const { searchParams } = new URL(req.url);
  const tenant = searchParams.get("tenant");

  if (!tenant) {
    return NextResponse.json({ error: "Missing tenant" }, { status: 400 });
  }

  const trials = await sql`
    SELECT
      nct_id,
      COALESCE(short_title_manual, short_title) AS short_title
    FROM clinical_trials
    WHERE tenant = ${tenant}
      AND is_active = true
      AND overall_status IN (
        'RECRUITING',
        'NOT_YET_RECRITING',
        'ENROLLING_BY_INVITATION'
      )
    ORDER BY last_synced_at DESC
  `;

  return NextResponse.json({ trials });
}
DELETE function · javascript · L8-L29 (22 LOC)
src/app/api/articles/actions/delete/route.js
export async function DELETE(req) {
    const adminCheck = requireAdmin(req);
    if (adminCheck instanceof NextResponse) return adminCheck;
    
    const { id } = await req.json(); // Parse JSON body from the request

    try {
        // Execute delete query
        await query("DELETE FROM article WHERE id = $1", [id]);

        return NextResponse.json({
            success: true,
            message: "Article deleted successfully!",
        });
    } catch (error) {
        console.error("Error deleting article:", error);
        return NextResponse.json(
            { success: false, message: "Internal server error" },
            { status: 500 }
        );
    }
}
POST function · javascript · L6-L44 (39 LOC)
src/app/api/articles/actions/feature/route.js
export async function POST(req) {
    const adminCheck = requireAdmin(req);
    if (adminCheck instanceof NextResponse) return adminCheck;
    
    const { articleId, shouldBeFeatured } = await req.json();

    try {
        // Fetch the current array of featured article IDs
        const fetchResult = await query(
            "SELECT article_ids FROM featured LIMIT 1"
        );
        let currentFeaturedIds = fetchResult.rows[0]?.article_ids || [];

        if (shouldBeFeatured) {
            if (!currentFeaturedIds.includes(articleId)) {
                currentFeaturedIds.push(articleId);
            }
        } else {
            currentFeaturedIds = currentFeaturedIds.filter(
                (id) => id !== articleId
            );
        }

        // Update the featured article IDs in the database
        await query("UPDATE featured SET article_ids = $1 WHERE id = 1", [
            currentFeaturedIds,
        ]);

        return NextResponse.json({
            message: "Featur
POST function · javascript · L7-L59 (53 LOC)
src/app/api/articles/actions/retract/route.js
export async function POST(req) {
    const adminCheck = requireAdmin(req);
    if (adminCheck instanceof NextResponse) return adminCheck;
    
    const { id } = await req.json(); // Parse JSON body from the request

    try {
        // Step 1: Retrieve the article, including image_url
        const result = await query("SELECT * FROM article WHERE id = $1", [id]);
        const article = result.rows[0];

        // Check if article exists
        if (!article) {
            return NextResponse.json(
                { success: false, message: "Article not found" },
                { status: 404 }
            );
        }

        // Step 2: Insert article into pending_article table, including the image_url field
        await query(
            "INSERT INTO pending_article (title, tags, innertext, summary, article_link, publisher, image_url, authors, publication_date, source_publication, image_credit, additional_editors) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)",
  
POST function · javascript · L6-L31 (26 LOC)
src/app/api/articles/actions/update/route.js
export async function POST(req) {
    const adminCheck = requireAdmin(req);
    if (adminCheck instanceof NextResponse) return adminCheck;
    
    const { id, title, tags, innertext, summary, article_link, image_url, authors, publication_date, source_publication, image_credit, additional_editors } =
    await req.json();

    try {
        // Execute the update query, including image_url
        await query(
            "UPDATE article SET title = $1, tags = $2, innertext = $3, summary = $4, article_link = $5, image_url = $6, authors = $7, publication_date = $8, source_publication = $9, image_credit = $10, additional_editors = $11 WHERE id = $12",
            [title, tags, innertext, summary, article_link, image_url, authors, publication_date, source_publication || null, image_credit || null, additional_editors || [], id]
        );

        return NextResponse.json({
            success: true,
            message: "Article updated successfully!",
        });
    } catch (error) {
   
POST function · javascript · L5-L79 (75 LOC)
src/app/api/articles/add/route.js
export async function POST(req) {
    try {
        const {
            title,
            tags,
            innertext,
            article_link,
            simplifyLength,
            publisher,
            image_url,
            role,
            userId,
            authors,
            publication_date,
            source_publication,
            image_credit,
            additional_editors,
        } = await req.json();

        // Generate the summary and simplified content
        const summary = await summarizeArticle(innertext);
        const simplified = await simplifyArticle(innertext, simplifyLength);

        // Start a transaction to ensure consistency
        await query("BEGIN");

        // Insert into the database, including the publisher and image URL
        const result = await query(
            `INSERT INTO pending_article
                (title, tags, innertext, summary, article_link, publisher, image_url, authors, publication_date, source_publication, image_cre
POST function · javascript · L7-L57 (51 LOC)
src/app/api/articles/assign/route.js
export async function POST(request) {
    const adminCheck = requireAdmin(request);
    if (adminCheck instanceof NextResponse) return adminCheck;
    
    try {
        const { editorIds, articleIds } = await request.json();

        // Start a transaction
        await query("BEGIN");

        // Check for existing assignments
        const existingAssignments = await query(
            "SELECT article_id, editor_id FROM article_assignments WHERE article_id = ANY($1) AND editor_id = ANY($2)",
            [articleIds, editorIds]
        );

        if (existingAssignments.rows.length > 0) {
            await query("ROLLBACK");
            return NextResponse.json(
                {
                    message:
                        "Some articles are already assigned to selected editors",
                },
                { status: 400 }
            );
        }

        // Insert new assignments
        for (const articleId of articleIds) {
            for (const editorId of edito
GET function · javascript · L6-L46 (41 LOC)
src/app/api/articles/featured/route.js
export async function GET(request) {
    
    try {
        // Fetch article IDs from the featured table
        const featuredResult = await query(
            "SELECT article_ids FROM featured LIMIT 1"
        );
        const articleIds = featuredResult.rows[0]?.article_ids;

        let articles = [];
        if (articleIds && articleIds.length) {
            // Fetch articles along with profile photos and names based on the fetched IDs
            const articlesResult = await query(
                `
                SELECT
                  a.*,
                  p.photo,
                  p.name,
                  p.degree,
                  p.university
                FROM article a
                LEFT JOIN profile p
                  ON (a.certifiedby->>'userId')::INT = p.user_id
                WHERE a.id = ANY($1::int[])
                ORDER BY array_position($1::int[], a.id);
                `,
                [articleIds]
              );
              articles = article
All rows above produced by Repobility · https://repobility.com
POST function · javascript · L17-L100 (84 LOC)
src/app/api/articles/generate-ai-image/route.js
export async function POST(req) {
    try {
        const { articleId, simplifiedText } = await req.json();

        if (!articleId || !simplifiedText) {
            return NextResponse.json(
                { success: false, message: "Missing articleId or simplifiedText" },
                { status: 400 }
            );
        }

        // ------------------------------
        // 1️⃣ Generate AI IMAGE (gpt-image-1)
        // ------------------------------
        const prompt = `
Create a clean, high-quality scientific illustration based on the following text:

"${simplifiedText}"

IMPORTANT:
- Do NOT include text in the image.
- Style should match NF Simplified's existing illustration style.
- The image should be informative, modern, and visually clean.
- Medium-quality tier.
`;

        const aiImage = await openai.images.generate({
            model: "gpt-image-1",
            prompt,
            size: "1792x1024", // landscape orientation as requested
            quality: "hig
GET function · javascript · L7-L39 (33 LOC)
src/app/api/articles/[id]/like/route.js
export async function GET(req, { params }) {
    try {
        const url = new URL(req.url);
        const userId = parseInt(url.searchParams.get("userId"), 10);
        const articleId = parseInt(params.id, 10);

        console.log("GET - userId:", userId, "articleId:", articleId); // Debugging

        if (isNaN(userId) || isNaN(articleId)) {
            return NextResponse.json(
                { success: false, message: "Invalid user ID or article ID" },
                { status: 400 }
            );
        }

        const result = await query(
            "SELECT EXISTS (SELECT 1 FROM article_likes WHERE user_id = $1 AND article_id = $2) AS is_favorited",
            [userId, articleId]
        );

        return NextResponse.json({
            success: true,
            message: "Favorite status fetched successfully",
            data: { isFavorited: result.rows[0].is_favorited },
        });
    } catch (error) {
        console.error("Error fetching favorite status:", error)
POST function · javascript · L42-L99 (58 LOC)
src/app/api/articles/[id]/like/route.js
export async function POST(req, { params }) {
    let userId;
    try {
        const body = await req.json();
        userId = parseInt(body.userId, 10);
    } catch (e) {
        console.error("Error parsing request body:", e);
        return NextResponse.json(
            { success: false, message: "Invalid request body" },
            { status: 400 }
        );
    }

    const articleId = parseInt(params.id, 10);

    console.log("POST - userId:", userId, "articleId:", articleId); // Debugging

    try {
        if (isNaN(userId) || isNaN(articleId)) {
            return NextResponse.json(
                { success: false, message: "Invalid user ID or article ID" },
                { status: 400 }
            );
        }

        await query("BEGIN");
        const existingFavorite = await query(
            "SELECT * FROM article_likes WHERE user_id = $1 AND article_id = $2",
            [userId, articleId]
        );

        if (existingFavorite.rows.length > 0) {
            
DELETE function · javascript · L102-L155 (54 LOC)
src/app/api/articles/[id]/like/route.js
export async function DELETE(req, { params }) {
    let userId;
    try {
        const body = await req.json();
        userId = parseInt(body.userId, 10);
    } catch (e) {
        console.error("Error parsing request body:", e);
        return NextResponse.json(
            { success: false, message: "Invalid request body" },
            { status: 400 }
        );
    }

    const articleId = parseInt(params.id, 10);

    console.log("DELETE - userId:", userId, "articleId:", articleId); // Debugging

    try {
        if (isNaN(userId) || isNaN(articleId)) {
            return NextResponse.json(
                { success: false, message: "Invalid user ID or article ID" },
                { status: 400 }
            );
        }

        await query("BEGIN");
        const result = await query(
            "DELETE FROM article_likes WHERE user_id = $1 AND article_id = $2 RETURNING *",
            [userId, articleId]
        );

        if (result.rows.length === 0) {
            awai
getFromArticle function · javascript · L8-L28 (21 LOC)
src/app/api/articles/[id]/route.js
async function getFromArticle(id) {
    return await query(
        `
        SELECT 
            a.*, 
            p.email, 
            p.name, 
            p.photo, 
            p.bio, 
            p.degree, 
            p.university, 
            p.linkedin, 
            p.lablink,
            (SELECT COUNT(*) FROM article_likes al WHERE al.article_id = a.id) AS like_count
        FROM article a
        LEFT JOIN profile p ON (a.certifiedby->>'userId')::INTEGER = p.user_id
        WHERE a.id = $1
        `,
        [id]
    );
}
getFromPending function · javascript · L31-L40 (10 LOC)
src/app/api/articles/[id]/route.js
async function getFromPending(id) {
    return await query(
        `
        SELECT *
        FROM pending_article
        WHERE id = $1
        `,
        [id]
    );
}
getFromPendingAssignments function · javascript · L43-L52 (10 LOC)
src/app/api/articles/[id]/route.js
async function getFromPendingAssignments(id) {
    return await query(
        `
        SELECT pa.*
        FROM pending_with_assignments pa
        WHERE pa.id = $1
        `,
        [id]
    );
}
GET function · javascript · L59-L102 (44 LOC)
src/app/api/articles/[id]/route.js
export async function GET(request, { params }) {
    const { id } = params;
    const articleId = parseInt(id, 10);

    if (isNaN(articleId)) {
        return NextResponse.json(
            { success: false, message: "Invalid article ID" },
            { status: 400 }
        );
    }

    try {
        // 1️⃣ Check main published article table
        const articleResult = await getFromArticle(articleId);
        if (articleResult.rows.length > 0) {
            return NextResponse.json(articleResult.rows[0]);
        }

        // 2️⃣ Check pending_article table
        const pendingResult = await getFromPending(articleId);
        if (pendingResult.rows.length > 0) {
            return NextResponse.json(pendingResult.rows[0]);
        }

        // 3️⃣ Check pending_with_assignments
        const assignedResult = await getFromPendingAssignments(articleId);
        if (assignedResult.rows.length > 0) {
            return NextResponse.json(assignedResult.rows[0]);
        }

        /
Open data scored by Repobility · https://repobility.com
PATCH function · javascript · L109-L145 (37 LOC)
src/app/api/articles/[id]/route.js
export async function PATCH(request, { params }) {
    const { id } = params;
    const articleId = parseInt(id);

    try {
        const { imageUrl } = await request.json();
        if (!imageUrl) {
            return NextResponse.json(
                { success: false, message: "No imageUrl provided" },
                { status: 400 }
            );
        }

        // update in main article table only
        const updateResult = await query(
            `
            UPDATE article
            SET image_url = $1
            WHERE id = $2
            RETURNING *
            `,
            [imageUrl, articleId]
        );

        return NextResponse.json({
            success: true,
            article: updateResult.rows[0],
        });

    } catch (error) {
        console.error("❌ PATCH image update error:", error);
        return NextResponse.json(
            { success: false, message: "Failed to update article image" },
            { status: 500 }
        );
    }
}
GET function · javascript · L6-L41 (36 LOC)
src/app/api/articles/liked/route.js
export async function GET(req) {
    const url = new URL(req.url);
    const userId = url.searchParams.get("userId");

    try {
        if (!userId || isNaN(parseInt(userId, 10))) {
            return NextResponse.json(
                { message: "Valid userId query parameter is required" },
                { status: 400 }
            );
        }

        const result = await query(
            `
            SELECT 
                a.*, 
                p.photo,
                p.name
            FROM article_likes al
            JOIN article a ON al.article_id = a.id
            LEFT JOIN profile p ON (a.certifiedby->>'userId')::INTEGER = p.user_id
            WHERE al.user_id = $1
            ORDER BY al.created_at DESC
            `,
            [parseInt(userId, 10)]
        );

        return NextResponse.json(result.rows); // Return articles with user photos and names
    } catch (error) {
        console.error("Error executing query:", error);
        return NextResponse.json(
DELETE function · javascript · L7-L28 (22 LOC)
src/app/api/articles/pending/actions/delete/route.js
export async function DELETE(req) {
    const adminCheck = requireAdmin(req);
    if (adminCheck instanceof NextResponse) return adminCheck;
    
    const { id } = await req.json(); // Parse JSON body from the request

    try {
        // Execute delete query
        await query("DELETE FROM pending_article WHERE id = $1", [id]);

        return NextResponse.json({
            success: true,
            message: "Pending article deleted successfully!",
        });
    } catch (error) {
        console.error("Error deleting pending article:", error);
        return NextResponse.json(
            { success: false, message: "Internal server error" },
            { status: 500 }
        );
    }
}
POST function · javascript · L7-L67 (61 LOC)
src/app/api/articles/pending/actions/publish/route.js
export async function POST(req) {
    const adminCheck = requireAdmin(req);
    if (adminCheck instanceof NextResponse) return adminCheck;
    
    const { id, certifiedby } = await req.json(); // Parse JSON body from the request

    try {
        // Fetch the article from the pending_article table, including image_url
        const result = await query(
            "SELECT * FROM pending_article WHERE id = $1",
            [id]
        );
        const article = result.rows[0];

        // Check if the article exists
        if (!article) {
            return NextResponse.json(
                { success: false, message: "Article not found" },
                { status: 404 }
            );
        }

        // Insert the article into the article table, including the certifiedby, image_url, and publication_date fields
        // Insert the article into the article table, now including authors
        await query(
            `INSERT INTO article
            (title, tags, innertext, su
POST function · javascript · L6-L31 (26 LOC)
src/app/api/articles/pending/actions/update/route.js
export async function POST(req) {
    const adminCheck = requireAdmin(req);
    if (adminCheck instanceof NextResponse) return adminCheck;
    
    const { id, title, tags, innertext, summary, article_link, image_url, authors, publication_date, source_publication, image_credit, additional_editors } =
    await req.json();

    try {
        // Execute the update query, including image_url
        await query(
            "UPDATE pending_article SET title = $1, tags = $2, innertext = $3, summary = $4, article_link = $5, image_url = $6, authors = $7, publication_date = $8, source_publication = $9, image_credit = $10, additional_editors = $11 WHERE id = $12",
            [title, tags, innertext, summary, article_link, image_url, authors, publication_date, source_publication || null, image_credit || null, additional_editors || [], id]
        );

        return NextResponse.json({
            success: true,
            message: "Pending article updated successfully!",
        });
    } cat
GET function · javascript · L7-L32 (26 LOC)
src/app/api/articles/pending/[id]/route.js
export async function GET(request, { params }) {
    const { id } = params;

    try {
        // Fetch a single pending article by ID
        const result = await query(
            "SELECT * FROM pending_article WHERE id = $1",
            [id]
        );

        if (result.rows.length === 0) {
            return NextResponse.json(
                { message: "Article not found" },
                { status: 404 }
            );
        }

        return NextResponse.json(result.rows[0]);
    } catch (error) {
        console.error("Error fetching article by ID:", error);
        return NextResponse.json(
            { message: "Error fetching article" },
            { status: 500 }
        );
    }
}
GET function · javascript · L6-L21 (16 LOC)
src/app/api/articles/pending/route.js
export async function GET(request) {
    try {
        // Fetch pending articles from the database
        const result = await query(
            "SELECT * FROM pending_article ORDER BY created_at DESC"
        );

        return NextResponse.json(result.rows);
    } catch (error) {
        console.error("Error fetching pending articles:", error);
        return NextResponse.json(
            { message: "Error fetching pending articles" },
            { status: 500 }
        );
    }
}
GET function · javascript · L6-L65 (60 LOC)
src/app/api/articles/pending-with-assignments/route.js
export async function GET() {
    try {
        const articlesWithAssignments = await query(`
            SELECT 
                pa.id AS article_id,
                pa.title,
                pa.tags,
                pa.innertext,
                pa.summary,
                pa.article_link,
                pa.created_at,
                pa.certifiedby,
                pa.publisher,
                pa.image_url,
                COALESCE(
                    json_agg(
                        json_build_object(
                            'id', ec.id,
                            'name', ec.first_name || ' ' || ec.last_name,
                            'email', ec.email
                        )
                    ) FILTER (WHERE ec.id IS NOT NULL),
                    '[]'
                ) AS assigned_editors
            FROM pending_article pa
            LEFT JOIN article_assignments aa ON pa.id = aa.article_id
            LEFT JOIN email_credentials ec ON aa.editor_id = ec.id
      
Repobility's GitHub App fixes findings like these · https://github.com/apps/repobility-bot
GET function · javascript · L6-L34 (29 LOC)
src/app/api/articles/recent/route.js
export async function GET() {
    try {
        // Fetch recent 6 articles along with profile photos and names
        const result = await query(
            `
            SELECT
              a.*,
              p.photo,
              p.name,
              p.degree,
              p.university
            FROM article a
            LEFT JOIN profile p
              ON (a.certifiedby->>'userId')::INT = p.user_id
            ORDER BY a.id DESC
            LIMIT 6;
            `
          );
          

        return NextResponse.json(result.rows); // Return recent articles with user photos and names
    } catch (error) {
        console.error("Error fetching recent articles:", error);
        return NextResponse.json(
            { message: "Error fetching recent articles" },
            { status: 500 }
        );
    }
}
GET function · javascript · L6-L30 (25 LOC)
src/app/api/articles/route.js
export async function GET() {
    try {
        const result = await query(`
            SELECT
              a.*,
              p.photo,
              p.name,
              p.degree,
              p.university
            FROM article a
            LEFT JOIN profile p
              ON (a.certifiedby->>'userId')::INT = p.user_id
            ORDER BY a.id DESC
          `);
          
        return NextResponse.json(result.rows); // Return articles with user photos and names
    } catch (error) {
        console.error("Error executing query", error);

        return NextResponse.json(
            { message: "Error executing query" },
            { status: 500 }
        );
    }
}
POST function · javascript · L6-L21 (16 LOC)
src/app/api/articles/simplify/route.js
export async function POST(req) {
    try {
        const { content, lengthString } = await req.json();

        // Use the utility function to simplify the article
        const simplified = await simplifyArticle(content, lengthString);

        return NextResponse.json({ simplified });
    } catch (error) {
        console.error("Error calling OpenAI API:", error);
        return NextResponse.json(
            { message: "Failed to simplify article" },
            { status: 500 }
        );
    }
}
page 1 / 4next ›