feat: add social media links to user profile

- Add YouTube, Instagram, Facebook, and TikTok URL fields to User model
- Create database migration for social media link columns
- Add custom validators to ensure URLs contain correct domains
- Update profile page with social media input fields
- Include social media URLs in GET /api/users/me response
- Add icons for each social platform in the UI

Users can now add links to their social media profiles. Each field
validates that the URL contains the appropriate domain (e.g.,
instagram.com for Instagram, youtube.com/youtu.be for YouTube).
This commit is contained in:
Radosław Gierwiało
2025-11-13 20:47:57 +01:00
parent ebf4b84ed2
commit 48f9dfe1b4
6 changed files with 150 additions and 2 deletions

View File

@@ -0,0 +1,5 @@
-- AlterTable
ALTER TABLE "users" ADD COLUMN "facebook_url" VARCHAR(255),
ADD COLUMN "instagram_url" VARCHAR(255),
ADD COLUMN "tiktok_url" VARCHAR(255),
ADD COLUMN "youtube_url" VARCHAR(255);

View File

@@ -23,6 +23,12 @@ model User {
lastName String? @map("last_name") @db.VarChar(100)
wsdcId String? @unique @map("wsdc_id") @db.VarChar(20)
// Social Media Links
youtubeUrl String? @map("youtube_url") @db.VarChar(255)
instagramUrl String? @map("instagram_url") @db.VarChar(255)
facebookUrl String? @map("facebook_url") @db.VarChar(255)
tiktokUrl String? @map("tiktok_url") @db.VarChar(255)
// Email Verification (Phase 1.5)
emailVerified Boolean @default(false) @map("email_verified")
verificationToken String? @unique @map("verification_token") @db.VarChar(255)

View File

@@ -10,13 +10,17 @@ const { sanitizeForEmail } = require('../utils/sanitize');
async function updateProfile(req, res, next) {
try {
const userId = req.user.id;
const { firstName, lastName, email, wsdcId } = req.body;
const { firstName, lastName, email, wsdcId, youtubeUrl, instagramUrl, facebookUrl, tiktokUrl } = req.body;
// Build update data
const updateData = {};
if (firstName !== undefined) updateData.firstName = firstName;
if (lastName !== undefined) updateData.lastName = lastName;
if (wsdcId !== undefined) updateData.wsdcId = wsdcId || null; // Allow empty string to clear WSDC ID
if (youtubeUrl !== undefined) updateData.youtubeUrl = youtubeUrl || null;
if (instagramUrl !== undefined) updateData.instagramUrl = instagramUrl || null;
if (facebookUrl !== undefined) updateData.facebookUrl = facebookUrl || null;
if (tiktokUrl !== undefined) updateData.tiktokUrl = tiktokUrl || null;
// Check if email is being changed
const currentUser = await prisma.user.findUnique({

View File

@@ -136,6 +136,38 @@ const updateProfileValidation = [
.trim()
.matches(/^\d{0,10}$/)
.withMessage('WSDC ID must be numeric and up to 10 digits'),
body('youtubeUrl')
.optional()
.trim()
.custom((value) => {
if (!value) return true; // Allow empty
return value.includes('youtube.com') || value.includes('youtu.be');
})
.withMessage('Must be a valid YouTube URL (youtube.com or youtu.be)'),
body('instagramUrl')
.optional()
.trim()
.custom((value) => {
if (!value) return true; // Allow empty
return value.includes('instagram.com');
})
.withMessage('Must be a valid Instagram URL (instagram.com)'),
body('facebookUrl')
.optional()
.trim()
.custom((value) => {
if (!value) return true; // Allow empty
return value.includes('facebook.com') || value.includes('fb.com');
})
.withMessage('Must be a valid Facebook URL (facebook.com or fb.com)'),
body('tiktokUrl')
.optional()
.trim()
.custom((value) => {
if (!value) return true; // Allow empty
return value.includes('tiktok.com');
})
.withMessage('Must be a valid TikTok URL (tiktok.com)'),
handleValidationErrors,
];

View File

@@ -20,6 +20,10 @@ router.get('/me', authenticate, async (req, res, next) => {
firstName: true,
lastName: true,
wsdcId: true,
youtubeUrl: true,
instagramUrl: true,
facebookUrl: true,
tiktokUrl: true,
avatar: true,
createdAt: true,
updatedAt: true,