ํ‹ฐ์Šคํ† ๋ฆฌ ๋ทฐ

zustand์™€ supabase๋ฅผ ํ™œ์šฉํ•ด์„œ ๋กœ๊ทธ์ธ ํ† ํฐ ์œ ํšจ์„ฑ ๊ฒ€์ฆ์„ ํ•˜๋ ค๊ณ  ํ•œ๋‹ค.
๋กœ๊ทธ์ธ ํ† ํฐ ์œ ํšจ์„ฑ ๊ฒ€์ฆ์ด ๋ฌด์—‡์ผ๊นŒ?

๋กœ๊ทธ์ธ ํ† ํฐ ์œ ํšจ์„ฑ ๊ฒ€์ฆ์ด๋ž€?

 

๐Ÿ“Œ์‚ฌ์šฉ์ž๊ฐ€ ๋ณด์œ ํ•œ ์ธ์ฆ ํ† ํฐ์ด ์œ ํšจํ•œ์ง€ ํ™•์ธํ•˜๋Š” ๊ณผ์ •
์ฆ‰, ์‚ฌ์šฉ์ž๊ฐ€ ๋กœ๊ทธ์ธ ์ƒํƒœ๋ฅผ ์œ ์ง€ํ•˜๊ณ  ์žˆ๋Š”์ง€ ํ™•์ธํ•˜๋Š” ์—ญํ• ์ด๋‹ค.

Supabase์—์„œ๋Š” auth.getSession() ๋˜๋Š” auth.refreshSession()์„ ์ด์šฉํ•ด์„œ ํ† ํฐ์„ ๊ฒ€์ฆํ•  ์ˆ˜ ์žˆ๋‹ค.

 

๋กœ๊ทธ์ธ ํ† ํฐ ๊ฒ€์ฆ ๋กœ์ง ํ๋ฆ„

 

๐Ÿ“๋กœ๊ทธ์ธ ๊ณผ์ •

  1. ์‚ฌ์šฉ์ž๊ฐ€ ์ด๋ฉ”์ผ๊ณผ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์ž…๋ ฅํ•˜์—ฌ ๋กœ๊ทธ์ธ ์š”์ฒญ์„ ๋ณด๋ƒ„
  2. supabase.auth.signInWithPassword()๋ฅผ ํ†ตํ•ด ์ธ์ฆ (๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ํ†ตํ•ด ์‚ฌ์šฉ์ž ์ธ์ฆํ•˜๋Š” ๋ฐฉ์‹)
  3. ์ธ์ฆ์ด ์„ฑ๊ณตํ•˜๋ฉด ์—‘์„ธ์Šค ํ† ํฐ(Access Token)๊ณผ ๋ฆฌํ”„๋ ˆ์‹œ ํ† ํฐ(Refresh Token) ์ด ๋ฐœ๊ธ‰๋จ
  4. Zustand ์Šคํ† ์–ด์— ๋กœ๊ทธ์ธ ์ƒํƒœ(isSignedIn)์™€ ์œ ์ € ์ •๋ณด(user) ์ €์žฅ

 

 

๐Ÿ“๋กœ๊ทธ์ธ ํ† ํฐ ์œ ํšจ์„ฑ ๊ฒ€์ฆ ๊ณผ์ •

์‚ฌ์šฉ์ž๊ฐ€ ํŽ˜์ด์ง€๋ฅผ ์ƒˆ๋กœ๊ณ ์นจํ•˜๊ฑฐ๋‚˜, ์•ฑ์„ ๋‹ค์‹œ ์‹คํ–‰ํ•  ๋•Œ

 

  1. Supabase auth.getSession()์„ ํ˜ธ์ถœํ•˜์—ฌ ํ˜„์žฌ ๋กœ๊ทธ์ธ ์„ธ์…˜์ด ์œ ํšจํ•œ์ง€ ํ™•์ธ
  2. ์„ธ์…˜์ด ์œ ํšจํ•˜๋ฉด
    • Zustand ์ƒํƒœ(isSignedIn, user)๋ฅผ ๊ฐฑ์‹ ํ•˜์—ฌ ๋กœ๊ทธ์ธ ์ƒํƒœ ์œ ์ง€.
  3. ์„ธ์…˜์ด ๋งŒ๋ฃŒ๋˜์—ˆ๊ฑฐ๋‚˜ ์—†์œผ๋ฉด
    • auth.refreshSession()์„ ํ˜ธ์ถœํ•˜์—ฌ ์ƒˆ๋กœ์šด ํ† ํฐ์„ ๋ฐœ๊ธ‰ ์‹œ๋„ (์ ์šฉ ์•ˆ ํ•จ)
    • ๋ฆฌํ”„๋ ˆ์‹œ ํ† ํฐ๋„ ๋งŒ๋ฃŒ๋˜์—ˆ๋‹ค๋ฉด ์‚ฌ์šฉ์ž๋ฅผ ๋กœ๊ทธ์•„์›ƒ ์ฒ˜๋ฆฌ

 

