Function bodies 345 total
resetSuperAdmin function · typescript · L45-L87 (43 LOC)scripts/reset-super-admin.ts
async function resetSuperAdmin() {
try {
console.log('Connecting to MongoDB...');
await mongoose.connect(MONGODB_URI);
console.log('Connected to MongoDB');
const SuperAdmin = mongoose.models.SuperAdmin || mongoose.model('SuperAdmin', SuperAdminSchema);
// Delete existing super admin
const deleted = await SuperAdmin.deleteOne({ email: SUPER_ADMIN_EMAIL.toLowerCase() });
if (deleted.deletedCount > 0) {
console.log('Deleted existing super admin');
}
// Hash password
const salt = await bcrypt.genSalt(12);
const hashedPassword = await bcrypt.hash(SUPER_ADMIN_PASSWORD, salt);
// Create new super admin
await SuperAdmin.create({
name: SUPER_ADMIN_NAME,
email: SUPER_ADMIN_EMAIL.toLowerCase(),
password: hashedPassword,
role: 'super_admin',
permissions: [],
isActive: true,
});
console.log('\nSuper Admin reset successfully!');
console.log('─'.repeat(50));
console.log('Email:', SUPERseedPlans function · typescript · L145-L179 (35 LOC)scripts/seed-plans.ts
async function seedPlans() {
try {
console.log('Connecting to MongoDB...');
await mongoose.connect(MONGODB_URI);
console.log('Connected to MongoDB');
const Plan = mongoose.models.Plan || mongoose.model('Plan', PlanSchema);
for (const planData of defaultPlans) {
const existing = await Plan.findOne({ slug: planData.slug });
if (existing) {
console.log(`Plan "${planData.name}" already exists, updating...`);
await Plan.updateOne({ slug: planData.slug }, planData);
} else {
await Plan.create(planData);
console.log(`Created plan: ${planData.name}`);
}
}
console.log('\n✅ Plans seeded successfully!');
console.log('\nAvailable plans:');
console.log('─'.repeat(50));
for (const plan of defaultPlans) {
console.log(` ${plan.name}: $${plan.price.monthly}/mo - ${plan.limits.maxPosts === -1 ? 'Unlimited' : plan.limits.maxPosts} posts`);
}
} catch (error) {
console.error('Error seedinseedSuperAdmin function · typescript · L45-L91 (47 LOC)scripts/seed-super-admin.ts
async function seedSuperAdmin() {
try {
console.log('Connecting to MongoDB...');
await mongoose.connect(MONGODB_URI);
console.log('Connected to MongoDB');
const SuperAdmin = mongoose.models.SuperAdmin || mongoose.model('SuperAdmin', SuperAdminSchema);
// Check if super admin already exists
const existing = await SuperAdmin.findOne({ email: SUPER_ADMIN_EMAIL.toLowerCase() });
if (existing) {
console.log('Super admin already exists with email:', SUPER_ADMIN_EMAIL);
console.log('If you need to reset the password, delete the existing admin first.');
process.exit(0);
}
// Hash password
const salt = await bcrypt.genSalt(12);
const hashedPassword = await bcrypt.hash(SUPER_ADMIN_PASSWORD, salt);
// Create super admin
const admin = await SuperAdmin.create({
name: SUPER_ADMIN_NAME,
email: SUPER_ADMIN_EMAIL.toLowerCase(),
password: hashedPassword,
role: 'super_admin',
permissions: [], // supAboutPage function · typescript · L13-L133 (121 LOC)src/app/about/page.tsx
export default async function AboutPage() {
const authors = await getAllAuthors();
return (
<div className="container mx-auto px-4 py-12">
<div className="mx-auto max-w-4xl">
{/* Hero */}
<div className="mb-16 text-center">
<h1 className="mb-4 text-4xl font-bold text-gray-900 dark:text-white md:text-5xl">
About {siteConfig.name}
</h1>
<p className="text-xl text-gray-600 dark:text-gray-400">
{siteConfig.description}
</p>
</div>
{/* Mission */}
<section className="mb-16">
<h2 className="mb-6 text-2xl font-bold text-gray-900 dark:text-white">
Our Mission
</h2>
<div className="prose prose-lg max-w-none dark:prose-invert">
<p className="text-gray-700 dark:text-gray-300">
We believe in sharing knowledge and helping developers grow. Our mission is to
provide high-quality, practical content thatfetchAuthor function · typescript · L37-L68 (32 LOC)src/app/admin/authors/[id]/edit/page.tsx
async function fetchAuthor() {
try {
const response = await fetch(`/api/admin/authors/${id}`);
const data = await response.json();
if (!response.ok) {
throw new Error(data.error || 'Failed to fetch author');
}
const author = data.data;
setFormData({
name: author.name || '',
email: author.email || '',
password: '', // Don't show existing password
avatar: author.avatar || '',
bio: author.bio || '',
role: author.role || 'Author',
canLogin: author.canLogin || false,
canPost: author.canPost || false,
social: {
twitter: author.social?.twitter || '',
github: author.social?.github || '',
linkedin: author.social?.linkedin || '',
website: author.social?.website || '',
},
});
} catch (err: any) {
setError(err.message);
} finally {
setFetching(falsNewAuthorPage function · typescript · L8-L320 (313 LOC)src/app/admin/authors/new/page.tsx
export default function NewAuthorPage() {
const router = useRouter();
const [loading, setLoading] = useState(false);
const [error, setError] = useState('');
const [showPassword, setShowPassword] = useState(false);
const [formData, setFormData] = useState({
name: '',
email: '',
password: '',
avatar: '',
bio: '',
role: 'Author',
canLogin: false,
canPost: false,
social: {
twitter: '',
github: '',
linkedin: '',
website: '',
},
});
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setLoading(true);
setError('');
// Validate password if canLogin is enabled
if (formData.canLogin && !formData.password) {
setError('Password is required when login is enabled');
setLoading(false);
return;
}
if (formData.password && formData.password.length < 6) {
setError('Password must be at least 6 characters');
setLoading(false);
return;
}
getValidImageSrc function · typescript · L12-L18 (7 LOC)src/app/admin/authors/page.tsx
function getValidImageSrc(image: string | undefined | null): string {
if (!image) return DEFAULT_AVATAR;
if (image.startsWith('/') || image.startsWith('http://') || image.startsWith('https://')) {
return image;
}
return DEFAULT_AVATAR;
}Repobility · severity-and-effort ranking · https://repobility.com
getAuthors function · typescript · L20-L48 (29 LOC)src/app/admin/authors/page.tsx
async function getAuthors(tenantId?: string) {
await connectDB();
// Build filter with tenant isolation
const filter: any = {};
if (tenantId) {
filter.tenantId = tenantId;
}
const authors = await Author.find(filter).sort({ createdAt: -1 }).lean();
// Get post count for each author
const authorsWithPostCount = await Promise.all(
authors.map(async (author: any) => {
const postFilter: any = { author: author._id };
if (tenantId) {
postFilter.tenantId = tenantId;
}
const postCount = await Post.countDocuments(postFilter);
return {
...author,
_id: author._id.toString(),
postCount,
};
})
);
return authorsWithPostCount;
}AdminAuthorsPage function · typescript · L50-L120 (71 LOC)src/app/admin/authors/page.tsx
export default async function AdminAuthorsPage() {
const session = await getServerSession(authOptions);
const tenantId = session?.user?.tenantId;
const authors = await getAuthors(tenantId);
return (
<div>
<div className="mb-8 flex items-center justify-between">
<h1 className="text-2xl font-bold text-gray-900 dark:text-white">Authors</h1>
<Link
href="/admin/authors/new"
className="flex items-center gap-2 rounded-lg bg-primary-600 px-4 py-2 text-sm font-medium text-white hover:bg-primary-700"
>
<Plus className="h-4 w-4" />
New Author
</Link>
</div>
<div className="grid gap-6 sm:grid-cols-2 lg:grid-cols-3">
{authors.length === 0 ? (
<div className="col-span-full rounded-xl bg-white p-12 text-center shadow-sm dark:bg-gray-800">
<p className="text-gray-500">No authors yet. Create your first author!</p>
</div>
) : (
authors.map((autAdminLayoutContent function · typescript · L22-L256 (235 LOC)src/app/admin/layout.tsx
function AdminLayoutContent({ children }: { children: React.ReactNode }) {
const { data: session, status } = useSession();
const router = useRouter();
const pathname = usePathname();
const [sidebarOpen, setSidebarOpen] = useState(false);
const [tenantSlug, setTenantSlug] = useState<string | null>(null);
const [redirecting, setRedirecting] = useState(false);
const isAdmin = session?.user?.role === 'admin';
const isEditor = session?.user?.role === 'editor';
const isAuthor = session?.user?.role === 'author';
const canPost = session?.user?.canPost;
const userTenantId = session?.user?.tenantId;
// Fetch tenant slug for redirect
useEffect(() => {
if (userTenantId && !tenantSlug) {
fetch('/api/user/tenant')
.then(res => res.json())
.then(data => {
if (data.success && data.data?.slug) {
setTenantSlug(data.data.slug);
}
})
.catch(console.error);
}
}, [userTenantId, tenantSlug]);
useEffAdminLayout function · typescript · L258-L264 (7 LOC)src/app/admin/layout.tsx
export default function AdminLayout({ children }: { children: React.ReactNode }) {
return (
<SessionProvider>
<AdminLayoutContent>{children}</AdminLayoutContent>
</SessionProvider>
);
}AdminLoginContent function · typescript · L9-L255 (247 LOC)src/app/admin/login/page.tsx
function AdminLoginContent() {
const { data: session, status } = useSession();
const router = useRouter();
const searchParams = useSearchParams();
const [loginType, setLoginType] = useState<'admin' | 'author'>('author');
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [showPassword, setShowPassword] = useState(false);
const [loading, setLoading] = useState(false);
const [error, setError] = useState('');
const [showForgotPassword, setShowForgotPassword] = useState(false);
// Get error from URL params
const urlError = searchParams.get('error');
useEffect(() => {
if (session?.user?.role === 'admin' || session?.user?.role === 'editor' || session?.user?.role === 'author') {
router.push('/admin');
}
}, [session, router]);
const handleAuthorLogin = async (e: React.FormEvent) => {
e.preventDefault();
setLoading(true);
setError('');
try {
const result = await signIn('credentials', {LoadingFallback function · typescript · L257-L263 (7 LOC)src/app/admin/login/page.tsx
function LoadingFallback() {
return (
<div className="flex min-h-screen items-center justify-center bg-gray-100 dark:bg-gray-900">
<div className="h-8 w-8 animate-spin rounded-full border-4 border-primary-600 border-t-transparent" />
</div>
);
}AdminLoginPage function · typescript · L265-L271 (7 LOC)src/app/admin/login/page.tsx
export default function AdminLoginPage() {
return (
<Suspense fallback={<LoadingFallback />}>
<AdminLoginContent />
</Suspense>
);
}getStats function · typescript · L9-L50 (42 LOC)src/app/admin/page.tsx
async function getStats(authorId?: string, tenantId?: string) {
await connectDB();
// Build filter with tenant isolation
const baseFilter: any = {};
// CRITICAL: Always filter by tenantId for data isolation
if (tenantId) {
baseFilter.tenantId = tenantId;
}
// If authorId is provided, filter stats by that author
if (authorId) {
baseFilter.author = authorId;
}
const [totalPosts, publishedPosts, totalAuthors, totalViews] = await Promise.all([
Post.countDocuments(baseFilter),
Post.countDocuments({ ...baseFilter, published: true }),
authorId ? 1 : Author.countDocuments(tenantId ? { tenantId } : {}),
Post.aggregate([
{ $match: baseFilter },
{ $group: { _id: null, total: { $sum: '$views' } } },
]),
]);
const recentPosts = await Post.find(baseFilter)
.sort({ createdAt: -1 })
.limit(5)
.populate('author', 'name')
.lean();
return {
totalPosts,
publishedPosts,
draftPosts: totalPosts - publishedPostOpen data scored by Repobility · https://repobility.com
AdminDashboard function · typescript · L52-L170 (119 LOC)src/app/admin/page.tsx
export default async function AdminDashboard() {
const session = await getServerSession(authOptions);
// For authors, only show their own posts
const isAuthor = session?.user?.role === 'author';
const canPost = session?.user?.canPost;
const authorId = isAuthor ? session?.user?.authorId : undefined;
const tenantId = session?.user?.tenantId;
const stats = await getStats(authorId, tenantId);
// Build stat cards based on role
const statCards = isAuthor
? [
{ label: 'Your Posts', value: stats.totalPosts, icon: FileText, color: 'bg-blue-500' },
{ label: 'Published', value: stats.publishedPosts, icon: TrendingUp, color: 'bg-green-500' },
{ label: 'Drafts', value: stats.draftPosts, icon: FileText, color: 'bg-yellow-500' },
{ label: 'Total Views', value: stats.totalViews, icon: Eye, color: 'bg-orange-500' },
]
: [
{ label: 'Total Posts', value: stats.totalPosts, icon: FileText, color: 'bg-blue-500' },
{ label: 'PgetPost function · typescript · L13-L30 (18 LOC)src/app/admin/posts/[id]/edit/page.tsx
async function getPost(id: string) {
await connectDB();
const post = await Post.findById(id).lean();
if (!post) return null;
return {
id: (post as any)._id.toString(),
title: (post as any).title,
description: (post as any).description,
content: (post as any).content,
image: (post as any).image,
category: (post as any).category,
tags: (post as any).tags,
author: (post as any).author.toString(),
featured: (post as any).featured,
published: (post as any).published,
};
}getAuthors function · typescript · L32-L39 (8 LOC)src/app/admin/posts/[id]/edit/page.tsx
async function getAuthors() {
await connectDB();
const authors = await Author.find().lean();
return authors.map((author: any) => ({
id: author._id.toString(),
name: author.name,
}));
}EditPostPage function · typescript · L41-L66 (26 LOC)src/app/admin/posts/[id]/edit/page.tsx
export default async function EditPostPage({ params }: EditPostPageProps) {
const session = await getServerSession(authOptions);
const isAuthor = session?.user?.role === 'author';
const authorId = isAuthor ? session?.user?.authorId : undefined;
const { id } = await params;
const [post, authors] = await Promise.all([getPost(id), getAuthors()]);
if (!post) {
notFound();
}
// Authors can only edit their own posts
if (isAuthor && post.author !== authorId) {
redirect('/admin/posts');
}
return (
<div>
<h1 className="mb-8 text-2xl font-bold text-gray-900 dark:text-white">
Edit Post
</h1>
<PostForm authors={authors} initialData={post} fixedAuthorId={authorId} />
</div>
);
}AdminPostsLoading function · typescript · L1-L59 (59 LOC)src/app/admin/posts/loading.tsx
export default function AdminPostsLoading() {
return (
<div>
<div className="mb-8 flex items-center justify-between">
<div className="h-8 w-32 bg-gray-200 dark:bg-gray-700 rounded animate-pulse" />
<div className="h-10 w-28 bg-gray-200 dark:bg-gray-700 rounded animate-pulse" />
</div>
<div className="rounded-xl bg-white shadow-sm dark:bg-gray-800">
<div className="overflow-x-auto">
<table className="w-full">
<thead className="border-b bg-gray-50 dark:border-gray-700 dark:bg-gray-900">
<tr>
<th className="px-6 py-4 text-left text-sm font-medium text-gray-500">Title</th>
<th className="px-6 py-4 text-left text-sm font-medium text-gray-500">Author</th>
<th className="px-6 py-4 text-left text-sm font-medium text-gray-500">Category</th>
<th className="px-6 py-4 text-left text-sm font-medium text-gray-500">Status</th>
<th classNagetAuthors function · typescript · L7-L14 (8 LOC)src/app/admin/posts/new/page.tsx
async function getAuthors() {
await connectDB();
const authors = await Author.find().lean();
return authors.map((author: any) => ({
id: author._id.toString(),
name: author.name,
}));
}NewPostPage function · typescript · L16-L31 (16 LOC)src/app/admin/posts/new/page.tsx
export default async function NewPostPage() {
const session = await getServerSession(authOptions);
const isAuthor = session?.user?.role === 'author';
const authorId = isAuthor ? session?.user?.authorId : undefined;
const authors = await getAuthors();
return (
<div>
<h1 className="mb-8 text-2xl font-bold text-gray-900 dark:text-white">
Create New Post
</h1>
<PostForm authors={authors} fixedAuthorId={authorId} />
</div>
);
}getPosts function · typescript · L12-L41 (30 LOC)src/app/admin/posts/page.tsx
async function getPosts(authorId?: string, tenantId?: string, page: number = 1) {
await connectDB();
// Build filter with tenant isolation
const filter: Record<string, unknown> = {};
// CRITICAL: Always filter by tenantId for data isolation
if (tenantId) {
filter.tenantId = tenantId;
}
if (authorId) {
filter.author = authorId;
}
const skip = (page - 1) * POSTS_PER_PAGE;
// Get posts with pagination and total count in parallel
const [posts, total] = await Promise.all([
Post.find(filter)
.sort({ createdAt: -1 })
.skip(skip)
.limit(POSTS_PER_PAGE)
.populate('author', 'name')
.lean(),
Post.countDocuments(filter),
]);
return { posts, total, page, totalPages: Math.ceil(total / POSTS_PER_PAGE) };
}Source: Repobility analyzer · https://repobility.com
AdminPostsPage function · typescript · L43-L207 (165 LOC)src/app/admin/posts/page.tsx
export default async function AdminPostsPage({
searchParams,
}: {
searchParams: Promise<{ page?: string }>;
}) {
const params = await searchParams;
const currentPage = Math.max(1, parseInt(params.page || '1', 10));
const session = await getServerSession(authOptions);
const isAuthor = session?.user?.role === 'author';
const authorId = isAuthor ? session?.user?.authorId : undefined;
const tenantId = session?.user?.tenantId;
const { posts, total, totalPages } = await getPosts(authorId, tenantId, currentPage);
return (
<div>
<div className="mb-8 flex items-center justify-between">
<h1 className="text-2xl font-bold text-gray-900 dark:text-white">
{isAuthor ? 'My Posts' : 'Posts'}
</h1>
<Link
href="/admin/posts/new"
className="flex items-center gap-2 rounded-lg bg-primary-600 px-4 py-2 text-sm font-medium text-white hover:bg-primary-700"
>
<Plus className="h-4 w-4" />
New Post
UsersPage function · typescript · L28-L319 (292 LOC)src/app/admin/users/page.tsx
export default function UsersPage() {
const [users, setUsers] = useState<UserData[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState('');
const [searchQuery, setSearchQuery] = useState('');
const [roleFilter, setRoleFilter] = useState<string>('all');
const [updating, setUpdating] = useState<string | null>(null);
useEffect(() => {
fetchUsers();
}, []);
const fetchUsers = async () => {
try {
const response = await fetch('/api/admin/users');
const data = await response.json();
if (data.success) {
setUsers(data.data);
} else {
setError(data.error || 'Failed to fetch users');
}
} catch (err) {
setError('Failed to fetch users');
} finally {
setLoading(false);
}
};
const handleRoleChange = async (userId: string, newRole: string) => {
setUpdating(userId);
try {
const response = await fetch('/api/admin/users', {
method: 'PATCH',
GET function · typescript · L11-L162 (152 LOC)src/app/api/admin/analytics/route.ts
export async function GET() {
try {
const sessionResult = await getOwnerSession();
if (!sessionResult || !sessionResult.user) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
const { user } = sessionResult;
await connectDB();
// Get tenant context
const { tenantId } = await getTenantFromHeaders();
const userTenantId = user.tenantId || tenantId;
if (!userTenantId) {
return NextResponse.json({ error: 'Tenant not found' }, { status: 404 });
}
// Check if analytics is allowed for this tenant
const tenant = await Tenant.findById(userTenantId).select('plan limits').lean();
if (!tenant) {
return NextResponse.json({ error: 'Tenant not found' }, { status: 404 });
}
// Analytics available for all paid plans (Basic, Pro, Enterprise)
const analyticsAllowed = tenant.limits?.analyticsAllowed ||
tenant.plan === 'basic' ||
tenant.plan === 'pro' ||
tenant.plan === 'enteGET function · typescript · L11-L31 (21 LOC)src/app/api/admin/authors/[id]/route.ts
export async function GET(request: NextRequest, { params }: RouteParams) {
try {
const sessionResult = await getOwnerSession();
if (!sessionResult || !sessionResult.user) {
return NextResponse.json({ error: 'Unauthorized - Admin access required' }, { status: 401 });
}
const { id } = await params;
await connectDB();
const author = await Author.findById(id);
if (!author) {
return NextResponse.json({ error: 'Author not found' }, { status: 404 });
}
return NextResponse.json({ success: true, data: author });
} catch (error: any) {
return NextResponse.json({ error: error.message }, { status: 500 });
}
}PUT function · typescript · L33-L73 (41 LOC)src/app/api/admin/authors/[id]/route.ts
export async function PUT(request: NextRequest, { params }: RouteParams) {
try {
const sessionResult = await getOwnerSession();
if (!sessionResult || !sessionResult.user) {
return NextResponse.json({ error: 'Unauthorized - Admin access required' }, { status: 401 });
}
const { id } = await params;
const body = await request.json();
const { name, email, password, avatar, bio, role, canLogin, canPost, social } = body;
await connectDB();
const author = await Author.findById(id);
if (!author) {
return NextResponse.json({ error: 'Author not found' }, { status: 404 });
}
// Update fields explicitly
if (name !== undefined) author.name = name;
if (email !== undefined) author.email = email;
if (avatar !== undefined) author.avatar = avatar;
if (bio !== undefined) author.bio = bio;
if (role !== undefined) author.role = role;
if (canLogin !== undefined) author.canLogin = canLogin;
if (canPost !== undefined) aDELETE function · typescript · L75-L95 (21 LOC)src/app/api/admin/authors/[id]/route.ts
export async function DELETE(request: NextRequest, { params }: RouteParams) {
try {
const sessionResult = await getOwnerSession();
if (!sessionResult || !sessionResult.user) {
return NextResponse.json({ error: 'Unauthorized - Admin access required' }, { status: 401 });
}
const { id } = await params;
await connectDB();
const author = await Author.findByIdAndDelete(id);
if (!author) {
return NextResponse.json({ error: 'Author not found' }, { status: 404 });
}
return NextResponse.json({ success: true, message: 'Author deleted' });
} catch (error: any) {
return NextResponse.json({ error: error.message }, { status: 500 });
}
}GET function · typescript · L10-L65 (56 LOC)src/app/api/admin/authors/route.ts
export async function GET(request: NextRequest) {
try {
const sessionResult = await getOwnerSession();
if (!sessionResult || !sessionResult.user) {
return NextResponse.json({ error: 'Unauthorized - Admin access required' }, { status: 401 });
}
const { user } = sessionResult;
// Parse pagination parameters
const searchParams = request.nextUrl.searchParams;
const { page, limit } = validatePagination(
searchParams.get('page') || undefined,
searchParams.get('limit') || ADMIN_PAGE_SIZE,
MAX_PAGE_SIZE
);
const skip = (page - 1) * limit;
await connectDB();
// Get tenant context
const { tenantId } = await getTenantFromHeaders();
const userTenantId = user.tenantId || tenantId;
// Build filter with tenant isolation
const filter: any = {};
if (userTenantId) {
filter.tenantId = userTenantId;
}
// Execute count and find in parallel for efficiency
const [total, authors] = await Promise.POST function · typescript · L67-L134 (68 LOC)src/app/api/admin/authors/route.ts
export async function POST(request: NextRequest) {
try {
const sessionResult = await getOwnerSession();
if (!sessionResult || !sessionResult.user) {
return NextResponse.json({ error: 'Unauthorized - Admin access required' }, { status: 401 });
}
const { user } = sessionResult;
await connectDB();
// Get tenant context
const { tenantId } = await getTenantFromHeaders();
const userTenantId = user.tenantId || tenantId;
// Check tenant limits if tenantId exists
if (userTenantId) {
const limitCheck = await checkTenantLimits(userTenantId, 'authors');
if (!limitCheck.allowed) {
return NextResponse.json({ error: limitCheck.message }, { status: 403 });
}
}
const body = await request.json();
const { name, email, password, avatar, bio, role, canLogin, canPost, social } = body;
if (!name || !email) {
return NextResponse.json({ error: 'Name and email are required' }, { status: 400 });
}
consWant fix-PRs on findings? Install Repobility's GitHub App · github.com/apps/repobility-bot
GET function · typescript · L6-L54 (49 LOC)src/app/api/admin/available-plans/route.ts
export async function GET() {
try {
const sessionResult = await getAdminSession();
if (!sessionResult || !sessionResult.user) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
await connectDB();
// Get all active plans sorted by sortOrder
const plans = await Plan.find({ isActive: true })
.sort({ sortOrder: 1 })
.select('name slug description price limits features sortOrder')
.lean();
// Format plans for the billing page
const formattedPlans = plans.map((plan) => ({
_id: plan._id,
name: plan.name,
slug: plan.slug,
description: plan.description,
price: {
monthly: plan.price.monthly,
yearly: plan.price.yearly,
},
limits: {
maxPosts: plan.limits.maxPosts,
maxAuthors: plan.limits.maxAuthors,
maxStorage: plan.limits.maxStorage,
maxUsers: plan.limits.maxUsers,
customDomainAllowed: plan.limits.customDomainAllowedGET function · typescript · L14-L46 (33 LOC)src/app/api/admin/categories/[id]/route.ts
export async function GET(request: NextRequest, { params }: RouteParams) {
try {
const sessionResult = await getAdminSession();
if (!sessionResult || !sessionResult.user) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
const { id } = await params;
await connectDB();
const category = await Category.findById(id).lean();
if (!category) {
return NextResponse.json({ error: 'Category not found' }, { status: 404 });
}
const postCount = await Post.countDocuments({ category: (category as any).slug, published: true });
return NextResponse.json({
_id: (category as any)._id.toString(),
name: (category as any).name,
slug: (category as any).slug,
description: (category as any).description,
color: (category as any).color,
isSystem: (category as any).isSystem || false,
postCount,
});
} catch (error: any) {
console.error('Error fetching category:', error);
returnPUT function · typescript · L49-L123 (75 LOC)src/app/api/admin/categories/[id]/route.ts
export async function PUT(request: NextRequest, { params }: RouteParams) {
try {
const sessionResult = await getAdminSession();
if (!sessionResult || !sessionResult.user) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
const { id } = await params;
await connectDB();
const body = await request.json();
const { name, description, color } = body;
if (!name || !name.trim()) {
return NextResponse.json({ error: 'Category name is required' }, { status: 400 });
}
const category = await Category.findById(id);
if (!category) {
return NextResponse.json({ error: 'Category not found' }, { status: 404 });
}
// Prevent renaming system categories (but allow color/description changes)
if (category.isSystem && name.trim().toLowerCase() !== category.name.toLowerCase()) {
return NextResponse.json(
{ error: 'Cannot rename system category. You can only change its color and description.' DELETE function · typescript · L126-L168 (43 LOC)src/app/api/admin/categories/[id]/route.ts
export async function DELETE(request: NextRequest, { params }: RouteParams) {
try {
const sessionResult = await getAdminSession();
if (!sessionResult || !sessionResult.user) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
const { id } = await params;
await connectDB();
const category = await Category.findById(id);
if (!category) {
return NextResponse.json({ error: 'Category not found' }, { status: 404 });
}
// Prevent deleting system categories (like "Review")
if (category.isSystem) {
return NextResponse.json(
{ error: 'Cannot delete system category. This category is required for the platform to function.' },
{ status: 400 }
);
}
// Check if category has posts
const postCount = await Post.countDocuments({ category: category.slug });
if (postCount > 0) {
return NextResponse.json(
{ error: `Cannot delete category with ${postCount} posts. Move orGET function · typescript · L10-L105 (96 LOC)src/app/api/admin/categories/route.ts
export async function GET() {
try {
const sessionResult = await getAdminSession();
if (!sessionResult || !sessionResult.user) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
const { user } = sessionResult;
await connectDB();
// Get tenant context
const { tenantId } = await getTenantFromHeaders();
const userTenantId = user.tenantId || tenantId;
// Build filter with tenant isolation
const filter: any = {};
if (userTenantId) {
filter.tenantId = userTenantId;
}
// Get categories from DB
const dbCategories = await Category.find(filter).sort({ name: 1 }).lean();
const dbSlugs = new Set(dbCategories.map((c: any) => c.slug));
// Get all unique categories from posts (including unpublished) for this tenant
const postCategories = await Post.distinct('category', filter);
// Find orphaned categories (exist in posts but not in Category collection)
const orphanedCategories = posPOST function · typescript · L108-L163 (56 LOC)src/app/api/admin/categories/route.ts
export async function POST(request: NextRequest) {
try {
const sessionResult = await getAdminSession();
if (!sessionResult || !sessionResult.user) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
const { user } = sessionResult;
await connectDB();
// Get tenant context
const { tenantId } = await getTenantFromHeaders();
const userTenantId = user.tenantId || tenantId;
const body = await request.json();
const { name, description, color } = body;
if (!name || !name.trim()) {
return NextResponse.json({ error: 'Category name is required' }, { status: 400 });
}
// Check if category already exists for this tenant
const existingQuery: any = {
name: { $regex: new RegExp(`^${escapeRegex(name.trim())}$`, 'i') }
};
if (userTenantId) {
existingQuery.tenantId = userTenantId;
}
const existing = await Category.findOne(existingQuery);
if (existing) {
return NextResPOST function · typescript · L6-L83 (78 LOC)src/app/api/admin/coupon/validate/route.ts
export async function POST(request: NextRequest) {
try {
const sessionResult = await getOwnerSession();
if (!sessionResult || !sessionResult.user) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
const { user } = sessionResult;
const tenantId = user.tenantId;
if (!tenantId) {
return NextResponse.json({ error: 'Tenant not found' }, { status: 404 });
}
await connectDB();
const body = await request.json();
const { code, planSlug, originalPrice } = body;
if (!code || !planSlug || originalPrice === undefined) {
return NextResponse.json(
{ error: 'Missing required fields: code, planSlug, originalPrice' },
{ status: 400 }
);
}
// Validate plan slug
if (!['basic', 'pro', 'enterprise'].includes(planSlug)) {
return NextResponse.json(
{ error: 'Invalid plan' },
{ status: 400 }
);
}
const result = await validateCoupon({
code,
GET function · typescript · L15-L72 (58 LOC)src/app/api/admin/custom-domain/route.ts
export async function GET() {
try {
const sessionResult = await getOwnerSession();
if (!sessionResult || !sessionResult.user) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
const { user } = sessionResult;
const tenantId = user.tenantId;
if (!tenantId) {
return NextResponse.json({ error: 'Tenant not found' }, { status: 404 });
}
await connectDB();
const tenant = await Tenant.findById(tenantId)
.select('customDomain customDomainStatus customDomainVerifiedAt limits.customDomainAllowed plan slug')
.lean();
if (!tenant) {
return NextResponse.json({ error: 'Tenant not found' }, { status: 404 });
}
return NextResponse.json({
success: true,
data: {
customDomain: tenant.customDomain || null,
status: tenant.customDomainStatus || 'none',
verifiedAt: tenant.customDomainVerifiedAt || null,
allowed: tenant.limits?.customDomainAllowed || false,
Repobility · severity-and-effort ranking · https://repobility.com
POST function · typescript · L75-L181 (107 LOC)src/app/api/admin/custom-domain/route.ts
export async function POST(request: NextRequest) {
try {
const sessionResult = await getOwnerSession();
if (!sessionResult || !sessionResult.user) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
const { user } = sessionResult;
const tenantId = user.tenantId;
if (!tenantId) {
return NextResponse.json({ error: 'Tenant not found' }, { status: 404 });
}
await connectDB();
const tenant = await Tenant.findById(tenantId);
if (!tenant) {
return NextResponse.json({ error: 'Tenant not found' }, { status: 404 });
}
// Check if custom domain is allowed for this plan
if (!tenant.limits?.customDomainAllowed) {
return NextResponse.json(
{ error: 'Custom domains are only available on Pro and Enterprise plans. Please upgrade.' },
{ status: 403 }
);
}
const body = await request.json();
const { domain } = body;
if (!domain) {
return NextResponse.jsonPATCH function · typescript · L184-L278 (95 LOC)src/app/api/admin/custom-domain/route.ts
export async function PATCH() {
try {
const sessionResult = await getOwnerSession();
if (!sessionResult || !sessionResult.user) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
const { user } = sessionResult;
const tenantId = user.tenantId;
if (!tenantId) {
return NextResponse.json({ error: 'Tenant not found' }, { status: 404 });
}
await connectDB();
const tenant = await Tenant.findById(tenantId);
if (!tenant) {
return NextResponse.json({ error: 'Tenant not found' }, { status: 404 });
}
if (!tenant.customDomain) {
return NextResponse.json(
{ error: 'No custom domain configured' },
{ status: 400 }
);
}
const domain = tenant.customDomain;
let verified = false;
let verificationMethod = '';
// Try CNAME verification
try {
const cnameRecords = await resolveCname(domain);
if (cnameRecords.some((record: string) =>
record.DELETE function · typescript · L281-L317 (37 LOC)src/app/api/admin/custom-domain/route.ts
export async function DELETE() {
try {
const sessionResult = await getOwnerSession();
if (!sessionResult || !sessionResult.user) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
const { user } = sessionResult;
const tenantId = user.tenantId;
if (!tenantId) {
return NextResponse.json({ error: 'Tenant not found' }, { status: 404 });
}
await connectDB();
await Tenant.findByIdAndUpdate(tenantId, {
$unset: {
customDomain: 1,
customDomainStatus: 1,
customDomainVerifiedAt: 1
},
});
return NextResponse.json({
success: true,
message: 'Custom domain removed successfully',
});
} catch (error) {
console.error('Error removing custom domain:', error);
return NextResponse.json(
{ error: 'Failed to remove custom domain' },
{ status: 500 }
);
}
}getDnsHost function · typescript · L320-L326 (7 LOC)src/app/api/admin/custom-domain/route.ts
function getDnsHost(domain: string): string {
const parts = domain.split('.');
if (parts.length > 2) {
return parts[0]; // Return subdomain (e.g., 'blog' from 'blog.example.com')
}
return '@'; // Root domain
}GET function · typescript · L8-L83 (76 LOC)src/app/api/admin/dashboard/route.ts
export async function GET(request: NextRequest) {
try {
const sessionResult = await getAdminSession();
if (!sessionResult || !sessionResult.user) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
const { user } = sessionResult;
const isAuthor = user.role === 'author';
await connectDB();
// Get tenant ID from session
const tenantId = user.tenantId;
// Build filter
const filter: any = {};
if (tenantId) {
filter.tenantId = tenantId;
}
// Authors only see their own posts in dashboard stats
const postFilter: any = { ...filter };
if (isAuthor && user.authorId) {
postFilter.author = user.authorId;
}
// Get stats
const [
totalPosts,
publishedPosts,
totalCategories,
totalAuthors,
viewsAggregate,
recentPosts,
] = await Promise.all([
Post.countDocuments(postFilter),
Post.countDocuments({ ...postFilter, published: true }),
GET function · typescript · L8-L33 (26 LOC)src/app/api/admin/pages/[id]/route.ts
export async function GET(
request: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
try {
const session = await getServerSession(authOptions);
if (!session?.user) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
const tenantId = (session.user as any).tenantId;
const { id } = await params;
await connectDB();
const page = await Page.findOne({ _id: id, tenantId }).lean();
if (!page) {
return NextResponse.json({ error: 'Page not found' }, { status: 404 });
}
return NextResponse.json({ success: true, data: page });
} catch (error: any) {
console.error('Error fetching page:', error);
return NextResponse.json({ error: error.message }, { status: 500 });
}
}PUT function · typescript · L36-L85 (50 LOC)src/app/api/admin/pages/[id]/route.ts
export async function PUT(
request: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
try {
const session = await getServerSession(authOptions);
if (!session?.user) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
const userRole = (session.user as any).role;
if (userRole === 'author') {
return NextResponse.json({ error: 'Authors cannot edit pages' }, { status: 403 });
}
const tenantId = (session.user as any).tenantId;
const { id } = await params;
const body = await request.json();
await connectDB();
// Check if slug is being changed and if new slug already exists
if (body.slug) {
const existingPage = await Page.findOne({
tenantId,
slug: body.slug.toLowerCase(),
_id: { $ne: id },
});
if (existingPage) {
return NextResponse.json({ error: 'A page with this slug already exists' }, { status: 400 });
}
body.slug = body.sluDELETE function · typescript · L88-L118 (31 LOC)src/app/api/admin/pages/[id]/route.ts
export async function DELETE(
request: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
try {
const session = await getServerSession(authOptions);
if (!session?.user) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
const userRole = (session.user as any).role;
if (userRole === 'author') {
return NextResponse.json({ error: 'Authors cannot delete pages' }, { status: 403 });
}
const tenantId = (session.user as any).tenantId;
const { id } = await params;
await connectDB();
const page = await Page.findOneAndDelete({ _id: id, tenantId });
if (!page) {
return NextResponse.json({ error: 'Page not found' }, { status: 404 });
}
return NextResponse.json({ success: true, message: 'Page deleted' });
} catch (error: any) {
console.error('Error deleting page:', error);
return NextResponse.json({ error: error.message }, { status: 500 });
}
}Open data scored by Repobility · https://repobility.com
GET function · typescript · L8-L30 (23 LOC)src/app/api/admin/pages/route.ts
export async function GET(request: NextRequest) {
try {
const session = await getServerSession(authOptions);
if (!session?.user) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
const tenantId = (session.user as any).tenantId;
if (!tenantId) {
return NextResponse.json({ error: 'No tenant associated' }, { status: 400 });
}
await connectDB();
const pages = await Page.find({ tenantId })
.sort({ createdAt: -1 })
.lean();
return NextResponse.json({ success: true, data: pages });
} catch (error: any) {
console.error('Error fetching pages:', error);
return NextResponse.json({ error: error.message }, { status: 500 });
}
}POST function · typescript · L33-L84 (52 LOC)src/app/api/admin/pages/route.ts
export async function POST(request: NextRequest) {
try {
const session = await getServerSession(authOptions);
if (!session?.user) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
const userRole = (session.user as any).role;
if (userRole === 'author') {
return NextResponse.json({ error: 'Authors cannot create pages' }, { status: 403 });
}
const tenantId = (session.user as any).tenantId;
if (!tenantId) {
return NextResponse.json({ error: 'No tenant associated' }, { status: 400 });
}
const body = await request.json();
const { title, slug, template, content, sections, heroImage, heroTitle, heroSubtitle, showInNavbar, showInFooter, published } = body;
if (!title || !slug) {
return NextResponse.json({ error: 'Title and slug are required' }, { status: 400 });
}
// Check if slug already exists
await connectDB();
const existingPage = await Page.findOne({ tenantId, slug: slug.tGET function · typescript · L11-L86 (76 LOC)src/app/api/admin/plan-request/route.ts
export async function GET() {
try {
const sessionResult = await getOwnerSession();
if (!sessionResult || !sessionResult.user) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
const { user } = sessionResult;
const tenantId = user.tenantId;
if (!tenantId) {
return NextResponse.json({ error: 'Tenant not found' }, { status: 404 });
}
await connectDB();
// Get the current pending request for this tenant (if any)
const pendingRequest = await PlanRequest.findOne({
tenantId,
status: 'pending',
})
.sort({ createdAt: -1 })
.lean();
// Get platform settings for contact info
let platformSettings = await PlatformSettings.findOne().lean() as IPlatformSettings | null;
if (!platformSettings) {
platformSettings = {
contactEmail: '[email protected]',
} as IPlatformSettings;
}
// Get tenant's current plan info including billing
const tenant = awpage 1 / 7next ›