Skip to content

Latest commit

ย 

History

History
1075 lines (926 loc) ยท 23.3 KB

File metadata and controls

1075 lines (926 loc) ยท 23.3 KB

๋‚ ์”จ ์•Œ๋ฆผ ๋ฐฑ์—”๋“œ API ๋ฌธ์„œ

๐ŸŒ ์„œ๋ฒ„ ์ •๋ณด

  • ๋ฒ ์ด์Šค URL: http://localhost:5000
  • ํฌํŠธ: 5000
  • ์‘๋‹ต ํ˜•์‹: JSON

๐Ÿ“‹ API ์—”๋“œํฌ์ธํŠธ ๋ชฉ๋ก

๐Ÿฅ ์„œ๋ฒ„ ์ƒํƒœ

1. ํ—ฌ์Šค ์ฒดํฌ

GET /health

์„ค๋ช…: ์„œ๋ฒ„ ์ƒํƒœ ํ™•์ธ
์‘๋‹ต:

{
    "status": "healthy",
    "timestamp": "2025-10-17T16:00:00.000000"
}

๐Ÿ” ์‚ฌ์šฉ์ž ์ธ์ฆ

2. ํšŒ์›๊ฐ€์ž…

POST /api/auth/register

์š”์ฒญ:

{
    "name": "๊น€์ฒ ์ˆ˜",
    "email": "kim@example.com",
    "password": "SecurePass123!",
    "phone": "010-1234-5678",
    "location": "์„œ์šธํŠน๋ณ„์‹œ ์ค‘๊ตฌ"
}

์‘๋‹ต:

{
    "message": "ํšŒ์›๊ฐ€์ž…์ด ์™„๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.",
    "user": {
        "id": 1,
        "name": "๊น€์ฒ ์ˆ˜",
        "email": "kim@example.com",
        "phone": "010-1234-5678",
        "location": "์„œ์šธํŠน๋ณ„์‹œ ์ค‘๊ตฌ",
        "is_active": true,
        "role": "user",
        "email_verified": false,
        "created_at": "2025-10-19T13:00:00.000000"
    },
    "tokens": {
        "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...",
        "refresh_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...",
        "token_type": "Bearer",
        "expires_in": 86400
    }
}

3. ๋กœ๊ทธ์ธ

POST /api/auth/login

์š”์ฒญ:

{
    "email": "kim@example.com",
    "password": "SecurePass123!"
}

์‘๋‹ต:

{
    "message": "๋กœ๊ทธ์ธ์— ์„ฑ๊ณตํ–ˆ์Šต๋‹ˆ๋‹ค.",
    "user": {
        "id": 1,
        "name": "๊น€์ฒ ์ˆ˜",
        "email": "kim@example.com",
        "last_login": "2025-10-19T13:30:00.000000"
    },
    "tokens": {
        "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...",
        "refresh_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...",
        "token_type": "Bearer",
        "expires_in": 86400
    }
}

4. ํ”„๋กœํ•„ ์กฐํšŒ (์ธ์ฆ ํ•„์š”)

GET /api/auth/me
Authorization: Bearer {access_token}

์‘๋‹ต:

{
    "user": {
        "id": 1,
        "name": "๊น€์ฒ ์ˆ˜",
        "email": "kim@example.com",
        "phone": "010-1234-5678",
        "location": "์„œ์šธํŠน๋ณ„์‹œ ์ค‘๊ตฌ",
        "is_active": true,
        "email_verified": false,
        "last_login": "2025-10-19T13:30:00.000000"
    }
}

5. ํ† ํฐ ๊ฐฑ์‹ 

POST /api/auth/refresh

์š”์ฒญ:

{
    "refresh_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9..."
}

์‘๋‹ต:

{
    "message": "ํ† ํฐ์ด ๊ฐฑ์‹ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.",
    "tokens": {
        "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...",
        "refresh_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...",
        "token_type": "Bearer",
        "expires_in": 86400
    }
}

6. ๋กœ๊ทธ์•„์›ƒ

POST /api/auth/logout

์‘๋‹ต:

{
    "message": "๋กœ๊ทธ์•„์›ƒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค."
}

๐Ÿ‘ฅ ์‚ฌ์šฉ์ž ๊ด€๋ฆฌ (๊ด€๋ฆฌ์ž์šฉ)

7. ์‚ฌ์šฉ์ž ๋ชฉ๋ก ์กฐํšŒ (๊ด€๋ฆฌ์ž ๊ถŒํ•œ ํ•„์š”)

GET /api/users
Authorization: Bearer {admin_access_token}

์‘๋‹ต:

[
    {
        "id": 1,
        "name": "๊น€์ฒ ์ˆ˜",
        "email": "kim@example.com",
        "phone": "010-1234-5678",
        "location": "์„œ์šธํŠน๋ณ„์‹œ ์ค‘๊ตฌ",
        "is_active": true,
        "email_verified": false,
        "last_login": "2025-10-19T13:30:00.000000",
        "created_at": "2025-10-19T13:00:00.000000"
    }
]