supabase API

supabase Docs๋ฅผ ๋ณด๋ฉด ๋‹ค์–‘ํ•œ API๋“ค์ด ์žˆ๋‹ค.

๊ทธ ์ค‘ ๋‚ด๊ฐ€ ์‚ฌ์šฉํ•œ API๋ฅผ ์ •๋ฆฌํ•ด๋ณด๊ฒ ๋‹ค.
 
๐Ÿ“signInWithPassword()

//์ด๋ฉ”์ผ๊ณผ ๋น„๋ฐ€๋ฒˆํ˜ธ๋กœ ๋กœ๊ทธ์ธ
const { data, error } = await supabase.auth.signInWithPassword({
  email: 'example@email.com',
  password: 'example-password',
})

// ์ „ํ™”๋ฒˆํ˜ธ์™€ ๋น„๋ฐ€๋ฒˆํ˜ธ๋กœ ๋กœ๊ทธ์ธ
const { data, error } = await supabase.auth.signInWithPassword({
  email: 'example@email.com',
  password: 'example-password',
})

 

 

 

 

 

๐Ÿ“getSession()

// ์„ธ์…˜ ๋ฐ์ดํ„ฐ ๊ฐ€์ ธ์˜ค๊ธฐ
const { data, error } = await supabase.auth.getSession()

 

 

 

Zustand + Supabase

 

zustand๋Š” ํด๋ผ์ด์–ธํŠธ์˜ ์ƒํƒœ๋ฅผ ์ „์—ญ์ ์œผ๋กœ ๊ด€๋ฆฌํ•œ๋‹ค.

supabase์˜ ๋กœ๊ทธ์ธ ๋กœ์ง๊ณผ ํ•จ๊ป˜ ์‚ฌ์šฉํ•˜์—ฌ ๋กœ๊ทธ์ธ/๋กœ๊ทธ์•„์›ƒ/ํ† ํฐ ์œ ํšจ์„ฑ์„ ์ „์—ญ์œผ๋กœ ๊ด€๋ฆฌํ•œ๋‹ค.

 

๋กœ์ง์˜ ํ๋ฆ„์€ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๊ตฌ์ƒํ•˜์˜€๋‹ค.

๋กœ๊ทธ์ธ ํ•จ์ˆ˜ : signInWithPassword๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋กœ๊ทธ์ธํ•˜๊ณ , ๊ทธ์— ํ•ด๋‹นํ•˜๋Š” ์œ ์ € ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์™€์„œ user ์ƒํƒœ์— set
๋กœ๊ทธ์•„์›ƒ ํ•จ์ˆ˜ : signOut๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋กœ๊ทธ์•„์›ƒ
ํ† ํฐ ์œ ํšจ์„ฑ ๊ฒ€์ฆ ํ•จ์ˆ˜ : getSession์„ ์‚ฌ์šฉํ•˜์—ฌ ์œ ํšจํ•˜๋ฉด ์œ ์ € ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์™€์„œ setํ•˜๊ณ  ์—†๋‹ค๋ฉด ๋กœ๊ทธ์•„์›ƒ ์ƒํƒœ์ฒ˜๋Ÿผ set 

 

 

๊ทธ๋Ÿฌ๋‚˜, store์— supabase ๋กœ์ง์„ ์ง์ ‘ ์‚ฌ์šฉํ–ˆ๋”๋‹ˆ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ๋‹ค.

 

