Skip to content

Commit 6aa601e

Browse files
committed
feat: complete backend setup
- Project scaffold and folder architecture - Neon PostgreSQL + Drizzle ORM schema (7 tables) - NextAuth.js v5 credentials auth + admin login page - Middleware route protection for /admin and /api/admin - Contact form API with Resend email notification - Career application API with email notification - Cloudinary image upload API (admin protected) - Full admin CRUD API routes (team, projects, blog, careers, applications, messages) - GitHub Actions CI workflow (type check + lint + build) - PR template, issue templates, CODEOWNERS - Environment variable setup
1 parent 2ecf410 commit 6aa601e

58 files changed

Lines changed: 1069 additions & 297 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

AGENTS.md

Lines changed: 113 additions & 107 deletions
Large diffs are not rendered by default.

CLAUDE.md

Lines changed: 113 additions & 107 deletions
Large diffs are not rendered by default.

middleware.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
1+
import type { NextRequest } from "next/server";
12
import { NextResponse } from "next/server";
2-
import { auth } from "@/lib/auth";
3+
import { getToken } from "next-auth/jwt";
34

4-
export const middleware = auth((request) => {
5+
export async function middleware(request: NextRequest) {
56
const { pathname } = request.nextUrl;
6-
const isAuthenticated = Boolean(request.auth);
7+
const token = await getToken({
8+
req: request,
9+
secret: process.env.NEXTAUTH_SECRET,
10+
});
11+
const isAuthenticated = Boolean(token);
712

813
if (pathname.startsWith("/api/admin") && !isAuthenticated) {
914
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
@@ -22,7 +27,7 @@ export const middleware = auth((request) => {
2227
}
2328

2429
return NextResponse.next();
25-
});
30+
}
2631

2732
export const config = {
2833
matcher: ["/admin/:path*", "/api/admin/:path*"],

scripts/seed-admin.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import { existsSync, readFileSync } from "node:fs";
2+
import { hash } from "bcryptjs";
3+
4+
function loadLocalEnv() {
5+
if (!existsSync(".env.local")) {
6+
return;
7+
}
8+
9+
const envFile = readFileSync(".env.local", "utf8");
10+
11+
for (const line of envFile.split(/\r?\n/)) {
12+
const match = line.match(/^([^#=]+)=(.*)$/);
13+
14+
if (match) {
15+
const value = match[2].trim().replace(/^(['"])(.*)\1$/, "$2");
16+
process.env[match[1].trim()] ??= value;
17+
}
18+
}
19+
}
20+
21+
async function main() {
22+
loadLocalEnv();
23+
24+
const [email, password] = process.argv.slice(2);
25+
26+
if (!email || !password) {
27+
throw new Error(
28+
"Usage: pnpm dlx tsx scripts/seed-admin.ts admin@example.com secure-password",
29+
);
30+
}
31+
32+
const { adminUsers, db } = await import("@/db");
33+
const password_hash = await hash(password, 12);
34+
35+
await db.insert(adminUsers).values({
36+
email,
37+
password_hash,
38+
});
39+
40+
console.log(`Admin user created successfully: ${email}`);
41+
console.log(
42+
"Run this once only. Delete this script or remove the credentials after use.",
43+
);
44+
}
45+
46+
main().catch((error: unknown) => {
47+
console.error(error);
48+
process.exit(1);
49+
});

src/app/(public)/about/page.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
1-
// Placeholder
1+
export default function AboutPage() {
2+
return null;
3+
}
Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
1-
// Placeholder
1+
export default function BlogPostPage() {
2+
return null;
3+
}

src/app/(public)/blog/page.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
1-
// Placeholder
1+
export default function BlogPage() {
2+
return null;
3+
}

src/app/(public)/careers/page.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
1-
// Placeholder
1+
export default function CareersPage() {
2+
return null;
3+
}

src/app/(public)/contact/page.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
1-
// Placeholder
1+
export default function ContactPage() {
2+
return null;
3+
}

src/app/(public)/layout.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,7 @@
1-
// Placeholder
1+
export default function PublicLayout({
2+
children,
3+
}: Readonly<{
4+
children: React.ReactNode;
5+
}>) {
6+
return children;
7+
}

0 commit comments

Comments
 (0)