8. ๊ด€๋ฆฌ์ž์šฉ ์‚ฌ์šฉ์ž ์ƒ์„ฑ (๊ด€๋ฆฌ์ž ๊ถŒํ•œ ํ•„์š”)

POST /api/admin/users
Authorization: Bearer {admin_access_token}

์š”์ฒญ:

{
    "name": "์ด์˜ํฌ",
    "email": "lee@example.com",
    "password": "TempPass123!",
    "phone": "010-2345-6789",
    "location": "๋ถ€์‚ฐ๊ด‘์—ญ์‹œ ์ค‘๊ตฌ",
    "role": "user"
}

๐Ÿช ์‹œ์žฅ ๊ด€๋ฆฌ

9. ์‹œ์žฅ ์กฐํšŒ/์ƒ์„ฑ

GET /api/markets
POST /api/markets

GET ์‘๋‹ต:

[
    {
        "id": 1,
        "name": "๋™๋Œ€๋ฌธ์‹œ์žฅ",
        "location": "์„œ์šธํŠน๋ณ„์‹œ ์ค‘๊ตฌ ์ฐฝ์‹ ๋™",
        "latitude": 37.5707,
        "longitude": 127.0087,
        "nx": 60,
        "ny": 127,
        "category": "์ „ํ†ต์‹œ์žฅ",
        "is_active": true,
        "created_at": "2025-10-17T07:00:00.000000"
    }
]

POST ์š”์ฒญ:

{
    "name": "์ƒˆ๋กœ์šด์‹œ์žฅ",
    "location": "๋Œ€์ „๊ด‘์—ญ์‹œ ์ค‘๊ตฌ",
    "latitude": 36.3214,
    "longitude": 127.4214,
    "category": "์ „ํ†ต์‹œ์žฅ"
}

9-1. ์‹œ์žฅ ๊ฒ€์ƒ‰

GET /api/markets/search?q={๊ฒ€์ƒ‰์–ด}&limit={๊ฐœ์ˆ˜}

์š”์ฒญ ํŒŒ๋ผ๋ฏธํ„ฐ:

  • q: ๊ฒ€์ƒ‰์–ด (์ตœ์†Œ 2๊ธ€์ž)
  • limit: ๊ฒฐ๊ณผ ๊ฐœ์ˆ˜ (๊ธฐ๋ณธ๊ฐ’: 20)

์‘๋‹ต:

{
    "query": "๋™๋Œ€๋ฌธ",
    "count": 2,
    "markets": [
        {
            "id": 1,
            "name": "๋™๋Œ€๋ฌธ์‹œ์žฅ",
            "location": "์„œ์šธํŠน๋ณ„์‹œ ์ค‘๊ตฌ ์ฐฝ์‹ ๋™",
            "latitude": 37.5707,
            "longitude": 127.0087,
            "nx": 60,
            "ny": 127,
            "category": "์ „ํ†ต์‹œ์žฅ",
            "is_active": true
        }
    ]
}

โญ ๊ด€์‹ฌ๋ชฉ๋ก ๊ด€๋ฆฌ

10. ์‚ฌ์šฉ์ž ๊ด€์‹ฌ๋ชฉ๋ก ์กฐํšŒ (์ธ์ฆ ํ•„์š”)

GET /api/watchlist
Authorization: Bearer {access_token}

์‘๋‹ต:

{
    "count": 2,
    "watchlist": [
        {
            "id": 1,
            "user_id": 1,
            "market_id": 1,
            "market_name": "๋™๋Œ€๋ฌธ์‹œ์žฅ",
            "market_location": "์„œ์šธํŠน๋ณ„์‹œ ์ค‘๊ตฌ ์ฐฝ์‹ ๋™",
            "market_coordinates": {
                "latitude": 37.5707,
                "longitude": 127.0087,
                "nx": 60,
                "ny": 127
            },
            "created_at": "2025-10-22T08:00:00.000000",
            "is_active": true,
            "notification_enabled": true
        }
    ]
}

11. ๊ด€์‹ฌ๋ชฉ๋ก์— ์‹œ์žฅ ์ถ”๊ฐ€ (์ธ์ฆ ํ•„์š”)

POST /api/watchlist
Authorization: Bearer {access_token}

์š”์ฒญ:

{
    "market_id": 1
}

์‘๋‹ต:

{
    "message": "๋™๋Œ€๋ฌธ์‹œ์žฅ์ด(๊ฐ€) ๊ด€์‹ฌ ๋ชฉ๋ก์— ์ถ”๊ฐ€๋˜์—ˆ์Šต๋‹ˆ๋‹ค.",
    "interest": {
        "id": 1,
        "user_id": 1,
        "market_id": 1,
        "market_name": "๋™๋Œ€๋ฌธ์‹œ์žฅ",
        "is_active": true,
        "notification_enabled": true,
        "created_at": "2025-10-22T08:00:00.000000"
    }
}

