diff --git a/shatter-backend/docs/API_REFERENCE.md b/shatter-backend/docs/API_REFERENCE.md index 134d5bb..cefde56 100644 --- a/shatter-backend/docs/API_REFERENCE.md +++ b/shatter-backend/docs/API_REFERENCE.md @@ -559,7 +559,7 @@ Update a user's profile. Users can only update their own profile. | `password` | string | Minimum 8 characters | | `bio` | string | | | `profilePhoto` | string | URL | -| `socialLinks` | object | `{ linkedin?: string, github?: string, other?: string[] }` — `other` replaces the whole array on update | +| `socialLinks` | object | `{ linkedin?: string, github?: string, other?: Array<{ label: string, url: string }> }` — each `other` entry has a required `label` and `url`; the array fully replaces the previous one on update | | `organization` | string | Where the user works/studies | | `title` | string | Job title or role | @@ -853,7 +853,7 @@ Join an event as a guest (no account required). | `socialLinks` | object | No* | Social links | | `socialLinks.linkedin` | string | No | LinkedIn URL | | `socialLinks.github` | string | No | GitHub URL | -| `socialLinks.other` | string[] | No | Array of additional social/profile URLs | +| `socialLinks.other` | `Array<{label, url}>` | No | Additional labeled social/profile links. Each entry requires both `label` (display name) and `url`. | | `organization` | string | No* | Where the guest works/studies | | `title` | string | No | Job title or role | diff --git a/shatter-backend/docs/DATABASE_SCHEMA.md b/shatter-backend/docs/DATABASE_SCHEMA.md index ef47b29..4cc4a4d 100644 --- a/shatter-backend/docs/DATABASE_SCHEMA.md +++ b/shatter-backend/docs/DATABASE_SCHEMA.md @@ -67,7 +67,7 @@ | `title` | String | No | — | Trimmed | | `bio` | String | No | — | Trimmed | | `profilePhoto` | String | No | — | | -| `socialLinks` | Object | No | — | `{ linkedin?: String, github?: String, other?: String[] }` — `other` is an array so users can add multiple miscellaneous links | +| `socialLinks` | Object | No | — | `{ linkedin?: String, github?: String, other?: Array<{ label: String, url: String }> }` — each `other` entry has a required `label` and `url` so users can add multiple labeled miscellaneous links | | `authProvider` | String (enum) | Yes | `'local'` | One of: `'local'`, `'linkedin'`, `'guest'` | | `lastLogin` | Date | No | `null` | | | `passwordChangedAt`| Date | No | `null` | | diff --git a/shatter-backend/src/controllers/auth_controller.ts b/shatter-backend/src/controllers/auth_controller.ts index 76d7df3..7aa3c11 100644 --- a/shatter-backend/src/controllers/auth_controller.ts +++ b/shatter-backend/src/controllers/auth_controller.ts @@ -285,7 +285,12 @@ export const linkedinCallback = async (req: Request, res: Response) => { let statePayload: { linking?: boolean; userId?: string }; try { statePayload = jwt.verify(state, JWT_SECRET) as { linking?: boolean; userId?: string }; - } catch { + } catch (e: any) { + console.error('LinkedIn state verify failed:', { + name: e?.name, + message: e?.message, + stateLen: state?.length, + }); return res.status(401).json({ error: 'Invalid state parameter' }); } diff --git a/shatter-backend/src/controllers/event_controller.ts b/shatter-backend/src/controllers/event_controller.ts index dfa4a8b..7cd1f26 100644 --- a/shatter-backend/src/controllers/event_controller.ts +++ b/shatter-backend/src/controllers/event_controller.ts @@ -277,7 +277,7 @@ export async function joinEventAsGuest(req: Request, res: Response) { const { name, email, socialLinks, organization, title } = req.body as { name?: string; email?: string; - socialLinks?: { linkedin?: string; github?: string; other?: string[] }; + socialLinks?: { linkedin?: string; github?: string; other?: { label: string; url: string }[] }; organization?: string; title?: string; }; @@ -295,7 +295,7 @@ export async function joinEventAsGuest(req: Request, res: Response) { const hasSocialLink = socialLinks && ( socialLinks.linkedin?.trim() || socialLinks.github?.trim() || - socialLinks.other?.some((s) => s.trim()) + socialLinks.other?.some((entry) => entry?.url?.trim()) ); const hasOrganization = organization && organization.trim(); diff --git a/shatter-backend/src/controllers/user_controller.ts b/shatter-backend/src/controllers/user_controller.ts index 42c7d7b..12b29ae 100644 --- a/shatter-backend/src/controllers/user_controller.ts +++ b/shatter-backend/src/controllers/user_controller.ts @@ -121,7 +121,7 @@ export const updateUser = async (req: Request, res: Response) => { password?: string; bio?: string; profilePhoto?: string; - socialLinks?: { linkedin?: string; github?: string; other?: string[] }; + socialLinks?: { linkedin?: string; github?: string; other?: { label: string; url: string }[] }; organization?: string; title?: string; }; @@ -174,6 +174,7 @@ export const updateUser = async (req: Request, res: Response) => { const result = await User.updateOne( { _id: userId }, { $set: updateFields }, + { runValidators: true }, ); if (result.matchedCount === 0) { diff --git a/shatter-backend/src/models/user_model.ts b/shatter-backend/src/models/user_model.ts index 1b7e0f6..1eea569 100644 --- a/shatter-backend/src/models/user_model.ts +++ b/shatter-backend/src/models/user_model.ts @@ -19,7 +19,7 @@ export interface IUser { socialLinks?: { linkedin?: string; github?: string; - other?: string[]; + other?: { label: string; url: string }[]; }; authProvider: 'local' | 'linkedin' | 'guest'; lastLogin?: Date; @@ -85,7 +85,18 @@ const UserSchema = new Schema( socialLinks: { linkedin: { type: String }, github: { type: String }, - other: { type: [String], default: undefined }, + other: { + type: [ + new Schema( + { + label: { type: String, required: true, trim: true }, + url: { type: String, required: true, trim: true }, + }, + { _id: false }, + ), + ], + default: undefined, + }, }, authProvider: { type: String,