.env ํŒŒ์ผ์—์„œ ํ™˜๊ฒฝ๋ณ€์ˆ˜๋ฅผ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์—ˆ๋‹ค.

SUPABASE_URL= ''
SUPABASE_KEY= ''

 

์œ„์—๋„ ์ ์—ˆ๋“ฏ zustand๋Š” 'ํด๋ผ์ด์–ธํŠธ'์˜ ์ƒํƒœ๋ฅผ ๊ด€๋ฆฌํ•œ๋‹ค.
ํ•˜์ง€๋งŒ supabase์˜ ํ‚ค๋Š” ์„œ๋ฒ„ ํ™˜๊ฒฝ๋ณ€์ˆ˜๋กœ ์‚ฌ์šฉ๋˜๊ณ  ์žˆ๋‹ค.

์ด์ „ ๊ณผ์ œ์—์„œ ์•Œ๊ฒŒ ๋œ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๋‚ด์šฉ์ด ์žˆ๋‹ค.

Next.js๋Š” NEXT_PUBLIC_ ์ ‘๋‘์‚ฌ๋ฅผ ๋ถ™์—ฌ์•ผ๋งŒ ํด๋ผ์ด์–ธํŠธ์—์„œ ํ™˜๊ฒฝ๋ณ€์ˆ˜๋ฅผ ๋…ธ์ถœํ•ฉ๋‹ˆ๋‹ค.
์ด๋ฅผ ํ†ตํ•ด ์„œ๋ฒ„ ํ™˜๊ฒฝ๋ณ€์ˆ˜์™€ ํด๋ผ์ด์–ธํŠธ ํ™˜๊ฒฝ๋ณ€์ˆ˜๋ฅผ ๋ช…ํ™•ํžˆ ๊ตฌ๋ถ„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

ํด๋ผ์ด์–ธํŠธ(zustand)์—์„œ ์‚ฌ์šฉํ•  ๊ฒฝ์šฐ์—๋Š” NEXT_PUBLIC_์„ ๊ผญ ๋ถ™์—ฌ์ฃผ์–ด์•ผ ํ•œ๋‹ค!

 

 NEXT_PUBLIC_์„ ๋ถ™์—ฌ์ฃผ๋ฉด ํด๋ผ์ด์–ธํŠธ์— ๋…ธ์ถœ๋œ๋‹ค๋Š” ๊ฑด๋ฐ, API ํ‚ค๋ฅผ ๋…ธ์ถœํ•ด๋„ ๋˜๋Š” ๊ฑธ๊นŒ?

๋ผ๋Š” ์ƒ๊ฐ์ด ๋“ค์—ˆ๋‹ค.

 

 

supabase ๋กœ์ง์„ ์„œ๋ฒ„ ์•ก์…˜์œผ๋กœ ๋ถ„๋ฆฌํ•˜๋ฉด ์„œ๋ฒ„์—์„œ ์„œ๋ฒ„ ํ‚ค๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋˜์ง€ ์•Š์„๊นŒ?
๋ผ๋Š” ์ƒ๊ฐ์œผ๋กœ NEXT_PUBLIC_์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ณ  ์„œ๋ฒ„ ์•ก์…˜์œผ๋กœ ๋ถ„๋ฆฌํ•˜์—ฌ ๊ตฌํ˜„ํ•˜์˜€๋‹ค.

 

ํ•˜์ง€๋งŒ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ ๊ฐ™์€ ์˜ค๋ฅ˜๊ฐ€ ๊ณ„์†๋˜์—ˆ๋‹ค.

 

