feat(core): setup postgres, minio, and authentik next-auth
This commit is contained in:
@@ -0,0 +1,55 @@
|
||||
import NextAuth, { NextAuthOptions } from "next-auth";
|
||||
import AuthentikProvider from "next-auth/providers/authentik";
|
||||
|
||||
export const authOptions: NextAuthOptions = {
|
||||
providers: [
|
||||
AuthentikProvider({
|
||||
clientId: process.env.AUTHENTIK_CLIENT_ID || "",
|
||||
clientSecret: process.env.AUTHENTIK_CLIENT_SECRET || "",
|
||||
issuer: process.env.AUTHENTIK_ISSUER || "",
|
||||
}),
|
||||
],
|
||||
callbacks: {
|
||||
async jwt({ token, user, profile }) {
|
||||
if (user) {
|
||||
token.id = user.id;
|
||||
}
|
||||
if (profile) {
|
||||
// Map Authentik groups/roles to our internal roles
|
||||
// This assumes Authentik sends groups in the profile
|
||||
const groups = (profile as any).groups || [];
|
||||
let role = "user";
|
||||
if (groups.includes("architools-admin")) role = "admin";
|
||||
else if (groups.includes("architools-manager")) role = "manager";
|
||||
|
||||
token.role = role;
|
||||
|
||||
// Map company based on groups or attributes
|
||||
let company = "group";
|
||||
if (groups.includes("company-beletage")) company = "beletage";
|
||||
else if (groups.includes("company-urban-switch"))
|
||||
company = "urban-switch";
|
||||
else if (groups.includes("company-studii-de-teren"))
|
||||
company = "studii-de-teren";
|
||||
|
||||
token.company = company;
|
||||
}
|
||||
return token;
|
||||
},
|
||||
async session({ session, token }) {
|
||||
if (session.user) {
|
||||
(session.user as any).id = token.id;
|
||||
(session.user as any).role = token.role || "user";
|
||||
(session.user as any).company = token.company || "group";
|
||||
}
|
||||
return session;
|
||||
},
|
||||
},
|
||||
pages: {
|
||||
// We can add custom sign-in pages later if needed
|
||||
},
|
||||
};
|
||||
|
||||
const handler = NextAuth(authOptions);
|
||||
|
||||
export { handler as GET, handler as POST };
|
||||
@@ -0,0 +1,130 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { prisma } from "@/core/storage/prisma";
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
const searchParams = request.nextUrl.searchParams;
|
||||
const namespace = searchParams.get("namespace");
|
||||
const key = searchParams.get("key");
|
||||
|
||||
if (!namespace) {
|
||||
return NextResponse.json(
|
||||
{ error: "Namespace is required" },
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
if (key) {
|
||||
// Get single item
|
||||
const item = await prisma.keyValueStore.findUnique({
|
||||
where: {
|
||||
namespace_key: {
|
||||
namespace,
|
||||
key,
|
||||
},
|
||||
},
|
||||
});
|
||||
return NextResponse.json({ value: item ? item.value : null });
|
||||
} else {
|
||||
// Get all items in namespace
|
||||
const items = await prisma.keyValueStore.findMany({
|
||||
where: { namespace },
|
||||
});
|
||||
|
||||
// Return as a record { [key]: value }
|
||||
const result: Record<string, any> = {};
|
||||
for (const item of items) {
|
||||
result[item.key] = item.value;
|
||||
}
|
||||
return NextResponse.json({ items: result });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Storage GET error:", error);
|
||||
return NextResponse.json(
|
||||
{ error: "Internal server error" },
|
||||
{ status: 500 },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
const body = await request.json();
|
||||
const { namespace, key, value } = body;
|
||||
|
||||
if (!namespace || !key) {
|
||||
return NextResponse.json(
|
||||
{ error: "Namespace and key are required" },
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
|
||||
await prisma.keyValueStore.upsert({
|
||||
where: {
|
||||
namespace_key: {
|
||||
namespace,
|
||||
key,
|
||||
},
|
||||
},
|
||||
update: {
|
||||
value,
|
||||
},
|
||||
create: {
|
||||
namespace,
|
||||
key,
|
||||
value,
|
||||
},
|
||||
});
|
||||
|
||||
return NextResponse.json({ success: true });
|
||||
} catch (error) {
|
||||
console.error("Storage POST error:", error);
|
||||
return NextResponse.json(
|
||||
{ error: "Internal server error" },
|
||||
{ status: 500 },
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export async function DELETE(request: NextRequest) {
|
||||
const searchParams = request.nextUrl.searchParams;
|
||||
const namespace = searchParams.get("namespace");
|
||||
const key = searchParams.get("key");
|
||||
|
||||
if (!namespace) {
|
||||
return NextResponse.json(
|
||||
{ error: "Namespace is required" },
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
if (key) {
|
||||
// Delete single item
|
||||
await prisma.keyValueStore
|
||||
.delete({
|
||||
where: {
|
||||
namespace_key: {
|
||||
namespace,
|
||||
key,
|
||||
},
|
||||
},
|
||||
})
|
||||
.catch(() => {
|
||||
// Ignore error if item doesn't exist
|
||||
});
|
||||
} else {
|
||||
// Clear namespace
|
||||
await prisma.keyValueStore.deleteMany({
|
||||
where: { namespace },
|
||||
});
|
||||
}
|
||||
return NextResponse.json({ success: true });
|
||||
} catch (error) {
|
||||
console.error("Storage DELETE error:", error);
|
||||
return NextResponse.json(
|
||||
{ error: "Internal server error" },
|
||||
{ status: 500 },
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user