Skip to content

Feat/student profile - #40

Open
mimionly wants to merge 21 commits into
Developer-Kommunity-24:mainfrom
mimionly:feat/student-profile
Open

mimionly wants to merge 21 commits into
Developer-Kommunity-24:mainfrom
mimionly:feat/student-profile

Conversation

@mimionly

Copy link
Copy Markdown

Student Profile API & System Documentation

Base URL

  • Local development: http://localhost:8787

Authentication & Authorization

All student endpoints require a valid Bearer JWT issued by Neon Auth:

  • Header: Authorization: Bearer <token>
  • Role Requirement: The authenticated user must have the role student.
    • Missing/invalid token $\rightarrow$ 401 Unauthorized
    • Non-student user (e.g. recruiter, admin) $\rightarrow$ 403 Forbidden

1. Get Student Profile

Retrieves the complete profile for the authenticated student. If a profile or contact record does not yet exist for this user, it is auto-initialized with defaults.

  • Endpoint: GET /api/student/profile
  • Authentication: Required (student)

Example Request

GET /api/student/profile HTTP/1.1
Host: localhost:8787
Authorization: Bearer <jwt_token>

Response Format (200 OK)

{
  "id": "11111111-1111-4111-8111-111111111111",
  "email": "ada@example.com",
  "fullName": "Ada Lovelace",
  "headline": "Full-Stack Software Engineer & CS Student",
  "bio": "Passionate about distributed systems and modern web apps.",
  "gradYear": 2026,
  "openToWork": true,
  "phone": "+1 (555) 019-2834",
  "resumeUrl": "https://example.com/resumes/ada.pdf",
  "githubUrl": "https://github.com/ada",
  "linkedinUrl": "https://linkedin.com/in/ada",
  "portfolioUrl": "https://ada.dev",
  "dk24Status": "none",
  "completionPercentage": 100,
  "skills": ["TypeScript", "React", "Node.js", "PostgreSQL"],
  "school": "University of Technology",
  "degree": "B.S. in Computer Science",
  "gpa": "3.9",
  "specialization": "Software Engineering",
  "experienceRole": "Software Engineer Intern",
  "experienceCompany": "Acme Corp",
  "experienceSummary": "Built full-stack features with Next.js and Hono.",
  "otherLinks": {
    "school": "University of Technology",
    "degree": "B.S. in Computer Science",
    "gpa": "3.9",
    "specialization": "Software Engineering",
    "portfolioUrl": "https://ada.dev"
  }
}

2. Update Student Profile

Updates the authenticated student's profile across all normalized tables (users, student_profiles, contact_details, skills, experience), recalculates the completion score, and returns the updated profile object.

  • Endpoint: PUT /api/student/profile
  • Authentication: Required (student)
  • Headers: Content-Type: application/json

Body Parameters (All Optional)

Field Type Description
fullName string User's full name (must be a non-empty string).
headline string | null Professional headline or tagline.
bio string | null Short biography / introduction.
gradYear number | null Graduation year (must be between 1900 and 2100).
openToWork boolean Job-seeking availability status.
phone string | null Contact phone number.
resumeUrl string | null Link to hosted PDF resume.
githubUrl string | null GitHub profile URL.
linkedinUrl string | null LinkedIn profile URL.
portfolioUrl string | null Personal website or portfolio URL.
skills string[] Array of skill tags (e.g. ["React", "Node.js"]). Duplicates are deduplicated.
school string | null University or institution name.
degree string | null Degree title (e.g. "B.S. in Computer Science").
gpa string | null Cumulative GPA.
specialization string | null Major or track focus.
experienceRole string | null Most recent job/internship role.
experienceCompany string | null Most recent company/organization name.
experienceSummary string | null Responsibilities and achievements.
otherLinks object | null Arbitrary JSON key-value store for extra metadata.

Example Request

PUT /api/student/profile HTTP/1.1
Host: localhost:8787
Authorization: Bearer <jwt_token>
Content-Type: application/json

{
  "fullName": "Ada Lovelace",
  "headline": "Backend Engineer",
  "bio": "Building fast cloud APIs.",
  "gradYear": 2026,
  "openToWork": true,
  "skills": ["TypeScript", "Drizzle ORM", "Cloudflare Workers"],
  "school": "University of Tech",
  "experienceRole": "Software Intern",
  "experienceCompany": "Vercel"
}

Response Format (200 OK)

Returns the same shape as GET /api/student/profile with all updated values and new completionPercentage.


3. Profile Completion Score Calculation

The API computes a weighted completion score (0% to 100%):

Profile Field Weight
fullName +15%
headline +15%
bio +15%
gradYear +15%
resumeUrl +15%
phone +10%
githubUrl +10%
linkedinUrl +5%
Total 100%

4. Error Handling & Status Codes

HTTP Status Error Type Scenario / Reason Example Response
400 Bad Request Validation Error Invalid payload, non-numeric gradYear, empty fullName, or invalid skills array. {"error": "Validation Error", "message": "gradYear must be a valid year number."}
400 Bad Request Malformed JSON Request body cannot be parsed as JSON. {"error": "Bad Request", "message": "Invalid JSON payload."}
401 Unauthorized Missing / Expired Auth No Bearer token provided or token expired. {"error": "Unauthorized", "message": "Missing or invalid token."}
403 Forbidden Role Mismatch Token is valid, but the user's role is not student. {"error": "Forbidden", "message": "Requires student role."}
404 Not Found Not Found Student user record does not exist in the database. {"error": "Not Found", "message": "Student account not found."}
500 Internal Server Error Server Error Database connection drop or unexpected server exception. {"error": "Internal Server Error", "message": "Unable to load profile right now."}