๐Ÿค” ์™œ ์„œ๋ฒ„ ์•ก์…˜์„ ์จ๋„ ์ž‘๋™๋˜์ง€ ์•Š๋Š”๊ฑธ๊นŒ?

  • ๋งŒ์•ฝ ์ •๋ง๋กœ ‘์„œ๋ฒ„ ์•ก์…˜์—์„œ๋งŒ’ ์ด ๋ณ€์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜๊ณ , ํด๋ผ์ด์–ธํŠธ(React ์ปดํฌ๋„ŒํŠธ๋‚˜ zustand ์Šคํ† ์–ด)์—์„œ๋Š” ์ „ํ˜€ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š”๋‹ค๋ฉด NEXT_PUBLIC_ ์—†์ด๋„ ์„œ๋ฒ„ ์ธก์—์„œ๋งŒ ์ ‘๊ทผ ๊ฐ€๋Šฅํ•˜๋‹ค.
  • ํ•˜์ง€๋งŒ Zustand Store(ํด๋ผ์ด์–ธํŠธ ์‚ฌ์ด๋“œ)์—์„œ Supabase API๋ฅผ ์ง์ ‘ ํ˜ธ์ถœํ•œ๋‹ค๋ฉด(๋˜๋Š” Supabase client๋ฅผ importํ•ด ์‚ฌ์šฉํ•˜๋Š” ๋กœ์ง์ด ํด๋ผ์ด์–ธํŠธ ์ฝ”๋“œ์—๋„ ์žˆ๋‹ค๋ฉด) ๊ฒฐ๊ตญ ํด๋ผ์ด์–ธํŠธ ๋ฒˆ๋“ค์— ํ™˜๊ฒฝ ๋ณ€์ˆ˜๊ฐ€ ํ•„์š”ํ•˜๊ฒŒ ๋œ๋‹ค. ์ด๋•Œ๋Š” NEXT_PUBLIC_์„ ๋ถ™์—ฌ์•ผ๋งŒ ์ฐธ์กฐ๊ฐ€ ๊ฐ€๋Šฅํ•˜๋‹ค.

 

๐Ÿค” API ํ‚ค๊ฐ€ ํด๋ผ์ด์–ธํŠธ์— ๋…ธ์ถœํ•ด๋„ ๋˜์ง€ ์•Š์„๊นŒ?

 

  • ๋…ธ์ถœ๋œ๋‹ค. ์ •ํ™•ํžˆ ๋งํ•˜๋ฉด, NEXT_PUBLIC_์œผ๋กœ ์‹œ์ž‘๋˜๋Š” ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋Š” ์ตœ์ข…์ ์œผ๋กœ ํด๋ผ์ด์–ธํŠธ JS ๋ฒˆ๋“ค์— ๊ฐ’์ด ์‚ฝ์ž…๋˜๋ฏ€๋กœ, ๋ธŒ๋ผ์šฐ์ € ๊ฐœ๋ฐœ์ž ๋„๊ตฌ๋‚˜ ์†Œ์Šค ๋งต ๋“ฑ์„ ํ†ตํ•ด ์–ผ๋งˆ๋“ ์ง€ ํ™•์ธ์ด ๊ฐ€๋Šฅํ•˜๋‹ค.
  • Supabase์˜ ๊ฒฝ์šฐ, anon key๋Š” ์›๋ž˜ ํด๋ผ์ด์–ธํŠธ ๊ณต๊ฐœ์šฉ ํ‚ค๋กœ ์„ค๊ณ„๋˜์–ด ์žˆ๋‹ค. ์ฆ‰, ์„œ๋น„์Šค ๋กค ํ‚ค(Service Role Key)์ฒ˜๋Ÿผ ๋ฏผ๊ฐํ•œ ๊ถŒํ•œ์ด ์žˆ๋Š” ํ‚ค๊ฐ€ ์•„๋‹ˆ๊ธฐ ๋•Œ๋ฌธ์— ๋…ธ์ถœ์ด ๋˜์–ด๋„ ํฐ ๋ฌธ์ œ๊ฐ€ ์—†๋„๋ก ๋งŒ๋“ค์–ด์ง„ ํ‚ค.
    • anon key๋Š” ์ด๋ฆ„ ๊ทธ๋Œ€๋กœ ์ต๋ช… ์‚ฌ์šฉ์ž๊ฐ€ ํ˜ธ์ถœํ•  ์ˆ˜ ์žˆ๋Š” API ๋ฒ”์œ„๋งŒ์„ ํ—ˆ์šฉํ•˜๋Š” “๊ณต๊ฐœ์šฉ ํ‚ค”
    • ๋ฏผ๊ฐํ•œ API(์˜ˆ: ๊ด€๋ฆฌ์ž ๊ถŒํ•œ์ด ํ•„์š”ํ•œ API)๋ฅผ ๋‹ค๋ฃจ๋ ค๋ฉด Supabase์—์„œ ์ œ๊ณตํ•˜๋Š” Service Key๋ฅผ ์„œ๋ฒ„ ์ „์šฉ ๋กœ์ง์— ์‚ฌ์šฉํ•ด์•ผ ํ•˜๊ณ , ๊ทธ ํ‚ค๋Š” ์ ˆ๋Œ€ ํด๋ผ์ด์–ธํŠธ์— ๋…ธ์ถœ๋˜๋ฉด ์•ˆ ๋œ๋‹ค.

 

