Skip to main content

User Profile Management

Overview​

The /api/profile endpoint manages user profile information including personal details, preferences, and account settings. This endpoint supports full CRUD operations for authenticated users.

Authentication​

Required: Valid session token Roles: All authenticated users (Subscriber, Member, Confidential, Admin)

Endpoints​

Get User Profile​

GET /api/profile

Headers:

Authorization: Bearer <session-token>
Accept: application/json

Response (200):

{
"id": "user_123",
"email": "john.doe@example.com",
"name": "John Doe",
"firstName": "John",
"lastName": "Doe",
"avatar": "https://storage.ring.ck.ua/avatars/user_123.jpg",
"role": "member",
"bio": "Software engineer passionate about Web3 and blockchain technology",
"location": "Kyiv, Ukraine",
"website": "https://johndoe.dev",
"linkedin": "https://linkedin.com/in/johndoe",
"github": "https://github.com/johndoe",
"twitter": "https://twitter.com/johndoe",
"company": "Tech Innovations Ltd",
"position": "Senior Developer",
"skills": ["JavaScript", "TypeScript", "React", "Node.js", "Blockchain"],
"interests": ["Web3", "DeFi", "Smart Contracts"],
"isVerified": true,
"isPublic": true,
"walletAddress": "0x742d35Cc6634C0532925a3b8D4C2C4e0C8A8C8C8",
"preferences": {
"language": "en",
"theme": "dark",
"notifications": {
"email": true,
"push": true,
"sms": false
},
"privacy": {
"showEmail": false,
"showWallet": false,
"showLocation": true
}
},
"createdAt": "2024-01-15T10:30:00Z",
"updatedAt": "2024-12-14T15:45:00Z",
"lastLoginAt": "2024-12-14T15:45:00Z"
}

Update User Profile​

PUT /api/profile

Headers:

Authorization: Bearer <session-token>
Content-Type: application/json

Request Body:

{
"name": "John Smith",
"firstName": "John",
"lastName": "Smith",
"bio": "Full-stack developer specializing in blockchain applications",
"location": "Lviv, Ukraine",
"website": "https://johnsmith.dev",
"linkedin": "https://linkedin.com/in/johnsmith",
"github": "https://github.com/johnsmith",
"twitter": "https://twitter.com/johnsmith",
"company": "Blockchain Solutions Inc",
"position": "Lead Developer",
"skills": ["JavaScript", "TypeScript", "React", "Node.js", "Solidity", "Web3"],
"interests": ["DeFi", "NFTs", "Smart Contracts", "Cryptocurrency"],
"isPublic": true,
"preferences": {
"language": "uk",
"theme": "light",
"notifications": {
"email": true,
"push": false,
"sms": true
},
"privacy": {
"showEmail": false,
"showWallet": true,
"showLocation": true
}
}
}

Response (200):

{
"success": true,
"message": "Profile updated successfully",
"data": {
"id": "user_123",
"updatedAt": "2024-12-14T16:00:00Z"
}
}

Upload Profile Avatar​

POST /api/profile/avatar

Headers:

Authorization: Bearer <session-token>
Content-Type: multipart/form-data

Request Body:

Content-Disposition: form-data; name="avatar"; filename="avatar.jpg"
Content-Type: image/jpeg

[Binary image data]

Response (200):

{
"success": true,
"message": "Avatar uploaded successfully",
"data": {
"avatarUrl": "https://storage.ring.ck.ua/avatars/user_123_1703434800.jpg",
"updatedAt": "2024-12-14T16:00:00Z"
}
}

Delete Profile Avatar​

DELETE /api/profile/avatar

Headers:

Authorization: Bearer <session-token>

Response (200):

{
"success": true,
"message": "Avatar deleted successfully",
"data": {
"avatarUrl": null,
"updatedAt": "2024-12-14T16:05:00Z"
}
}

Code Examples​

JavaScript/TypeScript​

