initial push
This commit is contained in:
52
src/app/api/admin/content/create/route.ts
Normal file
52
src/app/api/admin/content/create/route.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { ContentPageType } from "@prisma/client";
|
||||
import { requireAdminApiUser } from "@/lib/auth/admin";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
import { buildAppUrl } from "@/lib/http/url";
|
||||
|
||||
function redirectTo(request: Request, status: string) {
|
||||
return NextResponse.redirect(buildAppUrl(request, "/admin", { status }));
|
||||
}
|
||||
|
||||
function asString(formData: FormData, key: string) {
|
||||
const value = formData.get(key);
|
||||
return typeof value === "string" ? value.trim() : "";
|
||||
}
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const adminUser = await requireAdminApiUser();
|
||||
|
||||
if (!adminUser) {
|
||||
return redirectTo(request, "admin_error");
|
||||
}
|
||||
|
||||
const formData = await request.formData();
|
||||
|
||||
const typeValue = asString(formData, "type");
|
||||
const slug = asString(formData, "slug");
|
||||
const title = asString(formData, "title");
|
||||
const content = asString(formData, "content");
|
||||
const sortOrder = Number.parseInt(asString(formData, "sortOrder") || "0", 10);
|
||||
const isPublished = formData.get("isPublished") === "on";
|
||||
|
||||
if (!slug || !title || !content || (typeValue !== ContentPageType.FAQ && typeValue !== ContentPageType.MANUAL)) {
|
||||
return redirectTo(request, "admin_error");
|
||||
}
|
||||
|
||||
try {
|
||||
await prisma.contentPage.create({
|
||||
data: {
|
||||
type: typeValue as ContentPageType,
|
||||
slug,
|
||||
title,
|
||||
content,
|
||||
sortOrder: Number.isNaN(sortOrder) ? 0 : sortOrder,
|
||||
isPublished,
|
||||
},
|
||||
});
|
||||
|
||||
return redirectTo(request, "content_created");
|
||||
} catch {
|
||||
return redirectTo(request, "admin_error");
|
||||
}
|
||||
}
|
||||
54
src/app/api/admin/content/update/route.ts
Normal file
54
src/app/api/admin/content/update/route.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { ContentPageType } from "@prisma/client";
|
||||
import { requireAdminApiUser } from "@/lib/auth/admin";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
import { buildAppUrl } from "@/lib/http/url";
|
||||
|
||||
function redirectTo(request: Request, status: string) {
|
||||
return NextResponse.redirect(buildAppUrl(request, "/admin", { status }));
|
||||
}
|
||||
|
||||
function asString(formData: FormData, key: string) {
|
||||
const value = formData.get(key);
|
||||
return typeof value === "string" ? value.trim() : "";
|
||||
}
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const adminUser = await requireAdminApiUser();
|
||||
|
||||
if (!adminUser) {
|
||||
return redirectTo(request, "admin_error");
|
||||
}
|
||||
|
||||
const formData = await request.formData();
|
||||
|
||||
const id = asString(formData, "id");
|
||||
const typeValue = asString(formData, "type");
|
||||
const slug = asString(formData, "slug");
|
||||
const title = asString(formData, "title");
|
||||
const content = asString(formData, "content");
|
||||
const sortOrder = Number.parseInt(asString(formData, "sortOrder") || "0", 10);
|
||||
const isPublished = formData.get("isPublished") === "on";
|
||||
|
||||
if (!id || !slug || !title || !content || (typeValue !== ContentPageType.FAQ && typeValue !== ContentPageType.MANUAL)) {
|
||||
return redirectTo(request, "admin_error");
|
||||
}
|
||||
|
||||
try {
|
||||
await prisma.contentPage.update({
|
||||
where: { id },
|
||||
data: {
|
||||
type: typeValue as ContentPageType,
|
||||
slug,
|
||||
title,
|
||||
content,
|
||||
sortOrder: Number.isNaN(sortOrder) ? 0 : sortOrder,
|
||||
isPublished,
|
||||
},
|
||||
});
|
||||
|
||||
return redirectTo(request, "content_updated");
|
||||
} catch {
|
||||
return redirectTo(request, "admin_error");
|
||||
}
|
||||
}
|
||||
72
src/app/api/admin/scoring/route.ts
Normal file
72
src/app/api/admin/scoring/route.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { OverallScoreMethod } from "@prisma/client";
|
||||
import { requireAdminApiUser } from "@/lib/auth/admin";
|
||||
import { DEFAULT_SCORING_CONFIG } from "@/lib/scoring-config";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
import { buildAppUrl } from "@/lib/http/url";
|
||||
|
||||
function redirectTo(request: Request, status: string) {
|
||||
return NextResponse.redirect(buildAppUrl(request, "/admin", { status }));
|
||||
}
|
||||
|
||||
function asString(formData: FormData, key: string) {
|
||||
const value = formData.get(key);
|
||||
return typeof value === "string" ? value.trim() : "";
|
||||
}
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const adminUser = await requireAdminApiUser();
|
||||
|
||||
if (!adminUser) {
|
||||
return redirectTo(request, "admin_error");
|
||||
}
|
||||
|
||||
const formData = await request.formData();
|
||||
|
||||
const thresholdCandidate = Number.parseInt(asString(formData, "lowScoreThreshold") || "70", 10);
|
||||
const lowScoreThreshold = Number.isNaN(thresholdCandidate) ? 70 : Math.min(100, Math.max(0, thresholdCandidate));
|
||||
|
||||
const methodCandidate = asString(formData, "overallScoreMethod");
|
||||
const allowedMethods = Object.values(OverallScoreMethod);
|
||||
const overallScoreMethod = allowedMethods.includes(methodCandidate as OverallScoreMethod)
|
||||
? (methodCandidate as OverallScoreMethod)
|
||||
: OverallScoreMethod.EQUAL_ALL_MODULES;
|
||||
|
||||
const moduleWeights: Record<string, number> = {};
|
||||
|
||||
for (const [key, rawValue] of formData.entries()) {
|
||||
if (!key.startsWith("weight:")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const moduleKey = key.replace("weight:", "").trim();
|
||||
const numericValue = Number.parseFloat(typeof rawValue === "string" ? rawValue : String(rawValue));
|
||||
|
||||
if (moduleKey && !Number.isNaN(numericValue) && Number.isFinite(numericValue) && numericValue > 0) {
|
||||
moduleWeights[moduleKey] = numericValue;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
await prisma.scoringConfig.upsert({
|
||||
where: {
|
||||
key: DEFAULT_SCORING_CONFIG.key,
|
||||
},
|
||||
update: {
|
||||
lowScoreThreshold,
|
||||
overallScoreMethod,
|
||||
moduleWeights,
|
||||
},
|
||||
create: {
|
||||
key: DEFAULT_SCORING_CONFIG.key,
|
||||
lowScoreThreshold,
|
||||
overallScoreMethod,
|
||||
moduleWeights,
|
||||
},
|
||||
});
|
||||
|
||||
return redirectTo(request, "scoring_updated");
|
||||
} catch {
|
||||
return redirectTo(request, "admin_error");
|
||||
}
|
||||
}
|
||||
42
src/app/api/admin/sync/route.ts
Normal file
42
src/app/api/admin/sync/route.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { requireAdminApiUser } from "@/lib/auth/admin";
|
||||
import { runDailyLicitationsSync } from "@/lib/licitations/sync";
|
||||
|
||||
export const runtime = "nodejs";
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const user = await requireAdminApiUser();
|
||||
|
||||
if (!user) {
|
||||
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
||||
}
|
||||
|
||||
try {
|
||||
const body = (await request.json().catch(() => ({}))) as {
|
||||
municipalityId?: string;
|
||||
limit?: number;
|
||||
skip?: number;
|
||||
targetYear?: number;
|
||||
includePnt?: boolean;
|
||||
force?: boolean;
|
||||
};
|
||||
|
||||
const payload = await runDailyLicitationsSync({
|
||||
municipalityId: typeof body.municipalityId === "string" ? body.municipalityId : undefined,
|
||||
limit: typeof body.limit === "number" ? body.limit : undefined,
|
||||
skip: typeof body.skip === "number" ? body.skip : undefined,
|
||||
targetYear: typeof body.targetYear === "number" ? body.targetYear : undefined,
|
||||
includePnt: body.includePnt === true,
|
||||
force: body.force === true,
|
||||
});
|
||||
|
||||
return NextResponse.json({ ok: true, payload });
|
||||
} catch (error) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
error: error instanceof Error ? error.message : "No se pudo ejecutar el sync de licitaciones.",
|
||||
},
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
}
|
||||
38
src/app/api/admin/users/role/route.ts
Normal file
38
src/app/api/admin/users/role/route.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { UserRole } from "@prisma/client";
|
||||
import { requireAdminApiUser } from "@/lib/auth/admin";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
import { buildAppUrl } from "@/lib/http/url";
|
||||
|
||||
function redirectTo(request: Request, status: string) {
|
||||
return NextResponse.redirect(buildAppUrl(request, "/admin", { status }));
|
||||
}
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const adminUser = await requireAdminApiUser();
|
||||
|
||||
if (!adminUser) {
|
||||
return redirectTo(request, "admin_error");
|
||||
}
|
||||
|
||||
const formData = await request.formData();
|
||||
const userId = typeof formData.get("userId") === "string" ? formData.get("userId")!.toString() : "";
|
||||
const roleValue = typeof formData.get("role") === "string" ? formData.get("role")!.toString() : "";
|
||||
|
||||
if (!userId || (roleValue !== UserRole.USER && roleValue !== UserRole.ADMIN)) {
|
||||
return redirectTo(request, "admin_error");
|
||||
}
|
||||
|
||||
try {
|
||||
await prisma.user.update({
|
||||
where: { id: userId },
|
||||
data: {
|
||||
role: roleValue as UserRole,
|
||||
},
|
||||
});
|
||||
|
||||
return redirectTo(request, "role_updated");
|
||||
} catch {
|
||||
return redirectTo(request, "admin_error");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user