๐Ÿค” ์„œ๋ฒ„ ์•ก์…˜์ธ๋ฐ๋„ ํด๋ผ์ด์–ธํŠธ ๋…ธ์ถœ์ด ํ•„์š”ํ•œ ์ด์œ ?

 

  • ์„œ๋ฒ„ ์•ก์…˜ ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ๋ถ€๋ถ„์ด ํด๋ผ์ด์–ธํŠธ ์ฝ”๋“œ์— ์žˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.
  • ๋งŒ์•ฝ ํด๋ผ์ด์–ธํŠธ์—์„œ Supabase์˜ ๋กœ์šฐ ๋ ˆ๋ฒจ API๋ฅผ ์ง์ ‘ ํ˜ธ์ถœํ•œ๋‹ค๋ฉด, ๊ฒฐ๊ตญ ํด๋ผ์ด์–ธํŠธ ์ชฝ์—์„œ๋„ ํ”„๋กœ์ ํŠธ URL๊ณผ anon key๋ฅผ ์•Œ์•„์•ผ ํ•˜๋ฏ€๋กœ NEXT_PUBLIC_์ด ํ•„์š”ํ•˜๋‹ค.
  • ์ •๋ง ์„œ๋ฒ„์—์„œ๋งŒ ์ฒ˜๋ฆฌํ•˜๊ณ , ํด๋ผ์ด์–ธํŠธ์—์„œ๋Š” ์„œ๋ฒ„ ์•ก์…˜์„ ๋‹จ์ˆœ ํ˜ธ์ถœ๋งŒ ํ•œ๋‹ค๋ฉด(์˜ˆ: ํผ ์ „์†ก), ๋‚ด๋ถ€์ ์œผ๋กœ Supabase Client๋Š” ์ „๋ถ€ ์„œ๋ฒ„์—์„œ๋งŒ ์‚ฌ์šฉ๋˜๋ฏ€๋กœ ํด๋ผ์ด์–ธํŠธ ํ™˜๊ฒฝ ๋ณ€์ˆ˜๊ฐ€ ํ•„์š” ์—†๋‹ค. ๊ทธ๋Ÿด ๊ฒฝ์šฐ NEXT_PUBLIC_์—†์ด๋„ ๋™์ž‘ํ•œ๋‹ค.

 

// store > useAuthStore.ts

import { checkSession, getUserByEmail, logOutSupabase } from '@/services/signIn';
import { create } from 'zustand';
import { persist } from 'zustand/middleware';

interface AuthState {
  isSignedIn: boolean;
  user: User | null;
  signIn: (email: string) => void;
  logout: () => void;
}

interface User {
  email: string;
  nickname: string;
  id: string;
}