// Get user profile
async function getUserProfile() {
try {
const response = await fetch('/api/profile', {
headers: {
'Authorization': `Bearer ${sessionToken}`,
'Accept': 'application/json'
}
})

if (!response.ok) {
throw new Error('Failed to fetch profile')
}

const profile = await response.json()
return profile
} catch (error) {
console.error('Error fetching profile:', error)
throw error
}
}

// Update user profile
async function updateUserProfile(profileData: Partial<UserProfile>) {
try {
const response = await fetch('/api/profile', {
method: 'PUT',
headers: {
'Authorization': `Bearer ${sessionToken}`,
'Content-Type': 'application/json'
},
body: JSON.stringify(profileData)
})

if (!response.ok) {
throw new Error('Failed to update profile')
}

const result = await response.json()
return result
} catch (error) {
console.error('Error updating profile:', error)
throw error
}
}

// Upload avatar
async function uploadAvatar(file: File) {
try {
const formData = new FormData()
formData.append('avatar', file)

const response = await fetch('/api/profile/avatar', {
method: 'POST',
headers: {
'Authorization': `Bearer ${sessionToken}`
},
body: formData
})

if (!response.ok) {
throw new Error('Failed to upload avatar')
}

const result = await response.json()
return result
} catch (error) {
console.error('Error uploading avatar:', error)
throw error
}
}

React Hook Example​

import { useState, useEffect } from 'react'
import { useSession } from 'next-auth/react'

interface UserProfile {
id: string
email: string
name: string
firstName: string
lastName: string
avatar?: string
bio?: string
location?: string
// ... other fields
}

export function useUserProfile() {
const { data: session } = useSession()
const [profile, setProfile] = useState<UserProfile | null>(null)
const [loading, setLoading] = useState(true)
const [error, setError] = useState<string | null>(null)

useEffect(() => {
if (session?.user) {
fetchProfile()
}
}, [session])

const fetchProfile = async () => {
try {
setLoading(true)
const response = await fetch('/api/profile')

if (!response.ok) {
throw new Error('Failed to fetch profile')
}

const profileData = await response.json()
setProfile(profileData)
} catch (err) {
setError(err instanceof Error ? err.message : 'Unknown error')
} finally {
setLoading(false)
}
}

const updateProfile = async (updates: Partial<UserProfile>) => {
try {
const response = await fetch('/api/profile', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(updates)
})

if (!response.ok) {
throw new Error('Failed to update profile')
}

await fetchProfile() // Refresh profile data
} catch (err) {
setError(err instanceof Error ? err.message : 'Update failed')
throw err
}
}

return {
profile,
loading,
error,
updateProfile,
refetch: fetchProfile
}
}

cURL Examples​

# Get profile
curl -X GET "https://ring.ck.ua/api/profile" \
-H "Authorization: Bearer your-session-token" \
-H "Accept: application/json"

# Update profile
curl -X PUT "https://ring.ck.ua/api/profile" \
-H "Authorization: Bearer your-session-token" \
-H "Content-Type: application/json" \
-d '{
"name": "John Smith",
"bio": "Full-stack developer",
"location": "Kyiv, Ukraine",
"skills": ["JavaScript", "TypeScript", "React"]
}'

# Upload avatar
curl -X POST "https://ring.ck.ua/api/profile/avatar" \
-H "Authorization: Bearer your-session-token" \
-F "avatar=@/path/to/avatar.jpg"

# Delete avatar
curl -X DELETE "https://ring.ck.ua/api/profile/avatar" \
-H "Authorization: Bearer your-session-token"

Validation Rules​

Field Constraints​

FieldTypeMin LengthMax LengthRequiredPattern
namestring2100YesLetters, spaces, hyphens
firstNamestring150NoLetters, hyphens
lastNamestring150NoLetters, hyphens
biostring0500NoAny characters
locationstring0100NoAny characters
websitestring0200NoValid URL
linkedinstring0200NoLinkedIn URL
githubstring0200NoGitHub URL
twitterstring0200NoTwitter URL
companystring0100NoAny characters
positionstring0100NoAny characters

