Skip to content

Commit 535795d

Browse files
Merge pull request #1 from QuizCast/dev
deploying to Azure
2 parents b0ee0e1 + 43998e8 commit 535795d

19 files changed

+441
-1
lines changed

.dockerignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
.venv
2+
__pycache__

.github/workflows/azure-serveless.yml

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
name: Build and Deploy to Azure
2+
3+
on:
4+
push:
5+
branches: [main]
6+
workflow_dispatch:
7+
8+
env:
9+
IMAGE_NAME: supabse-app
10+
ACR_NAME: pawan
11+
CONTAINER_APP_NAME: supabase-fastapi
12+
RESOURCE_GROUP: github
13+
14+
jobs:
15+
build-and-deploy:
16+
runs-on: ubuntu-latest
17+
18+
steps:
19+
- name: Checkout Code
20+
uses: actions/checkout@v4
21+
22+
- name: Log in to Azure
23+
uses: azure/login@v2
24+
with:
25+
creds: ${{ secrets.AZURE_CREDENTIALS }}
26+
27+
- name: Log in to Azure Container Registry
28+
run: |
29+
az acr login --name ${{ env.ACR_NAME }}
30+
31+
- name: Create .env File with Secrets
32+
run: |
33+
echo "SUPABASE_URL=${{ secrets.SUPABASE_URL }}" >> .env
34+
echo "SUPABASE_KEY=${{ secrets.SUPABASE_KEY }}" >> .env
35+
echo "SUPABASE_BUCKET=${{ secrets.SUPABASE_BUCKET }}" >> .env
36+
echo ".env file created successfully."
37+
38+
- name: Build and Push Docker Image
39+
run: |
40+
IMAGE_TAG=${{ env.ACR_NAME }}.azurecr.io/${{ env.IMAGE_NAME }}:${{ github.sha }}
41+
docker build -t $IMAGE_TAG .
42+
docker push $IMAGE_TAG
43+
44+
- name: Deploy New Image to Azure Container App
45+
run: |
46+
IMAGE_TAG=${{ env.ACR_NAME }}.azurecr.io/${{ env.IMAGE_NAME }}:${{ github.sha }}
47+
az containerapp update \
48+
--name ${{ env.CONTAINER_APP_NAME }} \
49+
--resource-group ${{ env.RESOURCE_GROUP }} \
50+
--image $IMAGE_TAG

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
.venv
22
.env
3-
test_supabase.py
3+
test_supabase.py
4+
__pycache__