12. ๊ด€์‹ฌ๋ชฉ๋ก์—์„œ ์‹œ์žฅ ์ œ๊ฑฐ (์ธ์ฆ ํ•„์š”)

DELETE /api/watchlist/{market_id}
Authorization: Bearer {access_token}

์‘๋‹ต:

{
    "message": "๊ด€์‹ฌ ๋ชฉ๋ก์—์„œ ์ œ๊ฑฐ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.",
    "market_id": 1
}

13. ๊ด€์‹ฌ ์‹œ์žฅ ์•Œ๋ฆผ ์„ค์ • ํ† ๊ธ€ (์ธ์ฆ ํ•„์š”)

PUT /api/watchlist/{interest_id}/notification
Authorization: Bearer {access_token}

์‘๋‹ต:

{
    "message": "์•Œ๋ฆผ์ด ํ™œ์„ฑํ™”๋˜์—ˆ์Šต๋‹ˆ๋‹ค.",
    "interest": {
        "id": 1,
        "notification_enabled": true,
        "market_name": "๋™๋Œ€๋ฌธ์‹œ์žฅ"
    }
}

โš ๏ธ ํ”ผํ•ด ์ƒํƒœ ๊ด€๋ฆฌ

14. ํ”ผํ•ด ์ƒํƒœ ์กฐํšŒ/์ƒ์„ฑ

GET /api/damage-status
POST /api/damage-status

POST ์š”์ฒญ:

{
    "market_id": 1,
    "weather_event": "ํƒœํ’",
    "damage_level": "์‹ฌ๊ฐ",
    "description": "์ง€๋ถ• ์ผ๋ถ€ ์†์ƒ",
    "estimated_recovery_time": "2025-10-20T10:00:00"
}

๐ŸŒง๏ธ ๋น„ ์˜ˆ๋ณด ์•Œ๋ฆผ

15. ํŠน์ • ์‹œ์žฅ์˜ ๋น„ ์˜ˆ๋ณด ํ™•์ธ

GET /api/markets/{market_id}/rain-forecast?hours={์‹œ๊ฐ„}

์š”์ฒญ ํŒŒ๋ผ๋ฏธํ„ฐ:

  • market_id: ์‹œ์žฅ ID
  • hours: ์˜ˆ๋ณด ํ™•์ธ ์‹œ๊ฐ„ (๊ธฐ๋ณธ๊ฐ’: 24์‹œ๊ฐ„)

์‘๋‹ต:

{
    "status": "success",
    "market_id": 1,
    "forecast": {
        "has_rain": true,
        "market_name": "๋™๋Œ€๋ฌธ์‹œ์žฅ",
        "alerts": [
            {
                "datetime": "2025-10-22T14:00:00",
                "pop": 60,
                "pty": "1",
                "description": "๋น„"
            }
        ],
        "checked_hours": 24
    }
}

16. ๊ด€๋ฆฌ์ž์šฉ ์ˆ˜๋™ ๋น„ ์˜ˆ๋ณด ์•Œ๋ฆผ ํ™•์ธ (๊ด€๋ฆฌ์ž ๊ถŒํ•œ ํ•„์š”)

POST /api/admin/rain-alerts/check
Authorization: Bearer {admin_access_token}

์š”์ฒญ:

{
    "hours": 24
}

์‘๋‹ต:

{
    "status": "success",
    "message": "๋น„ ์˜ˆ๋ณด ์•Œ๋ฆผ ํ™•์ธ ์™„๋ฃŒ",
    "result": {
        "success": true,
        "checked_markets": 15,
        "alerts_sent": 3,
        "results": [
            {
                "market": "๋™๋Œ€๋ฌธ์‹œ์žฅ",
                "rain_forecast": true,
                "alert_result": {
                    "success": true,
                    "sent_count": 5
                }
            }
        ]
    }
}

๐ŸŒค๏ธ ๋‚ ์”จ ๋ฐ์ดํ„ฐ

17. ํ˜„์žฌ ๋‚ ์”จ ์กฐํšŒ

POST /api/weather/current

์š”์ฒญ:

{
    "latitude": 37.5665,
    "longitude": 126.9780,
    "location_name": "์„œ์šธ์‹œ์ฒญ"
}

์‘๋‹ต:

{
    "status": "success",
    "data": {
        "base_date": "20251017",
        "base_time": "1600",
        "nx": 60,
        "ny": 127,
        "temp": 23.1,
        "humidity": 65.0,
        "rain_1h": 0.0,
        "wind_speed": 2.1,
        "wind_direction": 120.0,
        "api_type": "current",
        "location_name": "์„œ์šธ์‹œ์ฒญ"
    }
}

18. ๋‚ ์”จ ์˜ˆ๋ณด ์กฐํšŒ

POST /api/weather/forecast

์š”์ฒญ:

{
    "latitude": 37.5665,
    "longitude": 126.9780,
    "location_name": "์„œ์šธ์‹œ์ฒญ"
}