Avatar Requirements​

  • File Types: JPEG, PNG, WebP
  • Max Size: 5MB
  • Dimensions: 100x100 to 2000x2000 pixels
  • Aspect Ratio: Square (1:1) recommended

Skills and Interests​

  • Max Items: 20 skills, 10 interests
  • Item Length: 2-50 characters each
  • Allowed Characters: Letters, numbers, spaces, hyphens, plus signs

Error Handling​

Validation Errors (400)​

{
"error": "VALIDATION_ERROR",
"message": "Invalid input data",
"details": [
{
"field": "name",
"message": "Name must be at least 2 characters long"
},
{
"field": "website",
"message": "Website must be a valid URL"
}
]
}

Unauthorized (401)​

{
"error": "UNAUTHORIZED",
"message": "Authentication required"
}

Profile Not Found (404)​

{
"error": "PROFILE_NOT_FOUND",
"message": "User profile not found"
}

File Upload Errors (400)​

{
"error": "INVALID_FILE",
"message": "Avatar file is too large or invalid format",
"details": {
"maxSize": "5MB",
"allowedTypes": ["image/jpeg", "image/png", "image/webp"]
}
}

Privacy Settings​

Visibility Controls​

interface PrivacySettings {
showEmail: boolean // Show email in public profile
showWallet: boolean // Show wallet address
showLocation: boolean // Show location information
showCompany: boolean // Show company information
showSocial: boolean // Show social media links
}

Public Profile Access​

  • Public Profiles: Visible to all users
  • Private Profiles: Only visible to user and admins
  • Confidential Members: Enhanced privacy controls

Data Export​

Export User Data​

GET /api/profile/export

Response (200):

{
"user": {
"profile": { /* full profile data */ },
"entities": [ /* user's entities */ ],
"opportunities": [ /* user's opportunities */ ],
"notifications": [ /* notification history */ ],
"activity": [ /* activity log */ ]
},
"exportedAt": "2024-12-14T16:00:00Z",
"format": "json"
}

Security Features​

Data Protection​

  • Encryption: Sensitive data encrypted at rest
  • Sanitization: All input sanitized and validated
  • Rate Limiting: 30 requests per minute per user
  • Audit Logging: All profile changes logged

Access Control​

  • Own Profile: Users can only modify their own profile
  • Admin Override: Admins can view/modify any profile
  • Role Restrictions: Some fields restricted by user role

Testing​

Unit Tests​

describe('Profile API', () => {
test('should get user profile', async () => {
const response = await request(app)
.get('/api/profile')
.set('Authorization', `Bearer ${validToken}`)
.expect(200)

expect(response.body).toHaveProperty('id')
expect(response.body).toHaveProperty('email')
expect(response.body).toHaveProperty('name')
})

test('should update profile with valid data', async () => {
const updateData = {
name: 'Updated Name',
bio: 'Updated bio'
}

const response = await request(app)
.put('/api/profile')
.set('Authorization', `Bearer ${validToken}`)
.send(updateData)
.expect(200)

expect(response.body.success).toBe(true)
})

test('should reject invalid profile data', async () => {
const invalidData = {
name: 'A', // Too short
website: 'not-a-url'
}

await request(app)
.put('/api/profile')
.set('Authorization', `Bearer ${validToken}`)
.send(invalidData)
.expect(400)
})
})

Troubleshooting​

Common Issues​

Issue: "Profile not found" Solution: Ensure user is authenticated and profile exists

Issue: "Avatar upload failed" Solution: Check file size (<5MB) and format (JPEG/PNG/WebP)

Issue: "Validation errors" Solution: Review field requirements and ensure data meets constraints

Issue: "Permission denied" Solution: Verify user has permission to modify the profile