diff --git a/server/controller/transcript.py b/server/controller/transcript.py new file mode 100644 index 0000000..d872e64 --- /dev/null +++ b/server/controller/transcript.py @@ -0,0 +1,64 @@ +import http + +from fastapi import APIRouter +from botocore.exceptions import ClientError + +from src.utils.settings import AWS_BUCKET_NAME +from src.schemas.base import GenericResponseModel +from src.schemas.transcription import ( + TranscribeAudioRequestSchema, + PollTranscriptionRequestSchema +) +from src.services.transcription import TranscriptionService + +transcription_router = APIRouter( + prefix="/v1/transcription", + tags=[TranscriptionRouterTags.transcribe] +) + +# Define the path operation decorator +@transcription_router.post( + "/create", + status_code=http.HTTPStatus.CREATED, + response_model=GenericResponseModel +) + +# Define an async function to create the transcribed audio with help from Service layer +async def transcribe_audio(req: TranscribeAudioRequestSchema): + transcribe_client = AWSTranscribeClient().get_client() + + service = TranscriptionService() + + filename, file_format = req.s3_filename.split(".") + job_name = req.job_name + language_code = req.language_code + + file_uri = service.generate_file_uri( + bucket_name=AWS_BUCKET_NAME, + filename=filename, + extension=file_format + ) + + try: + response = await service.transcribe_file( + transcribe_client=transcribe_client, + job_name=job_name, + file_uri=file_uri, + file_format=file_format, + language_code=language_code + ) + + return GenericResponseModel( + status_code=http.HTTPStatus.CREATED, + message="Successfully created audio transcription", + error="", + data=response, + ) + except TimeoutError: + return GenericResponseModel( + ... + ) + except ClientError: + return GenericResponseModel( + ... + ) \ No newline at end of file diff --git a/server/controller/user.py b/server/controller/user.py new file mode 100644 index 0000000..6fe6cb0 --- /dev/null +++ b/server/controller/user.py @@ -0,0 +1,64 @@ +import http +from fastapi import APIRouter, Depends, HTTPException, status +from sqlalchemy.orm import Session +from pydantic import BaseModel, EmailStr + +from database import get_db +from models.user import User as UserModel +from services.user_service import UserService +from schemas.user import UserCreate, UserResponse, TokenResponse +from schemas.generic import GenericResponseModel + +user_router = APIRouter( + prefix="/v1/users", + tags=["users"] +) + +@user_router.post( + "/register", + status_code=http.HTTPStatus.CREATED, + response_model=GenericResponseModel +) +async def register_user(user: UserCreate, db: Session = Depends(get_db)): + service = UserService(db) + try: + new_user = service.create_user(user) + return GenericResponseModel( + status_code=http.HTTPStatus.CREATED, + message="User successfully registered", + error="", + data={"user": new_user} + ) + except Exception as e: + raise HTTPException(status_code=400, detail=str(e)) + +@user_router.post( + "/login", + status_code=http.HTTPStatus.OK, + response_model=GenericResponseModel +) +async def login_user(email: EmailStr, password: str, db: Session = Depends(get_db)): + service = UserService(db) + try: + token = service.authenticate_user(email, password) + return GenericResponseModel( + status_code=http.HTTPStatus.OK, + message="User successfully logged in", + error="", + data={"token": token} + ) + except Exception as e: + raise HTTPException(status_code=400, detail=str(e)) + +@user_router.get( + "/me", + status_code=http.HTTPStatus.OK, + response_model=GenericResponseModel +) +async def get_user_details(current_user: UserModel = Depends(UserService.get_current_user)): + return GenericResponseModel( + status_code=http.HTTPStatus.OK, + message="User details retrieved successfully", + error="", + data={"user": current_user} + ) diff --git a/server/database.py b/server/database.py new file mode 100644 index 0000000..139f579 --- /dev/null +++ b/server/database.py @@ -0,0 +1,47 @@ +import os +from dotenv import load_dotenv +import psycopg2 +from psycopg2 import pool + +load_dotenv() + +# SEE QUICKSTART PYTHON COSMOSDB POSTGRESQL +# https://learn.microsoft.com/en-us/azure/cosmos-db/postgresql/quickstart-app-stacks-python +# https://devblogs.microsoft.com/cosmosdb/azure-cosmos-db-python-and-fastapi/ + +# NOTE: fill in these variables in .env +host = os.getenv('COSMOS_HOST') +dbname = "citus" +user = "citus" +password = os.getenv('COSMOS_DB_PASSWORD') +sslmode = "require" + +# Build a connection string from the variables +conn_string = "host={0} user={1} dbname={2} password={3} sslmode={4}".format(host, user, dbname, password, sslmode) + +# Create a connection pool +try: + postgreSQL_pool = psycopg2.pool.SimpleConnectionPool(1, 20, conn_string) + print("Connection pool created successfully") +except psycopg2.Error as e: + print(f"Error creating connection pool: {e}") + exit(1) + +def get_db_connection(): + conn = postgreSQL_pool.getconn() + try: + yield conn + finally: + postgreSQL_pool.putconn(conn) + +# # # Use getconn() to get a connection from the connection pool +# conn = postgreSQL_pool.getconn() + +# cursor = conn.cursor() + + + +# # Clean up +# conn.commit() +# cursor.close() +# conn.close() \ No newline at end of file diff --git a/server/models.py b/server/models.py new file mode 100644 index 0000000..764ccd2 --- /dev/null +++ b/server/models.py @@ -0,0 +1,119 @@ +from sqlalchemy import Column, Integer, String, DateTime, ForeignKey, Boolean, Float, Text +from sqlalchemy.sql.functions import current_timestamp +from sqlalchemy.ext.declarative import declarative_base + +Base = declarative_base() + +class User(Base): + __tablename__ = 'users' + id = Column(Integer, primary_key=True, autoincrement=True) + username = Column(String(32), unique=True, nullable=False) + email = Column(String(256), unique=True, nullable=False) + passwordHash = Column(String(256), nullable=False) + updated_at = Column(DateTime, default=current_timestamp(), onupdate=current_timestamp()) + created_at = Column(DateTime, default=current_timestamp()) + verified = Column(Boolean, default=False) + +class Team(Base): + __tablename__ = 'teams' + id = Column(Integer, primary_key=True, autoincrement=True) + name = Column(String(32), unique=True, nullable=False) + updated_at = Column(DateTime, default=current_timestamp(), onupdate=current_timestamp()) + created_at = Column(DateTime, default=current_timestamp()) + +class TeamStat(Base): + __tablename__ = 'teamStats' + id = Column(Integer, primary_key=True, autoincrement=True) + teamId = Column(Integer, ForeignKey('teams.id'), nullable=False) + boxscoreId = Column(Integer, ForeignKey('boxscores.id'), nullable=False) + createdAt = Column(DateTime, default=current_timestamp()) + startAt = Column(DateTime, nullable=False) + endAt = Column(DateTime, nullable=False) + points = Column(Integer, nullable=False) + rebounds = Column(Integer, nullable=False) + assists = Column(Integer, nullable=False) + steal = Column(Integer, nullable=False) + block = Column(Integer, nullable=False) + turnover = Column(Integer, nullable=False) + fgPercent = Column(Float, nullable=False) + fgAttempt = Column(Integer, nullable=False) + fgMade = Column(Integer, nullable=False) + fouls = Column(Integer, nullable=False) + +class Boxscore(Base): + __tablename__ = 'boxscores' + id = Column(Integer, primary_key=True, autoincrement=True) + userId = Column(Integer, ForeignKey('users.id'), nullable=False) + homeTeamId = Column(Integer, ForeignKey('teams.id'), nullable=False) + awayTeamId = Column(Integer, ForeignKey('teams.id'), nullable=False) + textFileId = Column(Integer, ForeignKey('textFiles.id'), nullable=False) + score = Column(String, nullable=False) + homeScore = Column(Integer, nullable=False) + awayScore = Column(Integer, nullable=False) + startAt = Column(DateTime, nullable=False) + endAt = Column(DateTime, nullable=False) + updatedAt = Column(DateTime, default=current_timestamp(), onupdate=current_timestamp()) + createdAt = Column(DateTime, default=current_timestamp()) + +class Player(Base): + __tablename__ = 'players' + id = Column(Integer, primary_key=True, autoincrement=True) + userId = Column(Integer, ForeignKey('users.id'), nullable=False) + teamId = Column(Integer, ForeignKey('teams.id'), nullable=False) + updatedAt = Column(DateTime, default=current_timestamp(), onupdate=current_timestamp()) + createdAt = Column(DateTime, default=current_timestamp()) + matches = Column(String, nullable=True) + +class PlayerStat(Base): + __tablename__ = 'playerStats' + id = Column(Integer, primary_key=True, autoincrement=True) + playerId = Column(Integer, ForeignKey('players.id'), nullable=False) + boxscoreId = Column(Integer, ForeignKey('boxscores.id'), nullable=False) + points = Column(Integer, nullable=False) + rebounds = Column(Integer, nullable=False) + assists = Column(Integer, nullable=False) + steal = Column(Integer, nullable=False) + block = Column(Integer, nullable=False) + turnover = Column(Integer, nullable=False) + fgPercent = Column(Float, nullable=False) + fgAttempt = Column(Integer, nullable=False) + fgMade = Column(Integer, nullable=False) + fouls = Column(Integer, nullable=False) + +class Token(Base): + __tablename__ = 'tokens' + id = Column(Integer, primary_key=True, autoincrement=True) + userId = Column(Integer, ForeignKey('users.id'), nullable=False) + token = Column(String(256), unique=True, nullable=False) + expiresAt = Column(DateTime, nullable=False) + used = Column(Boolean, default=False) + +class PasswordResetToken(Base): + __tablename__ = 'passwordResetTokens' + id = Column(Integer, primary_key=True, autoincrement=True) + userId = Column(Integer, ForeignKey('users.id'), nullable=False) + token = Column(String(256), unique=True, nullable=False) + expiresAt = Column(DateTime, nullable=False) + +class AccountVerificationToken(Base): + __tablename__ = 'accountVerificationTokens' + id = Column(Integer, primary_key=True, autoincrement=True) + userId = Column(Integer, ForeignKey('users.id'), nullable=False) + token = Column(String(256), unique=True, nullable=False) + expiresAt = Column(DateTime, nullable=False) + +class AudioFile(Base): + __tablename__ = 'audioFiles' + id = Column(Integer, primary_key=True, autoincrement=True) + userId = Column(Integer, ForeignKey('users.id'), nullable=False) + filePath = Column(String, nullable=False) + createdAt = Column(DateTime, default=current_timestamp()) + +class TextFile(Base): + __tablename__ = 'textFiles' + id = Column(Integer, primary_key=True, autoincrement=True) + userId = Column(Integer, ForeignKey('users.id'), nullable=False) + audioId = Column(Integer, ForeignKey('audioFiles.id'), nullable=False) + contents = Column(Text, nullable=False) + createdAt = Column(DateTime, default=current_timestamp()) + filepath = Column(String, nullable=False) diff --git a/server/models/account_verification_token.py b/server/models/account_verification_token.py new file mode 100644 index 0000000..fee9081 --- /dev/null +++ b/server/models/account_verification_token.py @@ -0,0 +1,11 @@ +from sqlalchemy import Column, Integer, String, DateTime, ForeignKey, Boolean, Float, Text +from sqlalchemy.sql.functions import current_timestamp +from sqlalchemy.ext.declarative import declarative_base + +class AccountVerificationToken(Base): + __tablename__ = 'accountVerificationTokens' + id = Column(Integer, primary_key=True, autoincrement=True) + userId = Column(Integer, ForeignKey('users.id'), nullable=False) + token = Column(String(256), unique=True, nullable=False) + expiresAt = Column(DateTime, nullable=False) + createdAt = Column(DateTime, default=current_timestamp()) \ No newline at end of file diff --git a/server/models/audio_file.py b/server/models/audio_file.py new file mode 100644 index 0000000..030367c --- /dev/null +++ b/server/models/audio_file.py @@ -0,0 +1,10 @@ +from sqlalchemy import Column, Integer, String, DateTime, ForeignKey, Boolean, Float, Text +from sqlalchemy.sql.functions import current_timestamp +from sqlalchemy.ext.declarative import declarative_base + +class AudioFile(Base): + __tablename__ = 'audioFiles' + id = Column(Integer, primary_key=True, autoincrement=True) + userId = Column(Integer, ForeignKey('users.id'), nullable=False) + filePath = Column(String, nullable=False) + createdAt = Column(DateTime, default=current_timestamp()) \ No newline at end of file diff --git a/server/models/box_score.py b/server/models/box_score.py new file mode 100644 index 0000000..f54e90b --- /dev/null +++ b/server/models/box_score.py @@ -0,0 +1,18 @@ +from sqlalchemy import Column, Integer, String, DateTime, ForeignKey, Boolean, Float, Text +from sqlalchemy.sql.functions import current_timestamp +from sqlalchemy.ext.declarative import declarative_base + +class Boxscore(Base): + __tablename__ = 'boxscores' + id = Column(Integer, primary_key=True, autoincrement=True) + userId = Column(Integer, ForeignKey('users.id'), nullable=False) + homeTeamId = Column(Integer, ForeignKey('teams.id'), nullable=False) + awayTeamId = Column(Integer, ForeignKey('teams.id'), nullable=False) + textFileId = Column(Integer, ForeignKey('textFiles.id'), nullable=False) + score = Column(String, nullable=False) + homeScore = Column(Integer, nullable=False) + awayScore = Column(Integer, nullable=False) + startAt = Column(DateTime, nullable=False) + endAt = Column(DateTime, nullable=False) + updatedAt = Column(DateTime, default=current_timestamp(), onupdate=current_timestamp()) + createdAt = Column(DateTime, default=current_timestamp()) \ No newline at end of file diff --git a/server/models/password_reset_token.py b/server/models/password_reset_token.py new file mode 100644 index 0000000..1bfc9fd --- /dev/null +++ b/server/models/password_reset_token.py @@ -0,0 +1,10 @@ +from sqlalchemy import Column, Integer, String, DateTime, ForeignKey, Boolean, Float, Text +from sqlalchemy.sql.functions import current_timestamp +from sqlalchemy.ext.declarative import declarative_base + +class PasswordResetToken(Base): + __tablename__ = 'passwordResetTokens' + id = Column(Integer, primary_key=True, autoincrement=True) + userId = Column(Integer, ForeignKey('users.id'), nullable=False) + token = Column(String(256), unique=True, nullable=False) + expiresAt = Column(DateTime, nullable=False) \ No newline at end of file diff --git a/server/models/player.py b/server/models/player.py new file mode 100644 index 0000000..a97180e --- /dev/null +++ b/server/models/player.py @@ -0,0 +1,12 @@ +from sqlalchemy import Column, Integer, String, DateTime, ForeignKey, Boolean, Float, Text +from sqlalchemy.sql.functions import current_timestamp +from sqlalchemy.ext.declarative import declarative_base + +class Player(Base): + __tablename__ = 'players' + id = Column(Integer, primary_key=True, autoincrement=True) + userId = Column(Integer, ForeignKey('users.id'), nullable=False) + teamId = Column(Integer, ForeignKey('teams.id'), nullable=False) + updatedAt = Column(DateTime, default=current_timestamp(), onupdate=current_timestamp()) + createdAt = Column(DateTime, default=current_timestamp()) + matches = Column(String, nullable=True) \ No newline at end of file diff --git a/server/models/player_stat.py b/server/models/player_stat.py new file mode 100644 index 0000000..17647f4 --- /dev/null +++ b/server/models/player_stat.py @@ -0,0 +1,19 @@ +from sqlalchemy import Column, Integer, String, DateTime, ForeignKey, Boolean, Float, Text +from sqlalchemy.sql.functions import current_timestamp +from sqlalchemy.ext.declarative import declarative_base + +class PlayerStat(Base): + __tablename__ = 'playerStats' + id = Column(Integer, primary_key=True, autoincrement=True) + playerId = Column(Integer, ForeignKey('players.id'), nullable=False) + boxscoreId = Column(Integer, ForeignKey('boxscores.id'), nullable=False) + points = Column(Integer, nullable=False) + rebounds = Column(Integer, nullable=False) + assists = Column(Integer, nullable=False) + steal = Column(Integer, nullable=False) + block = Column(Integer, nullable=False) + turnover = Column(Integer, nullable=False) + fgPercent = Column(Float, nullable=False) + fgAttempt = Column(Integer, nullable=False) + fgMade = Column(Integer, nullable=False) + fouls = Column(Integer, nullable=False) \ No newline at end of file diff --git a/server/models/team.py b/server/models/team.py new file mode 100644 index 0000000..f1b079e --- /dev/null +++ b/server/models/team.py @@ -0,0 +1,12 @@ +from sqlalchemy import Column, Integer, String, DateTime, ForeignKey, Boolean, Float, Text +from sqlalchemy.sql.functions import current_timestamp +from sqlalchemy.ext.declarative import declarative_base + +Base = declarative_base() + +class Team(Base): + __tablename__ = 'teams' + id = Column(Integer, primary_key=True, autoincrement=True) + name = Column(String(32), unique=True, nullable=False) + updated_at = Column(DateTime, default=current_timestamp(), onupdate=current_timestamp()) + created_at = Column(DateTime, default=current_timestamp()) \ No newline at end of file diff --git a/server/models/team_stat.py b/server/models/team_stat.py new file mode 100644 index 0000000..7af4146 --- /dev/null +++ b/server/models/team_stat.py @@ -0,0 +1,22 @@ +from sqlalchemy import Column, Integer, String, DateTime, ForeignKey, Boolean, Float, Text +from sqlalchemy.sql.functions import current_timestamp +from sqlalchemy.ext.declarative import declarative_base + +class TeamStat(Base): + __tablename__ = 'teamStats' + id = Column(Integer, primary_key=True, autoincrement=True) + teamId = Column(Integer, ForeignKey('teams.id'), nullable=False) + boxscoreId = Column(Integer, ForeignKey('boxscores.id'), nullable=False) + createdAt = Column(DateTime, default=current_timestamp()) + startAt = Column(DateTime, nullable=False) + endAt = Column(DateTime, nullable=False) + points = Column(Integer, nullable=False) + rebounds = Column(Integer, nullable=False) + assists = Column(Integer, nullable=False) + steal = Column(Integer, nullable=False) + block = Column(Integer, nullable=False) + turnover = Column(Integer, nullable=False) + fgPercent = Column(Float, nullable=False) + fgAttempt = Column(Integer, nullable=False) + fgMade = Column(Integer, nullable=False) + fouls = Column(Integer, nullable=False) \ No newline at end of file diff --git a/server/models/text_file.py b/server/models/text_file.py new file mode 100644 index 0000000..040957d --- /dev/null +++ b/server/models/text_file.py @@ -0,0 +1,12 @@ +from sqlalchemy import Column, Integer, String, DateTime, ForeignKey, Boolean, Float, Text +from sqlalchemy.sql.functions import current_timestamp +from sqlalchemy.ext.declarative import declarative_base + +class TextFile(Base): + __tablename__ = 'textFiles' + id = Column(Integer, primary_key=True, autoincrement=True) + userId = Column(Integer, ForeignKey('users.id'), nullable=False) + audioId = Column(Integer, ForeignKey('audioFiles.id'), nullable=False) + contents = Column(Text, nullable=False) + createdAt = Column(DateTime, default=current_timestamp()) + filepath = Column(String, nullable=False) \ No newline at end of file diff --git a/server/models/token.py b/server/models/token.py new file mode 100644 index 0000000..62e645a --- /dev/null +++ b/server/models/token.py @@ -0,0 +1,11 @@ +from sqlalchemy import Column, Integer, String, DateTime, ForeignKey, Boolean, Float, Text +from sqlalchemy.sql.functions import current_timestamp +from sqlalchemy.ext.declarative import declarative_base + +class Token(Base): + __tablename__ = 'tokens' + id = Column(Integer, primary_key=True, autoincrement=True) + userId = Column(Integer, ForeignKey('users.id'), nullable=False) + token = Column(String(256), unique=True, nullable=False) + expiresAt = Column(DateTime, nullable=False) + used = Column(Boolean, default=False) \ No newline at end of file diff --git a/server/models/user.py b/server/models/user.py new file mode 100644 index 0000000..c6fd52d --- /dev/null +++ b/server/models/user.py @@ -0,0 +1,15 @@ +from sqlalchemy import Column, Integer, String, DateTime, ForeignKey, Boolean, Float, Text +from sqlalchemy.sql.functions import current_timestamp +from sqlalchemy.ext.declarative import declarative_base + +Base = declarative_base() + +class User(Base): + __tablename__ = 'users' + id = Column(Integer, primary_key=True, autoincrement=True) + username = Column(String(32), unique=True, nullable=False) + email = Column(String(256), unique=True, nullable=False) + passwordHash = Column(String(256), nullable=False) + updated_at = Column(DateTime, default=current_timestamp(), onupdate=current_timestamp()) + created_at = Column(DateTime, default=current_timestamp()) + verified = Column(Boolean, default=False) \ No newline at end of file diff --git a/server/schemas/generic.py b/server/schemas/generic.py new file mode 100644 index 0000000..5e95a60 --- /dev/null +++ b/server/schemas/generic.py @@ -0,0 +1,7 @@ +from pydantic import BaseModel, EmailStr + +class GenericResponseModel(BaseModel): + status_code: int + message: str + error: str + data: dict = None \ No newline at end of file diff --git a/server/schemas/user.py b/server/schemas/user.py new file mode 100644 index 0000000..8852d5b --- /dev/null +++ b/server/schemas/user.py @@ -0,0 +1,15 @@ +from pydantic import BaseModel, EmailStr + +class UserCreate(BaseModel): + username: str + email: EmailStr + password: str + +class UserResponse(BaseModel): + id: int + username: str + email: EmailStr + +class TokenResponse(BaseModel): + access_token: str + token_type: str diff --git a/server/server.py b/server/server.py index d0028de..aced68c 100644 --- a/server/server.py +++ b/server/server.py @@ -1,16 +1,148 @@ -import uvicorn -from fastapi import FastAPI - -from middleware import cors_middleware -from routes.game_router import game_router - - -try: - app = FastAPI() - cors_middleware.add(app) - app.include_router(game_router) - uvicorn.run(app, port=9091) -except KeyboardInterrupt as e: - pass -except Exception as e: - print(e) +from fastapi import FastAPI, Depends, HTTPException, status, File, UploadFile +from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm +from pydantic import BaseModel +from typing import List +from passlib.context import CryptContext +from sqlalchemy.orm import sessionmaker, Session +from sqlalchemy.ext.declarative import declarative_base +from sqlalchemy import create_engine, Column, Integer, String, DateTime, ForeignKey +from sqlalchemy.sql.functions import current_timestamp +from enum import Enum +import os +from dotenv import load_dotenv +import shutil +from datetime import datetime, timedelta +import jwt + +# Import database connection and models +from database import get_db_connection +from models import User, Token +# pip install fastapi uvicorn psycopg2-binary passlib python-dotenv jwt +load_dotenv() + +app = FastAPI() +app.include_router(user_router) +app.include_router(transcript_router) + +# Password hashing configuration +pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") + +# OAuth2 configuration +oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token") + +SECRET_KEY = os.getenv('SECRET_KEY') +ALGORITHM = "HS256" +ACCESS_TOKEN_EXPIRE_MINUTES = 30 + +# Database models using SQLAlchemy ORM +Base = declarative_base() + + +# Dependency +# def get_db(): +# conn = next(get_db_connection()) +# return conn + +# def get_user_by_email(db, email: str): +# cursor = db.cursor() +# cursor.execute("SELECT * FROM users WHERE email = %s", (email,)) +# user = cursor.fetchone() +# cursor.close() +# return user + +# def create_user(db, user: UserCreate): +# hashed_password = pwd_context.hash(user.password) +# cursor = db.cursor() +# cursor.execute( +# "INSERT INTO users (name, email, password, created_at, updated_at) VALUES (%s, %s, %s, %s, %s) RETURNING id, name, email", +# (user.name, user.email, hashed_password, datetime.utcnow(), datetime.utcnow()) +# ) +# new_user = cursor.fetchone() +# db.commit() +# cursor.close() +# return new_user + +# def authenticate_user(db, email: str, password: str): +# user = get_user_by_email(db, email) +# if not user or not pwd_context.verify(password, user[4]): +# return False +# return user + +# def create_access_token(data: dict, expires_delta: timedelta = None): +# to_encode = data.copy() +# if expires_delta: +# expire = datetime.utcnow() + expires_delta +# else: +# expire = datetime.utcnow() + timedelta(minutes=15) +# to_encode.update({"exp": expire}) +# encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM) +# return encoded_jwt + +# def get_current_user(db: Session = Depends(get_db), token: str = Depends(oauth2_scheme)): +# credentials_exception = HTTPException( +# status_code=status.HTTP_401_UNAUTHORIZED, +# detail="Could not validate credentials", +# headers={"WWW-Authenticate": "Bearer"}, +# ) +# try: +# payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM]) +# email: str = payload.get("sub") +# if email is None: +# raise credentials_exception +# except jwt.PyJWTError: +# raise credentials_exception +# user = get_user_by_email(db, email) +# if user is None: +# raise credentials_exception +# return user + +# Routes +@app.get("/") +def example(): + return "Hello World DDD" + +@app.post("/register", response_model=UserResponse) +def register(user: UserCreate, db: Session = Depends(get_db)): + db_user = get_user_by_email(db, user.email) + if db_user: + raise HTTPException(status_code=400, detail="Email already registered") + new_user = create_user(db, user) + return {"id": new_user[0], "name": new_user[1], "email": new_user[2]} + +@app.post("/token") +def login(form_data: OAuth2PasswordRequestForm = Depends(), db: Session = Depends(get_db)): + user = authenticate_user(db, form_data.username, form_data.password) + if not user: + raise HTTPException(status_code=400, detail="Incorrect email or password") + access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES) + access_token = create_access_token(data={"sub": user[3]}, expires_delta=access_token_expires) + return {"access_token": access_token, "token_type": "bearer"} + +# @app.post("/upload", response_model=FileUploadResponse) +# def upload_file(file: UploadFile = File(...), current_user: User = Depends(get_current_user), db: Session = Depends(get_db)): +# user_directory = f"files/{current_user[3]}" +# os.makedirs(user_directory, exist_ok=True) +# file_path = os.path.join(user_directory, file.filename) +# with open(file_path, "wb") as buffer: +# shutil.copyfileobj(file.file, buffer) +# cursor = db.cursor() +# cursor.execute( +# "INSERT INTO files (filename, user_id) VALUES (%s, %s) RETURNING id, filename", +# (file.filename, current_user[0]) +# ) +# new_file = cursor.fetchone() +# db.commit() +# cursor.close() +# return {"filename": new_file[1]} + +# @app.get("/myfiles", response_model=List[FileUploadResponse]) +# def list_files(current_user: User = Depends(get_current_user), db: Session = Depends(get_db)): +# cursor = db.cursor() +# cursor.execute("SELECT filename FROM files WHERE user_id = %s", (current_user[0],)) +# files = cursor.fetchall() +# cursor.close() +# return [{"filename": file[0]} for file in files] + +if __name__ == "__main__": + import uvicorn + uvicorn.run(app, host="127.0.0.1", port=8000, log_level="info") diff --git a/server/services/transcript_service.py b/server/services/transcript_service.py new file mode 100644 index 0000000..462c446 --- /dev/null +++ b/server/services/transcript_service.py @@ -0,0 +1,35 @@ +from fastapi import FastAPI +from src.controllers import transcription + +# Create Transcription Service +class TranscriptionService: + async def transcribe_file(self, transcribe_client: any, + job_name: str, file_uri: str, + file_format: str, language_code = "id-ID"): + try: + transcribe_client.start_transcription_job( + TranscriptionJobName=job_name, + Media={ + "MediaFileUri": file_uri + }, + MediaFormat=file_format, + LanguageCode=language_code, + ) + + job_result = await self.poll_transcription_job( + transcribe_client=transcribe_client, + job_name=job_name + ) + + # Store to database + await self.store_transcription(item=job_result) + ... + + return job_result + + # return res + except TimeoutError: + return TimeoutError("Timeout when polling the transcription results") + except ClientError: + return ClientError("Transcription Job failed.", operation_name="start_transcription_job") + \ No newline at end of file diff --git a/server/services/user_service.py b/server/services/user_service.py new file mode 100644 index 0000000..78bbbec --- /dev/null +++ b/server/services/user_service.py @@ -0,0 +1,71 @@ +from sqlalchemy.orm import Session +from passlib.context import CryptContext +from datetime import datetime, timedelta +import jwt +from models import User as UserModel +from schemas.user import UserCreate, UserResponse +from fastapi import Depends, HTTPException, status +from fastapi.security import OAuth2PasswordBearer +import os +from dotenv import load_dotenv + +load_dotenv() + +SECRET_KEY = os.getenv('SECRET_KEY') +ALGORITHM = "HS256" +ACCESS_TOKEN_EXPIRE_MINUTES = 30 +pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") +oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/v1/users/login") + +class UserService: + def __init__(self, db: Session): + self.db = db + + def create_user(self, user: UserCreate): + hashed_password = pwd_context.hash(user.password) + db_user = UserModel( + username=user.username, + email=user.email, + passwordHash=hashed_password + ) + self.db.add(db_user) + self.db.commit() + self.db.refresh(db_user) + return db_user + + def authenticate_user(self, email: str, password: str): + user = self.db.query(UserModel).filter(UserModel.email == email).first() + if not user or not pwd_context.verify(password, user.passwordHash): + raise HTTPException(status_code=400, detail="Incorrect email or password") + access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES) + access_token = self.create_access_token(data={"sub": user.email}, expires_delta=access_token_expires) + return {"access_token": access_token, "token_type": "bearer"} + + def create_access_token(self, data: dict, expires_delta: timedelta = None): + to_encode = data.copy() + if expires_delta: + expire = datetime.utcnow() + expires_delta + else: + expire = datetime.utcnow() + timedelta(minutes=15) + to_encode.update({"exp": expire}) + encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM) + return encoded_jwt + + @staticmethod + def get_current_user(db: Session = Depends(get_db), token: str = Depends(oauth2_scheme)): + credentials_exception = HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="Could not validate credentials", + headers={"WWW-Authenticate": "Bearer"}, + ) + try: + payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM]) + email: str = payload.get("sub") + if email is None: + raise credentials_exception + except jwt.PyJWTError: + raise credentials_exception + user = db.query(UserModel).filter(UserModel.email == email).first() + if user is None: + raise credentials_exception + return user