์‘๋‹ต:

{
    "status": "success",
    "data": [
        {
            "base_date": "20251017",
            "base_time": "1630",
            "fcst_date": "20251017",
            "fcst_time": "1700",
            "temp": 22.0,
            "humidity": 70.0,
            "rain_1h": 0.0,
            "pty": "0",
            "sky": "3",
            "api_type": "forecast"
        }
    ]
}

19. ์ €์žฅ๋œ ๋‚ ์”จ ๋ฐ์ดํ„ฐ ์กฐํšŒ

GET /api/weather
GET /api/weather?location_name=๋™๋Œ€๋ฌธ
GET /api/weather?api_type=current
GET /api/weather?limit=50

์‘๋‹ต:

{
    "status": "success",
    "count": 21,
    "data": [
        {
            "id": 1,
            "location_name": "๋™๋Œ€๋ฌธ์‹œ์žฅ (์„œ์šธํŠน๋ณ„์‹œ ์ค‘๊ตฌ ์ฐฝ์‹ ๋™)",
            "api_type": "current",
            "temp": 23.1,
            "humidity": 65.0,
            "created_at": "2025-10-17T07:55:12.000000"
        }
    ]
}

๐Ÿค– ์Šค์ผ€์ค„๋Ÿฌ ๊ด€๋ฆฌ

20. ์Šค์ผ€์ค„๋Ÿฌ ์‹œ์ž‘

POST /api/scheduler/start

์‘๋‹ต:

{
    "status": "success",
    "message": "๋‚ ์”จ ์Šค์ผ€์ค„๋Ÿฌ๊ฐ€ ์‹œ์ž‘๋˜์—ˆ์Šต๋‹ˆ๋‹ค."
}

21. ์Šค์ผ€์ค„๋Ÿฌ ์ •์ง€

POST /api/scheduler/stop

22. ์Šค์ผ€์ค„๋Ÿฌ ์ƒํƒœ ์กฐํšŒ

GET /api/scheduler/status

์‘๋‹ต:

{
    "scheduler_running": true,
    "job_count": 1,
    "jobs": [
        {
            "id": "weather_collection_job",
            "name": "์‹œ์žฅ๋ณ„ ๋‚ ์”จ ๋ฐ์ดํ„ฐ ์ˆ˜์ง‘",
            "next_run": "2025-10-17T17:25:00.000000"
        }
    ]
}

23. ๋‚ ์”จ ๋ฐ์ดํ„ฐ ํ†ต๊ณ„

GET /api/scheduler/stats

์‘๋‹ต:

{
    "total_weather_records": 21,
    "current_weather_records": 3,
    "forecast_weather_records": 18,
    "active_markets": 3,
    "markets_with_coordinates": 3,
    "latest_weather_update": "2025-10-17T07:55:12.000000"
}

24. ์ˆ˜๋™ ๋‚ ์”จ ๋ฐ์ดํ„ฐ ์ˆ˜์ง‘

POST /api/scheduler/collect

์‘๋‹ต:

{
    "status": "success",
    "message": "๋‚ ์”จ ๋ฐ์ดํ„ฐ ์ˆ˜์ง‘์ด ์™„๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค."
}

๐Ÿ“ฑ FCM (ํ‘ธ์‹œ ์•Œ๋ฆผ) ๊ด€๋ฆฌ

25. FCM ํ† ํฐ ๋“ฑ๋ก/์—…๋ฐ์ดํŠธ (์ธ์ฆ ํ•„์š”)

POST /api/fcm/register
Authorization: Bearer {access_token}

์š”์ฒญ:

{
    "token": "FCM_REGISTRATION_TOKEN",
    "device_info": {
        "platform": "web",
        "browser": "Chrome 119.0.0.0",
        "timestamp": "2025-10-19T13:00:00Z"
    },
    "subscribe_topics": ["weather_alerts", "severe_weather"]
}

์‘๋‹ต:

{
    "message": "FCM ํ† ํฐ์ด ๋“ฑ๋ก๋˜์—ˆ์Šต๋‹ˆ๋‹ค.",
    "fcm_enabled": true,
    "subscribed_topics": ["weather_alerts", "severe_weather"]
}

26. FCM ์„ค์ • ์กฐํšŒ/์—…๋ฐ์ดํŠธ (์ธ์ฆ ํ•„์š”)

GET /api/fcm/settings
POST /api/fcm/settings
Authorization: Bearer {access_token}

GET ์‘๋‹ต:

{
    "fcm_enabled": true,
    "fcm_topics": ["weather_alerts", "severe_weather"],
    "device_info": {
        "platform": "web",
        "browser": "Chrome 119.0.0.0"
    },
    "has_token": true
}

POST ์š”์ฒญ (์„ค์ • ์—…๋ฐ์ดํŠธ):

{
    "enabled": true,
    "subscribe_topics": ["weather_alerts"],
    "unsubscribe_topics": ["severe_weather"]
}