Dockerfile

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# Use the official Python image
2+
FROM python:3.10-slim
3+
4+
# Set environment variables to prevent Python from writing .pyc files and to buffer stdout and stderr
5+
ENV PYTHONDONTWRITEBYTECODE=1
6+
ENV PYTHONUNBUFFERED=1
7+
8+
# Create and set the working directory
9+
WORKDIR /app
10+
11+
# Install system dependencies
12+
RUN apt-get update && apt-get install -y \
13+
gcc \
14+
libpq-dev \
15+
--no-install-recommends && \
16+
apt-get clean && \
17+
rm -rf /var/lib/apt/lists/*
18+
19+
# Copy the requirements file and install dependencies
20+
COPY requirements.txt .
21+
RUN pip install --no-cache-dir --upgrade pip && \
22+
pip install --no-cache-dir -r requirements.txt
23+
24+
# Copy the application files to the working directory
25+
COPY . .
26+
27+
# Expose the port
28+
EXPOSE 8000
29+
30+
# Run the application
31+
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]

app/api/endpoints/quizEntry.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
from typing import List, Union
2+
from fastapi import APIRouter, Request, HTTPException, Depends, WebSocket, WebSocketDisconnect
3+
from app.schemas import quizEntry_schema
4+
from app.core.config import SUPABASE_URL, SUPABASE_KEY
5+
from app.db.db import supabase
6+
from app.crud import quiz_crud
7+
import json
8+
9+
10+
router = APIRouter(
11+
prefix="/quiz",
12+
tags=["Quiz"],
13+
)
14+
15+
@router.post("/join", response_model=List[quizEntry_schema.Question])
16+
async def add_participant(participant:quizEntry_schema.Participant ):
17+
return quiz_crud.join_quiz(participant)
18+
19+
20+
@router.put("/updateScore", response_model=List[quizEntry_schema.LeaderBoard])
21+
async def submit_answer(answer: quizEntry_schema.UpdateScore):
22+
return quiz_crud.update_score(answer)
23+
24+
25+
@router.post("/addQuestions", response_model=quizEntry_schema.RoomKey)
26+
async def add_question(request: quizEntry_schema.AddQuestionsRequest):
27+
return quiz_crud.add_questions(request.questions, request.user_id)
28+
29+
# @router.websocket("/ws/{room_key}")
30+
# async def websocket_endpoint(websocket: WebSocket, room_key: int):
31+
# await manager.connect(websocket, room_key)
32+
# try:
33+
# while True:
34+
# await websocket.receive_text() # Keep connection alive
35+
# except WebSocketDisconnect:
36+
# manager.disconnect(websocket, room_key)
37+
38+
39+
# @router.post("/broadcast-message/")
40+
# async def broadcast_message(room_key: int, message: str):
41+
# await manager.broadcast(f"{room_key}", json.dumps({"type": "announcement", "message": message}))
42+
# return {"message": "Broadcast sent"}
43+
44+
# @router.post("/startQuiz")
45+
# async def create_quiz(room_key: int, host_id: int):
46+
# response = supabase.table("leaderboard").insert({
47+
# "room_key": room_key,
48+
# "id": host_id
49+
# }).execute()
50+
51+
# if response.error:
52+
# raise HTTPException(status_code=400, detail=response.error.message)
53+
54+
# return {"message": "Quiz created successfully", "room_key": room_key}
55+

app/api/endpoints/users.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
from typing import List
2+
from fastapi import APIRouter, Request, HTTPException, Depends
3+
from app.schemas import user_schema
4+
from app.core.config import SUPABASE_URL, SUPABASE_KEY
5+
from app.db.db import supabase
6+
from app.crud import user_crud
7+
8+
router = APIRouter(
9+
prefix="/user",
10+
tags=["users"],
11+
)
12+
13+
14+
@router.post("/users", response_model=List[user_schema.UserResponse])
15+
async def get_users():
16+
return user_crud.get_users()
17+
18+
@router.post("/create_user", response_model=user_schema.UserResponse)
19+
async def create_user(user: user_schema.UserCreate):
20+
return user_crud.create_user(user)
21+
22+
@router.put("/update_user", response_model=user_schema.UserResponse)
23+
async def update_user(user: user_schema.UserUpdate):
24+
return user_crud.update_user(user)

app/api/endpoints/webSocket_demo.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import os
2+
import dotenv
3+
from supabase import create_client, Client, AClient, acreate_client
4+
5+
dotenv.load_dotenv()
6+
SUPABASE_URL = os.getenv("SUPABASE_URL")
7+
SUPABASE_KEY = os.getenv("SUPABASE_KEY")
8+
9+
# supabase: Client = create_client(SUPABASE_URL, SUPABASE_KEY)
10+
11+
def handle_record_updated(payload):
12+
print("Record Updated")
13+
print(payload)
14+
15+
async def main():
16+
supabase: AClient = await acreate_client(SUPABASE_URL, SUPABASE_KEY)
17+
18+
await supabase.realtime.connect()
19+
20+
await (supabase.realtime
21+
.channel("my_channel")
22+
.on_postgres_changes("*", schema="public", table="demo-table", callback=handle_record_updated)
23+
.subscribe())
24+
25+
await supabase.realtime.listen()
26+
27+
import asyncio
28+
asyncio.run(main())

app/core/config.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import os
2+
import dotenv
3+
4+
dotenv.load_dotenv()
5+
6+
SUPABASE_URL = os.getenv("SUPABASE_URL")
7+
SUPABASE_KEY = os.getenv("SUPABASE_KEY")
8+
SUPABASE_BUCKET = os.getenv("SUPABASE_BUCKET")

app/crud/quiz_crud.py

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
from app.schemas import quizEntry_schema
2+
from fastapi import HTTPException, status
3+
from app.db.db import supabase
4+
from typing import List
5+
6+
7+
def join_quiz(participant: quizEntry_schema.Participant):
8+
try:
9+
# Check if the quiz exists
10+
user_id = supabase.table("leaderboard").select("id").eq("room_key", participant.room_key).execute()
11+
if not user_id.data:
12+
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Quiz not found")
13+
14+
# Register participant
15+
try:
16+
result = supabase.table("participants").insert({
17+
"room_key": participant.room_key,
18+
"user_id": user_id.data[0]["id"],
19+
"name": participant.name,
20+
"score": 0
21+
}).execute()
22+
except Exception as e:
23+
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Failed to register participant: {e}")
24+
25+
# Fetch quiz questions
26+
questions = supabase.table("questions").select("question", "answers", "correct_answer").eq("room_key", participant.room_key).execute()
27+
if not questions.data:
28+
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="No questions found for the quiz")
29+
30+
# Return list of questions
31+
return [quizEntry_schema.Question(
32+
id=result.data[0]["id"],
33+
room_key=participant.room_key,
34+
question=question["question"],
35+
answers=question["answers"]["answers"],
36+
correct_answer=question["correct_answer"]
37+
) for question in questions.data]
38+
39+
except Exception as e:
40+
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Failed to join quiz: {e}")
41+
42+
def update_score(answer: quizEntry_schema.UpdateScore):
43+
try:
44+
try:
45+
# Update the participant's score
46+
response = supabase.table("participants").update({
47+
"score": answer.score
48+
}).eq("id", answer.id).execute()
49+
50+
except Exception as e:
51+
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Failed to update score: {e}")
52+
53+
54+
# Fetch the updated leaderboard
55+
leaderboard = supabase.table("participants").select("*").eq("room_key", answer.room_key).order("score", desc=True).execute()
56+
57+
return [quizEntry_schema.LeaderBoard(
58+
id=participant["id"],
59+
room_key=participant["room_key"],
60+
name=participant["name"],
61+
score=participant["score"]
62+
) for participant in leaderboard.data]
63+
64+
except Exception as e:
65+
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Failed to update score: {e}")
66+
67+
def create_room(host_id: int):
68+
try:
69+
# create a new room
70+
response = supabase.table("leaderboard").insert({
71+
"id": host_id
72+
}).execute()
73+
74+
return {"message": "Room created successfully", "room_key": response.data[0]["room_key"]}
75+
76+
except Exception as e:
77+
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Failed to create room: {e}")
78+
79+
def add_questions(questions: List[quizEntry_schema.AddQuestion], user_id: int):
80+
try:
81+
# create a new room
82+
room_key = create_room(user_id)["room_key"]
83+
84+
if not room_key:
85+
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Failed to create room")
86+
87+
for question in questions:
88+
answers_json = {"answers": question.answers}
89+
supabase.table("questions").insert({
90+
"room_key": room_key,
91+
"question": question.question,
92+
"answers": answers_json,
93+
"correct_answer": question.correct_answer
94+
}).execute()
95+
96+
print("Questions added successfully")
97+
return quizEntry_schema.RoomKey(room_key=room_key)
98+
99+
except Exception as e:
100+
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Failed to add questions: {e}")
101+

app/crud/user.py

Whitespace-only changes.

app/crud/user_crud.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
from app.schemas import user_schema
2+
from fastapi import HTTPException, status
3+
from app.db.db import supabase
4+
from typing import List
5+
6+
def get_users() -> List[user_schema.UserResponse]:
7+
try:
8+
users = supabase.table("users").select("*").execute()
9+
return users.data
10+
except Exception as e:
11+
return{"error": f"Failed to retrieve users: {str(e)}"}
12+
13+
def create_user(user: user_schema.UserCreate) -> user_schema.UserResponse:
14+
try:
15+
new_user = {"name": user.name, "email": user.email, "is_active": user.is_active, "img_url": user.img_url}
16+
response = supabase.table("users").insert(new_user).execute()
17+
return response.data[0]
18+
except Exception as e:
19+
return{"error": f"Failed to create user: {str(e)}"}
20+
21+
def update_user(user: user_schema.UserUpdate) -> user_schema.UserResponse:
22+
try:
23+
updated_user = {"name": user.name, "email": user.email, "is_active": user.is_active, "img_url": user.img_url}
24+
response = supabase.table("users").update(updated_user).eq("id", user.id).execute()
25+
return response.data[0]
26+
except Exception as e:
27+
return{"error": f"Failed to update user: {str(e)}"}
28+

app/db/base.py

Whitespace-only changes.

app/db/db.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
from supabase import create_client, Client
2+
from app.core.config import SUPABASE_URL, SUPABASE_KEY, SUPABASE_BUCKET
3+
4+
5+
if not all([SUPABASE_URL, SUPABASE_KEY, SUPABASE_BUCKET]):
6+
raise EnvironmentError("One or more Supabase environment variables are missing")
7+
8+
# Initialize the Supabase client
9+
supabase: Client = create_client(SUPABASE_URL, SUPABASE_KEY)
10+

app/db/session.py

Whitespace-only changes.

app/main.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
from fastapi import FastAPI
2+
from fastapi.middleware.cors import CORSMiddleware
3+
from app.api.endpoints import users, quizEntry
4+
from app.db.db import supabase
5+
6+
7+
app = FastAPI()
8+
9+
origins = [
10+
"http://localhost",
11+
"http://localhost:8080",
12+
"http://localhost:3000",
13+
"http://localhost:8000",
14+
]
15+
16+
app.add_middleware(
17+
CORSMiddleware,
18+
allow_origins=origins,
19+
allow_credentials=True,
20+
allow_methods=["*"],
21+
allow_headers=["*"],
22+
)
23+
24+
app.include_router(users.router)
25+
app.include_router(quizEntry.router)
26+
27+
28+
@app.get("/")
29+
def read_root():
30+
return {"Message": "Welcome to Supabase Hackathon!"}

0 commit comments

Comments
 (0)