export const useAuthStore = create(
  persist<AuthState>(
    (set) => ({
      isSignedIn: false,
      user: null,

      // ๋กœ๊ทธ์ธ ํ•จ์ˆ˜
      signIn: async (email: string) => {
        const userData = await getUserByEmail(email); //supabase์—์„œ userData ๊ฐ€์ ธ์˜ค๊ธฐ
        if (!userData) return;
        set({ isSignedIn: true, user: { email: userData.email, nickname: userData.nickname, id: userData.id } }); //๋กœ๊ทธ์ธ ํ•œ ์œ ์ € ์ •๋ณด ๋‹ด์Œ
      },
      // ๋กœ๊ทธ์•„์›ƒ ํ•จ์ˆ˜
      logout: async () => {
        await logOutSupabase();
        localStorage.removeItem('auth_token'); //๋กœ๊ทธ์•„์›ƒ ์‹œ ๋กœ์ปฌ์Šคํ† ๋ฆฌ์ง€ ํ† ํฐ ์‚ญ์ œ
        set({ isSignedIn: false, user: null });
      },
      // ํ† ๊ทผ ์œ ํšจ ๊ฒ€์ฆ ํ•จ์ˆ˜
      checkAuth: async (email: string) => {
        const userData = await checkSession();

        if (userData) {
          set({ isSignedIn: true, user: { email, nickname: userData.nickname, id: userData.id } }); //์œ ํšจํ•  ๋•Œ ์œ ์ € ์ •๋ณด
        } else {
          set({ isSignedIn: false, user: null }); //์œ ํšจํ•˜์ง€ ์•Š์„ ๋•Œ ๋กœ๊ทธ์•„์›ƒ
        }
      },
    }),
    { name: 'auth-storage' }
  )
);

 

// services > signIn.ts

'use server';

import { EMAIL } from '@/constants/signUp';
import { supabase } from '@/utils/supabaseClient';

export interface SignInProps {
  email: string;
  password: string;
}

// supabase ๋กœ๊ทธ์ธ ์„œ๋ฒ„ ์•ก์…˜
export const signInSupabase = async ({ email, password }: SignInProps) => {
  const { data, error } = await supabase.auth.signInWithPassword({
    email,
    password,
  });
  if (error) {
    throw new Error(error.message);
  }
  return data;
};

// ์œ ์ € ์ •๋ณด ๊ฐ€์ ธ์˜ค๊ธฐ (nickname ํฌํ•จ)
export const getUserByEmail = async (email: string) => {
  const { data, error } = await supabase.from('users').select('id, email, nickname').eq(EMAIL, email).single();
  if (error) {
    console.error('์œ ์ € ์ •๋ณด ์กฐํšŒ ์‹คํŒจ', error.message);
    return null;
  }
  return data;
};

// supabase ๋กœ๊ทธ์•„์›ƒ ์„œ๋ฒ„ ์•ก์…˜
export const logOutSupabase = async () => {
  await supabase.auth.signOut();
};

// supabase ์„ธ์…˜ ํ™•์ธ ์„œ๋ฒ„ ์•ก์…˜
export const checkSession = async () => {
  const { data } = await supabase.auth.getSession();
  if (data?.session) {
    const email = data.session.user.email;
    const { data: userData, error } = await supabase
      .from('users')
      .select('id, nickname, email')
      .eq(EMAIL, email)
      .single();

    if (error) {
      console.log('์œ ์ € ์กฐํšŒ ์‹คํŒจ', error.message);
      return null;
    }
    return userData; //data๊ฐ€ ์žˆ๋‹ค๋ฉด userData
  }
  return null; //์„ธ์…˜์ด ์—†๋‹ค๋ฉด null
};

// supabase ์„ธ์…˜ ํ™•์ธ ์„œ๋ฒ„ ์•ก์…˜
export const checkUser = async () => {
  const { data } = await supabase.auth.getUser();
  if (data) {
    const email = data.user?.email;
    const { data: userData, error } = await supabase
      .from('users')
      .select('id, nickname, email')
      .eq(EMAIL, email)
      .single();

    if (error) {
      console.log('์œ ์ € ์กฐํšŒ ์‹คํŒจ', error.message);
      return null;
    }
    return userData; //data๊ฐ€ ์žˆ๋‹ค๋ฉด userData
  }
  return null; //์„ธ์…˜์ด ์—†๋‹ค๋ฉด null
};

 

๊ณต์ง€์‚ฌํ•ญ
์ตœ๊ทผ์— ์˜ฌ๋ผ์˜จ ๊ธ€
์ตœ๊ทผ์— ๋‹ฌ๋ฆฐ ๋Œ“๊ธ€
Total
Today
Yesterday
๋งํฌ
TAG
more
ยซ   2026/03   ยป
์ผ ์›” ํ™” ์ˆ˜ ๋ชฉ ๊ธˆ ํ† 
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31
๊ธ€ ๋ณด๊ด€ํ•จ