27. FCM ํ…Œ์ŠคํŠธ ์•Œ๋ฆผ ์ „์†ก (์ธ์ฆ ํ•„์š”)

POST /api/fcm/test
Authorization: Bearer {access_token}

์‘๋‹ต:

{
    "message": "ํ…Œ์ŠคํŠธ ์•Œ๋ฆผ์ด ์ „์†ก๋˜์—ˆ์Šต๋‹ˆ๋‹ค."
}

28. ๊ด€๋ฆฌ์ž์šฉ FCM ์•Œ๋ฆผ ์ „์†ก (๊ด€๋ฆฌ์ž ๊ถŒํ•œ ํ•„์š”)

POST /api/admin/fcm/send
Authorization: Bearer {admin_access_token}

์š”์ฒญ (์ฃผ์ œ๋กœ ์ „์†ก):

{
    "title": "๊ธฐ์ƒ ํŠน๋ณด",
    "body": "์„œ์šธ ์ง€์—ญ์— ํ˜ธ์šฐ ๊ฒฝ๋ณด๊ฐ€ ๋ฐœ๋ น๋˜์—ˆ์Šต๋‹ˆ๋‹ค.",
    "topic": "severe_weather",
    "data": {
        "type": "severe_weather",
        "location": "์„œ์šธ"
    }
}

์š”์ฒญ (ํŠน์ • ์‚ฌ์šฉ์ž๋“ค์—๊ฒŒ ์ „์†ก):

{
    "title": "๋‚ ์”จ ์•Œ๋ฆผ",
    "body": "๋‚ด์ผ ๋น„๊ฐ€ ์˜ˆ์ƒ๋ฉ๋‹ˆ๋‹ค.",
    "user_ids": [1, 2, 3],
    "data": {
        "type": "weather_forecast"
    }
}

์š”์ฒญ (์ „์ฒด ์‚ฌ์šฉ์ž์—๊ฒŒ ์ „์†ก):

{
    "title": "์ „์ฒด ๊ณต์ง€",
    "body": "๋‚ ์”จ ์„œ๋น„์Šค๊ฐ€ ์—…๋ฐ์ดํŠธ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.",
    "data": {
        "type": "announcement"
    }
}

์‘๋‹ต:

{
    "message": "์ „์ฒด 15๋ช…์˜ ์‚ฌ์šฉ์ž์—๊ฒŒ ์•Œ๋ฆผ์ด ์ „์†ก๋˜์—ˆ์Šต๋‹ˆ๋‹ค.",
    "result": {
        "success_count": 14,
        "failure_count": 1,
        "failed_tokens": ["invalid_token_example"]
    }
}

๐Ÿ—„๏ธ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋ทฐ์–ด

29. ์›น ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋ทฐ์–ด

GET /db-viewer

์„ค๋ช…: ๋ธŒ๋ผ์šฐ์ €์—์„œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋‚ด์šฉ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋Š” ์›น ์ธํ„ฐํŽ˜์ด์Šค

30. ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค API๋“ค

GET /db-viewer/api/stats     # ํ†ต๊ณ„
GET /db-viewer/api/users     # ์‚ฌ์šฉ์ž ๋ฐ์ดํ„ฐ
GET /db-viewer/api/markets   # ์‹œ์žฅ ๋ฐ์ดํ„ฐ
GET /db-viewer/api/weather   # ๋‚ ์”จ ๋ฐ์ดํ„ฐ
GET /db-viewer/api/damage    # ํ”ผํ•ด์ƒํƒœ ๋ฐ์ดํ„ฐ

๐Ÿ”ง ์‚ฌ์šฉ ์˜ˆ์‹œ

1. cURL๋กœ API ํ˜ธ์ถœ

# ํ—ฌ์Šค ์ฒดํฌ
curl http://localhost:5000/health

# ํšŒ์›๊ฐ€์ž…
curl -X POST http://localhost:5000/api/auth/register \
  -H "Content-Type: application/json" \
  -d '{"name": "๊น€์ฒ ์ˆ˜", "email": "kim@example.com", "password": "SecurePass123!"}'

# ๋กœ๊ทธ์ธ
curl -X POST http://localhost:5000/api/auth/login \
  -H "Content-Type: application/json" \
  -d '{"email": "kim@example.com", "password": "SecurePass123!"}'

# ์ธ์ฆ๋œ ํ”„๋กœํ•„ ์กฐํšŒ (ํ† ํฐ ํ•„์š”)
curl -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  http://localhost:5000/api/auth/me

# ๋ชจ๋“  ์‹œ์žฅ ์กฐํšŒ
curl http://localhost:5000/api/markets

# ํ˜„์žฌ ๋‚ ์”จ ์กฐํšŒ
curl -X POST http://localhost:5000/api/weather/current \
  -H "Content-Type: application/json" \
  -d '{"latitude": 37.5665, "longitude": 126.9780, "location_name": "์„œ์šธ"}'

