Skip to content

Commit

Permalink
Merge pull request #1 from QuizCast/dev
Browse files Browse the repository at this point in the history
deploying to Azure
  • Loading branch information
PinsaraPerera authored Dec 9, 2024
2 parents b0ee0e1 + 43998e8 commit 535795d
Show file tree
Hide file tree
Showing 19 changed files with 441 additions and 1 deletion.
2 changes: 2 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.venv
__pycache__
50 changes: 50 additions & 0 deletions .github/workflows/azure-serveless.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
name: Build and Deploy to Azure

on:
push:
branches: [main]
workflow_dispatch:

env:
IMAGE_NAME: supabse-app
ACR_NAME: pawan
CONTAINER_APP_NAME: supabase-fastapi
RESOURCE_GROUP: github

jobs:
build-and-deploy:
runs-on: ubuntu-latest

steps:
- name: Checkout Code
uses: actions/checkout@v4

- name: Log in to Azure
uses: azure/login@v2
with:
creds: ${{ secrets.AZURE_CREDENTIALS }}

- name: Log in to Azure Container Registry
run: |
az acr login --name ${{ env.ACR_NAME }}
- name: Create .env File with Secrets
run: |
echo "SUPABASE_URL=${{ secrets.SUPABASE_URL }}" >> .env
echo "SUPABASE_KEY=${{ secrets.SUPABASE_KEY }}" >> .env
echo "SUPABASE_BUCKET=${{ secrets.SUPABASE_BUCKET }}" >> .env
echo ".env file created successfully."
- name: Build and Push Docker Image
run: |
IMAGE_TAG=${{ env.ACR_NAME }}.azurecr.io/${{ env.IMAGE_NAME }}:${{ github.sha }}
docker build -t $IMAGE_TAG .
docker push $IMAGE_TAG
- name: Deploy New Image to Azure Container App
run: |
IMAGE_TAG=${{ env.ACR_NAME }}.azurecr.io/${{ env.IMAGE_NAME }}:${{ github.sha }}
az containerapp update \
--name ${{ env.CONTAINER_APP_NAME }} \
--resource-group ${{ env.RESOURCE_GROUP }} \
--image $IMAGE_TAG
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
.venv
.env
test_supabase.py
test_supabase.py
__pycache__
31 changes: 31 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Use the official Python image
FROM python:3.10-slim

# Set environment variables to prevent Python from writing .pyc files and to buffer stdout and stderr
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1

# Create and set the working directory
WORKDIR /app

# Install system dependencies
RUN apt-get update && apt-get install -y \
gcc \
libpq-dev \
--no-install-recommends && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*

# Copy the requirements file and install dependencies
COPY requirements.txt .
RUN pip install --no-cache-dir --upgrade pip && \
pip install --no-cache-dir -r requirements.txt

# Copy the application files to the working directory
COPY . .

# Expose the port
EXPOSE 8000

# Run the application
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
55 changes: 55 additions & 0 deletions app/api/endpoints/quizEntry.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
from typing import List, Union
from fastapi import APIRouter, Request, HTTPException, Depends, WebSocket, WebSocketDisconnect
from app.schemas import quizEntry_schema
from app.core.config import SUPABASE_URL, SUPABASE_KEY
from app.db.db import supabase
from app.crud import quiz_crud
import json


router = APIRouter(
prefix="/quiz",
tags=["Quiz"],
)

@router.post("/join", response_model=List[quizEntry_schema.Question])
async def add_participant(participant:quizEntry_schema.Participant ):
return quiz_crud.join_quiz(participant)


@router.put("/updateScore", response_model=List[quizEntry_schema.LeaderBoard])
async def submit_answer(answer: quizEntry_schema.UpdateScore):
return quiz_crud.update_score(answer)


@router.post("/addQuestions", response_model=quizEntry_schema.RoomKey)
async def add_question(request: quizEntry_schema.AddQuestionsRequest):
return quiz_crud.add_questions(request.questions, request.user_id)

