Skip to content

Commit 264db87

Browse files
committed
feat: Migrate client-side user profile fetching and state management to React Query and simplify server-side database connection logic.
1 parent 8b7b6c9 commit 264db87

6 files changed

Lines changed: 69 additions & 102 deletions

File tree

client/src/Context/Context.jsx

Lines changed: 12 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,30 @@
1-
import { createContext, useEffect, useState } from "react"
2-
import axios from "axios"
3-
import { useContext } from "react"
4-
import { useMemo } from "react"
1+
import { useQueryClient } from "@tanstack/react-query"
2+
import { createContext, useContext, useEffect, useMemo, useState } from "react"
3+
import { profileKeys } from "@/api/auth/profile"
4+
import { useProfileQuery } from "@/hooks/queries/useProfileQuery"
55

66
export const Context = createContext()
77

88
export const useProfile = () => useContext(Context)
99

1010
export const ContextProvider = ({ children }) => {
11-
const [user, setUser] = useState(null)
12-
const [loading, setLoading] = useState(true)
11+
const queryClient = useQueryClient()
12+
const { data: user, isLoading: loading, refetch } = useProfileQuery()
1313
const [musicConfig, setMusicConfig] = useState({})
1414

15-
useEffect(() => {
16-
if (user) return
17-
getProfile()
18-
}, [user])
19-
2015
useEffect(() => {
2116
const dataFromStorage = localStorage.getItem("musicConfig")
2217
if (dataFromStorage) {
2318
setMusicConfig(JSON.parse(dataFromStorage))
2419
}
2520
}, [])
2621

27-
const getProfile = async () => {
28-
try {
29-
const response = await axios.get(`${import.meta.env.VITE_API_URL}/api/profile`, {
30-
withCredentials: true,
31-
})
32-
if (response.status === 200) {
33-
setUser(response.data.user)
34-
}
35-
} catch (error) {
36-
console.error("Error fetching profile:", error.message)
37-
} finally {
38-
setLoading(false)
39-
}
22+
const setUser = (newUser) => {
23+
queryClient.setQueryData(profileKeys.current(), newUser)
24+
}
25+
26+
const getProfile = () => {
27+
refetch()
4028
}
4129

4230
const memoizedValue = useMemo(

client/src/Routes.jsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,16 @@ import { Context } from "./Context/Context"
1414
import { ChatContext } from "./Context/ChatContext"
1515
import GroupMusic from "./Pages/Music/GroupMusic"
1616
import BottomPlayer from "./Pages/Music/BottomPlayer"
17+
import { useProfileSuspenseQuery } from "./hooks/queries/useProfileQuery"
1718

1819
const Header = lazy(() => import("./components/LandingPage/Header"))
1920
const Footer = lazy(() => import("./components/LandingPage/Footer"))
2021

22+
const ProfileLoader = ({ children }) => {
23+
useProfileSuspenseQuery()
24+
return children
25+
}
26+
2127
// Lazy-load components
2228
const Login = lazy(() => import("./components/Auth/Login"))
2329
const PasskeyLogin = lazy(() => import("./components/Auth/PassKeyLogin"))
@@ -118,7 +124,9 @@ export const ProtectedRoutes = () => {
118124
>
119125
<div className="mt-[60px] h-[calc(100vh-60px)]">
120126
<Suspense fallback={<Fallback />}>
121-
<Outlet />
127+
<ProfileLoader>
128+
<Outlet />
129+
</ProfileLoader>
122130
</Suspense>
123131
</div>
124132
</main>

client/src/api/auth/profile.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import axios from "axios"
2+
3+
export const profileKeys = {
4+
all: ["profile"],
5+
current: () => [...profileKeys.all, "current"],
6+
}
7+
8+
export const fetchProfile = async () => {
9+
const response = await axios.get(`${import.meta.env.VITE_API_URL}/api/profile`, {
10+
withCredentials: true,
11+
})
12+
return response.data.user
13+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { useQuery, useSuspenseQuery } from "@tanstack/react-query"
2+
import { fetchProfile, profileKeys } from "@/api/auth/profile"
3+
4+
export const useProfileQuery = (options = {}) => {
5+
return useQuery({
6+
queryKey: profileKeys.current(),
7+
queryFn: fetchProfile,
8+
staleTime: 1000 * 60 * 10,
9+
gcTime: 1000 * 60 * 30,
10+
retry: false,
11+
...options,
12+
})
13+
}
14+
15+
export const useProfileSuspenseQuery = (options = {}) => {
16+
return useSuspenseQuery({
17+
queryKey: profileKeys.current(),
18+
queryFn: fetchProfile,
19+
staleTime: 1000 * 60 * 10,
20+
gcTime: 1000 * 60 * 30,
21+
...options,
22+
})
23+
}

server/index.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,12 @@ sequelize.authenticate().then(() => {
2929
})
3030
})
3131

32-
process.on("SIGTERM", () => {
33-
server.close(() => sequelize.close().finally(() => process.exit(0)))
32+
process.on("SIGTERM", async () => {
33+
console.log("SIGTERM received, shutting down gracefully...")
34+
server.close(async () => {
35+
await sequelize.close()
36+
process.exit(0)
37+
})
3438
})
3539

3640
process.on("uncaughtException", (err) => {

server/utils/sequelize.js

Lines changed: 6 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -1,91 +1,22 @@
1-
// Force IPv4 first (critical for Render)
2-
const dns = require("dns")
3-
dns.setDefaultResultOrder("ipv4first")
4-
51
const { configDotenv } = require("dotenv")
62
const Sequelize = require("sequelize")
73

84
configDotenv()
95

10-
// Connection pool configuration
11-
const poolConfig = {
12-
max: 20,
13-
min: 5,
14-
idle: 10000,
15-
acquire: 30000,
16-
evict: 30000,
17-
}
18-
19-
// IMPORTANT: For Supabase pooler, use DATABASE_URL instead of separate params
206
const sequelize = new Sequelize(process.env.DATABASE_URL, {
217
dialect: "postgres",
22-
protocol: "postgres",
238
logging: false,
24-
9+
pool: {
10+
max: 5,
11+
min: 0,
12+
idle: 30000,
13+
},
2514
dialectOptions: {
2615
ssl: {
2716
require: true,
28-
rejectUnauthorized: false, // ← FIX self-signed cert error
17+
rejectUnauthorized: false,
2918
},
3019
},
31-
32-
pool: poolConfig,
33-
34-
retry: {
35-
max: 3,
36-
match: [
37-
/ConnectionError/,
38-
/SequelizeConnectionError/,
39-
/SequelizeConnectionRefusedError/,
40-
/SequelizeHostNotFoundError/,
41-
/SequelizeHostNotReachableError/,
42-
/SequelizeInvalidConnectionError/,
43-
/SequelizeConnectionTimedOutError/,
44-
/TimeoutError/,
45-
],
46-
},
47-
})
48-
49-
// Retry logic
50-
const connectWithRetry = async (retries = 5, delay = 5000) => {
51-
let retryCount = 0
52-
53-
while (retryCount < retries) {
54-
try {
55-
await sequelize.authenticate()
56-
console.log("Database Connected Successfully")
57-
return
58-
} catch (err) {
59-
retryCount++
60-
console.error(`Connection attempt ${retryCount} failed:`, err.message)
61-
62-
if (retryCount >= retries) {
63-
console.error("Maximum retry attempts reached. Cannot connect to database.")
64-
throw err
65-
}
66-
67-
console.log(`Retrying in ${delay}ms...`)
68-
await new Promise((resolve) => setTimeout(resolve, delay))
69-
}
70-
}
71-
}
72-
73-
// Start connection
74-
connectWithRetry().catch((err) => {
75-
console.error("Fatal database connection error:", err)
76-
process.exit(1)
77-
})
78-
79-
// Clean shutdown
80-
process.on("SIGINT", async () => {
81-
try {
82-
await sequelize.close()
83-
console.log("Database connection closed due to app termination")
84-
process.exit(0)
85-
} catch (error) {
86-
console.error("Error during database disconnection:", error)
87-
process.exit(1)
88-
}
8920
})
9021

9122
module.exports = sequelize

0 commit comments

Comments
 (0)