# ์Šค์ผ€์ค„๋Ÿฌ ์‹œ์ž‘
curl -X POST http://localhost:5000/api/scheduler/start

# ๋‚ ์”จ ํ†ต๊ณ„ ์กฐํšŒ
curl http://localhost:5000/api/scheduler/stats

# FCM ํ† ํฐ ๋“ฑ๋ก (์ธ์ฆ ํ•„์š”)
curl -X POST http://localhost:5000/api/fcm/register \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -d '{"token": "FCM_TOKEN", "device_info": {"platform": "web"}}'

# FCM ํ…Œ์ŠคํŠธ ์•Œ๋ฆผ ์ „์†ก (์ธ์ฆ ํ•„์š”)
curl -X POST http://localhost:5000/api/fcm/test \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"

# ๊ด€๋ฆฌ์ž์šฉ ์ „์ฒด FCM ์•Œ๋ฆผ ์ „์†ก
curl -X POST http://localhost:5000/api/admin/fcm/send \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_ADMIN_TOKEN" \
  -d '{"title": "๊ธฐ์ƒ ํŠน๋ณด", "body": "ํ˜ธ์šฐ ๊ฒฝ๋ณด ๋ฐœ๋ น"}'

# ์‹œ์žฅ ๊ฒ€์ƒ‰
curl "http://localhost:5000/api/markets/search?q=๋™๋Œ€๋ฌธ&limit=10"

# ๊ด€์‹ฌ๋ชฉ๋ก ์กฐํšŒ (์ธ์ฆ ํ•„์š”)
curl -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  http://localhost:5000/api/watchlist

# ๊ด€์‹ฌ๋ชฉ๋ก์— ์‹œ์žฅ ์ถ”๊ฐ€ (์ธ์ฆ ํ•„์š”)
curl -X POST http://localhost:5000/api/watchlist \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -d '{"market_id": 1}'

# ๊ด€์‹ฌ๋ชฉ๋ก์—์„œ ์‹œ์žฅ ์ œ๊ฑฐ (์ธ์ฆ ํ•„์š”)
curl -X DELETE http://localhost:5000/api/watchlist/1 \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN"

# ์‹œ์žฅ์˜ ๋น„ ์˜ˆ๋ณด ํ™•์ธ
curl "http://localhost:5000/api/markets/1/rain-forecast?hours=24"

# ๊ด€๋ฆฌ์ž์šฉ ์ˆ˜๋™ ๋น„ ์˜ˆ๋ณด ์•Œ๋ฆผ ํ™•์ธ (๊ด€๋ฆฌ์ž ๊ถŒํ•œ ํ•„์š”)
curl -X POST http://localhost:5000/api/admin/rain-alerts/check \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_ADMIN_TOKEN" \
  -d '{"hours": 24}'

2. JavaScript/Fetch๋กœ ํ˜ธ์ถœ

// ํšŒ์›๊ฐ€์ž…
const registerResponse = await fetch('http://localhost:5000/api/auth/register', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    name: '๊น€์ฒ ์ˆ˜',
    email: 'kim@example.com',
    password: 'SecurePass123!'
  })
});
const registerData = await registerResponse.json();
const accessToken = registerData.tokens.access_token;

// ๋กœ๊ทธ์ธ
const loginResponse = await fetch('http://localhost:5000/api/auth/login', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    email: 'kim@example.com',
    password: 'SecurePass123!'
  })
});
const loginData = await loginResponse.json();

// ์ธ์ฆ๋œ ํ”„๋กœํ•„ ์กฐํšŒ
const profileResponse = await fetch('http://localhost:5000/api/auth/me', {
  headers: { 'Authorization': `Bearer ${accessToken}` }
});
const profile = await profileResponse.json();

// ์‹œ์žฅ ๋ชฉ๋ก ์กฐํšŒ
fetch('http://localhost:5000/api/markets')
  .then(response => response.json())
  .then(data => console.log(data));

// ํ˜„์žฌ ๋‚ ์”จ ์กฐํšŒ
fetch('http://localhost:5000/api/weather/current', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    latitude: 37.5665,
    longitude: 126.9780,
    location_name: '์„œ์šธ์‹œ์ฒญ'
  })
})
.then(response => response.json())
.then(data => console.log(data));

// FCM ํ† ํฐ ๋“ฑ๋ก
fetch('http://localhost:5000/api/fcm/register', {
  method: 'POST',
  headers: { 
    'Content-Type': 'application/json',
    'Authorization': `Bearer ${accessToken}`
  },
  body: JSON.stringify({
    token: 'FCM_REGISTRATION_TOKEN',
    device_info: { platform: 'web', browser: 'Chrome' },
    subscribe_topics: ['weather_alerts']
  })
})
.then(response => response.json())
.then(data => console.log(data));

// FCM ํ…Œ์ŠคํŠธ ์•Œ๋ฆผ
fetch('http://localhost:5000/api/fcm/test', {
  method: 'POST',
  headers: { 'Authorization': `Bearer ${accessToken}` }
})
.then(response => response.json())
.then(data => console.log(data));

// ์‹œ์žฅ ๊ฒ€์ƒ‰
fetch('http://localhost:5000/api/markets/search?q=๋™๋Œ€๋ฌธ&limit=10')
  .then(response => response.json())
  .then(data => console.log(data));

// ๊ด€์‹ฌ๋ชฉ๋ก ์กฐํšŒ
fetch('http://localhost:5000/api/watchlist', {
  headers: { 'Authorization': `Bearer ${accessToken}` }
})
.then(response => response.json())
.then(data => console.log(data));

// ๊ด€์‹ฌ๋ชฉ๋ก์— ์‹œ์žฅ ์ถ”๊ฐ€
fetch('http://localhost:5000/api/watchlist', {
  method: 'POST',
  headers: { 
    'Content-Type': 'application/json',
    'Authorization': `Bearer ${accessToken}`
  },
  body: JSON.stringify({ market_id: 1 })
})
.then(response => response.json())
.then(data => console.log(data));

// ์‹œ์žฅ์˜ ๋น„ ์˜ˆ๋ณด ํ™•์ธ
fetch('http://localhost:5000/api/markets/1/rain-forecast?hours=24')
  .then(response => response.json())
  .then(data => console.log(data));

3. Python requests๋กœ ํ˜ธ์ถœ

import requests

# ํšŒ์›๊ฐ€์ž…
register_data = {
    "name": "๊น€์ฒ ์ˆ˜",
    "email": "kim@example.com",
    "password": "SecurePass123!"
}
register_response = requests.post('http://localhost:5000/api/auth/register', 
                                 json=register_data)
register_result = register_response.json()
access_token = register_result['tokens']['access_token']

# ๋กœ๊ทธ์ธ
login_data = {
    "email": "kim@example.com",
    "password": "SecurePass123!"
}
login_response = requests.post('http://localhost:5000/api/auth/login', 
                              json=login_data)
login_result = login_response.json()

# ์ธ์ฆ๋œ ํ”„๋กœํ•„ ์กฐํšŒ
headers = {"Authorization": f"Bearer {access_token}"}
profile_response = requests.get('http://localhost:5000/api/auth/me', 
                               headers=headers)
profile = profile_response.json()

# ์‹œ์žฅ ๋ชฉ๋ก ์กฐํšŒ
response = requests.get('http://localhost:5000/api/markets')
markets = response.json()

# ํ˜„์žฌ ๋‚ ์”จ ์กฐํšŒ
weather_data = {
    "latitude": 37.5665,
    "longitude": 126.9780,
    "location_name": "์„œ์šธ์‹œ์ฒญ"
}
response = requests.post('http://localhost:5000/api/weather/current', 
                        json=weather_data)
weather = response.json()

# FCM ํ† ํฐ ๋“ฑ๋ก
fcm_data = {
    "token": "FCM_REGISTRATION_TOKEN",
    "device_info": {"platform": "python", "version": "3.9"},
    "subscribe_topics": ["weather_alerts"]
}
fcm_response = requests.post('http://localhost:5000/api/fcm/register',
                            json=fcm_data, headers=headers)
fcm_result = fcm_response.json()

# FCM ํ…Œ์ŠคํŠธ ์•Œ๋ฆผ
test_response = requests.post('http://localhost:5000/api/fcm/test',
                             headers=headers)
test_result = test_response.json()

๐Ÿ“ ์ค‘์š” ์ฐธ๊ณ ์‚ฌํ•ญ

  1. ์ธ์ฆ ์‹œ์Šคํ…œ: JWT ํ† ํฐ ๊ธฐ๋ฐ˜ ์ธ์ฆ์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค

    • ํšŒ์›๊ฐ€์ž…/๋กœ๊ทธ์ธ ์‹œ access_token๊ณผ refresh_token์„ ๋ฐœ๊ธ‰๋ฐ›์Šต๋‹ˆ๋‹ค
    • access_token์€ 24์‹œ๊ฐ„, refresh_token์€ 30์ผ๊ฐ„ ์œ ํšจํ•ฉ๋‹ˆ๋‹ค
    • ์ธ์ฆ์ด ํ•„์š”ํ•œ API๋Š” Authorization: Bearer {access_token} ํ—ค๋”๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค
  2. ์‚ฌ์šฉ์ž ๊ถŒํ•œ ์‹œ์Šคํ…œ:

    • user: ์ผ๋ฐ˜ ์‚ฌ์šฉ์ž (ํšŒ์›๊ฐ€์ž…์œผ๋กœ ์ƒ์„ฑ)
    • admin: ๊ด€๋ฆฌ์ž (์„œ๋ฒ„ ์Šคํฌ๋ฆฝํŠธ๋กœ๋งŒ ์ƒ์„ฑ, ID: snslab / PW: snslab@cu)
    • ๊ด€๋ฆฌ์ž ๊ถŒํ•œ์ด ํ•„์š”ํ•œ API๋Š” ๋ณ„๋„ ํ‘œ์‹œ
  3. ๊ด€์‹ฌ๋ชฉ๋ก ์‹œ์Šคํ…œ:

    • ์‚ฌ์šฉ์ž๋Š” ์—ฌ๋Ÿฌ ์‹œ์žฅ์„ ๊ด€์‹ฌ๋ชฉ๋ก์— ์ถ”๊ฐ€ ๊ฐ€๋Šฅ
    • ๊ฐ ์‹œ์žฅ๋ณ„๋กœ ๊ฐœ๋ณ„ ์•Œ๋ฆผ ์„ค์ • ๊ฐ€๋Šฅ
    • ๊ด€์‹ฌ ์‹œ์žฅ์˜ ๋น„ ์˜ˆ๋ณด ์‹œ ์ž๋™ FCM ์•Œ๋ฆผ ์ „์†ก
  4. ๋น„ ์˜ˆ๋ณด ์•Œ๋ฆผ:

    • ๋งค ์‹œ๊ฐ„๋งˆ๋‹ค ์ž๋™์œผ๋กœ ํ–ฅํ›„ 24์‹œ๊ฐ„ ๋น„ ์˜ˆ๋ณด ํ™•์ธ
    • ๊ฐ•์ˆ˜ํ™•๋ฅ  30% ์ด์ƒ ๋˜๋Š” ๊ฐ•์ˆ˜ํ˜•ํƒœ ์กด์žฌ ์‹œ ์•Œ๋ฆผ
    • ๊ด€์‹ฌ ์‹œ์žฅ ๋“ฑ๋ก ์‚ฌ์šฉ์ž์—๊ฒŒ๋งŒ ๊ฐœ๋ณ„ ์•Œ๋ฆผ ์ „์†ก
  5. ํŒจ์Šค์›Œ๋“œ ์š”๊ตฌ์‚ฌํ•ญ:

    • ์ตœ์†Œ 8์ž ์ด์ƒ
    • ๋Œ€๋ฌธ์ž, ์†Œ๋ฌธ์ž, ์ˆซ์ž ํฌํ•จ ํ•„์ˆ˜
  6. CORS: ๋‹ค๋ฅธ ๋„๋ฉ”์ธ์—์„œ ํ˜ธ์ถœ ์‹œ CORS ์„ค์ •์ด ํ•„์š”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค

  7. Rate Limit: ๊ธฐ์ƒ์ฒญ API ํ˜ธ์ถœ ์ œํ•œ์ด ์žˆ์œผ๋ฏ€๋กœ ๋„ˆ๋ฌด ์ž์ฃผ ํ˜ธ์ถœํ•˜์ง€ ๋งˆ์„ธ์š”

  8. ์—๋Ÿฌ ์ฒ˜๋ฆฌ: ๋ชจ๋“  API๋Š” ์‹คํŒจ ์‹œ {"error": "๋ฉ”์‹œ์ง€"} ํ˜•ํƒœ๋กœ ์‘๋‹ตํ•ฉ๋‹ˆ๋‹ค

  9. FCM ์„ค์ •:

    • Firebase ํ”„๋กœ์ ํŠธ ์„ค์ • ๋ฐ ์„œ๋น„์Šค ๊ณ„์ • ํ‚ค๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค
    • ํ™˜๊ฒฝ๋ณ€์ˆ˜ FIREBASE_SERVICE_ACCOUNT_KEY ๋˜๋Š” FIREBASE_SERVICE_ACCOUNT_JSON ์„ค์ • ํ•„์š”
    • ํด๋ผ์ด์–ธํŠธ๋ณ„ FCM SDK ์„ค์ •์€ client_fcm_config/ ๋””๋ ‰ํ† ๋ฆฌ ์ฐธ์กฐ
  10. ๋‚ ์”จ ์•Œ๋ฆผ:

    • ์ž๋™ ์Šค์ผ€์ค„๋Ÿฌ๊ฐ€ ์‹ฌ๊ฐํ•œ ๋‚ ์”จ ์กฐ๊ฑด ๊ฐ์ง€ ์‹œ FCM ์•Œ๋ฆผ ์ž๋™ ์ „์†ก
    • ํญ์—ผ(35ยฐC ์ด์ƒ), ํ•œํŒŒ(-10ยฐC ์ดํ•˜), ํ˜ธ์šฐ(10mm/h ์ด์ƒ), ๊ฐ•ํ’(14m/s ์ด์ƒ) ์กฐ๊ฑด
    • ๊ด€์‹ฌ ์‹œ์žฅ ๊ธฐ๋ฐ˜ ๋น„ ์˜ˆ๋ณด ์•Œ๋ฆผ (๊ฐ•์ˆ˜ํ™•๋ฅ  30% ์ด์ƒ)

๐Ÿš€ ์„œ๋ฒ„ ์‹คํ–‰

python app.py
# ์„œ๋ฒ„ ์ฃผ์†Œ: http://localhost:5000