# @router.websocket("/ws/{room_key}")
# async def websocket_endpoint(websocket: WebSocket, room_key: int):
# await manager.connect(websocket, room_key)
# try:
# while True:
# await websocket.receive_text() # Keep connection alive
# except WebSocketDisconnect:
# manager.disconnect(websocket, room_key)


# @router.post("/broadcast-message/")
# async def broadcast_message(room_key: int, message: str):
# await manager.broadcast(f"{room_key}", json.dumps({"type": "announcement", "message": message}))
# return {"message": "Broadcast sent"}

# @router.post("/startQuiz")
# async def create_quiz(room_key: int, host_id: int):
# response = supabase.table("leaderboard").insert({
# "room_key": room_key,
# "id": host_id
# }).execute()

# if response.error:
# raise HTTPException(status_code=400, detail=response.error.message)

# return {"message": "Quiz created successfully", "room_key": room_key}

24 changes: 24 additions & 0 deletions app/api/endpoints/users.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
from typing import List
from fastapi import APIRouter, Request, HTTPException, Depends
from app.schemas import user_schema
from app.core.config import SUPABASE_URL, SUPABASE_KEY
from app.db.db import supabase
from app.crud import user_crud

router = APIRouter(
prefix="/user",
tags=["users"],
)


@router.post("/users", response_model=List[user_schema.UserResponse])
async def get_users():
return user_crud.get_users()

@router.post("/create_user", response_model=user_schema.UserResponse)
async def create_user(user: user_schema.UserCreate):
return user_crud.create_user(user)

@router.put("/update_user", response_model=user_schema.UserResponse)
async def update_user(user: user_schema.UserUpdate):
return user_crud.update_user(user)
28 changes: 28 additions & 0 deletions app/api/endpoints/webSocket_demo.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import os
import dotenv
from supabase import create_client, Client, AClient, acreate_client

dotenv.load_dotenv()
SUPABASE_URL = os.getenv("SUPABASE_URL")
SUPABASE_KEY = os.getenv("SUPABASE_KEY")

# supabase: Client = create_client(SUPABASE_URL, SUPABASE_KEY)

def handle_record_updated(payload):
print("Record Updated")
print(payload)

async def main():
supabase: AClient = await acreate_client(SUPABASE_URL, SUPABASE_KEY)

await supabase.realtime.connect()

await (supabase.realtime
.channel("my_channel")
.on_postgres_changes("*", schema="public", table="demo-table", callback=handle_record_updated)
.subscribe())

await supabase.realtime.listen()

import asyncio
asyncio.run(main())
8 changes: 8 additions & 0 deletions app/core/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import os
import dotenv

dotenv.load_dotenv()

SUPABASE_URL = os.getenv("SUPABASE_URL")
SUPABASE_KEY = os.getenv("SUPABASE_KEY")
SUPABASE_BUCKET = os.getenv("SUPABASE_BUCKET")
101 changes: 101 additions & 0 deletions app/crud/quiz_crud.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
from app.schemas import quizEntry_schema
from fastapi import HTTPException, status
from app.db.db import supabase
from typing import List


def join_quiz(participant: quizEntry_schema.Participant):
try:
# Check if the quiz exists
user_id = supabase.table("leaderboard").select("id").eq("room_key", participant.room_key).execute()
if not user_id.data:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Quiz not found")

# Register participant
try:
result = supabase.table("participants").insert({
"room_key": participant.room_key,
"user_id": user_id.data[0]["id"],
"name": participant.name,
"score": 0
}).execute()
except Exception as e:
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Failed to register participant: {e}")

# Fetch quiz questions
questions = supabase.table("questions").select("question", "answers", "correct_answer").eq("room_key", participant.room_key).execute()
if not questions.data:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="No questions found for the quiz")

# Return list of questions
return [quizEntry_schema.Question(
id=result.data[0]["id"],
room_key=participant.room_key,
question=question["question"],
answers=question["answers"]["answers"],
correct_answer=question["correct_answer"]
) for question in questions.data]

