diff --git a/src/app/api/user/settings/route.ts b/src/app/api/user/settings/route.ts
new file mode 100644
index 0000000..7573a52
--- /dev/null
+++ b/src/app/api/user/settings/route.ts
@@ -0,0 +1,65 @@
+import { NextRequest, NextResponse } from "next/server";
+import { z } from "zod";
+import { verifySessionToken, COOKIE_NAME } from "@/lib/auth/session";
+import { getDb, schema } from "@/lib/db";
+import { eq } from "drizzle-orm";
+
+const settingsSchema = z.object({
+ displayName: z.string().max(100).optional(),
+ bio: z.string().max(300).optional(),
+ location: z.string().max(100).optional(),
+ portfolioUrl: z.string().url().or(z.literal("")).optional(),
+ linkedinUrl: z.string().url().or(z.literal("")).optional(),
+ isPublic: z.boolean().default(true),
+ skills: z.array(z.string()).default([]),
+});
+
+export async function PATCH(request: NextRequest) {
+ const token = request.cookies.get(COOKIE_NAME)?.value;
+ if (!token) {
+ return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
+ }
+
+ const sessionUser = await verifySessionToken(token);
+ if (!sessionUser) {
+ return NextResponse.json({ error: "Invalid session" }, { status: 401 });
+ }
+
+ try {
+ const body = await request.json();
+ const data = settingsSchema.parse(body);
+ const db = await getDb();
+
+ // Update user record
+ await db
+ .update(schema.users)
+ .set({
+ displayName: data.displayName !== undefined ? data.displayName : undefined,
+ bio: data.bio !== undefined ? data.bio : undefined,
+ location: data.location !== undefined ? data.location : undefined,
+ portfolioUrl: data.portfolioUrl || null,
+ linkedinUrl: data.linkedinUrl || null,
+ isPublic: data.isPublic,
+ updatedAt: new Date(),
+ })
+ .where(eq(schema.users.id, sessionUser.id));
+
+ // Update profile skills if provided
+ if (data.skills) {
+ await db
+ .update(schema.profiles)
+ .set({
+ skills: data.skills,
+ updatedAt: new Date(),
+ })
+ .where(eq(schema.profiles.userId, sessionUser.id));
+ }
+
+ return NextResponse.json({ success: true, message: "Settings saved successfully" });
+ } catch (error: any) {
+ if (error instanceof z.ZodError) {
+ return NextResponse.json({ error: error.errors[0].message }, { status: 400 });
+ }
+ return NextResponse.json({ error: error.message || "Failed to update settings" }, { status: 500 });
+ }
+}
diff --git a/src/app/dashboard/page.tsx b/src/app/dashboard/page.tsx
index a4ce8f7..cff7fb3 100644
--- a/src/app/dashboard/page.tsx
+++ b/src/app/dashboard/page.tsx
@@ -89,20 +89,26 @@ export default async function DashboardPage() {
-
diff --git a/src/app/dashboard/settings/SettingsForm.tsx b/src/app/dashboard/settings/SettingsForm.tsx
new file mode 100644
index 0000000..087c7da
--- /dev/null
+++ b/src/app/dashboard/settings/SettingsForm.tsx
@@ -0,0 +1,249 @@
+"use client";
+
+import { useState } from "react";
+import { useRouter } from "next/navigation";
+
+export function SettingsForm({
+ initialUser,
+ initialSkills,
+}: {
+ initialUser: {
+ githubUsername: string;
+ displayName: string | null;
+ bio: string | null;
+ location: string | null;
+ portfolioUrl: string | null;
+ linkedinUrl: string | null;
+ isPublic: boolean;
+ };
+ initialSkills: string[];
+}) {
+ const router = useRouter();
+ const [displayName, setDisplayName] = useState(initialUser.displayName || "");
+ const [bio, setBio] = useState(initialUser.bio || "");
+ const [location, setLocation] = useState(initialUser.location || "");
+ const [portfolioUrl, setPortfolioUrl] = useState(initialUser.portfolioUrl || "");
+ const [linkedinUrl, setLinkedinUrl] = useState(initialUser.linkedinUrl || "");
+ const [isPublic, setIsPublic] = useState(initialUser.isPublic);
+ const [skillInput, setSkillInput] = useState("");
+ const [skills, setSkills] = useState(initialSkills || []);
+ const [submitting, setSubmitting] = useState(false);
+ const [message, setMessage] = useState<{ type: "success" | "error"; text: string } | null>(null);
+
+ const addSkill = () => {
+ const trimmed = skillInput.trim();
+ if (trimmed && !skills.includes(trimmed)) {
+ setSkills([...skills, trimmed]);
+ setSkillInput("");
+ }
+ };
+
+ const removeSkill = (skill: string) => {
+ setSkills(skills.filter((s) => s !== skill));
+ };
+
+ const handleSubmit = async (e: React.FormEvent) => {
+ e.preventDefault();
+ setSubmitting(true);
+ setMessage(null);
+
+ try {
+ const res = await fetch("/api/user/settings", {
+ method: "PATCH",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({
+ displayName,
+ bio,
+ location,
+ portfolioUrl: portfolioUrl || "",
+ linkedinUrl: linkedinUrl || "",
+ isPublic,
+ skills,
+ }),
+ });
+
+ const data = await res.json();
+ if (!res.ok) {
+ throw new Error(data.error || "Failed to update profile settings");
+ }
+
+ setMessage({ type: "success", text: "Settings saved successfully!" });
+ router.refresh();
+ } catch (err: any) {
+ setMessage({ type: "error", text: err.message });
+ } finally {
+ setSubmitting(false);
+ }
+ };
+
+ return (
+
+ );
+}
diff --git a/src/app/dashboard/settings/page.tsx b/src/app/dashboard/settings/page.tsx
new file mode 100644
index 0000000..7108e38
--- /dev/null
+++ b/src/app/dashboard/settings/page.tsx
@@ -0,0 +1,90 @@
+import { cookies } from "next/headers";
+import { redirect } from "next/navigation";
+import Link from "next/link";
+import { verifySessionToken, COOKIE_NAME } from "@/lib/auth/session";
+import { getDb, schema } from "@/lib/db";
+import { eq } from "drizzle-orm";
+import { SettingsForm } from "./SettingsForm";
+
+export const metadata = {
+ title: "Profile & Privacy Settings — TechNexusOrg",
+};
+
+export const dynamic = "force-dynamic";
+
+export default async function SettingsPage() {
+ const cookieStore = await cookies();
+ const token = cookieStore.get(COOKIE_NAME)?.value;
+
+ if (!token) {
+ redirect("/api/auth/github");
+ }
+
+ const sessionUser = await verifySessionToken(token);
+ if (!sessionUser) {
+ redirect("/api/auth/github");
+ }
+
+ const db = await getDb();
+
+ const userRes = await db
+ .select()
+ .from(schema.users)
+ .where(eq(schema.users.id, sessionUser.id))
+ .limit(1);
+
+ const profileRes = await db
+ .select()
+ .from(schema.profiles)
+ .where(eq(schema.profiles.userId, sessionUser.id))
+ .limit(1);
+
+ if (userRes.length === 0) {
+ redirect("/api/auth/github");
+ }
+
+ const user = userRes[0];
+ const profile = profileRes[0] || null;
+
+ return (
+
+
+
+
+ Passport Settings
+
+
+ Manage your public contributor identity, privacy controls, and technical skills.
+
+
+
+
+ View Public Passport ↗
+
+
+ ← Back to Dashboard
+
+
+
+
+
+
+ );
+}
diff --git a/src/app/people/[username]/opengraph-image.tsx b/src/app/people/[username]/opengraph-image.tsx
new file mode 100644
index 0000000..0a99efb
--- /dev/null
+++ b/src/app/people/[username]/opengraph-image.tsx
@@ -0,0 +1,160 @@
+import { ImageResponse } from "next/og";
+import { getContributorPassport } from "@/lib/passport";
+
+export const runtime = "nodejs";
+
+export const size = {
+ width: 1200,
+ height: 630,
+};
+
+export const contentType = "image/png";
+
+export default async function Image({ params }: { params: Promise<{ username: string }> }) {
+ const { username } = await params;
+ const data = await getContributorPassport(username);
+
+ const isFound = !("notFound" in data) && !("isPrivate" in data);
+ const user = isFound ? data.user : null;
+ const stats = isFound ? data.statistics : null;
+
+ return new ImageResponse(
+ (
+
+
+
+
+ TN
+
+
+ TechNexusOrg
+
+
+
+ Verified Contributor Passport
+
+
+
+
+ {user?.avatarUrl ? (
+

+ ) : (
+
+ {username[0]?.toUpperCase()}
+
+ )}
+
+
+
+ {user?.displayName || `@${username}`}
+
+
+ @{username} • Level: {user ? user.level.replace("_", " ").toUpperCase() : "EXPLORER"}
+
+
+
+
+
+
+
+ {stats ? stats.mergedPrs : 0}
+
+
Merged PRs
+
+
+
+ {stats ? stats.projectsContributed : 0}
+
+
Official Projects
+
+
+
+ {stats ? stats.credentialsEarned : 0}
+
+
Verified Credentials
+
+
+
+ 100%
+
+
GitHub Evidence
+
+
+
+
+
github.com/TechNexusOrg
+
Proof of Work Backed by GitHub Evidence
+
+
+ ),
+ {
+ ...size,
+ }
+ );
+}
diff --git a/src/app/people/[username]/page.tsx b/src/app/people/[username]/page.tsx
new file mode 100644
index 0000000..025ef67
--- /dev/null
+++ b/src/app/people/[username]/page.tsx
@@ -0,0 +1,335 @@
+import { cookies } from "next/headers";
+import Link from "next/link";
+import type { Metadata } from "next";
+import { getContributorPassport } from "@/lib/passport";
+import { verifySessionToken, COOKIE_NAME } from "@/lib/auth/session";
+
+export const dynamic = "force-dynamic";
+
+export async function generateMetadata({
+ params,
+}: {
+ params: Promise<{ username: string }>;
+}): Promise {
+ const { username } = await params;
+ return {
+ title: `@${username} — Contributor Passport | TechNexusOrg`,
+ description: `Public proof of work, verified open-source contributions, and verified credentials for @${username} on TechNexusOrg.`,
+ openGraph: {
+ title: `@${username} — Contributor Passport | TechNexusOrg`,
+ description: `Verified open-source contributions and proof of work on official TechNexusOrg repositories.`,
+ },
+ };
+}
+
+export default async function ContributorPassportPage({
+ params,
+}: {
+ params: Promise<{ username: string }>;
+}) {
+ const { username } = await params;
+ const cookieStore = await cookies();
+ const token = cookieStore.get(COOKIE_NAME)?.value;
+ const sessionUser = token ? await verifySessionToken(token) : null;
+
+ const data = await getContributorPassport(username, sessionUser?.id);
+
+ if ("notFound" in data) {
+ return (
+
+
+ ?
+
+
+ Contributor @{username} Not Found
+
+
+ No registered or verified contributor with this GitHub handle was found in the TechNexusOrg directory.
+
+
+
+ ← Browse Contributor Directory
+
+
+
+ );
+ }
+
+ if ("isPrivate" in data) {
+ return (
+
+
+ 🔒
+
+
+ Private Contributor Profile
+
+
+ @{data.username} has chosen to keep their contributor passport private.
+
+
+
+ ← Browse Contributor Directory
+
+
+
+ );
+ }
+
+ const { user, profile, statistics, credentials, timeline } = data;
+ const isOwner = sessionUser?.id === user.id;
+
+ return (
+
+ {/* Contributor Passport Hero Header */}
+
+
+
+ {user.avatarUrl ? (
+

+ ) : (
+
+ {user.githubUsername[0]?.toUpperCase()}
+
+ )}
+
+
+
+ {user.displayName || user.githubUsername}
+
+
+ {user.level.replace("_", " ")}
+
+ {user.foundingNumber && (
+
+ Founding #{String(user.foundingNumber).padStart(4, "0")}
+
+ )}
+
+
+ @{user.githubUsername} • Public Contributor Passport
+
+ {user.bio && (
+
+ {user.bio}
+
+ )}
+
+
+
+
+
+
+ {/* Metadata links (location, portfolio) */}
+ {(user.location || user.portfolioUrl || user.linkedinUrl) && (
+
+ )}
+
+
+ {/* Verified Statistics Grid */}
+
+
+
+ {statistics.mergedPrs}
+
+
PRs Merged
+
Verified on GitHub
+
+
+
+ {statistics.projectsContributed}
+
+
Official Projects
+
Code repositories
+
+
+
+ {statistics.credentialsEarned}
+
+
Credentials Earned
+
Verifiable records
+
+
+
+ {profile?.experienceLevel || "Beginner"}
+
+
Experience Tier
+
Verified level
+
+
+
+ {/* Demonstrated Skills */}
+ {profile?.skills && profile.skills.length > 0 && (
+
+
+ Demonstrated Skills & Technologies
+
+
+ {profile.skills.map((skill) => (
+
+ {skill}
+
+ ))}
+
+
+ )}
+
+ {/* Verified Credentials Showcase */}
+
+
+
+ Verified Credentials ({credentials.length})
+
+ Public Proof of Work
+
+
+ {credentials.length === 0 ? (
+
+
+ No credentials minted yet.
+
+
+ Credentials are automatically issued when pull requests are merged into official repositories.
+
+
+ ) : (
+
+ {credentials.map((cred) => (
+
+
+
+ {cred.status}
+
+
+ {new Date(cred.issuedAt).toLocaleDateString()}
+
+
+
{cred.title}
+
{cred.description}
+
+
+ Inspect Cryptographic Evidence
+ →
+
+
+
+ ))}
+
+ )}
+
+
+ {/* Real Contribution Timeline */}
+
+
+
+ Verified Contribution Timeline
+
+
+ {timeline.length} verified events
+
+
+
+
+
+ {timeline.map((event) => (
+ -
+
+
+
+
+
+ {event.title}
+
+
+
+
+ {event.description}
+
+ {event.url && (
+
+ )}
+
+ ))}
+
+
+
+
+ );
+}
diff --git a/src/app/people/page.tsx b/src/app/people/page.tsx
new file mode 100644
index 0000000..4cdbeb1
--- /dev/null
+++ b/src/app/people/page.tsx
@@ -0,0 +1,109 @@
+import { getDb, schema } from "@/lib/db";
+import { eq, desc } from "drizzle-orm";
+import Link from "next/link";
+
+export const dynamic = "force-dynamic";
+
+export const metadata = {
+ title: "Public Contributor Directory — TechNexusOrg",
+ description:
+ "Explore genuine open-source engineers building verified public proof of work across official TechNexusOrg repositories.",
+};
+
+export default async function PeopleDirectoryPage() {
+ const db = await getDb();
+
+ const publicUsers = await db
+ .select({
+ id: schema.users.id,
+ githubUsername: schema.users.githubUsername,
+ displayName: schema.users.displayName,
+ avatarUrl: schema.users.avatarUrl,
+ level: schema.users.level,
+ foundingNumber: schema.users.foundingNumber,
+ createdAt: schema.users.createdAt,
+ })
+ .from(schema.users)
+ .where(eq(schema.users.isPublic, true))
+ .orderBy(desc(schema.users.createdAt))
+ .limit(50);
+
+ return (
+
+ {/* Header */}
+
+
+ Contributor Directory
+
+
+ Engineers with public proof of work. Every contributor passport links to verified pull requests, reviews, and credentials.
+
+
+
+ {publicUsers.length === 0 ? (
+
+
+ Directory Status
+
+
+ No Public Contributors Registered Yet
+
+
+ Connect your GitHub account to initialize your Contributor Passport and be listed in the inaugural public directory.
+
+
+
+ ) : (
+
+ {publicUsers.map((user: any) => (
+
+
+ {user.avatarUrl ? (
+

+ ) : (
+
+ {user.githubUsername[0]?.toUpperCase()}
+
+ )}
+
+
+ {user.displayName || user.githubUsername}
+
+
+ @{user.githubUsername}
+
+
+
+
+
+
+ {user.level.replace("_", " ")}
+
+ {user.foundingNumber && (
+
+ #{String(user.foundingNumber).padStart(4, "0")}
+
+ )}
+
+
+ ))}
+
+ )}
+
+ );
+}
diff --git a/src/components/Navbar.tsx b/src/components/Navbar.tsx
index dd5b6cc..1793d54 100644
--- a/src/components/Navbar.tsx
+++ b/src/components/Navbar.tsx
@@ -33,6 +33,9 @@ export async function Navbar() {
Issues
+
+ Contributors
+
diff --git a/src/lib/passport/index.test.ts b/src/lib/passport/index.test.ts
new file mode 100644
index 0000000..902bb3c
--- /dev/null
+++ b/src/lib/passport/index.test.ts
@@ -0,0 +1,115 @@
+import { describe, it, expect, beforeAll } from "vitest";
+import { getDb, schema } from "@/lib/db";
+import { runMigrations } from "@/lib/db/migrate";
+import { getContributorPassport } from "./index";
+
+describe("Contributor Passport Engine", () => {
+ beforeAll(async () => {
+ (process.env as Record).NODE_ENV = "test";
+ await runMigrations();
+
+ const db = await getDb();
+
+ // Seed public contributor
+ await db.insert(schema.users).values({
+ id: "usr_passport_public_01",
+ githubId: 88776655,
+ githubUsername: "sarahdev",
+ displayName: "Sarah Engineer",
+ email: "sarah@example.com",
+ avatarUrl: "https://avatars.githubusercontent.com/u/88776655",
+ role: "contributor",
+ level: "contributor",
+ foundingNumber: 7,
+ isPublic: true,
+ isOnboarded: true,
+ });
+
+ // Seed private contributor
+ await db.insert(schema.users).values({
+ id: "usr_passport_private_02",
+ githubId: 44332211,
+ githubUsername: "alexstealth",
+ displayName: "Alex Stealth",
+ email: "alex@example.com",
+ role: "contributor",
+ level: "explorer",
+ isPublic: false,
+ isOnboarded: true,
+ });
+
+ // Seed official project
+ await db.insert(schema.projects).values({
+ id: "proj_passport_test_01",
+ name: "Platform Core",
+ slug: "platform-core",
+ githubRepo: "TechNexusOrg/platform",
+ description: "Core platform repo",
+ primaryLanguage: "TypeScript",
+ languages: ["TypeScript"],
+ isOfficial: true,
+ });
+
+ // Seed contribution for sarahdev
+ await db.insert(schema.contributions).values({
+ id: "contrib_sarah_01",
+ userId: "usr_passport_public_01",
+ projectId: "proj_passport_test_01",
+ githubPrNumber: 101,
+ prTitle: "feat: add contributor passport module",
+ prUrl: "https://github.com/TechNexusOrg/platform/pull/101",
+ state: "merged",
+ isFirstPr: true,
+ mergedAt: new Date("2026-09-25T10:00:00Z"),
+ verifiedAt: new Date("2026-09-25T10:05:00Z"),
+ });
+
+ // Seed credential for sarahdev
+ await db.insert(schema.credentials).values({
+ id: "cred_tn_first-pr-merged_sarah01",
+ userId: "usr_passport_public_01",
+ type: "first_pr_merged",
+ title: "First PR Merged — Open Source Contributor",
+ description: "Verified merge of PR #101",
+ status: "active",
+ issuer: "TechNexusOrg",
+ evidenceData: { prNumber: 101, repo: "TechNexusOrg/platform" },
+ verificationUrl: "http://localhost:3000/verify/cred_tn_first-pr-merged_sarah01",
+ });
+ });
+
+ it("returns notFound for unknown username", async () => {
+ const res = await getContributorPassport("non_existent_user_9999");
+ expect("notFound" in res && res.notFound).toBe(true);
+ });
+
+ it("returns isPrivate for private user when viewer is unauthenticated or different", async () => {
+ const res = await getContributorPassport("alexstealth");
+ expect("isPrivate" in res && res.isPrivate).toBe(true);
+
+ const resDiffViewer = await getContributorPassport("alexstealth", "other_user_id");
+ expect("isPrivate" in resDiffViewer && resDiffViewer.isPrivate).toBe(true);
+ });
+
+ it("returns full profile for private user when viewer is the owner", async () => {
+ const res = await getContributorPassport("alexstealth", "usr_passport_private_02");
+ expect("user" in res).toBe(true);
+ if ("user" in res) {
+ expect(res.user.githubUsername).toBe("alexstealth");
+ }
+ });
+
+ it("returns verified statistics, credentials, and timeline for public contributor", async () => {
+ const res = await getContributorPassport("sarahdev");
+ expect("user" in res).toBe(true);
+ if ("user" in res) {
+ expect(res.user.githubUsername).toBe("sarahdev");
+ expect(res.user.foundingNumber).toBe(7);
+ expect(res.statistics.mergedPrs).toBe(1);
+ expect(res.statistics.projectsContributed).toBe(1);
+ expect(res.statistics.credentialsEarned).toBe(1);
+ expect(res.credentials).toHaveLength(1);
+ expect(res.timeline.length).toBeGreaterThanOrEqual(2); // joined + pr_merged + cred
+ }
+ });
+});
diff --git a/src/lib/passport/index.ts b/src/lib/passport/index.ts
new file mode 100644
index 0000000..6d076f5
--- /dev/null
+++ b/src/lib/passport/index.ts
@@ -0,0 +1,234 @@
+import { getDb, schema } from "@/lib/db";
+import { eq, desc, sql } from "drizzle-orm";
+
+export interface PassportTimelineItem {
+ id: string;
+ type: "pr_merged" | "credential_awarded" | "joined";
+ title: string;
+ description: string;
+ timestamp: Date;
+ url?: string;
+ meta?: Record;
+}
+
+export interface ContributorPassportData {
+ user: {
+ id: string;
+ githubUsername: string;
+ displayName: string | null;
+ avatarUrl: string | null;
+ bio: string | null;
+ location: string | null;
+ portfolioUrl: string | null;
+ linkedinUrl: string | null;
+ role: string;
+ level: string;
+ foundingNumber: number | null;
+ isPublic: boolean;
+ createdAt: Date;
+ };
+ profile: {
+ skills: string[];
+ interests: string[];
+ experienceLevel: string;
+ preferredLanguages: string[];
+ contributionPreferences: string[];
+ } | null;
+ statistics: {
+ mergedPrs: number;
+ openedPrs: number;
+ projectsContributed: number;
+ credentialsEarned: number;
+ };
+ credentials: Array<{
+ id: string;
+ type: string;
+ title: string;
+ description: string;
+ status: string;
+ issuedAt: Date;
+ verificationUrl: string;
+ evidenceData: any;
+ }>;
+ contributions: Array<{
+ id: string;
+ projectName: string;
+ githubRepo: string;
+ prNumber: number;
+ prTitle: string;
+ prUrl: string;
+ state: string;
+ isFirstPr: boolean;
+ mergedAt: Date | null;
+ verifiedAt: Date | null;
+ }>;
+ timeline: PassportTimelineItem[];
+ isPrivateProfile?: boolean;
+}
+
+export async function getContributorPassport(
+ username: string,
+ viewerId?: string
+): Promise {
+ const db = await getDb();
+
+ // Case-insensitive lookup by GitHub username
+ const matchingUsers = await db
+ .select()
+ .from(schema.users)
+ .where(sql`lower(${schema.users.githubUsername}) = lower(${username})`)
+ .limit(1);
+
+ if (matchingUsers.length === 0) {
+ return { notFound: true };
+ }
+
+ const user = matchingUsers[0];
+
+ // Privacy Check: If private and viewer is not the owner
+ if (!user.isPublic && user.id !== viewerId) {
+ return { isPrivate: true, username: user.githubUsername };
+ }
+
+ // Fetch profile
+ const profiles = await db
+ .select()
+ .from(schema.profiles)
+ .where(eq(schema.profiles.userId, user.id))
+ .limit(1);
+
+ const profile = profiles[0] || null;
+
+ // Fetch contributions joined with projects
+ const rawContributions = await db
+ .select({
+ id: schema.contributions.id,
+ prNumber: schema.contributions.githubPrNumber,
+ prTitle: schema.contributions.prTitle,
+ prUrl: schema.contributions.prUrl,
+ state: schema.contributions.state,
+ isFirstPr: schema.contributions.isFirstPr,
+ mergedAt: schema.contributions.mergedAt,
+ verifiedAt: schema.contributions.verifiedAt,
+ createdAt: schema.contributions.createdAt,
+ projectName: schema.projects.name,
+ githubRepo: schema.projects.githubRepo,
+ projectId: schema.projects.id,
+ })
+ .from(schema.contributions)
+ .innerJoin(schema.projects, eq(schema.contributions.projectId, schema.projects.id))
+ .where(eq(schema.contributions.userId, user.id))
+ .orderBy(desc(schema.contributions.createdAt));
+
+ // Fetch credentials
+ const credentials = await db
+ .select({
+ id: schema.credentials.id,
+ type: schema.credentials.type,
+ title: schema.credentials.title,
+ description: schema.credentials.description,
+ status: schema.credentials.status,
+ issuedAt: schema.credentials.issuedAt,
+ verificationUrl: schema.credentials.verificationUrl,
+ evidenceData: schema.credentials.evidenceData,
+ })
+ .from(schema.credentials)
+ .where(eq(schema.credentials.userId, user.id))
+ .orderBy(desc(schema.credentials.issuedAt));
+
+ // Compute real statistics
+ const mergedPrs = rawContributions.filter((c: any) => c.state === "merged").length;
+ const openedPrs = rawContributions.length;
+ const distinctProjects = new Set(rawContributions.map((c: any) => c.projectId)).size;
+
+ // Build unified chronological timeline
+ const timeline: PassportTimelineItem[] = [];
+
+ // Account creation event
+ timeline.push({
+ id: `event_join_${user.id}`,
+ type: "joined",
+ title: "Joined TechNexusOrg",
+ description: "Verified GitHub identity linked to contributor infrastructure",
+ timestamp: user.createdAt,
+ });
+
+ // Contribution events
+ for (const contrib of rawContributions) {
+ if (contrib.state === "merged") {
+ timeline.push({
+ id: `event_contrib_${contrib.id}`,
+ type: "pr_merged",
+ title: `Merged PR #${contrib.prNumber} in ${contrib.githubRepo}`,
+ description: contrib.prTitle,
+ timestamp: contrib.mergedAt || contrib.createdAt,
+ url: contrib.prUrl,
+ meta: {
+ isFirstPr: contrib.isFirstPr,
+ },
+ });
+ }
+ }
+
+ // Credential events
+ for (const cred of credentials) {
+ timeline.push({
+ id: `event_cred_${cred.id}`,
+ type: "credential_awarded",
+ title: `Earned Credential: ${cred.title}`,
+ description: cred.description,
+ timestamp: cred.issuedAt,
+ url: cred.verificationUrl,
+ });
+ }
+
+ // Sort descending by timestamp
+ timeline.sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime());
+
+ return {
+ user: {
+ id: user.id,
+ githubUsername: user.githubUsername,
+ displayName: user.displayName,
+ avatarUrl: user.avatarUrl,
+ bio: user.bio,
+ location: user.location,
+ portfolioUrl: user.portfolioUrl,
+ linkedinUrl: user.linkedinUrl,
+ role: user.role,
+ level: user.level,
+ foundingNumber: user.foundingNumber,
+ isPublic: user.isPublic,
+ createdAt: user.createdAt,
+ },
+ profile: profile
+ ? {
+ skills: profile.skills || [],
+ interests: profile.interests || [],
+ experienceLevel: profile.experienceLevel || "beginner",
+ preferredLanguages: profile.preferredLanguages || [],
+ contributionPreferences: profile.contributionPreferences || [],
+ }
+ : null,
+ statistics: {
+ mergedPrs,
+ openedPrs,
+ projectsContributed: distinctProjects,
+ credentialsEarned: credentials.length,
+ },
+ credentials,
+ contributions: rawContributions.map((c: any) => ({
+ id: c.id,
+ projectName: c.projectName,
+ githubRepo: c.githubRepo,
+ prNumber: c.prNumber,
+ prTitle: c.prTitle,
+ prUrl: c.prUrl,
+ state: c.state,
+ isFirstPr: c.isFirstPr,
+ mergedAt: c.mergedAt,
+ verifiedAt: c.verifiedAt,
+ })),
+ timeline,
+ };
+}