← back to kyle94024__Science-Simplified

Function bodies 169 total

All specs Real LLM only Function bodies
POST function · javascript · L6-L21 (16 LOC)
src/app/api/articles/summarize/route.js
export async function POST(req) {
    try {
        const { content } = await req.json();

        // Use the utility function to summarize the article
        const summary = await summarizeArticle(content);

        return NextResponse.json({ summary });
    } catch (error) {
        console.error("Error calling OpenAI API:", error);
        return NextResponse.json(
            { message: "Failed to summarize article" },
            { status: 500 }
        );
    }
}
POST function · javascript · L6-L28 (23 LOC)
src/app/api/articles/unassign/route.js
export async function POST(request) {
    const adminCheck = requireAdmin(request);
    if (adminCheck instanceof NextResponse) return adminCheck;
    
    try {
        const { articleId, editorId } = await request.json();

        await query(
            `DELETE FROM article_assignments WHERE article_id = $1 AND editor_id = $2`,
            [articleId, editorId]
        );

        return NextResponse.json({
            message: "Article unassigned successfully",
        });
    } catch (error) {
        console.error("Error unassigning article:", error);
        return NextResponse.json(
            { message: "Error unassigning article" },
            { status: 500 }
        );
    }
}
POST function · javascript · L7-L41 (35 LOC)
src/app/api/auth/create-editor/route.js
export async function POST(request) {
  // 1) block non‑admins
  const guard = requireAdmin(request);
  if (guard instanceof NextResponse) return guard;

  // 2) pull the new user data + role
  const { firstName, lastName, email, password } = await request.json();
  if (!firstName || !lastName || !email || !password ) {
    return NextResponse.json({ message: "Missing fields" }, { status: 400 });
  }

  // 3) hash & insert
  const passwordHash = await bcrypt.hash(password, 10);
  const insertUser = await query(
    `INSERT INTO email_credentials
       (first_name, last_name, email, password_hash, role)
     VALUES ($1,$2,$3,$4,$5)
     RETURNING id, email`,
    [firstName, lastName, email.toLowerCase(), passwordHash, "editor"]
  );
  const userId = insertUser.rows[0].id;

  // 4) profile table
  await query(
    `INSERT INTO profile (user_id, name, email, photo, bio)
     VALUES ($1,$2,$3,$4,$5)`,
    [userId, `${firstName} ${lastName}`, email.toLowerCase(), null, null]
  );

  // 5) 
POST function · javascript · L8-L92 (85 LOC)
src/app/api/auth/login/route.js
export async function POST(req) {
    try {
        let { email, password } = await req.json();

        // Get user from the database
        email = email.toLowerCase();                     // ← normalize input

        // Now compare against a lower‑cased column value
        const userResult = await query(
        `SELECT * 
            FROM email_credentials 
            WHERE LOWER(email) = $1`,
        [email]
        );


        if (userResult.rows.length === 0) {
            return NextResponse.json(
                { message: "Invalid credentials" },
                { status: 401 }
            );
        }

        const user = userResult.rows[0];
        const passwordMatch = await bcrypt.compare(
            password,
            user.password_hash
        );

        if (!passwordMatch) {
            return NextResponse.json(
                { message: "Invalid credentials" },
                { status: 401 }
            );
        }

        // Check if user is admin
    
POST function · javascript · L4-L23 (20 LOC)
src/app/api/auth/logout/route.js
export async function POST() {
    try {
        // Clear the auth cookie by setting it with an expired date
        const response = NextResponse.json({ message: "Logout successful" });

        // Set the cookie to expire immediately
        response.headers.set(
            "Set-Cookie",
            "auth=; Max-Age=0; path=/; HttpOnly; secure; SameSite=strict"
        );

        return response;
    } catch (error) {
        console.error("Logout error:", error);
        return NextResponse.json(
            { message: "Error during logout" },
            { status: 500 }
        );
    }
}
GET function · javascript · L5-L33 (29 LOC)
src/app/api/auth/session/route.js
export async function GET(req) {
    const token = req.cookies.get("auth")?.value; // Get the token from cookies

    if (!token) {
        return NextResponse.json({ isLoggedIn: false }, { status: 200 });
    }

    try {
        const decoded = verify(
            token,
            process.env.JWT_SECRET || "your-secret-key"
        ); // Verify the token

        return NextResponse.json(
            {
                isLoggedIn: true,
                email: decoded.email,
                userId: decoded.id,
                name: decoded.name,
                isAdmin: decoded.isAdmin,
                role: decoded.role,
            },
            { status: 200 }
        );
    } catch (error) {
        console.error("Session verification error:", error);
        return NextResponse.json({ isLoggedIn: false }, { status: 200 });
    }
}
POST function · javascript · L8-L76 (69 LOC)
src/app/api/auth/signup/route.js
export async function POST(req) {
    try {
        const { firstName, lastName, email, password } = await req.json();

        // Hash password
        const passwordHash = await bcrypt.hash(password, 10);

        // Insert user into the email_credentials table
        const result = await query(
            `INSERT INTO email_credentials (first_name, last_name, email, password_hash) 
            VALUES ($1, $2, $3, $4) 
            RETURNING id, email`,
            [firstName, lastName, email, passwordHash]
        );

        const userId = result.rows[0].id; // Get the ID of the newly created user
        const userEmail = result.rows[0].email; // Get the email of the newly created user

        // Create a blank profile for the user
        await query(
            `INSERT INTO profile (user_id, name, email, photo, bio) 
            VALUES ($1, $2, $3, $4, $5)`,
            [userId, `${firstName} ${lastName}`, userEmail, null, null] // Include email
        );

        


       
Repobility · open methodology · https://repobility.com/research/
POST function · javascript · L5-L52 (48 LOC)
src/app/api/auth/update-password/route.js
export async function POST(req) {
    try {
        const { currentPassword, newPassword, email } = await req.json();

        // Get user from the database (replace with session user info)
        const userResult = await query(
            "SELECT * FROM email_credentials WHERE email = $1",
            [email]
        );

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

        const user = userResult.rows[0];
        const passwordMatch = await bcrypt.compare(
            currentPassword,
            user.password_hash
        );

        if (!passwordMatch) {
            return NextResponse.json(
                { message: "Current password is incorrect" },
                { status: 401 }
            );
        }

        // Hash new password
        const newPasswordHash = await bcrypt.hash(newPassword, 10);

        // Update password in the da
GET function · javascript · L4-L62 (59 LOC)
src/app/api/clinical-trials/active/route.js
export async function GET() {
  try {
    const tenant = process.env.NEXT_PUBLIC_SITE_KEY;

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

    const trials = await sql`
      SELECT
        nct_id,
        COALESCE(short_title_manual, short_title) AS short_title,
        COALESCE(ai_summary_manual, ai_summary)   AS ai_summary,
        tenant,
        overall_status,
        start_date,
        primary_completion_date,
        conditions,

        raw_data->'protocolSection'->'contactsLocationsModule'->'locations'->0->>'city'
          AS location_city,
        raw_data->'protocolSection'->'contactsLocationsModule'->'locations'->0->>'state'
          AS location_state,
        raw_data->'protocolSection'->'contactsLocationsModule'->'locations'->0->>'country'
          AS location_country,
          raw_data->'protocolSection'->'contactsLocationsModule'->'locations'
  AS locations,

   
GET function · javascript · L1-L40 (40 LOC)
src/app/api/clinical-trials/list/route.js
export async function GET(req) {
  const { searchParams } = new URL(req.url);

  const page = Number(searchParams.get("page") || 1);
  const limit = 6;
  const offset = (page - 1) * limit;
  const q = searchParams.get("q") || "";

  const tenant = process.env.NEXT_PUBLIC_SITE_KEY;
  if (!tenant) {
    return NextResponse.json(
      { success: false, error: "Missing tenant" },
      { status: 400 }
    );
  }

  const trials = await sql`
  SELECT
    nct_id,
    overall_status,
    COALESCE(ai_summary_manual, ai_summary) AS ai_summary
  FROM clinical_trials
  WHERE is_active = true
    AND tenant = ${tenant}
    AND (
      nct_id ILIKE ${"%" + q + "%"}
      OR COALESCE(ai_summary_manual, ai_summary) ILIKE ${"%" + q + "%"}
    )
  ORDER BY last_synced_at DESC
  LIMIT ${limit}
  OFFSET ${offset}
`;

  return NextResponse.json({
    success: true,
    trials,
    total: Number(total[0].count),
    page,
  });
}
GET function · javascript · L4-L65 (62 LOC)
src/app/api/clinical-trials/[nctId]/route.js
export async function GET(req, { params }) {
  const { nctId } = params;
  const tenant = process.env.NEXT_PUBLIC_SITE_KEY;

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

  try {
    const result = await sql`
      SELECT
        nct_id,
        tenant,
        overall_status,
        start_date,
        primary_completion_date,
        completion_date,
        last_update_date,
        conditions,
        keywords,

        COALESCE(short_title_manual, short_title)               AS short_title,
        COALESCE(ai_summary_manual, ai_summary)                 AS ai_summary,
        COALESCE(ai_purpose_manual, ai_purpose)                 AS ai_purpose,
        COALESCE(ai_treatments_manual, ai_treatments)           AS ai_treatments,
        COALESCE(ai_design_manual, ai_design)                   AS ai_design,
        COALESCE(ai_eligibility_manual, ai_eligibility)         AS ai_eligibility,
       
generatePatientSummary function · javascript · L10-L33 (24 LOC)
src/app/api/clinical-trials/regenerate/[nctid]/route.js
async function generatePatientSummary(trial) {
  const protocol = trial.protocolSection;

  const prompt = `
Summarize this clinical trial for patients in simple language.

Title: ${protocol.identificationModule?.briefTitle}
Conditions: ${protocol.conditionsModule?.conditions?.join(", ")}
Eligibility: ${protocol.eligibilityModule?.eligibilityCriteria}
Locations: ${protocol.contactsLocationsModule?.locations
    ?.map((l) => `${l.city}, ${l.country}`)
    .join("; ")}

Summary:
`;

  const response = await openai.chat.completions.create({
    model: "gpt-4o-mini",
    messages: [{ role: "user", content: prompt }],
    temperature: 0.3,
  });

  return response.choices[0].message.content.trim();
}
POST function · javascript · L35-L67 (33 LOC)
src/app/api/clinical-trials/regenerate/[nctid]/route.js
export async function POST(req, { params }) {
  const { nctId } = params;

  const rows = await sql`
    SELECT raw_data
    FROM clinical_trials
    WHERE nct_id = ${nctId}
    LIMIT 1
  `;

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

  const trial = rows[0].raw_data;
  const aiSummary = await generatePatientSummary(trial);

  const sourceHash = crypto
    .createHash("sha256")
    .update(JSON.stringify(trial))
    .digest("hex");

  await sql`
    UPDATE clinical_trials
    SET
      ai_summary = ${aiSummary},
      source_hash = ${sourceHash},
      ai_summary_updated_at = NOW()
    WHERE nct_id = ${nctId}
  `;

  return NextResponse.json({ success: true });
}
normalizeDate function · javascript · L49-L54 (6 LOC)
src/app/api/clinical-trials/sync/route.js
function normalizeDate(dateStr) {
  if (!dateStr) return null;
  if (/^\d{4}-\d{2}$/.test(dateStr)) return `${dateStr}-01`;
  if (/^\d{4}-\d{2}-\d{2}$/.test(dateStr)) return dateStr;
  return null;
}
trialMatchesTenant function · javascript · L56-L77 (22 LOC)
src/app/api/clinical-trials/sync/route.js
function trialMatchesTenant(trial, tenantKey) {
  const config = TENANT_CONFIG[tenantKey];
  if (!config) return false;

  const conditions = trial.protocolSection?.conditionsModule?.conditions || [];
  const haystack = conditions.join(" ").toLowerCase();

  const matchesRequired = config.required.some((term) =>
    haystack.includes(term.toLowerCase())
  );

  if (!matchesRequired) return false;

  if (config.exclude?.length) {
    const hasExcluded = config.exclude.some((term) =>
      haystack.includes(term.toLowerCase())
    );
    if (hasExcluded) return false;
  }

  return true;
}
Same scanner, your repo: https://repobility.com — Repobility
buildSourceHash function · javascript · L79-L92 (14 LOC)
src/app/api/clinical-trials/sync/route.js
function buildSourceHash(trial) {
  const p = trial.protocolSection;
  return crypto
    .createHash("sha256")
    .update(
      JSON.stringify({
        title: p.identificationModule?.briefTitle ?? "",
        conditions: p.conditionsModule?.conditions ?? [],
        eligibility: p.eligibilityModule?.eligibilityCriteria ?? "",
        status: p.statusModule?.overallStatus ?? "",
      })
    )
    .digest("hex");
}
aiCall function · javascript · L94-L105 (12 LOC)
src/app/api/clinical-trials/sync/route.js
async function aiCall(prompt) {
  const res = await openai.chat.completions.create({
    model: "gpt-4o",
    messages: [
      { role: "system", content: SYSTEM_PROMPT },
      { role: "user", content: prompt },
    ],
    temperature: 0.3,
  });

  return res.choices[0].message.content.trim();
}
generateShortTitle function · javascript · L109-L168 (60 LOC)
src/app/api/clinical-trials/sync/route.js
async function generateShortTitle(trial) {
  const p = trial.protocolSection;
  const studyType = p.designModule?.studyType?.toLowerCase() || "";
  const conditions = p.conditionsModule?.conditions?.join(", ") || "";
  const drugs = p.armsInterventionsModule?.interventions
    ?.map((i) => i.name)
    .filter(Boolean)
    .join(", ");

  if (studyType === "interventional" && drugs) {
    const drugsText = drugs || "Not specified";
    const conditionsText = conditions || "Not specified";

    const prompt = `
Create a short, patient-friendly study title.

Rules (must follow ALL):
- Format EXACTLY: [Drug or Drugs] for [Specific disease or tumor]
- Use the SPECIFIC tumor or condition being treated
- If the condition occurs within a larger disorder, name only the tumor
- Use real drug name(s)
- 3–6 words total
- No punctuation
- No phase numbers
- Do NOT use the words "study", "trial", or "treatment"

Drug(s): ${drugsText}
Condition being treated: ${conditionsText}

Return ONLY the title.
generatePatientSummary function · javascript · L170-L203 (34 LOC)
src/app/api/clinical-trials/sync/route.js
async function generatePatientSummary(trial) {
  const p = trial.protocolSection;
  const conditions = p.conditionsModule?.conditions?.join(", ") || "the condition being studied";
  const studyType = p.designModule?.studyType?.toLowerCase() || "observational";
  const interventions = p.armsInterventionsModule?.interventions?.map((i) => i.name).filter(Boolean) || [];

  return aiCall(`
Write ONE clear, patient-friendly summary paragraph (5–7 sentences).

Explain:
1. The specific disease or tumor being treated (be precise)
2. What type of study this is (interventional or observational)
3. What drug, device, or approach is being tested
4. What type of drug or approach this is and how it works in simple terms
5. What participants are asked to do
6. How the information from the study will be used

STRICT RULES (must follow ALL):
- Use plain text only
- DO NOT use markdown, asterisks (*), bold, italics, or symbols
- Always name the SPECIFIC disease or tumor being treated
- If the condition i
generatePurpose function · javascript · L205-L244 (40 LOC)
src/app/api/clinical-trials/sync/route.js
async function generatePurpose(trial) {
  const p = trial.protocolSection;
  const indication = p.conditionsModule?.conditions?.join(", ") || "";
  const drugs = p.armsInterventionsModule?.interventions?.map((i) => i.name).filter(Boolean).join(", ");
  const source = [
    p.identificationModule?.briefSummary,
    p.descriptionModule?.briefSummary,
    p.descriptionModule?.detailedDescription,
  ].filter(Boolean).join("\n\n");

  const result = await aiCall(`
Answer this question for a patient:

"What is the purpose of this study?"

STRICT RULES (must follow ALL):
- Clearly state WHAT drug, therapy, or approach is being tested
- Clearly state the SPECIFIC disease, tumor, or condition being treated
- Explain WHAT the study is measuring (for example: tumor shrinkage, safety, side effects, symptom control)
- Use concrete, plain language
- Do NOT describe disease biology
- Do NOT use vague phrases like "to better understand", "researchers want to learn more", "being evaluated", "for future
generateTreatments function · javascript · L246-L270 (25 LOC)
src/app/api/clinical-trials/sync/route.js
async function generateTreatments(trial) {
  const interventions = trial.protocolSection?.armsInterventionsModule?.interventions?.map((i) => i.name).filter(Boolean) || [];
  if (!interventions.length) return "This study does not test a specific drug or device.";

  const result = await aiCall(`
Answer this question for a patient: "What treatments are being tested?"

Rules:
- Clearly name the treatment(s)
- If the study does not explain how they work, say so briefly and neutrally
- Do NOT mention missing text
- Do NOT ask for more information
- Do NOT speculate
- 1–2 sentences
- Plain language

Treatments: ${interventions.join(", ")}
`);

  const forbidden = ["paste", "provide more", "not enough information", "cannot determine", "i need more"];
  if (!result || forbidden.some((f) => result.toLowerCase().includes(f))) {
    return `The study is testing ${interventions.join(", ")}.`;
  }
  return result.trim();
}
generateDesign function · javascript · L272-L292 (21 LOC)
src/app/api/clinical-trials/sync/route.js
async function generateDesign(trial) {
  const p = trial.protocolSection;
  const studyType = p.designModule?.studyType?.toLowerCase();

  if (studyType === "observational") {
    const text = p.detailedDescription || p.designModule?.description || "";
    if (!text) return "This is an observational study where researchers collect information over time without assigning treatments or interventions.";
    return aiCall(`Explain how this observational study works. Rules: Describe what information is collected and how participants are followed. Mention surveys, interviews, medical record review, imaging, or follow-ups if listed. Do NOT describe treatments or assignments. Do NOT ask for more information. Do NOT mention missing text. 2–4 sentences. Plain language. Text: ${text}`);
  }

  const source = [
    p.designModule?.studyType,
    p.designModule?.allocation,
    p.designModule?.interventionModel,
    p.designModule?.masking,
    p.armsInterventionsModule?.armGroups?.map((a) => a.lab
generateEligibility function · javascript · L294-L296 (3 LOC)
src/app/api/clinical-trials/sync/route.js
async function generateEligibility(trial) {
  return aiCall(`Create two bullet lists: Who may be able to join, Who may not be able to join. Rules: Use only the eligibility text. Plain language. No extra explanations. Text: ${trial.protocolSection?.eligibilityModule?.eligibilityCriteria || ""}`);
}
Want fix-PRs on findings? Install Repobility's GitHub App · github.com/apps/repobility-bot
generateParticipation function · javascript · L298-L309 (12 LOC)
src/app/api/clinical-trials/sync/route.js
async function generateParticipation(trial) {
  const text = trial.protocolSection?.detailedDescription || trial.protocolSection?.descriptionModule?.briefSummary || "";
  if (!text || text.trim().length < 40) return "The study team will explain what participation involves.";

  const result = await aiCall(`You are writing for patients. Answer the question: "What is participation like?" STRICT RULES: Describe what participants actually DO. Mention visits, procedures, surveys, imaging, medications, or follow-ups if listed. If randomized, say participants may be assigned to a group. Do NOT mention missing information. Do NOT say text was not provided. Do NOT hedge or apologize. Do NOT ask questions. 2–3 sentences MAX. Plain, neutral language. Use ONLY the text provided. Text: ${text}`);

  const badPhrases = ["not provided", "wasn't provided", "missing", "no information", "cannot summarize", "can't summarize", "please", "paste", "looks like", "based on the text provided"];
  if (!result |
generateLeadership function · javascript · L311-L319 (9 LOC)
src/app/api/clinical-trials/sync/route.js
async function generateLeadership(trial) {
  const sponsor = trial.protocolSection?.sponsorCollaboratorsModule?.leadSponsor?.name;
  const contacts = trial.protocolSection?.contactsLocationsModule?.centralContacts || [];

  if (!sponsor && contacts.length === 0) return "This study is being run by the study team listed on ClinicalTrials.gov.";
  if (contacts.length === 0) return `This study is being run by ${sponsor}.`;

  return aiCall(`Explain who is running the study. Rules: Use plain text only. Do NOT ask the reader for information. Do NOT say "share it and I can add it". Do NOT mention missing information. Name the sponsor. List contact details exactly as provided. Short and factual. Sponsor: ${sponsor || ""} Contacts: ${contacts.map((c) => `${c.name} (${c.phone || "no phone listed"}, ${c.email || "no email listed"})`).join("\n")}`);
}
generatePriorResearch function · javascript · L321-L326 (6 LOC)
src/app/api/clinical-trials/sync/route.js
async function generatePriorResearch(trial) {
  const condition = trial.protocolSection?.conditionsModule?.conditions?.join(", ");
  const interventions = trial.protocolSection?.armsInterventionsModule?.interventions?.map((i) => i.name).join(", ");

  return aiCall(`You are writing a short "Prior Research" section for a patient-facing clinical trial summary. This section should summarize general background research related to the condition and treatment. Important rules: Do NOT claim that this trial itself produced these results. Do NOT invent study names, years, authors, or citations. Speak generally (e.g. "previous studies have shown...", "earlier research suggests..."). If little is known, say so clearly. 1–2 short paragraphs, plain language. Condition: ${condition || "Not specified"} Treatment / Intervention: ${interventions || "Not specified"}`);
}
generateLocations function · javascript · L328-L332 (5 LOC)
src/app/api/clinical-trials/sync/route.js
function generateLocations(trial) {
  const locations = trial.protocolSection?.contactsLocationsModule?.locations || [];
  if (!locations.length) return "This is a decentralized study, which means it can be done remotely.";
  return locations.map((l) => `${l.city}, ${l.state}, ${l.country}`).filter(Boolean).join("; ");
}
fetchAllStudies function · javascript · L336-L363 (28 LOC)
src/app/api/clinical-trials/sync/route.js
async function fetchAllStudies(tenant) {
  const SEARCH_TERMS = TENANT_CONFIG[tenant].required;
  const BASE_URL = "https://clinicaltrials.gov/api/v2/studies";
  const PAGE_SIZE = 50;
  const MAX_STUDIES = 500;

  let allStudies = [];
  let pageToken = null;

  do {
    const url = new URL(BASE_URL);
    url.searchParams.set("query.cond", SEARCH_TERMS.join(" OR "));
    url.searchParams.set("pageSize", PAGE_SIZE.toString());
    if (pageToken) {
      url.searchParams.set("pageToken", pageToken);
    }

    const res = await fetch(url.toString());
    const data = await res.json();

    const studies = data.studies || [];
    allStudies.push(...studies);

    pageToken = data.nextPageToken || null;
  } while (pageToken && allStudies.length < MAX_STUDIES);

  return allStudies.slice(0, MAX_STUDIES);
}
checkShouldSkip function · javascript · L367-L404 (38 LOC)
src/app/api/clinical-trials/sync/route.js
async function checkShouldSkip(nctId, study) {
  const newHash = buildSourceHash(study);

  const existing = await sql`
    SELECT
      source_hash,
      last_synced_at,
      ai_purpose,
      ai_treatments,
      ai_design,
      ai_eligibility,
      ai_participation,
      ai_leadership,
      ai_prior_research,
      ai_locations
    FROM clinical_trials
    WHERE nct_id = ${nctId}
    LIMIT 1
  `;

  const hasAllAI =
    existing[0]?.ai_purpose &&
    existing[0]?.ai_treatments &&
    existing[0]?.ai_design &&
    existing[0]?.ai_eligibility &&
    existing[0]?.ai_participation &&
    existing[0]?.ai_leadership &&
    existing[0]?.ai_prior_research &&
    existing[0]?.ai_locations;

  const shouldSkip =
    existing.length &&
    existing[0].source_hash === newHash &&
    new Date(existing[0].last_synced_at).getTime() > Date.now() - WEEK_MS &&
    hasAllAI;

  return shouldSkip;
}
processStudy function · javascript · L408-L486 (79 LOC)
src/app/api/clinical-trials/sync/route.js
async function processStudy(study, tenant) {
  const p = study.protocolSection;
  const nctId = p?.identificationModule?.nctId;
  const newHash = buildSourceHash(study);

  const aiPayload = {
    shortTitle: await generateShortTitle(study),
    summary: await generatePatientSummary(study),
    purpose: await generatePurpose(study),
    treatments: await generateTreatments(study),
    design: await generateDesign(study),
    eligibility: await generateEligibility(study),
    participation: await generateParticipation(study),
    leadership: await generateLeadership(study),
    priorResearch: await generatePriorResearch(study),
    locations: generateLocations(study),
  };

  if (
    !aiPayload.purpose ||
    !aiPayload.design ||
    !aiPayload.eligibility ||
    !aiPayload.participation ||
    !aiPayload.leadership ||
    !aiPayload.priorResearch ||
    !aiPayload.locations
  ) {
    throw new Error("Incomplete AI payload");
  }

  await sql`
    INSERT INTO clinical_trials (
      nc
GET function · javascript · L490-L611 (122 LOC)
src/app/api/clinical-trials/sync/route.js
export async function GET(req) {
  const TENANT = defaultTenant.shortName?.toUpperCase();

  if (!TENANT || !TENANT_CONFIG[TENANT]) {
    return new Response(
      JSON.stringify({ success: false, error: "Invalid tenant" }),
      { status: 400, headers: { "Content-Type": "application/json" } }
    );
  }

  const encoder = new TextEncoder();

  const stream = new ReadableStream({
    async start(controller) {
      const send = (data) => {
        controller.enqueue(encoder.encode(`data: ${JSON.stringify(data)}\n\n`));
      };

      try {
        // Step 1: Fetch all studies
        send({ type: "status", message: "Fetching studies from ClinicalTrials.gov...", tenant: TENANT });

        const allStudies = await fetchAllStudies(TENANT);

        // Filter to matching studies
        const matchingStudies = allStudies.filter((s) => trialMatchesTenant(s, TENANT));

        send({
          type: "status",
          message: `Found ${allStudies.length} studies, ${matchingStudies.lengt
Repobility · severity-and-effort ranking · https://repobility.com
normalizeDate function · javascript · L55-L60 (6 LOC)
src/app/api/clinical-trials/sync/[tenant]/route.js
function normalizeDate(dateStr) {
  if (!dateStr) return null;
  if (/^\d{4}-\d{2}$/.test(dateStr)) return `${dateStr}-01`;
  if (/^\d{4}-\d{2}-\d{2}$/.test(dateStr)) return dateStr;
  return null;
}
trialMatchesTenant function · javascript · L62-L86 (25 LOC)
src/app/api/clinical-trials/sync/[tenant]/route.js
function trialMatchesTenant(trial, tenantKey) {
  const config = TENANT_CONFIG[tenantKey];
  if (!config) return false;

  const conditions = trial.protocolSection?.conditionsModule?.conditions || [];

  const haystack = conditions.join(" ").toLowerCase();

  // MUST match at least one required term
  const matchesRequired = config.required.some((term) =>
    haystack.includes(term.toLowerCase())
  );

  if (!matchesRequired) return false;

  // MUST NOT contain excluded terms
  if (config.exclude?.length) {
    const hasExcluded = config.exclude.some((term) =>
      haystack.includes(term.toLowerCase())
    );
    if (hasExcluded) return false;
  }

  return true;
}
buildSourceHash function · javascript · L88-L101 (14 LOC)
src/app/api/clinical-trials/sync/[tenant]/route.js
function buildSourceHash(trial) {
  const p = trial.protocolSection;
  return crypto
    .createHash("sha256")
    .update(
      JSON.stringify({
        title: p.identificationModule?.briefTitle ?? "",
        conditions: p.conditionsModule?.conditions ?? [],
        eligibility: p.eligibilityModule?.eligibilityCriteria ?? "",
        status: p.statusModule?.overallStatus ?? "",
      })
    )
    .digest("hex");
}
aiCall function · javascript · L103-L114 (12 LOC)
src/app/api/clinical-trials/sync/[tenant]/route.js
async function aiCall(prompt) {
  const res = await openai.chat.completions.create({
    model: "gpt-5.2",
    messages: [
      { role: "system", content: SYSTEM_PROMPT },
      { role: "user", content: prompt },
    ],
    temperature: 0.3,
  });

  return res.choices[0].message.content.trim();
}
generateShortTitle function · javascript · L117-L176 (60 LOC)
src/app/api/clinical-trials/sync/[tenant]/route.js
async function generateShortTitle(trial) {
  const p = trial.protocolSection;
  const studyType = p.designModule?.studyType?.toLowerCase() || "";
  const conditions = p.conditionsModule?.conditions?.join(", ") || "";
  const drugs = p.armsInterventionsModule?.interventions
    ?.map((i) => i.name)
    .filter(Boolean)
    .join(", ");

  if (studyType === "interventional" && drugs) {
    const drugsText = drugs || "Not specified";
    const conditionsText = conditions || "Not specified";

    const prompt = `
Create a short, patient-friendly study title.

Rules (must follow ALL):
- Format EXACTLY: [Drug or Drugs] for [Specific disease or tumor]
- Use the SPECIFIC tumor or condition being treated
- If the condition occurs within a larger disorder, name only the tumor
- Use real drug name(s)
- 3–6 words total
- No punctuation
- No phase numbers
- Do NOT use the words “study”, “trial”, or “treatment”

Drug(s): ${drugsText}
Condition being treated: ${conditionsText}

Return ONLY the title.
generatePatientSummary function · javascript · L179-L224 (46 LOC)
src/app/api/clinical-trials/sync/[tenant]/route.js
async function generatePatientSummary(trial) {
  const p = trial.protocolSection;

  const conditions =
    p.conditionsModule?.conditions?.join(", ") || "the condition being studied";

  const studyType = p.designModule?.studyType?.toLowerCase() || "observational";

  const interventions =
    p.armsInterventionsModule?.interventions
      ?.map((i) => i.name)
      .filter(Boolean) || [];

  return aiCall(`
Write ONE clear, patient-friendly summary paragraph (5–7 sentences).

Explain:
1. The specific disease or tumor being treated (be precise)
2. What type of study this is (interventional or observational)
3. What drug, device, or approach is being tested
4. What type of drug or approach this is and how it works in simple terms
5. What participants are asked to do
6. How the information from the study will be used

STRICT RULES (must follow ALL):
- Use plain text only
- DO NOT use markdown, asterisks (*), bold, italics, or symbols
- Always name the SPECIFIC disease or tumor being tre
generatePurpose function · javascript · L228-L299 (72 LOC)
src/app/api/clinical-trials/sync/[tenant]/route.js
async function generatePurpose(trial) {
  const p = trial.protocolSection;

  const indication = p.conditionsModule?.conditions?.join(", ") || "";

  const drugs = p.armsInterventionsModule?.interventions
    ?.map((i) => i.name)
    .filter(Boolean)
    .join(", ");

  const source = [
    p.identificationModule?.briefSummary,
    p.descriptionModule?.briefSummary,
    p.descriptionModule?.detailedDescription,
  ]
    .filter(Boolean)
    .join("\n\n");

  const result = await aiCall(`
Answer this question for a patient:

"What is the purpose of this study?"

STRICT RULES (must follow ALL):
- Clearly state WHAT drug, therapy, or approach is being tested
- Clearly state the SPECIFIC disease, tumor, or condition being treated
  (not just the broader diagnosis)
- Explain WHAT the study is measuring (for example: tumor shrinkage, safety, side effects, symptom control)
- Use concrete, plain language
- Do NOT describe disease biology
- Do NOT use vague phrases like:
  "to better understand"
generateTreatments function · javascript · L301-L343 (43 LOC)
src/app/api/clinical-trials/sync/[tenant]/route.js
async function generateTreatments(trial) {
  const interventions =
    trial.protocolSection?.armsInterventionsModule?.interventions
      ?.map((i) => i.name)
      .filter(Boolean) || [];

  // ✅ SAFE FALLBACK — NEVER null
  if (!interventions.length) {
    return "This study does not test a specific drug or device.";
  }

  const result = await aiCall(`
Answer this question for a patient:

"What treatments are being tested?"

Rules:
- Clearly name the treatment(s)
- If the study does not explain how they work, say so briefly and neutrally
- Do NOT mention missing text
- Do NOT ask for more information
- Do NOT speculate
- 1–2 sentences
- Plain language

Treatments:
${interventions.join(", ")}
`);

  const forbidden = [
    "paste",
    "provide more",
    "not enough information",
    "cannot determine",
    "i need more",
  ];

  if (!result || forbidden.some((f) => result.toLowerCase().includes(f))) {
    return `The study is testing ${interventions.join(", ")}.`;
  }

  return re
Repobility · open methodology · https://repobility.com/research/
generateDesign function · javascript · L345-L403 (59 LOC)
src/app/api/clinical-trials/sync/[tenant]/route.js
async function generateDesign(trial) {
  const p = trial.protocolSection;
  const studyType = p.designModule?.studyType?.toLowerCase();

  // OBSERVATIONAL
  if (studyType === "observational") {
    const text = p.detailedDescription || p.designModule?.description || "";

    if (!text) {
      return "This is an observational study where researchers collect information over time without assigning treatments or interventions.";
    }

    return aiCall(`
Explain how this observational study works.

Rules:
- Describe what information is collected and how participants are followed
- Mention surveys, interviews, medical record review, imaging, or follow-ups if listed
- Do NOT describe treatments or assignments
- Do NOT ask for more information
- Do NOT mention missing text
- 2–4 sentences
- Plain language

Text:
${text}
`);
  }

  // INTERVENTIONAL
  const source = [
    p.designModule?.studyType,
    p.designModule?.allocation,
    p.designModule?.interventionModel,
    p.designModule?.m
generateEligibility function · javascript · L405-L419 (15 LOC)
src/app/api/clinical-trials/sync/[tenant]/route.js
async function generateEligibility(trial) {
  return aiCall(`
Create two bullet lists:
- Who may be able to join
- Who may not be able to join

Rules:
- Use only the eligibility text
- Plain language
- No extra explanations

Text:
${trial.protocolSection?.eligibilityModule?.eligibilityCriteria || ""}
`);
}
generateParticipation function · javascript · L439-L494 (56 LOC)
src/app/api/clinical-trials/sync/[tenant]/route.js
async function generateParticipation(trial) {
  const text =
    trial.protocolSection?.detailedDescription ||
    trial.protocolSection?.descriptionModule?.briefSummary ||
    "";

  if (!text || text.trim().length < 40) {
    return "The study team will explain what participation involves.";
  }

  const result = await aiCall(`
You are writing for patients.

Answer the question:
"What is participation like?"

STRICT RULES (must follow ALL):
- Describe what participants actually DO
- Mention visits, procedures, surveys, imaging, medications, or follow-ups if listed
- If randomized, say participants may be assigned to a group
- Do NOT mention missing information
- Do NOT say text was not provided
- Do NOT hedge or apologize
- Do NOT ask questions
- 2–3 sentences MAX
- Plain, neutral language
- Use ONLY the text provided
- If participation details are vague, give a general but neutral description

Text:
${text}
`);

  const badPhrases = [
    "not provided",
    "wasn't provided",
    "
generateLeadership function · javascript · L506-L547 (42 LOC)
src/app/api/clinical-trials/sync/[tenant]/route.js
async function generateLeadership(trial) {
  const sponsor =
    trial.protocolSection?.sponsorCollaboratorsModule?.leadSponsor?.name;

  const contacts =
    trial.protocolSection?.contactsLocationsModule?.centralContacts || [];

  // Clean fallback when no contact info exists
  if (!sponsor && contacts.length === 0) {
    return "This study is being run by the study team listed on ClinicalTrials.gov.";
  }

  if (contacts.length === 0) {
    return `This study is being run by ${sponsor}.`;
  }

  return aiCall(`
Explain who is running the study.

Rules (must follow ALL):
- Use plain text only
- Do NOT ask the reader for information
- Do NOT say "share it and I can add it"
- Do NOT mention missing information
- Name the sponsor
- List contact details exactly as provided
- Short and factual

Sponsor:
${sponsor || ""}

Contacts:
${contacts
  .map(
    (c) =>
      `${c.name} (${c.phone || "no phone listed"}, ${
        c.email || "no email listed"
      })`
  )
  .join("\n")}
`);
}
generatePriorResearch function · javascript · L549-L579 (31 LOC)
src/app/api/clinical-trials/sync/[tenant]/route.js
async function generatePriorResearch(trial) {
  const condition =
    trial.protocolSection?.conditionsModule?.conditions?.join(", ");

  const interventions =
    trial.protocolSection?.armsInterventionsModule?.interventions
      ?.map((i) => i.name)
      .join(", ");

  return aiCall(`
You are writing a short "Prior Research" section for a patient-facing clinical trial summary.

This section should summarize **general background research** related to the condition and treatment,
and may include information from published literature (e.g. PubMed, prior clinical studies, or well-established research).

Important rules:
- Do NOT claim that this trial itself produced these results
- Do NOT invent study names, years, authors, or citations
- Speak generally (e.g. "previous studies have shown...", "earlier research suggests...")
- If little is known, say so clearly
- 1–2 short paragraphs, plain language

Condition:
${condition || "Not specified"}

Treatment / Intervention:
${interventions
generateLocations function · javascript · L581-L593 (13 LOC)
src/app/api/clinical-trials/sync/[tenant]/route.js
function generateLocations(trial) {
  const locations =
    trial.protocolSection?.contactsLocationsModule?.locations || [];

  if (!locations.length) {
    return "This is a decentralized study, which means it can be done remotely.";
  }

  return locations
    .map((l) => `${l.city}, ${l.state}, ${l.country}`)
    .filter(Boolean)
    .join("; ");
}
GET function · javascript · L597-L845 (249 LOC)
src/app/api/clinical-trials/sync/[tenant]/route.js
export async function GET(req, context) {
  const { tenant } = await context.params;
  const TENANT = tenant?.toUpperCase();

  if (!TENANT || !TENANT_CONFIG[TENANT]) {
    return NextResponse.json(
      { success: false, error: "Invalid tenant" },
      { status: 400 }
    );
  }

  const SEARCH_TERMS = TENANT_CONFIG[TENANT].required;

  // ---- NEW: pagination + safety cap ----
  const BASE_URL = "https://clinicaltrials.gov/api/v2/studies";
  const PAGE_SIZE = 50;
  const MAX_STUDIES = 500;

  let allStudies = [];
  let pageToken = null;

  try {
    do {
      const url = new URL(BASE_URL);
      url.searchParams.set("query.cond", SEARCH_TERMS.join(" OR "));
      url.searchParams.set("pageSize", PAGE_SIZE.toString());
      if (pageToken) {
        url.searchParams.set("pageToken", pageToken);
      }

      const res = await fetch(url.toString());
      const data = await res.json();

      const studies = data.studies || [];
      allStudies.push(...studies);

      pageToken = 
POST function · javascript · L6-L27 (22 LOC)
src/app/api/contact/article-request/route.js
export async function POST(req) {
    try {
        const { article_link, submitted_by, anonymous } = await req.json();

        // Insert article request; conditionally include 'requested_by' based on anonymity
        const result = await query(
            `INSERT INTO requested_articles (article_link${
                anonymous ? "" : ", requested_by"
            }) 
            VALUES ($1${anonymous ? "" : ", $2"}) RETURNING *;`,
            anonymous ? [article_link] : [article_link, submitted_by]
        );

        return NextResponse.json(result.rows[0]);
    } catch (error) {
        console.error("Error adding article request:", error);
        return NextResponse.json(
            { error: "Error adding article request" },
            { status: 500 }
        );
    }
}
Same scanner, your repo: https://repobility.com — Repobility
POST function · javascript · L6-L30 (25 LOC)
src/app/api/contact/bug-report/route.js
export async function POST(req) {
    try {
        const { report, submitted_by, anonymous } = await req.json();
        const submittedAt = new Date().toISOString();

        // Insert bug report; conditionally include 'submitted_by' based on anonymity
        const result = await query(
            `INSERT INTO bug_reports (report, submitted_at${
                anonymous ? "" : ", submitted_by"
            }) 
            VALUES ($1, $2${anonymous ? "" : ", $3"}) RETURNING *;`,
            anonymous
                ? [JSON.stringify(report), submittedAt] // Save report as JSON if needed
                : [JSON.stringify(report), submittedAt, submitted_by] // Use the submitted_by variable
        );

        return NextResponse.json(result.rows[0]);
    } catch (error) {
        console.error("Error adding bug report:", error);
        return NextResponse.json(
            { error: "Error adding bug report" },
            { status: 500 }
        );
    }
}
GET function · javascript · L5-L49 (45 LOC)
src/app/api/cron/clinical-trials/route.js
export async function GET(req) {
  const authHeader = req.headers.get("authorization");

  if (authHeader !== `Bearer ${process.env.CRON_SECRET}`) {
    return NextResponse.json(
      { success: false, error: "Unauthorized" },
      { status: 401 }
    );
  }

  const baseUrl =
    process.env.NEXT_PUBLIC_SITE_URL ||
    `https://${req.headers.get("host")}`;

  const results = [];

  for (const tenant of TENANTS) {
    try {
      const res = await fetch(
        `${baseUrl}/api/clinical-trials/sync/${tenant}`,
        { cache: "no-store" }
      );

      const data = await res.json();

      results.push({
        tenant,
        success: res.ok,
        response: data,
      });
    } catch (err) {
      results.push({
        tenant,
        success: false,
        error: String(err),
      });
    }
  }

  return NextResponse.json({
    success: true,
    ranAt: new Date().toISOString(),
    results,
  });
}
GET function · javascript · L6-L36 (31 LOC)
src/app/api/editors/assigned-articles/route.js
export async function GET(request) {
    const editorId = request.nextUrl.searchParams.get("editorId");

    if (!editorId) {
        return NextResponse.json(
            { message: "Editor ID is required" },
            { status: 400 }
        );
    }

    try {
        const result = await query(
            `
            SELECT pa.* 
            FROM pending_article pa
            JOIN article_assignments aa ON pa.id = aa.article_id
            WHERE aa.editor_id = $1
            ORDER BY pa.created_at DESC
        `,
            [editorId]
        );

        return NextResponse.json(result.rows);
    } catch (error) {
        console.error("Error fetching assigned articles:", error);
        return NextResponse.json(
            { message: "Error fetching assigned articles" },
            { status: 500 }
        );
    }
}
‹ prevpage 2 / 4next ›