Function bodies 169 total
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-ONgenerateShortTitle 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?.toOpen 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 teEditorItem 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.nameArticleCard 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="teRepobility'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_participatiWant 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: "FeaturPOST 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_crePOST 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 editoGET 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 = articleAll 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: "higGET 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) {
awaigetFromArticle 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, suPOST 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!",
});
} catGET 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 ›