Skip to content

Commit 4037bea

Browse files
authored
Merge pull request #236 from codeunia-dev/fix/ci-cd-pipeline-and-performance-optimizations
🚀 Comprehensive CI/CD Pipeline & Performance Optimizations
2 parents c7b2890 + 6e91555 commit 4037bea

File tree

41,240 files changed

+711
-6056799
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41,240 files changed

+711
-6056799
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
# dependencies
44
/node_modules
5+
/node_modules/
6+
**/node_modules/
57
/.pnp
68
.pnp.*
79
.yarn/*

app/api/certificates/bulk-generate/route.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,11 @@ export async function POST(request: NextRequest) {
143143
await new Promise(resolve => setTimeout(resolve, 200));
144144

145145
} catch (error) {
146-
console.error(`Error generating certificate for ${participant.name}:`, error);
146+
console.error('Error generating certificate for participant:', {
147+
participantId: participant.id,
148+
participantName: participant.name,
149+
error: error instanceof Error ? error.message : 'Unknown error'
150+
});
147151
errors.push({
148152
participantId: participant.id,
149153
name: participant.name,

app/api/certificates/bulk-send-email/route.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,12 @@ export async function POST(request: NextRequest) {
6969
await new Promise(resolve => setTimeout(resolve, 1000));
7070

7171
} catch (error) {
72-
console.error(`Error sending email for ${cert.name}:`, error);
72+
console.error('Error sending email for certificate:', {
73+
certId: cert.certId,
74+
certName: cert.name,
75+
email: cert.email,
76+
error: error instanceof Error ? error.message : 'Unknown error'
77+
});
7378
errors.push({
7479
certId: cert.certId,
7580
name: cert.name,

app/api/certificates/generate/route.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,10 @@ export async function POST(request: NextRequest) {
6666
template_url: templateUrl,
6767
placeholders
6868
});
69-
console.log(`✅ Activity logged: certificate_earned for user ${user.id} with cert ${certId}`);
69+
console.log('✅ Activity logged: certificate_earned', {
70+
userId: user.id,
71+
certId: certId
72+
});
7073
} catch (activityError) {
7174
console.error('❌ Failed to log certificate earned activity:', activityError);
7275
// Don't fail the certificate generation if activity logging fails

app/api/certificates/upload-qr/route.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,24 @@ export async function POST(request: NextRequest) {
2525
);
2626
}
2727

28-
// Convert data URL to blob
28+
// Validate and convert data URL to blob (SSRF protection)
29+
if (!qrCodeDataUrl.startsWith('data:image/')) {
30+
return NextResponse.json(
31+
{ error: 'Invalid QR code data URL format' },
32+
{ status: 400 }
33+
);
34+
}
35+
36+
// Additional validation for data URL format
37+
const dataUrlPattern = /^data:image\/(png|jpeg|jpg|gif|webp);base64,/i;
38+
if (!dataUrlPattern.test(qrCodeDataUrl)) {
39+
return NextResponse.json(
40+
{ error: 'Invalid image format in data URL' },
41+
{ status: 400 }
42+
);
43+
}
44+
45+
// Convert data URL to blob (safe - no external requests)
2946
const qrCodeBlob = await fetch(qrCodeDataUrl).then(r => r.blob());
3047
const qrFileName = `qr-codes/${certId}.png`;
3148

app/api/email-preview/internship/route.ts

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,16 @@ export const runtime = 'nodejs';
44

55
// NextResponse imported but not used in GET handler - keeping for potential future use
66

7+
// HTML escaping function to prevent XSS
8+
function escapeHtml(unsafe: string): string {
9+
return unsafe
10+
.replace(/&/g, "&")
11+
.replace(/</g, "&lt;")
12+
.replace(/>/g, "&gt;")
13+
.replace(/"/g, "&quot;")
14+
.replace(/'/g, "&#039;");
15+
}
16+
717
// Mock data for email preview
818
const mockData = {
919
applicantName: 'John Doe',
@@ -59,23 +69,23 @@ const getApplicantConfirmationTemplate = (data: typeof mockData) => {
5969
<div class="content">
6070
<div class="success-badge">✅ Successfully Applied</div>
6171
62-
<p>Hi ${applicantName},</p>
72+
<p>Hi ${escapeHtml(applicantName)},</p>
6373
64-
<p>Great news! Your application for <strong>${internshipTitle}</strong> has been successfully submitted and confirmed.</p>
74+
<p>Great news! Your application for <strong>${escapeHtml(internshipTitle)}</strong> has been successfully submitted and confirmed.</p>
6575
6676
<div class="details-card">
6777
<h3 style="margin-top: 0; color: #1f2937;">Application Details</h3>
6878
<div class="detail-row">
6979
<span class="detail-label">Internship:</span>
70-
<span class="detail-value">${internshipTitle}</span>
80+
<span class="detail-value">${escapeHtml(internshipTitle)}</span>
7181
</div>
7282
<div class="detail-row">
7383
<span class="detail-label">Domain:</span>
74-
<span class="detail-value">${domain}</span>
84+
<span class="detail-value">${escapeHtml(domain)}</span>
7585
</div>
7686
<div class="detail-row">
7787
<span class="detail-label">Level:</span>
78-
<span class="detail-value">${level}</span>
88+
<span class="detail-value">${escapeHtml(level)}</span>
7989
</div>
8090
<div class="detail-row">
8191
<span class="detail-label">Duration:</span>
@@ -185,15 +195,15 @@ const getAdminNotificationTemplate = (data: typeof mockData) => {
185195
<h3 style="margin-top: 0; color: #1f2937;">Application Details</h3>
186196
<div class="detail-row">
187197
<span class="detail-label">Internship:</span>
188-
<span class="detail-value">${internshipTitle}</span>
198+
<span class="detail-value">${escapeHtml(internshipTitle)}</span>
189199
</div>
190200
<div class="detail-row">
191201
<span class="detail-label">Domain:</span>
192-
<span class="detail-value">${domain}</span>
202+
<span class="detail-value">${escapeHtml(domain)}</span>
193203
</div>
194204
<div class="detail-row">
195205
<span class="detail-label">Level:</span>
196-
<span class="detail-value">${level}</span>
206+
<span class="detail-value">${escapeHtml(level)}</span>
197207
</div>
198208
<div class="detail-row">
199209
<span class="detail-label">Duration:</span>
@@ -371,15 +381,15 @@ const getStatusUpdateTemplate = (data: {
371381
<h3 style="margin-top: 0; color: #1f2937;">Application Details</h3>
372382
<div class="detail-row">
373383
<span class="detail-label">Internship:</span>
374-
<span class="detail-value">${internshipTitle}</span>
384+
<span class="detail-value">${escapeHtml(internshipTitle)}</span>
375385
</div>
376386
<div class="detail-row">
377387
<span class="detail-label">Domain:</span>
378-
<span class="detail-value">${domain}</span>
388+
<span class="detail-value">${escapeHtml(domain)}</span>
379389
</div>
380390
<div class="detail-row">
381391
<span class="detail-label">Level:</span>
382-
<span class="detail-value">${level}</span>
392+
<span class="detail-value">${escapeHtml(level)}</span>
383393
</div>
384394
<div class="detail-row">
385395
<span class="detail-label">Duration:</span>
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import { NextResponse } from 'next/server';
2+
import { createClient } from '@/lib/supabase/server';
3+
4+
export async function GET() {
5+
try {
6+
const supabase = await createClient();
7+
8+
const { data, error } = await supabase
9+
.from('reserved_usernames')
10+
.select('*')
11+
.eq('is_active', true)
12+
.order('username');
13+
14+
if (error) {
15+
console.error('Error fetching reserved usernames:', error);
16+
return NextResponse.json(
17+
{ error: 'Failed to fetch reserved usernames' },
18+
{ status: 500 }
19+
);
20+
}
21+
22+
return NextResponse.json(data || []);
23+
} catch (error) {
24+
console.error('Error in reserved usernames API:', error);
25+
return NextResponse.json(
26+
{ error: 'Internal server error' },
27+
{ status: 500 }
28+
);
29+
}
30+
}

app/layout.tsx

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,23 @@ export default function RootLayout({
4040
return (
4141
<html lang="en" suppressHydrationWarning>
4242
<head>
43+
{/* Critical CSS for above-the-fold content */}
44+
<style dangerouslySetInnerHTML={{
45+
__html: `
46+
/* Critical CSS for initial render */
47+
body { margin: 0; font-family: var(--font-geist-sans), system-ui, sans-serif; }
48+
.hero-section { min-height: 100vh; display: flex; align-items: center; }
49+
.loading-spinner { animation: spin 1s linear infinite; }
50+
@keyframes spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
51+
.fade-in { animation: fadeIn 0.5s ease-in; }
52+
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
53+
`
54+
}} />
55+
56+
{/* Preload critical resources */}
57+
<link rel="preload" href="/fonts/geist-sans.woff2" as="font" type="font/woff2" crossOrigin="anonymous" />
58+
<link rel="preload" href="/fonts/geist-mono.woff2" as="font" type="font/woff2" crossOrigin="anonymous" />
59+
4360
{/* Structured Data */}
4461
<script
4562
type="application/ld+json"

components/home/HeroSection2.tsx

Lines changed: 44 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -72,13 +72,34 @@ const Particles = dynamic(() => import("@/components/ui/particles").then(mod =>
7272

7373
export function HeroSection2() {
7474
const { quality } = useLocalPerformanceMonitor()
75+
const [isVisible, setIsVisible] = useState(false)
76+
77+
// Intersection observer for lazy loading 3D content
78+
useEffect(() => {
79+
const observer = new IntersectionObserver(
80+
([entry]) => {
81+
if (entry.isIntersecting) {
82+
setIsVisible(true)
83+
observer.disconnect()
84+
}
85+
},
86+
{ threshold: 0.1 }
87+
)
88+
89+
const element = document.getElementById('hero-globe')
90+
if (element) {
91+
observer.observe(element)
92+
}
93+
94+
return () => observer.disconnect()
95+
}, [])
7596

7697
// Memoized globe configuration with performance-based adjustments
7798
const globeConfig = useMemo(() => {
7899
const baseConfig = {
79-
pointSize: 4,
100+
pointSize: 3, // Reduced from 4
80101
globeColor: "#062056",
81-
showAtmosphere: true,
102+
showAtmosphere: false, // Disabled by default for better performance
82103
atmosphereColor: "#FFFFFF",
83104
atmosphereAltitude: 0.1,
84105
emissive: "#062056",
@@ -91,31 +112,31 @@ export function HeroSection2() {
91112
pointLight: "#ffffff",
92113
arcTime: 1000,
93114
arcLength: 0.9,
94-
rings: 1,
95-
maxRings: 3,
115+
rings: 0, // Reduced from 1
116+
maxRings: 2, // Reduced from 3
96117
initialPosition: { lat: 22.3193, lng: 114.1694 },
97118
autoRotate: true,
98-
autoRotateSpeed: 0.5,
119+
autoRotateSpeed: 0.3, // Reduced from 0.5
99120
}
100121

101122
// Adjust quality based on performance
102123
switch (quality) {
103124
case 'low':
104125
return {
105126
...baseConfig,
106-
pointSize: 2,
127+
pointSize: 1,
107128
showAtmosphere: false,
108129
rings: 0,
109-
maxRings: 1,
110-
autoRotateSpeed: 0.2,
130+
maxRings: 0,
131+
autoRotateSpeed: 0.1,
111132
}
112133
case 'medium':
113134
return {
114135
...baseConfig,
115-
pointSize: 3,
116-
rings: 1,
117-
maxRings: 2,
118-
autoRotateSpeed: 0.3,
136+
pointSize: 2,
137+
rings: 0,
138+
maxRings: 1,
139+
autoRotateSpeed: 0.2,
119140
}
120141
default:
121142
return baseConfig
@@ -634,17 +655,23 @@ export function HeroSection2() {
634655
</div>
635656

636657

637-
<div className="relative animate-fade-in w-full lg:order-2" style={{ animationDelay: '0.6s' }}>
658+
<div id="hero-globe" className="relative animate-fade-in w-full lg:order-2" style={{ animationDelay: '0.6s' }}>
638659
<div className="h-[300px] sm:h-[400px] md:h-[500px] lg:h-[600px] w-full relative">
639660
<div className="absolute inset-0 bg-gradient-to-r from-primary/20 to-purple-500/20 rounded-2xl sm:rounded-3xl blur-2xl sm:blur-3xl opacity-30 animate-pulse" />
640661
<div className="relative z-10 h-full">
641-
<Suspense fallback={
662+
{isVisible ? (
663+
<Suspense fallback={
664+
<div className="h-full w-full flex items-center justify-center">
665+
<div className="animate-pulse text-primary">Loading...</div>
666+
</div>
667+
}>
668+
<World globeConfig={globeConfig} data={sampleArcs} />
669+
</Suspense>
670+
) : (
642671
<div className="h-full w-full flex items-center justify-center">
643672
<div className="animate-pulse text-primary">Loading...</div>
644673
</div>
645-
}>
646-
<World globeConfig={globeConfig} data={sampleArcs} />
647-
</Suspense>
674+
)}
648675
</div>
649676
</div>
650677

0 commit comments

Comments
 (0)