except Exception as e:
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Failed to join quiz: {e}")

def update_score(answer: quizEntry_schema.UpdateScore):
try:
try:
# Update the participant's score
response = supabase.table("participants").update({
"score": answer.score
}).eq("id", answer.id).execute()

except Exception as e:
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Failed to update score: {e}")


# Fetch the updated leaderboard
leaderboard = supabase.table("participants").select("*").eq("room_key", answer.room_key).order("score", desc=True).execute()

return [quizEntry_schema.LeaderBoard(
id=participant["id"],
room_key=participant["room_key"],
name=participant["name"],
score=participant["score"]
) for participant in leaderboard.data]

except Exception as e:
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Failed to update score: {e}")

def create_room(host_id: int):
try:
# create a new room
response = supabase.table("leaderboard").insert({
"id": host_id
}).execute()

return {"message": "Room created successfully", "room_key": response.data[0]["room_key"]}

except Exception as e:
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Failed to create room: {e}")

def add_questions(questions: List[quizEntry_schema.AddQuestion], user_id: int):
try:
# create a new room
room_key = create_room(user_id)["room_key"]

if not room_key:
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Failed to create room")

for question in questions:
answers_json = {"answers": question.answers}
supabase.table("questions").insert({
"room_key": room_key,
"question": question.question,
"answers": answers_json,
"correct_answer": question.correct_answer
}).execute()

print("Questions added successfully")
return quizEntry_schema.RoomKey(room_key=room_key)

except Exception as e:
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Failed to add questions: {e}")

Empty file removed app/crud/user.py
Empty file.
28 changes: 28 additions & 0 deletions app/crud/user_crud.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
from app.schemas import user_schema
from fastapi import HTTPException, status
from app.db.db import supabase
from typing import List

def get_users() -> List[user_schema.UserResponse]:
try:
users = supabase.table("users").select("*").execute()
return users.data
except Exception as e:
return{"error": f"Failed to retrieve users: {str(e)}"}

def create_user(user: user_schema.UserCreate) -> user_schema.UserResponse:
try:
new_user = {"name": user.name, "email": user.email, "is_active": user.is_active, "img_url": user.img_url}
response = supabase.table("users").insert(new_user).execute()
return response.data[0]
except Exception as e:
return{"error": f"Failed to create user: {str(e)}"}

def update_user(user: user_schema.UserUpdate) -> user_schema.UserResponse:
try:
updated_user = {"name": user.name, "email": user.email, "is_active": user.is_active, "img_url": user.img_url}
response = supabase.table("users").update(updated_user).eq("id", user.id).execute()
return response.data[0]
except Exception as e:
return{"error": f"Failed to update user: {str(e)}"}

Empty file removed app/db/base.py
Empty file.
10 changes: 10 additions & 0 deletions app/db/db.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from supabase import create_client, Client
from app.core.config import SUPABASE_URL, SUPABASE_KEY, SUPABASE_BUCKET


if not all([SUPABASE_URL, SUPABASE_KEY, SUPABASE_BUCKET]):
raise EnvironmentError("One or more Supabase environment variables are missing")

# Initialize the Supabase client
supabase: Client = create_client(SUPABASE_URL, SUPABASE_KEY)

Empty file removed app/db/session.py
Empty file.
30 changes: 30 additions & 0 deletions app/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from app.api.endpoints import users, quizEntry
from app.db.db import supabase


app = FastAPI()

origins = [
"http://localhost",
"http://localhost:8080",
"http://localhost:3000",
"http://localhost:8000",
]

app.add_middleware(
CORSMiddleware,
allow_origins=origins,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)

app.include_router(users.router)
app.include_router(quizEntry.router)


@app.get("/")
def read_root():
return {"Message": "Welcome to Supabase Hackathon!"}
Loading

0 comments on commit 535795d

Please sign in to comment.