From b6ff7b915a34a1e599a939947370816483aa8aa5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 28 May 2026 03:53:56 +0000 Subject: [PATCH 1/2] Fix validation and middleware issues --- Client/src/components/MentorProgramPanel.tsx | 10 ++-- Client/src/components/MissionsHub.tsx | 4 +- Client/src/lib/api-client.ts | 13 ++--- Client/src/services/auth.service.ts | 49 +++++++++++------ backend/lib/supabase/index.ts | 53 +++++++++++++++++++ backend/lib/validations/leaderboard.ts | 3 +- backend/lib/validations/profile.ts | 2 +- backend/middleware.ts | 4 +- .../20260327000013_create_rls_policies.sql | 3 ++ 9 files changed, 111 insertions(+), 30 deletions(-) create mode 100644 backend/lib/supabase/index.ts diff --git a/Client/src/components/MentorProgramPanel.tsx b/Client/src/components/MentorProgramPanel.tsx index 7f730b7..3f6b189 100644 --- a/Client/src/components/MentorProgramPanel.tsx +++ b/Client/src/components/MentorProgramPanel.tsx @@ -136,12 +136,16 @@ const MentorProgramPanel = () => { try { const myRequests = await api.get('/community/mentors/requests'); setRequests(myRequests); - } catch (err) {} + } catch (err) { + void err; + } try { const me = await api.get('/community/mentors/me'); setMyProfile(me); - } catch (err) {} + } catch (err) { + void err; + } } catch (error) { setMessage('Failed to load mission data.'); } finally { @@ -171,7 +175,7 @@ const MentorProgramPanel = () => { }; const handleApplyFilters = async () => { - const query: any = {}; + const query: Record = {}; if (filters.skill.trim()) query.skill = filters.skill.trim(); if (filters.experienceLevel) query.experienceLevel = filters.experienceLevel; const list = await api.get('/community/mentors', query); diff --git a/Client/src/components/MissionsHub.tsx b/Client/src/components/MissionsHub.tsx index de10c42..94e3376 100644 --- a/Client/src/components/MissionsHub.tsx +++ b/Client/src/components/MissionsHub.tsx @@ -37,7 +37,7 @@ interface Mission { difficulty: 'easy' | 'medium' | 'hard'; requirement_type?: string; status: 'in_progress' | 'pending_verification' | 'completed'; - progress: any; + progress: Record | null; time_remaining_ms: number; } @@ -215,7 +215,7 @@ type ViewType = 'all' | 'my'; const MissionsHub = () => { const [missions, setMissions] = useState([]); - const [profile, setProfile] = useState(null); + const [profile, setProfile] = useState(null); const [userId, setUserId] = useState(null); const [loading, setLoading] = useState(true); const [activeFilter, setActiveFilter] = useState('all'); diff --git a/Client/src/lib/api-client.ts b/Client/src/lib/api-client.ts index 57505e1..dceee0e 100644 --- a/Client/src/lib/api-client.ts +++ b/Client/src/lib/api-client.ts @@ -15,7 +15,7 @@ export class ApiError extends Error { constructor( message: string, public status: number, - public data?: any + public data?: unknown ) { super(message); this.name = 'ApiError'; @@ -33,9 +33,10 @@ interface RequestOptions extends RequestInit { * Get authentication token from Clerk session */ async function getAuthToken(): Promise { - if (typeof window !== 'undefined' && (window as any).Clerk && (window as any).Clerk.session) { + const clerk = typeof window !== 'undefined' ? (window as Window & { Clerk?: { session?: { getToken?: () => Promise } } }).Clerk : undefined + if (clerk?.session?.getToken) { try { - return await (window as any).Clerk.session.getToken(); + return await clerk.session.getToken(); } catch (e) { console.warn("Failed to get Clerk token", e); return null; @@ -157,7 +158,7 @@ export const api = { /** * POST request */ - post: (endpoint: string, data?: any) => + post: (endpoint: string, data?: unknown) => request(endpoint, { method: 'POST', body: data ? JSON.stringify(data) : undefined, @@ -166,7 +167,7 @@ export const api = { /** * PATCH request */ - patch: (endpoint: string, data?: any) => + patch: (endpoint: string, data?: unknown) => request(endpoint, { method: 'PATCH', body: data ? JSON.stringify(data) : undefined, @@ -175,7 +176,7 @@ export const api = { /** * PUT request */ - put: (endpoint: string, data?: any) => + put: (endpoint: string, data?: unknown) => request(endpoint, { method: 'PUT', body: data ? JSON.stringify(data) : undefined, diff --git a/Client/src/services/auth.service.ts b/Client/src/services/auth.service.ts index 097fec5..b354a23 100644 --- a/Client/src/services/auth.service.ts +++ b/Client/src/services/auth.service.ts @@ -8,18 +8,37 @@ import { api } from '@/lib/api-client'; import type { AuthResponse } from '@/types/api'; +type ClerkUser = { + id: string + primaryEmailAddress?: { emailAddress: string } | null + fullName?: string | null + imageUrl?: string + username?: string | null +} + +type AppUser = { + id: string + email?: string + full_name: string + avatar_url?: string + username?: string | null +} + +const getClerk = () => (typeof window !== 'undefined' ? (window as Window & { Clerk?: { user?: ClerkUser; session?: { signOut?: () => Promise }; signOut?: () => Promise } }).Clerk : undefined) + export const authService = { - signUp: async (data: any): Promise => { + signUp: async (_data: unknown): Promise => { throw new Error('Please use Clerk component instead'); }, - signIn: async (data: any): Promise => { + signIn: async (_data: unknown): Promise => { throw new Error('Please use Clerk component instead'); }, signOut: async (): Promise => { - if (typeof window !== 'undefined' && (window as any).Clerk) { - await (window as any).Clerk.signOut(); + const clerk = getClerk() + if (clerk?.signOut) { + await clerk.signOut(); } }, @@ -27,9 +46,10 @@ export const authService = { await this.signOut(); }, - getUser: (): any | null => { - if (typeof window !== 'undefined' && (window as any).Clerk) { - const user = (window as any).Clerk.user; + getUser: (): AppUser | null => { + const clerk = getClerk() + if (clerk?.user) { + const user = clerk.user; if (!user) return null; return { id: user.id, @@ -42,23 +62,20 @@ export const authService = { return null; }, - updateUser: (userData: any): void => { + updateUser: (_userData: unknown): void => { // Left empty. Profile modifications should happen via Clerk Dashboard or Clerk UI Components console.warn('Profile modifications should be done via Clerk'); }, - forgotPassword: async (data: any): Promise => {}, - verifyOTP: async (data: any): Promise => {}, + forgotPassword: async (_data: unknown): Promise => {}, + verifyOTP: async (_data: unknown): Promise => {}, isAuthenticated: (): boolean => { - if (typeof window !== 'undefined' && (window as any).Clerk) { - return !!(window as any).Clerk.session; - } - return false; + return Boolean(getClerk()?.session); }, signInWithProvider: async (provider: 'github' | 'google'): Promise => {}, sendMagicLink: async (email: string): Promise => {}, - verifyMagicLink: async (email: string, otp: string): Promise => ({}), - resetPassword: async (data: any): Promise => {}, + verifyMagicLink: async (_email: string, _otp: string): Promise> => ({}), + resetPassword: async (_data: unknown): Promise => {}, }; diff --git a/backend/lib/supabase/index.ts b/backend/lib/supabase/index.ts new file mode 100644 index 0000000..8a561b9 --- /dev/null +++ b/backend/lib/supabase/index.ts @@ -0,0 +1,53 @@ +import { createClient as createSupabaseClient } from '@supabase/supabase-js' + +type QueryResult = { + data: T | null + error: Error | null +} + +const getRequiredEnv = (name: string) => { + const value = process.env[name] + if (!value) { + throw new Error('Missing Supabase environment variables') + } + return value +} + +export function getServerClient() { + const url = getRequiredEnv('NEXT_PUBLIC_SUPABASE_URL') + const serviceRoleKey = getRequiredEnv('SUPABASE_SERVICE_ROLE_KEY') + + return createSupabaseClient(url, serviceRoleKey, { + auth: { + persistSession: false, + autoRefreshToken: false, + }, + }) +} + +export function getClientSupabase() { + const url = getRequiredEnv('NEXT_PUBLIC_SUPABASE_URL') + const anonKey = getRequiredEnv('NEXT_PUBLIC_SUPABASE_ANON_KEY') + + return createSupabaseClient(url, anonKey, { + auth: { + persistSession: true, + autoRefreshToken: true, + }, + }) +} + +export async function executeQuery( + queryFn: (client: ReturnType) => Promise +): Promise> { + try { + const data = await queryFn(getServerClient()) + return { data, error: null } + } catch (error) { + return { + data: null, + error: error instanceof Error ? error : new Error('Unknown database error'), + } + } +} + diff --git a/backend/lib/validations/leaderboard.ts b/backend/lib/validations/leaderboard.ts index 36dd168..06f8540 100644 --- a/backend/lib/validations/leaderboard.ts +++ b/backend/lib/validations/leaderboard.ts @@ -10,7 +10,8 @@ export const leaderboardUpdateSchema = z.object({ .string() .uuid('Event ID must be a valid UUID'), user_id: z - .string(), + .string() + .uuid('User ID must be a valid UUID'), score: z .number() .int('Score must be an integer') diff --git a/backend/lib/validations/profile.ts b/backend/lib/validations/profile.ts index a38c0bf..b3a981f 100644 --- a/backend/lib/validations/profile.ts +++ b/backend/lib/validations/profile.ts @@ -37,7 +37,7 @@ export const profileUpdateSchema = z.object({ .or(z.literal('')), skills: z .array(z.string()) - .max(15, 'Cannot have more than 15 skills') + .max(10, 'Cannot have more than 10 skills') .optional(), university: z.string().max(100).optional().or(z.literal('')), education: z.string().max(100).optional().or(z.literal('')), diff --git a/backend/middleware.ts b/backend/middleware.ts index ff74d26..450bae0 100644 --- a/backend/middleware.ts +++ b/backend/middleware.ts @@ -10,7 +10,7 @@ import type { NextRequest } from 'next/server' * 2. Handles Cross-Origin Resource Sharing (CORS) for all API routes */ -export default clerkMiddleware((auth, request) => { +export const middleware = clerkMiddleware((auth, request) => { // Get origin from request headers const origin = request.headers.get('origin') const isDev = process.env.NODE_ENV === 'development' @@ -43,6 +43,8 @@ export default clerkMiddleware((auth, request) => { ...(isAllowedOrigin ? { 'Access-Control-Allow-Credentials': 'true' } : {}), }, }) + + export default middleware } // Handle actual requests diff --git a/backend/supabase/migrations/20260327000013_create_rls_policies.sql b/backend/supabase/migrations/20260327000013_create_rls_policies.sql index c47c5e2..b254f7f 100644 --- a/backend/supabase/migrations/20260327000013_create_rls_policies.sql +++ b/backend/supabase/migrations/20260327000013_create_rls_policies.sql @@ -20,6 +20,7 @@ ALTER TABLE activity_cooldowns ENABLE ROW LEVEL SECURITY; -- ============================================================================= -- Users can view their own XP transactions +-- Clerk user ID = user_id CREATE POLICY "Users can view own XP transactions" ON xp_transactions FOR SELECT USING (auth.uid() = user_id); @@ -52,6 +53,7 @@ WITH CHECK ( -- ============================================================================= -- Anyone can view active badges +-- All users can view active badges CREATE POLICY "Anyone can view active badges" ON badges FOR SELECT USING (is_active = true); @@ -101,6 +103,7 @@ USING ( -- ============================================================================= -- Users can view their own badges +-- Clerk user ID = user_id CREATE POLICY "Users can view own badges" ON user_badges FOR SELECT USING (auth.uid() = user_id); From ce84ac14b743b6496addb017b4abcbd5dce96136 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 28 May 2026 03:55:29 +0000 Subject: [PATCH 2/2] Finalize validation and middleware fixes --- Client/src/components/MentorProgramPanel.tsx | 27 +++---- Client/src/components/MissionsHub.tsx | 5 +- backend/lib/validations/profile.ts | 10 +-- backend/lib/validations/resource.ts | 2 +- backend/lib/validations/sponsor.ts | 6 +- backend/middleware.ts | 81 ++++++++++---------- 6 files changed, 66 insertions(+), 65 deletions(-) diff --git a/Client/src/components/MentorProgramPanel.tsx b/Client/src/components/MentorProgramPanel.tsx index 3f6b189..73764fb 100644 --- a/Client/src/components/MentorProgramPanel.tsx +++ b/Client/src/components/MentorProgramPanel.tsx @@ -1,10 +1,11 @@ -import { FormEvent, useEffect, useMemo, useState } from 'react'; +import { FormEvent, useCallback, useEffect, useMemo, useState } from 'react'; import { api, ApiError } from '@/lib/api-client'; -import { Calendar, Copy, ExternalLink, Loader2, ShieldAlert, Star, Video, Users, Target, Zap, ChevronRight, Search, Check, ShieldCheck, Clock, MessageSquare, Send } from 'lucide-react'; +import { Calendar, Copy, ExternalLink, Loader2, ShieldAlert, Star, Video, Users, Target, Zap, ChevronRight, Search, Check, ShieldCheck, Clock, MessageSquare, Send, type LucideIcon } from 'lucide-react'; import { motion, AnimatePresence } from 'framer-motion'; type ExperienceLevel = 'junior' | 'mid' | 'senior' | 'expert'; type RequestStatus = 'pending' | 'accepted' | 'declined' | 'canceled' | 'completed'; +type MentorRequestAction = 'accept' | 'decline' | 'confirm_complete' | 'cancel' | 'complete'; interface Mentor { id: string; @@ -116,11 +117,7 @@ const MentorProgramPanel = () => { return `${JITSI_BASE_URL}/${MENTORSHIP_ROOM_PREFIX}-${roomSeed}`; }; - useEffect(() => { - void loadData(); - }, []); - - const loadData = async () => { + const loadData = useCallback(async () => { try { setLoading(true); const [mentorList, mentorStats] = await Promise.all([ @@ -129,8 +126,8 @@ const MentorProgramPanel = () => { ]); setMentors(mentorList); setStats(mentorStats); - if (mentorList.length > 0 && !selectedMentorId) { - setSelectedMentorId(mentorList[0].id); + if (mentorList.length > 0) { + setSelectedMentorId((current) => current || mentorList[0].id); } try { @@ -151,7 +148,11 @@ const MentorProgramPanel = () => { } finally { setLoading(false); } - }; + }, []); + + useEffect(() => { + void loadData(); + }, [loadData]); const handleApplyAsMentor = async (e: FormEvent) => { e.preventDefault(); @@ -229,7 +230,7 @@ const MentorProgramPanel = () => { } catch (err) { setMessage('Feedback loop failed.'); } }; - const handleRequestAction = async (requestId: string, action: any) => { + const handleRequestAction = async (requestId: string, action: MentorRequestAction) => { try { await api.patch(`/community/mentors/requests/${requestId}`, { action }); loadData(); @@ -280,7 +281,7 @@ const MentorProgramPanel = () => {
{['directory', 'requests'].map((tab) => (