5. Frontend Flow (apps/web)

  1. Auto Mode Detection:
    • On load, apps/web/app/(onboarding)/student/page.tsx calls GET /api/student/profile.
    • If profile completion is $&gt; 15%$ (or fullName, headline, and school are present), the UI automatically switches into Dashboard Mode.
    • If it's a new profile ($0%$), the UI enters the 4-Step Onboarding Wizard:
      • Step 1: Personal & Contact Details
      • Step 2: Education & Academics
      • Step 3: Skills & Experience
      • Step 4: Links & Resume
  2. Local Fallback:
    • In offline/unauthenticated dev mode, the frontend seamlessly caches and restores profile state using localStorage.getItem('student_profile').
image

@Kaushik4141 Kaushik4141 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

revert all the changes in web and packages/db folders

Comment thread apps/api/src/middleware/auth.ts Outdated
id: string
fullName: string | null
email: string
role: 'student' | 'recruiter' | 'admin'

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

check userRoleEnum in packages/db/src/schema/enums.ts we have student | recruiter | core_admin | club_admin

Comment thread apps/api/src/middleware/auth.ts Outdated
* - 403 if the user exists but is not a student.
* On success, sets `user` on the context for downstream handlers.
*/
export function requireStudentAuth() {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: keep requireAuth as the only JWT verifier, and for the student check add a secound middleware just to role check and pass both on the router
e.g: studentRouter.use('*' , requireAuth, requireStudentRole)

basically requireStudentRole should only check the role

Comment thread apps/api/src/middleware/db.ts Outdated
}>(async (c, next) => {
const bindings = env(c)
const db = createDb(bindings.DATABASE_URL)
const db = await getDbClient(bindings)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

revert these changes and delete apps/api/src/db.ts

here the line above does exact same thing what getDBCliet is doing

Comment thread .github/workflows/neon_workflow.yml Outdated
Comment thread apps/api/package.json

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

revert this file
we don's use aws-sdk/client-s3 , @aws-sdk/s3-request-presigner, or dotenv anywhere

Comment thread apps/api/wrangler.jsonc Outdated
"main": "src/index.ts",
"compatibility_date": "2026-08-08",
"compatibility_flags": [
"nodejs_compat"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

revert this change
adding nodejs_compact would removes the serverless feature of workers
workers don't run on node.js they run on v8 isolates

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mimionly don't remove nodejs_compact but can i know why are u using it ?

@dev-shetty dev-shetty left a comment •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good work @mimionly but general comment, this PR touches so many things. It is touching frontend, some part of backend, then adding some migration scripts, and adding some packages somewhere.

Please create PRs for a one particular task, you can create 10s of small PR than 1 big one, it becomes easy to review and deploy

Comment on lines +48 to +55
if (profile.fullName && profile.fullName.trim() !== '') percentage += 15
if (profile.headline && profile.headline.trim() !== '') percentage += 15
if (profile.bio && profile.bio.trim() !== '') percentage += 15
if (profile.gradYear !== null && profile.gradYear !== undefined) percentage += 15
if (profile.phone && profile.phone.trim() !== '') percentage += 10
if (profile.resumeUrl && profile.resumeUrl.trim() !== '') percentage += 15
if (profile.githubUrl && profile.githubUrl.trim() !== '') percentage += 10
if (profile.linkedinUrl && profile.linkedinUrl.trim() !== '') percentage += 5

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There should be a better way of doing this, create a map, and assign the keys with some points, check which and all keys exist and add up the respective points.

This will get weird in the long run.

Comment on lines +37 to +46
export function calculateCompletionPercentage(profile: {
fullName?: string | null
headline?: string | null
bio?: string | null
gradYear?: number | null
phone?: string | null
resumeUrl?: string | null
githubUrl?: string | null
linkedinUrl?: string | null
}): number {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Type can be moved oustside

Comment on lines +85 to +96
.values({
userId,
headline: null,
bio: null,
gradYear: null,
openToWork: false,
resumeUrl: null,
githubUrl: null,
linkedinUrl: null,
otherLinks: null,
dk24Status: 'none',
})

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can u put this in a object called initialValues

so u can just reference it here

Comment on lines +135 to +151
let experienceRole: string | null = null
let experienceCompany: string | null = null
let experienceSummary: string | null = null

const dbExperience = dbExperiences[0]
if (dbExperience) {
experienceRole = dbExperience.role || null
experienceCompany = dbExperience.companyName || null
experienceSummary = dbExperience.contributions || null
}

// 5. Extract education details from otherLinks
let school: string | null = null
let degree: string | null = null
let gpa: string | null = null
let specialization: string | null = null
let portfolioUrl: string | null = null

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why are we defining variables for each key, use object destructuring

developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring

body: UpdateProfilePayload,
) {
// Update using db client directly (neon-http driver does not support transactions)
const tx = db

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why? cant we just use it as db? @Kaushik4141 this needs to be consistent all across

if one function uses tx and other uses db it will get confusing

@Kaushik4141 Kaushik4141 Aug 26, 2026 •

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why? cant we just use it as db? @Kaushik4141 this needs to be consistent all across

if one function uses tx and other uses db it will get confusing

here this is not a transaction so here we can use db itself
but should we use db itself even when it is transaction

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants