ariansyahdedy commited on
Commit
1256bd3
·
0 Parent(s):
.gitignore ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ venv
2
+ __pycache__
Dockerfile ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Use a lightweight Python image
2
+ FROM python:3.9-slim
3
+
4
+ # Create working directory
5
+ WORKDIR /app
6
+
7
+ # Copy requirements first for caching
8
+ COPY requirements.txt /app/
9
+
10
+ # Install dependencies
11
+ RUN pip install --no-cache-dir -r requirements.txt
12
+
13
+ # Copy the rest of the application
14
+ COPY app /app/app
15
+
16
+ # Expose port (Hugging Face Spaces typically uses 7860 by default, but you can adjust if needed)
17
+ EXPOSE 7860
18
+
19
+ # Start the app with uvicorn
20
+ CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "7860"]
app/config.py ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+
3
+ # In a real environment, you would secure these properly.
4
+ GOOGLE_CLIENT_ID = os.environ.get("GOOGLE_CLIENT_ID", "50786072753-s9nq1ma3nv44k382b5mcnvmeggt1cvha.apps.googleusercontent.com")
5
+ GOOGLE_CLIENT_SECRET = os.environ.get("GOOGLE_CLIENT_SECRET", "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")
6
+ GOOGLE_REDIRECT_URI = os.environ.get("GOOGLE_REDIRECT_URI", "http://localhost:8000/auth/callback")
7
+
8
+ # For YouTube Data API. The user must consent to at least read their channel info and videos.
9
+ YOUTUBE_SCOPES = ["https://www.googleapis.com/auth/youtube.readonly", "https://www.googleapis.com/auth/youtube.force-ssl"]
app/main.py ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI, Request
2
+ from fastapi.templating import Jinja2Templates
3
+ from fastapi.responses import HTMLResponse
4
+ from starlette.middleware.sessions import SessionMiddleware
5
+ import os
6
+
7
+ # Routers
8
+ from app.routes import auth, youtube
9
+
10
+ # For rendering HTML templates
11
+ templates = Jinja2Templates(directory="app/templates")
12
+
13
+ app = FastAPI()
14
+
15
+ # Set a secret key for session cookies (Use a strong key in production!)
16
+ app.add_middleware(SessionMiddleware, secret_key="CHANGE_THIS_SECRET")
17
+
18
+ # Include our routers
19
+ app.include_router(auth.router)
20
+ app.include_router(youtube.router)
21
+
22
+ @app.get("/", response_class=HTMLResponse)
23
+ async def read_root(request: Request):
24
+ return templates.TemplateResponse("index.html", {"request": request})
25
+
26
+ @app.get("/success", response_class=HTMLResponse)
27
+ async def read_success(request: Request):
28
+ return templates.TemplateResponse("success.html", {"request": request})
app/routes/auth.py ADDED
@@ -0,0 +1,64 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import APIRouter, Request, Response, status
2
+ from fastapi.responses import RedirectResponse, HTMLResponse
3
+ from starlette.middleware.sessions import SessionMiddleware
4
+ from google_auth_oauthlib.flow import Flow
5
+ import os
6
+
7
+ from app.config import GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, GOOGLE_REDIRECT_URI, YOUTUBE_SCOPES
8
+
9
+ router = APIRouter()
10
+
11
+
12
+ def create_flow():
13
+ return Flow.from_client_config(
14
+ {
15
+ "web": {
16
+ "client_id": GOOGLE_CLIENT_ID,
17
+ "project_id": "youtube-fastapi-sample",
18
+ "auth_uri": "https://accounts.google.com/o/oauth2/auth",
19
+ "token_uri": "https://oauth2.googleapis.com/token",
20
+ "client_secret": GOOGLE_CLIENT_SECRET,
21
+ }
22
+ },
23
+ scopes=YOUTUBE_SCOPES,
24
+ )
25
+
26
+ @router.get("/login")
27
+ async def login(request: Request):
28
+ flow = create_flow()
29
+ flow.redirect_uri = GOOGLE_REDIRECT_URI # must set the redirect_uri separately
30
+ authorization_url, state = flow.authorization_url(
31
+ access_type="offline",
32
+ include_granted_scopes="true",
33
+ prompt="select_account"
34
+ )
35
+ request.session["state"] = state
36
+ return RedirectResponse(authorization_url)
37
+
38
+
39
+ @router.get("/auth/callback")
40
+ async def auth_callback(request: Request):
41
+ """Handle OAuth callback from Google with ?code= and ?state=."""
42
+ state = request.session.get("state")
43
+ if not state:
44
+ return HTMLResponse("<h1>Session state not found. Please /login again.</h1>", status_code=400)
45
+
46
+ flow = create_flow()
47
+ flow.fetch_token(authorization_response=str(request.url))
48
+
49
+ # Get the credentials object
50
+ credentials = flow.credentials
51
+ if not credentials or not credentials.valid:
52
+ return HTMLResponse("<h1>Invalid credentials. Please /login again.</h1>", status_code=400)
53
+
54
+ # Store credentials in session. In production, store securely (e.g. in DB, encrypted).
55
+ request.session["credentials"] = {
56
+ "token": credentials.token,
57
+ "refresh_token": credentials.refresh_token,
58
+ "token_uri": credentials.token_uri,
59
+ "client_id": credentials.client_id,
60
+ "client_secret": credentials.client_secret,
61
+ "scopes": credentials.scopes
62
+ }
63
+
64
+ return RedirectResponse(url="/success", status_code=status.HTTP_302_FOUND)
app/routes/youtube.py ADDED
@@ -0,0 +1,77 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import APIRouter, Request, HTTPException
2
+ from google.oauth2.credentials import Credentials
3
+ from googleapiclient.discovery import build
4
+
5
+ router = APIRouter()
6
+
7
+ def get_credentials_from_session(session) -> Credentials:
8
+ """Utility to build a Credentials object from stored session data."""
9
+ creds_data = session.get("credentials")
10
+ if not creds_data:
11
+ return None
12
+ return Credentials(
13
+ token=creds_data["token"],
14
+ refresh_token=creds_data["refresh_token"],
15
+ token_uri=creds_data["token_uri"],
16
+ client_id=creds_data["client_id"],
17
+ client_secret=creds_data["client_secret"],
18
+ scopes=creds_data["scopes"]
19
+ )
20
+
21
+ @router.get("/list-channels")
22
+ async def list_channels(request: Request):
23
+ """Return the user’s list of YouTube channels."""
24
+ creds = get_credentials_from_session(request.session)
25
+ if not creds or not creds.valid:
26
+ raise HTTPException(status_code=401, detail="Unauthorized. Please /login first.")
27
+
28
+ youtube = build("youtube", "v3", credentials=creds)
29
+ response = youtube.channels().list(
30
+ part="id,snippet",
31
+ mine=True
32
+ ).execute()
33
+
34
+ channels = []
35
+ for item in response.get("items", []):
36
+ channels.append({
37
+ "channelId": item["id"],
38
+ "title": item["snippet"]["title"]
39
+ })
40
+
41
+ return {"channels": channels}
42
+
43
+
44
+ @router.get("/list-videos")
45
+ async def list_videos(request: Request, channel_id: str):
46
+ """List videos for the specified channel."""
47
+ creds = get_credentials_from_session(request.session)
48
+ if not creds or not creds.valid:
49
+ raise HTTPException(status_code=401, detail="Unauthorized. Please /login first.")
50
+
51
+ youtube = build("youtube", "v3", credentials=creds)
52
+
53
+ # Example: listing videos from a channel’s "uploads" playlist
54
+ # 1) Retrieve the uploads playlist from channel
55
+ channel_response = youtube.channels().list(
56
+ part="contentDetails",
57
+ id=channel_id
58
+ ).execute()
59
+
60
+ uploads_playlist_id = channel_response["items"][0]["contentDetails"]["relatedPlaylists"]["uploads"]
61
+
62
+ # 2) Retrieve items from the uploads playlist
63
+ playlist_items_response = youtube.playlistItems().list(
64
+ part="snippet",
65
+ playlistId=uploads_playlist_id,
66
+ maxResults=10
67
+ ).execute()
68
+
69
+ videos = []
70
+ for item in playlist_items_response.get("items", []):
71
+ snippet = item["snippet"]
72
+ videos.append({
73
+ "videoId": snippet["resourceId"]["videoId"],
74
+ "title": snippet["title"]
75
+ })
76
+
77
+ return {"videos": videos}
app/templates/index.html ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>FastAPI + Google OAuth2 Example</title>
5
+ </head>
6
+ <body>
7
+ <h1>Welcome to the FastAPI + Google OAuth2 Example</h1>
8
+ <p>Please <a href="/login">Login with Google</a> to see your YouTube channels.</p>
9
+ </body>
10
+ </html>
app/templates/success.html ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>Login Successful</title>
5
+ </head>
6
+ <body>
7
+ <h1>Login Successful!</h1>
8
+ <p>You can now view your <a href="/list-channels" target="_blank">YouTube Channels (JSON)</a>.</p>
9
+ <p>After you get a channel ID, you can try <code>/list-videos?channel_id=XYZ</code> to see its videos.</p>
10
+ </body>
11
+ </html>
requirements.txt ADDED
Binary file (1.54 kB). View file