Spaces:
Sleeping
Sleeping
Ahmed Mostafa commited on
Commit ·
6405808
1
Parent(s): d74863e
server v2
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- .env.example +0 -99
- .gitattributes +0 -35
- .gitignore +40 -0
- .python-version +1 -0
- Dockerfile +3 -2
- README.md +0 -16
- check_models.py +24 -0
- examples/sample_output.md +105 -0
- get_ffmpeg.py +48 -0
- outputs/.gitkeep +2 -0
- packages.txt +0 -1
- pyproject.toml +34 -0
- requirements.txt +1 -0
- run.py +1 -1
- src/__pycache__/__init__.cpython-312.pyc +0 -0
- src/__pycache__/__init__.cpython-314.pyc +0 -0
- src/ai_modules/README.md +0 -137
- src/ai_modules/__pycache__/__init__.cpython-312.pyc +0 -0
- src/ai_modules/__pycache__/__init__.cpython-314.pyc +0 -0
- src/ai_modules/categorization/__pycache__/__init__.cpython-312.pyc +0 -0
- src/ai_modules/categorization/__pycache__/categorizer.cpython-312.pyc +0 -0
- src/ai_modules/summarization/__pycache__/__init__.cpython-312.pyc +0 -0
- src/ai_modules/summarization/__pycache__/__init__.cpython-314.pyc +0 -0
- src/ai_modules/summarization/__pycache__/note_generator.cpython-312.pyc +0 -0
- src/ai_modules/summarization/__pycache__/note_generator.cpython-314.pyc +0 -0
- src/ai_modules/summarization/__pycache__/schemas.cpython-312.pyc +0 -0
- src/ai_modules/transcription/__init__.py +0 -0
- src/ai_modules/transcription/__pycache__/__init__.cpython-312.pyc +0 -0
- src/ai_modules/transcription/__pycache__/__init__.cpython-314.pyc +0 -0
- src/ai_modules/transcription/__pycache__/audio_downloader.cpython-312.pyc +0 -0
- src/ai_modules/transcription/__pycache__/audio_downloader.cpython-314.pyc +0 -0
- src/ai_modules/transcription/__pycache__/whisper_transcriber.cpython-312.pyc +0 -0
- src/ai_modules/transcription/__pycache__/whisper_transcriber.cpython-314.pyc +0 -0
- src/api/__pycache__/main.cpython-312.pyc +0 -0
- src/api/__pycache__/main.cpython-314.pyc +0 -0
- src/api/auth_routes.py +56 -43
- src/api/main.py +19 -18
- src/api/notes_routes.py +66 -57
- src/auth/__pycache__/dependencies.cpython-312.pyc +0 -0
- src/auth/dependencies.py +23 -29
- src/{ai_modules/categorization → categorization}/README.md +0 -0
- src/{ai_modules → categorization}/__init__.py +0 -0
- src/{ai_modules/categorization → categorization}/categorizer.py +0 -0
- src/db/__pycache__/models.cpython-312.pyc +0 -0
- src/db/firebase.py +53 -0
- src/db/models.py +29 -36
- src/{ai_modules/recommendation → recommendation}/README.md +0 -0
- src/{ai_modules/categorization → recommendation}/__init__.py +0 -0
- src/{ai_modules/recommendation → recommendation}/recommender.py +0 -0
- src/{ai_modules/summarization → summarization}/README.md +0 -0
.env.example
DELETED
|
@@ -1,99 +0,0 @@
|
|
| 1 |
-
# ========================================
|
| 2 |
-
# YouTube Study Notes AI - Environment Configuration
|
| 3 |
-
# ========================================
|
| 4 |
-
|
| 5 |
-
# ----------------------------------------
|
| 6 |
-
# Google Gemini API
|
| 7 |
-
# ----------------------------------------
|
| 8 |
-
# Get your API key from: https://makersuite.google.com/app/apikey
|
| 9 |
-
GOOGLE_API_KEY=AIzaSyD_wHludyQbdlNk9rTvitwV1lWkJPqoYbE
|
| 10 |
-
|
| 11 |
-
# ----------------------------------------
|
| 12 |
-
# Database Configuration (Supabase PostgreSQL)
|
| 13 |
-
# ----------------------------------------
|
| 14 |
-
# Format: postgresql+asyncpg://[user]:[password]@[host]:[port]/[database]
|
| 15 |
-
#
|
| 16 |
-
# For Supabase:
|
| 17 |
-
# 1. Go to your Supabase project dashboard
|
| 18 |
-
# 2. Navigate to Settings > Database
|
| 19 |
-
# 3. Under "Connection string", select "URI" mode
|
| 20 |
-
# 4. Copy the connection string and replace "postgresql://" with "postgresql+asyncpg://"
|
| 21 |
-
#
|
| 22 |
-
# Example:
|
| 23 |
-
# DATABASE_URL=postgresql+asyncpg://postgres.[your-project-ref]:[your-password]@aws-0-us-east-1.pooler.supabase.com:5432/postgres
|
| 24 |
-
#6SsbAvgkhJTEl2CS
|
| 25 |
-
# postgresql://postgres:[YOUR-PASSWORD]@db.jqjjanimclxplgvoiwyk.supabase.co:5432/postgres
|
| 26 |
-
# DATABASE_URL="postgresql+asyncpg://postgres.jqjjanimclxplgvoiwyk:AliProject2026@aws-1-eu-west-1.pooler.supabase.com:6543/postgres?ssl=require"
|
| 27 |
-
|
| 28 |
-
DATABASE_URL="postgresql+asyncpg://postgres.mupnxzgkcdmwhojmhxdh:6SsbAvgkhJTEl2CS@aws-1-eu-west-1.pooler.supabase.com:6543/postgres?ssl=require"
|
| 29 |
-
|
| 30 |
-
# ----------------------------------------
|
| 31 |
-
# Authentication & Security
|
| 32 |
-
# ----------------------------------------
|
| 33 |
-
# Generate a secure secret key with: python -c "import secrets; print(secrets.token_urlsafe(32))"
|
| 34 |
-
SECRET_KEY=5j55fk-ljD_Urkih0dvZ11WQ0iTYwwjPyQ7N7Un0eDY
|
| 35 |
-
ACCESS_TOKEN_EXPIRE_MINUTES=60
|
| 36 |
-
ALGORITHM=HS256
|
| 37 |
-
|
| 38 |
-
# ----------------------------------------
|
| 39 |
-
# API Server Configuration
|
| 40 |
-
# ----------------------------------------
|
| 41 |
-
|
| 42 |
-
# 192.168.1.101
|
| 43 |
-
API_HOST=0.0.0.0
|
| 44 |
-
API_PORT=8000
|
| 45 |
-
|
| 46 |
-
# ----------------------------------------
|
| 47 |
-
# Whisper Model Configuration
|
| 48 |
-
# ----------------------------------------
|
| 49 |
-
# Options: tiny, base, small, medium, large
|
| 50 |
-
# Larger models are more accurate but slower
|
| 51 |
-
WHISPER_MODEL_SIZE=base
|
| 52 |
-
|
| 53 |
-
# ----------------------------------------
|
| 54 |
-
# Processing Limits
|
| 55 |
-
# ----------------------------------------
|
| 56 |
-
# Maximum video duration in seconds (2 hours = 7200)
|
| 57 |
-
MAX_VIDEO_DURATION=7200
|
| 58 |
-
|
| 59 |
-
# ----------------------------------------
|
| 60 |
-
# Output Configuration
|
| 61 |
-
# ----------------------------------------
|
| 62 |
-
OUTPUT_FORMAT=markdown
|
| 63 |
-
OUTPUT_DIR=outputs
|
| 64 |
-
|
| 65 |
-
# ----------------------------------------
|
| 66 |
-
# Logging
|
| 67 |
-
# ----------------------------------------
|
| 68 |
-
LOG_LEVEL=INFO
|
| 69 |
-
LOG_FILE=app.log
|
| 70 |
-
|
| 71 |
-
# ----------------------------------------
|
| 72 |
-
# IMPORTANT NOTES FOR SUPABASE SETUP
|
| 73 |
-
# ----------------------------------------
|
| 74 |
-
#
|
| 75 |
-
# After creating your Supabase project:
|
| 76 |
-
#
|
| 77 |
-
# 1. DATABASE_URL Setup:
|
| 78 |
-
# - Go to Project Settings > Database
|
| 79 |
-
# - Find "Connection string" section
|
| 80 |
-
# - Select "URI" mode
|
| 81 |
-
# - Copy the connection string
|
| 82 |
-
# - Replace "postgresql://" with "postgresql+asyncpg://"
|
| 83 |
-
# - Replace [YOUR-PASSWORD] with your actual database password
|
| 84 |
-
#
|
| 85 |
-
# 2. Security:
|
| 86 |
-
# - NEVER commit this .env file to version control
|
| 87 |
-
# - Add .env to your .gitignore file
|
| 88 |
-
# - Generate a strong SECRET_KEY for production
|
| 89 |
-
#
|
| 90 |
-
# 3. Connection Pooling:
|
| 91 |
-
# - Supabase provides connection pooling by default
|
| 92 |
-
# - Use the "pooler" connection string for better performance
|
| 93 |
-
# - Example: aws-0-us-east-1.pooler.supabase.com
|
| 94 |
-
#
|
| 95 |
-
# 4. SSL Mode:
|
| 96 |
-
# - Supabase requires SSL connections (enabled by default with asyncpg)
|
| 97 |
-
# - If you encounter SSL errors, you can disable verification (NOT RECOMMENDED for production):
|
| 98 |
-
# DATABASE_URL=postgresql+asyncpg://...?ssl=require
|
| 99 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.gitattributes
DELETED
|
@@ -1,35 +0,0 @@
|
|
| 1 |
-
*.7z filter=lfs diff=lfs merge=lfs -text
|
| 2 |
-
*.arrow filter=lfs diff=lfs merge=lfs -text
|
| 3 |
-
*.bin filter=lfs diff=lfs merge=lfs -text
|
| 4 |
-
*.bz2 filter=lfs diff=lfs merge=lfs -text
|
| 5 |
-
*.ckpt filter=lfs diff=lfs merge=lfs -text
|
| 6 |
-
*.ftz filter=lfs diff=lfs merge=lfs -text
|
| 7 |
-
*.gz filter=lfs diff=lfs merge=lfs -text
|
| 8 |
-
*.h5 filter=lfs diff=lfs merge=lfs -text
|
| 9 |
-
*.joblib filter=lfs diff=lfs merge=lfs -text
|
| 10 |
-
*.lfs.* filter=lfs diff=lfs merge=lfs -text
|
| 11 |
-
*.mlmodel filter=lfs diff=lfs merge=lfs -text
|
| 12 |
-
*.model filter=lfs diff=lfs merge=lfs -text
|
| 13 |
-
*.msgpack filter=lfs diff=lfs merge=lfs -text
|
| 14 |
-
*.npy filter=lfs diff=lfs merge=lfs -text
|
| 15 |
-
*.npz filter=lfs diff=lfs merge=lfs -text
|
| 16 |
-
*.onnx filter=lfs diff=lfs merge=lfs -text
|
| 17 |
-
*.ot filter=lfs diff=lfs merge=lfs -text
|
| 18 |
-
*.parquet filter=lfs diff=lfs merge=lfs -text
|
| 19 |
-
*.pb filter=lfs diff=lfs merge=lfs -text
|
| 20 |
-
*.pickle filter=lfs diff=lfs merge=lfs -text
|
| 21 |
-
*.pkl filter=lfs diff=lfs merge=lfs -text
|
| 22 |
-
*.pt filter=lfs diff=lfs merge=lfs -text
|
| 23 |
-
*.pth filter=lfs diff=lfs merge=lfs -text
|
| 24 |
-
*.rar filter=lfs diff=lfs merge=lfs -text
|
| 25 |
-
*.safetensors filter=lfs diff=lfs merge=lfs -text
|
| 26 |
-
saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
| 27 |
-
*.tar.* filter=lfs diff=lfs merge=lfs -text
|
| 28 |
-
*.tar filter=lfs diff=lfs merge=lfs -text
|
| 29 |
-
*.tflite filter=lfs diff=lfs merge=lfs -text
|
| 30 |
-
*.tgz filter=lfs diff=lfs merge=lfs -text
|
| 31 |
-
*.wasm filter=lfs diff=lfs merge=lfs -text
|
| 32 |
-
*.xz filter=lfs diff=lfs merge=lfs -text
|
| 33 |
-
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
-
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
-
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.gitignore
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Ignore environment file with secrets
|
| 2 |
+
.env
|
| 3 |
+
firebase-service-account.json
|
| 4 |
+
serviceAccountKey.json
|
| 5 |
+
|
| 6 |
+
# Ignore Python cache
|
| 7 |
+
__pycache__/
|
| 8 |
+
*.py[cod]
|
| 9 |
+
*$py.class
|
| 10 |
+
*.so
|
| 11 |
+
.Python
|
| 12 |
+
|
| 13 |
+
# Ignore virtual environments
|
| 14 |
+
venv/
|
| 15 |
+
env/
|
| 16 |
+
ENV/
|
| 17 |
+
|
| 18 |
+
# Ignore temporary files
|
| 19 |
+
temp/*
|
| 20 |
+
!temp/.gitkeep
|
| 21 |
+
|
| 22 |
+
# Ignore output files
|
| 23 |
+
outputs/*
|
| 24 |
+
!outputs/.gitkeep
|
| 25 |
+
|
| 26 |
+
# Ignore logs
|
| 27 |
+
*.log
|
| 28 |
+
|
| 29 |
+
# Ignore OS files
|
| 30 |
+
.DS_Store
|
| 31 |
+
Thumbs.db
|
| 32 |
+
|
| 33 |
+
# Ignore IDE files
|
| 34 |
+
.vscode/
|
| 35 |
+
.idea/
|
| 36 |
+
*.swp
|
| 37 |
+
*.swo
|
| 38 |
+
|
| 39 |
+
# Ignore downloaded models (Whisper caches)
|
| 40 |
+
~/.cache/whisper/
|
.python-version
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
3.14
|
Dockerfile
CHANGED
|
@@ -15,6 +15,7 @@ RUN apt-get update && apt-get install -y \
|
|
| 15 |
WORKDIR /app
|
| 16 |
|
| 17 |
# Install Python dependencies
|
|
|
|
| 18 |
COPY requirements.txt .
|
| 19 |
RUN pip install --no-cache-dir -r requirements.txt
|
| 20 |
|
|
@@ -24,5 +25,5 @@ COPY . .
|
|
| 24 |
# Hugging Face Spaces expects the app to run on port 7860
|
| 25 |
EXPOSE 7860
|
| 26 |
|
| 27 |
-
# Run the application
|
| 28 |
-
CMD ["python", "app.py"]
|
|
|
|
| 15 |
WORKDIR /app
|
| 16 |
|
| 17 |
# Install Python dependencies
|
| 18 |
+
# We copy requirements first to leverage Docker cache
|
| 19 |
COPY requirements.txt .
|
| 20 |
RUN pip install --no-cache-dir -r requirements.txt
|
| 21 |
|
|
|
|
| 25 |
# Hugging Face Spaces expects the app to run on port 7860
|
| 26 |
EXPOSE 7860
|
| 27 |
|
| 28 |
+
# Run the application using the entry point we created
|
| 29 |
+
CMD ["python", "app.py","server"]
|
README.md
DELETED
|
@@ -1,16 +0,0 @@
|
|
| 1 |
-
---
|
| 2 |
-
title: AIdea Note Generator
|
| 3 |
-
emoji: 📝
|
| 4 |
-
colorFrom: blue
|
| 5 |
-
colorTo: purple
|
| 6 |
-
sdk: docker
|
| 7 |
-
app_file: app.py
|
| 8 |
-
pinned: false
|
| 9 |
-
---
|
| 10 |
-
|
| 11 |
-
# YouTube Study Notes AI
|
| 12 |
-
|
| 13 |
-

|
| 14 |
-

|
| 15 |
-
|
| 16 |
-
An intelligent AI system that automatically generates structured study notes from YouTube educational videos using speech recognition and large language models.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
check_models.py
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import google.generativeai as genai
|
| 2 |
+
import os
|
| 3 |
+
from dotenv import load_dotenv
|
| 4 |
+
|
| 5 |
+
load_dotenv()
|
| 6 |
+
api_key = os.getenv("GOOGLE_API_KEY")
|
| 7 |
+
|
| 8 |
+
if not api_key:
|
| 9 |
+
print("❌Can't find the key.env")
|
| 10 |
+
else:
|
| 11 |
+
genai.configure(api_key=api_key)
|
| 12 |
+
print("🔍 Searching for available models...")
|
| 13 |
+
try:
|
| 14 |
+
found = False
|
| 15 |
+
for m in genai.list_models():
|
| 16 |
+
if "generateContent" in m.supported_generation_methods:
|
| 17 |
+
print(f"✅ Available: {m.name}")
|
| 18 |
+
found = True
|
| 19 |
+
if not found:
|
| 20 |
+
print("⚠️ No models found that support text generation.")
|
| 21 |
+
except Exception as e:
|
| 22 |
+
print(f"❌ An error occurred: {e}")
|
| 23 |
+
|
| 24 |
+
input("\nPress Enter to exit...")
|
examples/sample_output.md
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Introduction to Neural Networks
|
| 2 |
+
|
| 3 |
+
**Source:** [https://youtube.com/watch?v=example](https://youtube.com/watch?v=example)
|
| 4 |
+
**Duration:** 18:45
|
| 5 |
+
**Generated:** AI Study Notes
|
| 6 |
+
|
| 7 |
+
---
|
| 8 |
+
|
| 9 |
+
# Introduction to Neural Networks
|
| 10 |
+
|
| 11 |
+
## What is a Neural Network?
|
| 12 |
+
|
| 13 |
+
- **Artificial Neural Network (ANN)**: A computing system inspired by biological neural networks
|
| 14 |
+
- Composed of interconnected nodes (neurons) organized in layers
|
| 15 |
+
- learns to perform tasks by considering examples without task-specific programming
|
| 16 |
+
- **Key components**:
|
| 17 |
+
- Input layer: Receives initial data
|
| 18 |
+
- Hidden layers: Process information
|
| 19 |
+
- Output layer: Produces final results
|
| 20 |
+
|
| 21 |
+
## Basic Architecture
|
| 22 |
+
|
| 23 |
+
- **Neurons**: Basic computational units that receive input and produce output
|
| 24 |
+
- **Weights**: Parameters that determine the strength of connections
|
| 25 |
+
- **Bias**: Additional parameter to adjust the output
|
| 26 |
+
- **Activation Function**: Introduces non-linearity to the network
|
| 27 |
+
- Common functions: ReLU, Sigmoid, Tanh
|
| 28 |
+
|
| 29 |
+
## How Neural Networks Learn
|
| 30 |
+
|
| 31 |
+
- **Training Process**: Iterative adjustment of weights and biases
|
| 32 |
+
- **Forward Propagation**:
|
| 33 |
+
- Input data flows through the network
|
| 34 |
+
- Each neuron applies weights and activation function
|
| 35 |
+
- Output is computed at the end
|
| 36 |
+
|
| 37 |
+
- **Backpropagation**:
|
| 38 |
+
- Compare output with expected result
|
| 39 |
+
- Calculate error/loss
|
| 40 |
+
- Propagate error backwards through network
|
| 41 |
+
- Update weights using gradient descent
|
| 42 |
+
|
| 43 |
+
## Training Components
|
| 44 |
+
|
| 45 |
+
- **Loss Function**: Measures how far predictions are from actual values
|
| 46 |
+
- Mean Squared Error (MSE) for regression
|
| 47 |
+
- Cross-Entropy for classification
|
| 48 |
+
|
| 49 |
+
- **Optimizer**: Algorithm to update weights
|
| 50 |
+
- **Gradient Descent**: Basic optimization method
|
| 51 |
+
- **Adam**: Adaptive learning rate optimizer (popular choice)
|
| 52 |
+
- **SGD**: Stochastic Gradient Descent
|
| 53 |
+
|
| 54 |
+
## Common Applications
|
| 55 |
+
|
| 56 |
+
- **Image Recognition**: Identifying objects in photos
|
| 57 |
+
- **Natural Language Processing**: Understanding and generating text
|
| 58 |
+
- **Speech Recognition**: Converting audio to text
|
| 59 |
+
- **Game Playing**: Learning to play games through reinforcement
|
| 60 |
+
- **Recommendation Systems**: Suggesting content based on preferences
|
| 61 |
+
|
| 62 |
+
## Deep Learning
|
| 63 |
+
|
| 64 |
+
- **Definition**: Neural networks with multiple hidden layers (deep networks)
|
| 65 |
+
- More layers enable learning of hierarchical features
|
| 66 |
+
- **Examples**:
|
| 67 |
+
- Convolutional Neural Networks (CNN): For image processing
|
| 68 |
+
- Recurrent Neural Networks (RNN): For sequential data
|
| 69 |
+
- Transformers: For language understanding
|
| 70 |
+
|
| 71 |
+
## Key Concepts
|
| 72 |
+
|
| 73 |
+
- **Overfitting**: Model learns training data too well, poor generalization
|
| 74 |
+
- Solution: Regularization, dropout, more data
|
| 75 |
+
|
| 76 |
+
- **Underfitting**: Model too simple to capture patterns
|
| 77 |
+
- Solution: More complex model, more features
|
| 78 |
+
|
| 79 |
+
- **Hyperparameters**: Settings configured before training
|
| 80 |
+
- Learning rate
|
| 81 |
+
- Number of layers
|
| 82 |
+
- Number of neurons per layer
|
| 83 |
+
- Batch size
|
| 84 |
+
|
| 85 |
+
## Training Best Practices
|
| 86 |
+
|
| 87 |
+
- Start with a simple architecture
|
| 88 |
+
- Use appropriate activation functions
|
| 89 |
+
- Normalize input data
|
| 90 |
+
- Split data into training, validation, and test sets
|
| 91 |
+
- Monitor training and validation loss
|
| 92 |
+
- Use regularization techniques to prevent overfitting
|
| 93 |
+
- Experiment with different optimizers and learning rates
|
| 94 |
+
|
| 95 |
+
## Challenges
|
| 96 |
+
|
| 97 |
+
- Requires large amounts of data
|
| 98 |
+
- Computationally expensive (GPU recommended)
|
| 99 |
+
- Can be difficult to interpret ("black box")
|
| 100 |
+
- Choosing right architecture requires experience
|
| 101 |
+
- Risk of overfitting with complex models
|
| 102 |
+
|
| 103 |
+
## Summary
|
| 104 |
+
|
| 105 |
+
Neural networks are powerful machine learning models that can learn complex patterns from data. They consist of layers of interconnected neurons that process information through weighted connections. Through the process of forward propagation and backpropagation, the network learns to minimize errors and make accurate predictions. While they require significant computational resources and data, they have revolutionized fields like computer vision, natural language processing, and many other domains of artificial intelligence.
|
get_ffmpeg.py
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import zipfile
|
| 3 |
+
import urllib.request
|
| 4 |
+
import sys
|
| 5 |
+
|
| 6 |
+
# Direct download URL
|
| 7 |
+
url = "https://www.gyan.dev/ffmpeg/builds/ffmpeg-release-essentials.zip"
|
| 8 |
+
zip_filename = "ffmpeg_temp.zip"
|
| 9 |
+
|
| 10 |
+
def progress_bar(block_num, block_size, total_size):
|
| 11 |
+
downloaded = block_num * block_size
|
| 12 |
+
if total_size > 0:
|
| 13 |
+
percent = downloaded * 100 / total_size
|
| 14 |
+
sys.stdout.write(f"\r⏳ Downloading: {percent:.1f}% ({downloaded / (1024*1024):.1f} MB)")
|
| 15 |
+
sys.stdout.flush()
|
| 16 |
+
|
| 17 |
+
print("🚀 Starting FFmpeg download (approx 130MB)...")
|
| 18 |
+
|
| 19 |
+
try:
|
| 20 |
+
# 1. Download with progress bar
|
| 21 |
+
urllib.request.urlretrieve(url, zip_filename, progress_bar)
|
| 22 |
+
print("\n\n📦 Download complete! Extracting files...")
|
| 23 |
+
|
| 24 |
+
# 2. Extract specific files
|
| 25 |
+
with zipfile.ZipFile(zip_filename, 'r') as z:
|
| 26 |
+
count = 0
|
| 27 |
+
for filename in z.namelist():
|
| 28 |
+
if filename.endswith("bin/ffmpeg.exe") or filename.endswith("bin/ffprobe.exe"):
|
| 29 |
+
target_name = os.path.basename(filename)
|
| 30 |
+
print(f" Extracting -> {target_name}")
|
| 31 |
+
with open(target_name, "wb") as f:
|
| 32 |
+
f.write(z.read(filename))
|
| 33 |
+
count += 1
|
| 34 |
+
|
| 35 |
+
# 3. Cleanup
|
| 36 |
+
if os.path.exists(zip_filename):
|
| 37 |
+
os.remove(zip_filename)
|
| 38 |
+
|
| 39 |
+
if count == 2:
|
| 40 |
+
print("\n✅ Success! FFmpeg installed successfully.")
|
| 41 |
+
print("You can now run: python run.py server")
|
| 42 |
+
else:
|
| 43 |
+
print("\n⚠️ Warning: Could not find ffmpeg files in the zip.")
|
| 44 |
+
|
| 45 |
+
except Exception as e:
|
| 46 |
+
print(f"\n❌ Error occurred: {e}")
|
| 47 |
+
|
| 48 |
+
input("\nPress Enter to exit...")
|
outputs/.gitkeep
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Outputs directory
|
| 2 |
+
# Generated study notes will be saved here
|
packages.txt
DELETED
|
@@ -1 +0,0 @@
|
|
| 1 |
-
ffmpeg
|
|
|
|
|
|
pyproject.toml
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[project]
|
| 2 |
+
name = "program"
|
| 3 |
+
version = "0.1.0"
|
| 4 |
+
description = "Add your description here"
|
| 5 |
+
readme = "README.md"
|
| 6 |
+
requires-python = ">=3.14"
|
| 7 |
+
dependencies = [
|
| 8 |
+
"aiofiles==23.2.1",
|
| 9 |
+
"asyncpg==0.31.0",
|
| 10 |
+
"bcrypt==4.1.2",
|
| 11 |
+
"email-validator>=2.3.0",
|
| 12 |
+
"fastapi==0.109.0",
|
| 13 |
+
"google-api-python-client==2.115.0",
|
| 14 |
+
"google-genai>=1.2.0",
|
| 15 |
+
"google-generativeai==0.3.2",
|
| 16 |
+
"greenlet==3.3.1",
|
| 17 |
+
"httpx==0.26.0",
|
| 18 |
+
"langchain==0.1.0",
|
| 19 |
+
"langchain-google-genai==0.0.5",
|
| 20 |
+
"openai-whisper==20250625",
|
| 21 |
+
"passlib[bcrypt]==1.7.4",
|
| 22 |
+
"pydantic-core==2.41.5",
|
| 23 |
+
"pydantic-settings==2.1.0",
|
| 24 |
+
"pydantic[email]==2.12.5",
|
| 25 |
+
"pydub==0.25.1",
|
| 26 |
+
"python-dotenv==1.0.0",
|
| 27 |
+
"python-jose[cryptography]==3.3.0",
|
| 28 |
+
"python-multipart==0.0.6",
|
| 29 |
+
"sqlmodel==0.0.14",
|
| 30 |
+
"torch>=2.10.0",
|
| 31 |
+
"torchaudio>=2.10.0",
|
| 32 |
+
"uvicorn[standard]==0.27.0",
|
| 33 |
+
"yt-dlp==2024.12.23",
|
| 34 |
+
]
|
requirements.txt
CHANGED
|
@@ -26,3 +26,4 @@ bcrypt==4.1.2
|
|
| 26 |
google-api-python-client==2.115.0
|
| 27 |
pydantic-core==2.41.5
|
| 28 |
ffmpeg-python
|
|
|
|
|
|
| 26 |
google-api-python-client==2.115.0
|
| 27 |
pydantic-core==2.41.5
|
| 28 |
ffmpeg-python
|
| 29 |
+
firebase-admin==6.5.0
|
run.py
CHANGED
|
@@ -3,11 +3,11 @@ Main entry point for YouTube Study Notes AI.
|
|
| 3 |
Provides CLI interface and server startup.
|
| 4 |
"""
|
| 5 |
|
|
|
|
| 6 |
import sys
|
| 7 |
import argparse
|
| 8 |
from pathlib import Path
|
| 9 |
|
| 10 |
-
# Import necessary modules for server and middleware
|
| 11 |
from src.utils.logger import setup_logger
|
| 12 |
from src.utils.config import settings
|
| 13 |
|
|
|
|
| 3 |
Provides CLI interface and server startup.
|
| 4 |
"""
|
| 5 |
|
| 6 |
+
import os
|
| 7 |
import sys
|
| 8 |
import argparse
|
| 9 |
from pathlib import Path
|
| 10 |
|
|
|
|
| 11 |
from src.utils.logger import setup_logger
|
| 12 |
from src.utils.config import settings
|
| 13 |
|
src/__pycache__/__init__.cpython-312.pyc
CHANGED
|
Binary files a/src/__pycache__/__init__.cpython-312.pyc and b/src/__pycache__/__init__.cpython-312.pyc differ
|
|
|
src/__pycache__/__init__.cpython-314.pyc
CHANGED
|
Binary files a/src/__pycache__/__init__.cpython-314.pyc and b/src/__pycache__/__init__.cpython-314.pyc differ
|
|
|
src/ai_modules/README.md
DELETED
|
@@ -1,137 +0,0 @@
|
|
| 1 |
-
# AI Modules Overview 🤖
|
| 2 |
-
|
| 3 |
-
## Overview of AI Modules
|
| 4 |
-
|
| 5 |
-
This directory contains all AI-related modules in the project, divided into 4 main components:
|
| 6 |
-
|
| 7 |
-
## The Four Modules
|
| 8 |
-
|
| 9 |
-
### 1. 🎤 Transcription
|
| 10 |
-
**Responsibility:** Convert YouTube videos to written text.
|
| 11 |
-
|
| 12 |
-
**Files:**
|
| 13 |
-
- `audio_downloader.py` - Download audio from YouTube
|
| 14 |
-
- `whisper_transcriber.py` - Convert audio to text using Whisper
|
| 15 |
-
- `audio_processor.py` - Process and validate audio file quality
|
| 16 |
-
|
| 17 |
-
**Technologies:** OpenAI Whisper, yt-dlp, PyTorch
|
| 18 |
-
|
| 19 |
-
---
|
| 20 |
-
|
| 21 |
-
### 2. 📝 Summarization
|
| 22 |
-
**Responsibility:** Convert text to organized study notes.
|
| 23 |
-
|
| 24 |
-
**Files:**
|
| 25 |
-
- `note_generator.py` - Generate notes using Gemini
|
| 26 |
-
- `schemas.py` - Define data structure
|
| 27 |
-
- `segmenter.py` - Split long texts
|
| 28 |
-
|
| 29 |
-
**Technologies:** Google Gemini, Pydantic
|
| 30 |
-
|
| 31 |
-
---
|
| 32 |
-
|
| 33 |
-
### 3. 🎯 Recommendation
|
| 34 |
-
**Responsibility:** Suggest new educational videos to users.
|
| 35 |
-
|
| 36 |
-
**Files:**
|
| 37 |
-
- `recommender.py` - Search YouTube and analyze interests
|
| 38 |
-
|
| 39 |
-
**Technologies:** YouTube Data API v3
|
| 40 |
-
|
| 41 |
-
---
|
| 42 |
-
|
| 43 |
-
### 4. 🏷️ Categorization
|
| 44 |
-
**Responsibility:** Automatically categorize notes.
|
| 45 |
-
|
| 46 |
-
**Files:**
|
| 47 |
-
- `categorizer.py` - Categorize text using AI
|
| 48 |
-
|
| 49 |
-
**Technologies:** Google Gemini
|
| 50 |
-
|
| 51 |
-
---
|
| 52 |
-
|
| 53 |
-
## Complete Workflow (Full Pipeline)
|
| 54 |
-
|
| 55 |
-
```mermaid
|
| 56 |
-
graph LR
|
| 57 |
-
A[YouTube URL] --> B[Transcription]
|
| 58 |
-
B --> C[Summarization]
|
| 59 |
-
C --> D[Categorization]
|
| 60 |
-
D --> E[Database]
|
| 61 |
-
E --> F[Recommendation]
|
| 62 |
-
F --> G[New Videos]
|
| 63 |
-
```
|
| 64 |
-
|
| 65 |
-
1. **User enters YouTube URL** → Transcription module
|
| 66 |
-
2. **Audio is downloaded and converted to text** → Summarization module
|
| 67 |
-
3. **Text is summarized and organized** → Categorization module
|
| 68 |
-
4. **Summary is categorized** → Database
|
| 69 |
-
5. **Based on saved notes** → Recommendation module
|
| 70 |
-
6. **New videos are suggested** → User
|
| 71 |
-
|
| 72 |
-
---
|
| 73 |
-
|
| 74 |
-
## For Team Members: How to Start?
|
| 75 |
-
|
| 76 |
-
### If you're responsible for Transcription:
|
| 77 |
-
1. Open `transcription/` directory
|
| 78 |
-
2. Read the `README.md` file inside
|
| 79 |
-
3. Test the code using the provided examples
|
| 80 |
-
4. Develop the proposed features
|
| 81 |
-
|
| 82 |
-
### If you're responsible for Summarization:
|
| 83 |
-
1. Open `summarization/` directory
|
| 84 |
-
2. Read the `README.md` file
|
| 85 |
-
3. Try modifying the prompts to improve summary quality
|
| 86 |
-
4. Add new features (like translation)
|
| 87 |
-
|
| 88 |
-
### If you're responsible for Recommendation:
|
| 89 |
-
1. Open `recommendation/` directory
|
| 90 |
-
2. Read the `README.md` file
|
| 91 |
-
3. Develop caching mechanism to save API quota
|
| 92 |
-
4. Improve recommendation accuracy
|
| 93 |
-
|
| 94 |
-
### If you're responsible for Categorization:
|
| 95 |
-
1. Open `categorization/` directory
|
| 96 |
-
2. Read the `README.md` file
|
| 97 |
-
3. Add a predefined list of categories
|
| 98 |
-
4. Improve the prompt to increase accuracy
|
| 99 |
-
|
| 100 |
-
---
|
| 101 |
-
|
| 102 |
-
## General Notes
|
| 103 |
-
|
| 104 |
-
### Shared Libraries
|
| 105 |
-
All modules use:
|
| 106 |
-
- `src.utils.logger` - For logging
|
| 107 |
-
- `src.utils.config` - For reading settings from `.env`
|
| 108 |
-
|
| 109 |
-
### Testing
|
| 110 |
-
To test any module, use:
|
| 111 |
-
```bash
|
| 112 |
-
cd D:\faculty\Class4\grad\program
|
| 113 |
-
python -m pytest tests/
|
| 114 |
-
```
|
| 115 |
-
|
| 116 |
-
### Required Environment
|
| 117 |
-
Make sure to install the libraries:
|
| 118 |
-
```bash
|
| 119 |
-
pip install -r requirements.txt
|
| 120 |
-
```
|
| 121 |
-
|
| 122 |
-
### Required Environment Variables
|
| 123 |
-
In `.env` file:
|
| 124 |
-
```
|
| 125 |
-
GOOGLE_API_KEY=your_google_api_key_here
|
| 126 |
-
WHISPER_MODEL_SIZE=base
|
| 127 |
-
DATABASE_URL=your_database_url
|
| 128 |
-
```
|
| 129 |
-
|
| 130 |
-
---
|
| 131 |
-
|
| 132 |
-
## Team Communication
|
| 133 |
-
- If you encounter a problem in a specific module, **open an Issue** on GitHub.
|
| 134 |
-
- If you add a new feature, **update the README** file for that module.
|
| 135 |
-
- Before committing, make sure the code runs without errors.
|
| 136 |
-
|
| 137 |
-
**Good luck to the team! 🚀**
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
src/ai_modules/__pycache__/__init__.cpython-312.pyc
DELETED
|
Binary file (173 Bytes)
|
|
|
src/ai_modules/__pycache__/__init__.cpython-314.pyc
DELETED
|
Binary file (175 Bytes)
|
|
|
src/ai_modules/categorization/__pycache__/__init__.cpython-312.pyc
DELETED
|
Binary file (188 Bytes)
|
|
|
src/ai_modules/categorization/__pycache__/categorizer.cpython-312.pyc
DELETED
|
Binary file (2.53 kB)
|
|
|
src/ai_modules/summarization/__pycache__/__init__.cpython-312.pyc
DELETED
|
Binary file (187 Bytes)
|
|
|
src/ai_modules/summarization/__pycache__/__init__.cpython-314.pyc
DELETED
|
Binary file (189 Bytes)
|
|
|
src/ai_modules/summarization/__pycache__/note_generator.cpython-312.pyc
DELETED
|
Binary file (5.68 kB)
|
|
|
src/ai_modules/summarization/__pycache__/note_generator.cpython-314.pyc
DELETED
|
Binary file (6.85 kB)
|
|
|
src/ai_modules/summarization/__pycache__/schemas.cpython-312.pyc
DELETED
|
Binary file (2.83 kB)
|
|
|
src/ai_modules/transcription/__init__.py
DELETED
|
File without changes
|
src/ai_modules/transcription/__pycache__/__init__.cpython-312.pyc
DELETED
|
Binary file (187 Bytes)
|
|
|
src/ai_modules/transcription/__pycache__/__init__.cpython-314.pyc
DELETED
|
Binary file (189 Bytes)
|
|
|
src/ai_modules/transcription/__pycache__/audio_downloader.cpython-312.pyc
DELETED
|
Binary file (7.23 kB)
|
|
|
src/ai_modules/transcription/__pycache__/audio_downloader.cpython-314.pyc
DELETED
|
Binary file (8.09 kB)
|
|
|
src/ai_modules/transcription/__pycache__/whisper_transcriber.cpython-312.pyc
DELETED
|
Binary file (7.98 kB)
|
|
|
src/ai_modules/transcription/__pycache__/whisper_transcriber.cpython-314.pyc
DELETED
|
Binary file (8.93 kB)
|
|
|
src/api/__pycache__/main.cpython-312.pyc
CHANGED
|
Binary files a/src/api/__pycache__/main.cpython-312.pyc and b/src/api/__pycache__/main.cpython-312.pyc differ
|
|
|
src/api/__pycache__/main.cpython-314.pyc
CHANGED
|
Binary files a/src/api/__pycache__/main.cpython-314.pyc and b/src/api/__pycache__/main.cpython-314.pyc differ
|
|
|
src/api/auth_routes.py
CHANGED
|
@@ -10,7 +10,7 @@ from pydantic import BaseModel, EmailStr, Field
|
|
| 10 |
from sqlmodel import select
|
| 11 |
from sqlmodel.ext.asyncio.session import AsyncSession
|
| 12 |
|
| 13 |
-
from src.db.
|
| 14 |
from src.db.models import User
|
| 15 |
from src.auth.security import hash_password, verify_password, create_access_token
|
| 16 |
from src.utils.logger import setup_logger
|
|
@@ -44,7 +44,7 @@ class SignupRequest(BaseModel):
|
|
| 44 |
class UserResponse(BaseModel):
|
| 45 |
"""Response model for user data (without password)."""
|
| 46 |
|
| 47 |
-
id:
|
| 48 |
email: str
|
| 49 |
username: str
|
| 50 |
role: str
|
|
@@ -55,7 +55,7 @@ class UserResponse(BaseModel):
|
|
| 55 |
class Config:
|
| 56 |
json_schema_extra = {
|
| 57 |
"example": {
|
| 58 |
-
"id":
|
| 59 |
"email": "student@example.com",
|
| 60 |
"username": "Student123",
|
| 61 |
"role": "user",
|
|
@@ -85,19 +85,22 @@ class TokenResponse(BaseModel):
|
|
| 85 |
"/signup", response_model=UserResponse, status_code=status.HTTP_201_CREATED
|
| 86 |
)
|
| 87 |
async def signup(
|
| 88 |
-
signup_data: SignupRequest
|
| 89 |
):
|
| 90 |
"""
|
| 91 |
-
Register a new user.
|
| 92 |
"""
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
)
|
| 97 |
-
result = await session.exec(statement)
|
| 98 |
-
existing_user = result.first()
|
| 99 |
|
| 100 |
-
if
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 101 |
raise HTTPException(
|
| 102 |
status_code=status.HTTP_409_CONFLICT,
|
| 103 |
detail="Email or Username already registered",
|
|
@@ -106,53 +109,63 @@ async def signup(
|
|
| 106 |
# Create new user with hashed password
|
| 107 |
hashed_password_value = hash_password(signup_data.password)
|
| 108 |
|
| 109 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 110 |
email=signup_data.email,
|
| 111 |
username=signup_data.username,
|
| 112 |
-
password_hash=hashed_password_value,
|
| 113 |
role="user",
|
| 114 |
age=signup_data.age,
|
| 115 |
gender=signup_data.gender,
|
| 116 |
-
|
| 117 |
-
|
| 118 |
-
session.add(new_user)
|
| 119 |
-
await session.commit()
|
| 120 |
-
await session.refresh(new_user)
|
| 121 |
-
|
| 122 |
-
logger.info(f"New user registered: {new_user.email}")
|
| 123 |
-
|
| 124 |
-
return UserResponse(
|
| 125 |
-
id=new_user.id,
|
| 126 |
-
email=new_user.email,
|
| 127 |
-
username=new_user.username,
|
| 128 |
-
role=new_user.role,
|
| 129 |
-
age=new_user.age,
|
| 130 |
-
gender=new_user.gender,
|
| 131 |
-
created_at=str(new_user.created_at),
|
| 132 |
)
|
| 133 |
|
| 134 |
|
| 135 |
@router.post("/login", response_model=TokenResponse)
|
| 136 |
async def login(
|
| 137 |
-
form_data: OAuth2PasswordRequestForm = Depends()
|
| 138 |
-
session: AsyncSession = Depends(get_session),
|
| 139 |
):
|
| 140 |
"""
|
| 141 |
-
Authenticate user and return JWT access token.
|
| 142 |
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 143 |
# Find user by username
|
| 144 |
-
|
| 145 |
-
|
| 146 |
-
user = result.first()
|
| 147 |
|
| 148 |
# If not found by username, try finding by email
|
| 149 |
-
if not
|
| 150 |
-
|
| 151 |
-
|
| 152 |
-
user = result.first()
|
| 153 |
|
| 154 |
# Verify user exists and password is correct
|
| 155 |
-
if not
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 156 |
raise HTTPException(
|
| 157 |
status_code=status.HTTP_401_UNAUTHORIZED,
|
| 158 |
detail="Incorrect username or password",
|
|
@@ -162,10 +175,10 @@ async def login(
|
|
| 162 |
# Create access token
|
| 163 |
access_token_expires = timedelta(minutes=settings.access_token_expire_minutes)
|
| 164 |
access_token = create_access_token(
|
| 165 |
-
data={"sub":
|
| 166 |
)
|
| 167 |
|
| 168 |
-
logger.info(f"User logged in: {
|
| 169 |
|
| 170 |
return TokenResponse(
|
| 171 |
access_token=access_token,
|
|
|
|
| 10 |
from sqlmodel import select
|
| 11 |
from sqlmodel.ext.asyncio.session import AsyncSession
|
| 12 |
|
| 13 |
+
from src.db.firebase import get_firebase_db
|
| 14 |
from src.db.models import User
|
| 15 |
from src.auth.security import hash_password, verify_password, create_access_token
|
| 16 |
from src.utils.logger import setup_logger
|
|
|
|
| 44 |
class UserResponse(BaseModel):
|
| 45 |
"""Response model for user data (without password)."""
|
| 46 |
|
| 47 |
+
id: str
|
| 48 |
email: str
|
| 49 |
username: str
|
| 50 |
role: str
|
|
|
|
| 55 |
class Config:
|
| 56 |
json_schema_extra = {
|
| 57 |
"example": {
|
| 58 |
+
"id": "abc-123",
|
| 59 |
"email": "student@example.com",
|
| 60 |
"username": "Student123",
|
| 61 |
"role": "user",
|
|
|
|
| 85 |
"/signup", response_model=UserResponse, status_code=status.HTTP_201_CREATED
|
| 86 |
)
|
| 87 |
async def signup(
|
| 88 |
+
signup_data: SignupRequest
|
| 89 |
):
|
| 90 |
"""
|
| 91 |
+
Register a new user using Firestore.
|
| 92 |
"""
|
| 93 |
+
db = get_firebase_db()
|
| 94 |
+
if db is None:
|
| 95 |
+
raise HTTPException(status_code=500, detail="Firebase not configured")
|
|
|
|
|
|
|
|
|
|
| 96 |
|
| 97 |
+
# Check if email or username already exists
|
| 98 |
+
users_ref = db.collection("users")
|
| 99 |
+
|
| 100 |
+
email_check = users_ref.where("email", "==", signup_data.email).limit(1).stream()
|
| 101 |
+
username_check = users_ref.where("username", "==", signup_data.username).limit(1).stream()
|
| 102 |
+
|
| 103 |
+
if next(email_check, None) or next(username_check, None):
|
| 104 |
raise HTTPException(
|
| 105 |
status_code=status.HTTP_409_CONFLICT,
|
| 106 |
detail="Email or Username already registered",
|
|
|
|
| 109 |
# Create new user with hashed password
|
| 110 |
hashed_password_value = hash_password(signup_data.password)
|
| 111 |
|
| 112 |
+
user_dict = {
|
| 113 |
+
"email": signup_data.email,
|
| 114 |
+
"username": signup_data.username,
|
| 115 |
+
"password_hash": hashed_password_value,
|
| 116 |
+
"role": "user",
|
| 117 |
+
"age": signup_data.age,
|
| 118 |
+
"gender": signup_data.gender,
|
| 119 |
+
"created_at": datetime.utcnow()
|
| 120 |
+
}
|
| 121 |
+
|
| 122 |
+
_, new_user_ref = users_ref.add(user_dict)
|
| 123 |
+
|
| 124 |
+
logger.info(f"New user registered in Firestore: {signup_data.email}")
|
| 125 |
+
|
| 126 |
+
return UserResponse(
|
| 127 |
+
id=new_user_ref.id,
|
| 128 |
email=signup_data.email,
|
| 129 |
username=signup_data.username,
|
|
|
|
| 130 |
role="user",
|
| 131 |
age=signup_data.age,
|
| 132 |
gender=signup_data.gender,
|
| 133 |
+
created_at=str(user_dict["created_at"]),
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 134 |
)
|
| 135 |
|
| 136 |
|
| 137 |
@router.post("/login", response_model=TokenResponse)
|
| 138 |
async def login(
|
| 139 |
+
form_data: OAuth2PasswordRequestForm = Depends()
|
|
|
|
| 140 |
):
|
| 141 |
"""
|
| 142 |
+
Authenticate user and return JWT access token using Firestore.
|
| 143 |
"""
|
| 144 |
+
db = get_firebase_db()
|
| 145 |
+
if db is None:
|
| 146 |
+
raise HTTPException(status_code=500, detail="Firebase not configured")
|
| 147 |
+
|
| 148 |
+
users_ref = db.collection("users")
|
| 149 |
+
|
| 150 |
# Find user by username
|
| 151 |
+
query = users_ref.where("username", "==", form_data.username).limit(1).stream()
|
| 152 |
+
user_doc = next(query, None)
|
|
|
|
| 153 |
|
| 154 |
# If not found by username, try finding by email
|
| 155 |
+
if not user_doc:
|
| 156 |
+
query = users_ref.where("email", "==", form_data.username).limit(1).stream()
|
| 157 |
+
user_doc = next(query, None)
|
|
|
|
| 158 |
|
| 159 |
# Verify user exists and password is correct
|
| 160 |
+
if not user_doc:
|
| 161 |
+
raise HTTPException(
|
| 162 |
+
status_code=status.HTTP_401_UNAUTHORIZED,
|
| 163 |
+
detail="Incorrect username or password",
|
| 164 |
+
headers={"WWW-Authenticate": "Bearer"},
|
| 165 |
+
)
|
| 166 |
+
|
| 167 |
+
user_data = user_doc.to_dict()
|
| 168 |
+
if not verify_password(form_data.password, user_data["password_hash"]):
|
| 169 |
raise HTTPException(
|
| 170 |
status_code=status.HTTP_401_UNAUTHORIZED,
|
| 171 |
detail="Incorrect username or password",
|
|
|
|
| 175 |
# Create access token
|
| 176 |
access_token_expires = timedelta(minutes=settings.access_token_expire_minutes)
|
| 177 |
access_token = create_access_token(
|
| 178 |
+
data={"sub": user_data["username"]}, expires_delta=access_token_expires
|
| 179 |
)
|
| 180 |
|
| 181 |
+
logger.info(f"User logged in from Firestore: {user_data['username']}")
|
| 182 |
|
| 183 |
return TokenResponse(
|
| 184 |
access_token=access_token,
|
src/api/main.py
CHANGED
|
@@ -8,16 +8,15 @@ from fastapi import FastAPI, HTTPException, BackgroundTasks, Depends
|
|
| 8 |
from fastapi.middleware.cors import CORSMiddleware
|
| 9 |
from pydantic import BaseModel, HttpUrl
|
| 10 |
|
| 11 |
-
from src.
|
| 12 |
-
from src.
|
| 13 |
-
from src.
|
| 14 |
from src.utils.logger import setup_logger
|
| 15 |
-
from src.db.
|
| 16 |
from src.db.models import Note, User
|
| 17 |
from src.auth.dependencies import get_current_user
|
| 18 |
from src.api.auth_routes import router as auth_router
|
| 19 |
from src.api.notes_routes import router as notes_router
|
| 20 |
-
from sqlmodel.ext.asyncio.session import AsyncSession
|
| 21 |
|
| 22 |
logger = setup_logger(__name__)
|
| 23 |
|
|
@@ -49,8 +48,8 @@ tasks: Dict[str, Dict] = {}
|
|
| 49 |
|
| 50 |
@asynccontextmanager
|
| 51 |
async def lifespan(app: FastAPI):
|
| 52 |
-
logger.info("Lifespan: Initializing
|
| 53 |
-
|
| 54 |
yield
|
| 55 |
|
| 56 |
|
|
@@ -102,7 +101,7 @@ async def generate(
|
|
| 102 |
|
| 103 |
|
| 104 |
async def process_video_and_save(
|
| 105 |
-
task_id: str, youtube_url: str, language: str, user_id:
|
| 106 |
):
|
| 107 |
audio_file = None
|
| 108 |
try:
|
|
@@ -127,16 +126,18 @@ async def process_video_and_save(
|
|
| 127 |
video_info["duration"],
|
| 128 |
)
|
| 129 |
|
| 130 |
-
|
| 131 |
-
|
| 132 |
-
|
| 133 |
-
|
| 134 |
-
|
| 135 |
-
|
| 136 |
-
|
| 137 |
-
|
| 138 |
-
|
| 139 |
-
|
|
|
|
|
|
|
| 140 |
|
| 141 |
tasks[task_id]["notes"] = final_notes
|
| 142 |
tasks[task_id]["keyPoints"] = json_notes.get("key_points", []) if isinstance(json_notes, dict) else []
|
|
|
|
| 8 |
from fastapi.middleware.cors import CORSMiddleware
|
| 9 |
from pydantic import BaseModel, HttpUrl
|
| 10 |
|
| 11 |
+
from src.transcription.audio_downloader import YouTubeDownloader
|
| 12 |
+
from src.transcription.whisper_transcriber import WhisperTranscriber
|
| 13 |
+
from src.summarization.note_generator import NoteGenerator
|
| 14 |
from src.utils.logger import setup_logger
|
| 15 |
+
from src.db.firebase import get_firebase_db
|
| 16 |
from src.db.models import Note, User
|
| 17 |
from src.auth.dependencies import get_current_user
|
| 18 |
from src.api.auth_routes import router as auth_router
|
| 19 |
from src.api.notes_routes import router as notes_router
|
|
|
|
| 20 |
|
| 21 |
logger = setup_logger(__name__)
|
| 22 |
|
|
|
|
| 48 |
|
| 49 |
@asynccontextmanager
|
| 50 |
async def lifespan(app: FastAPI):
|
| 51 |
+
logger.info("Lifespan: Initializing Firebase...")
|
| 52 |
+
get_firebase_db()
|
| 53 |
yield
|
| 54 |
|
| 55 |
|
|
|
|
| 101 |
|
| 102 |
|
| 103 |
async def process_video_and_save(
|
| 104 |
+
task_id: str, youtube_url: str, language: str, user_id: str
|
| 105 |
):
|
| 106 |
audio_file = None
|
| 107 |
try:
|
|
|
|
| 126 |
video_info["duration"],
|
| 127 |
)
|
| 128 |
|
| 129 |
+
db = get_firebase_db()
|
| 130 |
+
if db:
|
| 131 |
+
note_data = {
|
| 132 |
+
"user_id": user_id,
|
| 133 |
+
"video_url": youtube_url,
|
| 134 |
+
"video_title": video_info["title"],
|
| 135 |
+
"summary_content": final_notes,
|
| 136 |
+
"created_at": datetime.utcnow()
|
| 137 |
+
}
|
| 138 |
+
db.collection("notes").add(note_data)
|
| 139 |
+
else:
|
| 140 |
+
logger.warning("Firestore not initialized, note not saved to DB but generated in memory.")
|
| 141 |
|
| 142 |
tasks[task_id]["notes"] = final_notes
|
| 143 |
tasks[task_id]["keyPoints"] = json_notes.get("key_points", []) if isinstance(json_notes, dict) else []
|
src/api/notes_routes.py
CHANGED
|
@@ -12,7 +12,7 @@ from fastapi.responses import FileResponse, JSONResponse
|
|
| 12 |
from pydantic import BaseModel, HttpUrl, Field
|
| 13 |
from sqlmodel import Session, select
|
| 14 |
|
| 15 |
-
from src.db.
|
| 16 |
from src.db.models import User, Note
|
| 17 |
from src.auth.dependencies import get_current_user
|
| 18 |
from src.ai_modules.categorization.categorizer import CategorizationService
|
|
@@ -45,13 +45,13 @@ class CreateNoteRequest(BaseModel):
|
|
| 45 |
|
| 46 |
|
| 47 |
class NoteResponse(BaseModel):
|
| 48 |
-
id:
|
| 49 |
video_url: str
|
| 50 |
video_title: str
|
| 51 |
summary_text: str
|
| 52 |
video_duration: Optional[int]
|
| 53 |
language: str
|
| 54 |
-
user_id:
|
| 55 |
category: Optional[str]
|
| 56 |
created_at: str
|
| 57 |
|
|
@@ -125,69 +125,74 @@ async def get_generated_note_content(filename: str):
|
|
| 125 |
# End of New Endpoints
|
| 126 |
# ==========================================
|
| 127 |
|
| 128 |
-
# ... (Database endpoints kept for compatibility if needed later) ...
|
| 129 |
-
# You can leave the rest of the file as is, or I can include it below just in case.
|
| 130 |
-
# For brevity, I'll include the standard DB create/get just to not break anything.
|
| 131 |
-
|
| 132 |
|
| 133 |
@router.get("/{note_id}", response_model=NoteResponse)
|
| 134 |
async def get_note(
|
| 135 |
-
note_id:
|
| 136 |
-
session: AsyncSession = Depends(get_session),
|
| 137 |
current_user: User = Depends(get_current_user),
|
| 138 |
):
|
| 139 |
"""
|
| 140 |
-
Get a specific note by ID.
|
| 141 |
"""
|
| 142 |
-
|
| 143 |
-
|
| 144 |
-
|
|
|
|
|
|
|
|
|
|
| 145 |
|
| 146 |
-
if not
|
| 147 |
raise HTTPException(status_code=404, detail="Note not found")
|
| 148 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 149 |
return NoteResponse(
|
| 150 |
-
id=
|
| 151 |
-
video_url=
|
| 152 |
-
video_title=
|
| 153 |
-
summary_text=
|
| 154 |
video_duration=None,
|
| 155 |
language="en",
|
| 156 |
-
user_id=
|
| 157 |
-
category=
|
| 158 |
-
created_at=str(
|
| 159 |
)
|
| 160 |
|
| 161 |
|
| 162 |
@router.get("", response_model=List[NoteResponse])
|
| 163 |
async def list_user_notes(
|
| 164 |
-
session: AsyncSession = Depends(get_session),
|
| 165 |
current_user: User = Depends(get_current_user),
|
| 166 |
):
|
| 167 |
"""
|
| 168 |
-
List all notes belonging to the current user.
|
| 169 |
"""
|
| 170 |
-
|
| 171 |
-
|
| 172 |
-
|
| 173 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 174 |
)
|
| 175 |
-
result = await session.exec(statement)
|
| 176 |
-
notes = result.all()
|
| 177 |
|
| 178 |
return [
|
| 179 |
NoteResponse(
|
| 180 |
-
id=
|
| 181 |
-
video_url=
|
| 182 |
-
video_title=
|
| 183 |
-
summary_text=
|
| 184 |
-
video_duration=None,
|
| 185 |
-
language="en",
|
| 186 |
-
user_id=
|
| 187 |
-
category=
|
| 188 |
-
created_at=str(
|
| 189 |
)
|
| 190 |
-
for
|
|
|
|
| 191 |
]
|
| 192 |
|
| 193 |
|
|
@@ -195,29 +200,33 @@ async def list_user_notes(
|
|
| 195 |
async def create_note(
|
| 196 |
note_data: CreateNoteRequest,
|
| 197 |
current_user: User = Depends(get_current_user),
|
| 198 |
-
session: AsyncSession = Depends(get_session),
|
| 199 |
):
|
| 200 |
# Automatically categorize the note
|
| 201 |
category = await categorizer.categorize_text(note_data.summary_text)
|
| 202 |
|
| 203 |
-
|
| 204 |
-
|
| 205 |
-
|
| 206 |
-
|
| 207 |
-
|
| 208 |
-
|
| 209 |
-
|
| 210 |
-
|
| 211 |
-
|
| 212 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 213 |
return NoteResponse(
|
| 214 |
-
id=
|
| 215 |
-
video_url=
|
| 216 |
-
video_title=
|
| 217 |
-
summary_text=
|
| 218 |
video_duration=None,
|
| 219 |
language="en",
|
| 220 |
-
user_id=
|
| 221 |
-
category=
|
| 222 |
-
created_at=str(
|
| 223 |
)
|
|
|
|
| 12 |
from pydantic import BaseModel, HttpUrl, Field
|
| 13 |
from sqlmodel import Session, select
|
| 14 |
|
| 15 |
+
from src.db.firebase import get_firebase_db
|
| 16 |
from src.db.models import User, Note
|
| 17 |
from src.auth.dependencies import get_current_user
|
| 18 |
from src.ai_modules.categorization.categorizer import CategorizationService
|
|
|
|
| 45 |
|
| 46 |
|
| 47 |
class NoteResponse(BaseModel):
|
| 48 |
+
id: str # Changed to str for Firestore IDs
|
| 49 |
video_url: str
|
| 50 |
video_title: str
|
| 51 |
summary_text: str
|
| 52 |
video_duration: Optional[int]
|
| 53 |
language: str
|
| 54 |
+
user_id: str # Changed to str
|
| 55 |
category: Optional[str]
|
| 56 |
created_at: str
|
| 57 |
|
|
|
|
| 125 |
# End of New Endpoints
|
| 126 |
# ==========================================
|
| 127 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 128 |
|
| 129 |
@router.get("/{note_id}", response_model=NoteResponse)
|
| 130 |
async def get_note(
|
| 131 |
+
note_id: str,
|
|
|
|
| 132 |
current_user: User = Depends(get_current_user),
|
| 133 |
):
|
| 134 |
"""
|
| 135 |
+
Get a specific note by ID from Firestore.
|
| 136 |
"""
|
| 137 |
+
db = get_firebase_db()
|
| 138 |
+
if db is None:
|
| 139 |
+
raise HTTPException(status_code=500, detail="Firebase not configured")
|
| 140 |
+
|
| 141 |
+
note_ref = db.collection("notes").document(note_id)
|
| 142 |
+
note_doc = note_ref.get()
|
| 143 |
|
| 144 |
+
if not note_doc.exists:
|
| 145 |
raise HTTPException(status_code=404, detail="Note not found")
|
| 146 |
|
| 147 |
+
note_data = note_doc.to_dict()
|
| 148 |
+
if note_data.get("user_id") != current_user.id:
|
| 149 |
+
raise HTTPException(status_code=403, detail="Forbidden")
|
| 150 |
+
|
| 151 |
return NoteResponse(
|
| 152 |
+
id=note_doc.id,
|
| 153 |
+
video_url=note_data["video_url"],
|
| 154 |
+
video_title=note_data["video_title"],
|
| 155 |
+
summary_text=note_data["summary_content"],
|
| 156 |
video_duration=None,
|
| 157 |
language="en",
|
| 158 |
+
user_id=note_data["user_id"],
|
| 159 |
+
category=note_data.get("category"),
|
| 160 |
+
created_at=str(note_data.get("created_at")),
|
| 161 |
)
|
| 162 |
|
| 163 |
|
| 164 |
@router.get("", response_model=List[NoteResponse])
|
| 165 |
async def list_user_notes(
|
|
|
|
| 166 |
current_user: User = Depends(get_current_user),
|
| 167 |
):
|
| 168 |
"""
|
| 169 |
+
List all notes belonging to the current user from Firestore.
|
| 170 |
"""
|
| 171 |
+
db = get_firebase_db()
|
| 172 |
+
if db is None:
|
| 173 |
+
return []
|
| 174 |
+
|
| 175 |
+
notes_ref = db.collection("notes")
|
| 176 |
+
query = (
|
| 177 |
+
notes_ref.where("user_id", "==", current_user.id)
|
| 178 |
+
.order_by("created_at", direction="DESCENDING")
|
| 179 |
+
.stream()
|
| 180 |
)
|
|
|
|
|
|
|
| 181 |
|
| 182 |
return [
|
| 183 |
NoteResponse(
|
| 184 |
+
id=doc.id,
|
| 185 |
+
video_url=data["video_url"],
|
| 186 |
+
video_title=data["video_title"],
|
| 187 |
+
summary_text=data["summary_content"],
|
| 188 |
+
video_duration=None,
|
| 189 |
+
language="en",
|
| 190 |
+
user_id=data["user_id"],
|
| 191 |
+
category=data.get("category"),
|
| 192 |
+
created_at=str(data.get("created_at")),
|
| 193 |
)
|
| 194 |
+
for doc in query
|
| 195 |
+
if (data := doc.to_dict())
|
| 196 |
]
|
| 197 |
|
| 198 |
|
|
|
|
| 200 |
async def create_note(
|
| 201 |
note_data: CreateNoteRequest,
|
| 202 |
current_user: User = Depends(get_current_user),
|
|
|
|
| 203 |
):
|
| 204 |
# Automatically categorize the note
|
| 205 |
category = await categorizer.categorize_text(note_data.summary_text)
|
| 206 |
|
| 207 |
+
db = get_firebase_db()
|
| 208 |
+
if db is None:
|
| 209 |
+
raise HTTPException(status_code=500, detail="Firebase not configured")
|
| 210 |
+
|
| 211 |
+
note_dict = {
|
| 212 |
+
"video_url": str(note_data.video_url),
|
| 213 |
+
"video_title": note_data.video_title,
|
| 214 |
+
"summary_content": note_data.summary_text,
|
| 215 |
+
"user_id": current_user.id,
|
| 216 |
+
"category": category,
|
| 217 |
+
"created_at": datetime.utcnow()
|
| 218 |
+
}
|
| 219 |
+
|
| 220 |
+
_, new_note_ref = db.collection("notes").add(note_dict)
|
| 221 |
+
|
| 222 |
return NoteResponse(
|
| 223 |
+
id=new_note_ref.id,
|
| 224 |
+
video_url=note_dict["video_url"],
|
| 225 |
+
video_title=note_dict["video_title"],
|
| 226 |
+
summary_text=note_dict["summary_content"],
|
| 227 |
video_duration=None,
|
| 228 |
language="en",
|
| 229 |
+
user_id=note_dict["user_id"],
|
| 230 |
+
category=note_dict["category"],
|
| 231 |
+
created_at=str(note_dict["created_at"]),
|
| 232 |
)
|
src/auth/__pycache__/dependencies.cpython-312.pyc
CHANGED
|
Binary files a/src/auth/__pycache__/dependencies.cpython-312.pyc and b/src/auth/__pycache__/dependencies.cpython-312.pyc differ
|
|
|
src/auth/dependencies.py
CHANGED
|
@@ -7,9 +7,8 @@ from fastapi import Depends, HTTPException, status
|
|
| 7 |
from fastapi.security import OAuth2PasswordBearer
|
| 8 |
from sqlmodel import select
|
| 9 |
|
| 10 |
-
from src.db.
|
| 11 |
from src.db.models import User
|
| 12 |
-
from sqlmodel.ext.asyncio.session import AsyncSession
|
| 13 |
from src.auth.security import decode_access_token
|
| 14 |
|
| 15 |
# OAuth2 scheme for extracting bearer tokens from Authorization header
|
|
@@ -17,28 +16,10 @@ oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/auth/login")
|
|
| 17 |
|
| 18 |
|
| 19 |
async def get_current_user(
|
| 20 |
-
token: str = Depends(oauth2_scheme)
|
| 21 |
) -> User:
|
| 22 |
"""
|
| 23 |
-
Get the currently authenticated user from JWT token.
|
| 24 |
-
|
| 25 |
-
This dependency extracts the JWT token from the Authorization header,
|
| 26 |
-
validates it, and retrieves the corresponding user from the database.
|
| 27 |
-
|
| 28 |
-
Args:
|
| 29 |
-
token: JWT token from Authorization header
|
| 30 |
-
session: Database session
|
| 31 |
-
|
| 32 |
-
Returns:
|
| 33 |
-
User object if authentication is successful
|
| 34 |
-
|
| 35 |
-
Raises:
|
| 36 |
-
HTTPException: 401 Unauthorized if token is invalid or user not found
|
| 37 |
-
|
| 38 |
-
Usage:
|
| 39 |
-
@app.get("/protected")
|
| 40 |
-
async def protected_route(current_user: User = Depends(get_current_user)):
|
| 41 |
-
return {"message": f"Hello {current_user.username}"}
|
| 42 |
"""
|
| 43 |
credentials_exception = HTTPException(
|
| 44 |
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
@@ -56,15 +37,28 @@ async def get_current_user(
|
|
| 56 |
if username is None:
|
| 57 |
raise credentials_exception
|
| 58 |
|
| 59 |
-
# Retrieve user from
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 65 |
raise credentials_exception
|
| 66 |
|
| 67 |
-
|
|
|
|
|
|
|
| 68 |
|
| 69 |
|
| 70 |
async def get_current_active_user(
|
|
|
|
| 7 |
from fastapi.security import OAuth2PasswordBearer
|
| 8 |
from sqlmodel import select
|
| 9 |
|
| 10 |
+
from src.db.firebase import get_firebase_db
|
| 11 |
from src.db.models import User
|
|
|
|
| 12 |
from src.auth.security import decode_access_token
|
| 13 |
|
| 14 |
# OAuth2 scheme for extracting bearer tokens from Authorization header
|
|
|
|
| 16 |
|
| 17 |
|
| 18 |
async def get_current_user(
|
| 19 |
+
token: str = Depends(oauth2_scheme)
|
| 20 |
) -> User:
|
| 21 |
"""
|
| 22 |
+
Get the currently authenticated user from JWT token (using Firestore).
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 23 |
"""
|
| 24 |
credentials_exception = HTTPException(
|
| 25 |
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
|
|
| 37 |
if username is None:
|
| 38 |
raise credentials_exception
|
| 39 |
|
| 40 |
+
# Retrieve user from Firestore
|
| 41 |
+
db = get_firebase_db()
|
| 42 |
+
if db is None:
|
| 43 |
+
# Fallback for when Firebase is not configured
|
| 44 |
+
return User(
|
| 45 |
+
id="mock_id",
|
| 46 |
+
email="mock@example.com",
|
| 47 |
+
username=username,
|
| 48 |
+
password_hash="mock",
|
| 49 |
+
role="user"
|
| 50 |
+
)
|
| 51 |
+
|
| 52 |
+
users_ref = db.collection("users")
|
| 53 |
+
query = users_ref.where("username", "==", username).limit(1).stream()
|
| 54 |
+
user_doc = next(query, None)
|
| 55 |
+
|
| 56 |
+
if user_doc is None:
|
| 57 |
raise credentials_exception
|
| 58 |
|
| 59 |
+
user_data = user_doc.to_dict()
|
| 60 |
+
user_data["id"] = user_doc.id
|
| 61 |
+
return User(**user_data)
|
| 62 |
|
| 63 |
|
| 64 |
async def get_current_active_user(
|
src/{ai_modules/categorization → categorization}/README.md
RENAMED
|
File without changes
|
src/{ai_modules → categorization}/__init__.py
RENAMED
|
File without changes
|
src/{ai_modules/categorization → categorization}/categorizer.py
RENAMED
|
File without changes
|
src/db/__pycache__/models.cpython-312.pyc
CHANGED
|
Binary files a/src/db/__pycache__/models.cpython-312.pyc and b/src/db/__pycache__/models.cpython-312.pyc differ
|
|
|
src/db/firebase.py
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Firebase initialization and helper functions.
|
| 3 |
+
"""
|
| 4 |
+
|
| 5 |
+
import firebase_admin
|
| 6 |
+
from firebase_admin import credentials, firestore, auth
|
| 7 |
+
from pathlib import Path
|
| 8 |
+
from src.utils.config import settings
|
| 9 |
+
from src.utils.logger import setup_logger
|
| 10 |
+
|
| 11 |
+
logger = setup_logger(__name__)
|
| 12 |
+
|
| 13 |
+
_db = None
|
| 14 |
+
|
| 15 |
+
def get_firebase_db():
|
| 16 |
+
"""
|
| 17 |
+
Initialize Firebase Admin SDK and return Firestore client.
|
| 18 |
+
"""
|
| 19 |
+
global _db
|
| 20 |
+
if _db is not None:
|
| 21 |
+
return _db
|
| 22 |
+
|
| 23 |
+
try:
|
| 24 |
+
service_account_path = Path(settings.firebase_service_account_path)
|
| 25 |
+
|
| 26 |
+
if not service_account_path.exists():
|
| 27 |
+
logger.warning(f"Firebase service account file not found at {service_account_path}. Using fallback/mock behavior if applicable.")
|
| 28 |
+
# In a real production app, we might want to raise an error here.
|
| 29 |
+
# For now, we'll return None and handle it in the services.
|
| 30 |
+
return None
|
| 31 |
+
|
| 32 |
+
cred = credentials.Certificate(str(service_account_path))
|
| 33 |
+
firebase_admin.initialize_app(cred, {
|
| 34 |
+
'storageBucket': settings.firebase_storage_bucket
|
| 35 |
+
})
|
| 36 |
+
|
| 37 |
+
_db = firestore.client()
|
| 38 |
+
logger.info("Firebase initialized successfully.")
|
| 39 |
+
return _db
|
| 40 |
+
except Exception as e:
|
| 41 |
+
logger.error(f"Failed to initialize Firebase: {e}")
|
| 42 |
+
return None
|
| 43 |
+
|
| 44 |
+
def verify_token(id_token: str):
|
| 45 |
+
"""
|
| 46 |
+
Verify a Firebase ID token.
|
| 47 |
+
"""
|
| 48 |
+
try:
|
| 49 |
+
decoded_token = auth.verify_id_token(id_token)
|
| 50 |
+
return decoded_token
|
| 51 |
+
except Exception as e:
|
| 52 |
+
logger.error(f"Token verification failed: {e}")
|
| 53 |
+
return None
|
src/db/models.py
CHANGED
|
@@ -5,48 +5,41 @@ Optimized for cloud deployment and mobile app integration.
|
|
| 5 |
|
| 6 |
from datetime import datetime
|
| 7 |
from typing import Optional, List
|
| 8 |
-
from
|
| 9 |
|
| 10 |
|
| 11 |
-
class User(
|
| 12 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 13 |
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
username: str = Field(max_length=100, nullable=False)
|
| 17 |
-
password_hash: str = Field(max_length=255, nullable=False)
|
| 18 |
-
role: str = Field(default="user", max_length=50, nullable=False)
|
| 19 |
-
age: Optional[int] = Field(default=None)
|
| 20 |
-
gender: Optional[str] = Field(default=None, max_length=20)
|
| 21 |
-
created_at: datetime = Field(default_factory=datetime.utcnow, nullable=False)
|
| 22 |
|
| 23 |
-
notes: List["Note"] = Relationship(back_populates="owner")
|
| 24 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 25 |
|
| 26 |
-
class Category(SQLModel, table=True):
|
| 27 |
-
__tablename__ = "categories"
|
| 28 |
|
| 29 |
-
|
| 30 |
-
name: str = Field(index=True, max_length=100, nullable=False)
|
| 31 |
-
description: Optional[str] = Field(default=None, max_length=500)
|
| 32 |
-
user_id: int = Field(foreign_key="users.id", index=True, nullable=False)
|
| 33 |
-
created_at: datetime = Field(default_factory=datetime.utcnow, nullable=False)
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
class Note(SQLModel, table=True):
|
| 37 |
"""
|
| 38 |
-
Note model
|
| 39 |
-
Removed content_json and keywords to prevent UndefinedColumnError.
|
| 40 |
"""
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
summary_content: str = Field(nullable=False)
|
| 49 |
-
category: Optional[str] = Field(default="Uncategorized", max_length=100)
|
| 50 |
-
created_at: datetime = Field(default_factory=datetime.utcnow, nullable=False)
|
| 51 |
-
|
| 52 |
-
owner: Optional[User] = Relationship(back_populates="notes")
|
|
|
|
| 5 |
|
| 6 |
from datetime import datetime
|
| 7 |
from typing import Optional, List
|
| 8 |
+
from pydantic import BaseModel, Field
|
| 9 |
|
| 10 |
|
| 11 |
+
class User(BaseModel):
|
| 12 |
+
"""User model for Firestore 'users' collection."""
|
| 13 |
+
id: Optional[str] = Field(default=None)
|
| 14 |
+
email: str
|
| 15 |
+
username: str
|
| 16 |
+
password_hash: str
|
| 17 |
+
role: str = "user"
|
| 18 |
+
age: Optional[int] = None
|
| 19 |
+
gender: Optional[str] = None
|
| 20 |
+
created_at: datetime = Field(default_factory=datetime.utcnow)
|
| 21 |
|
| 22 |
+
class Config:
|
| 23 |
+
from_attributes = True
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 24 |
|
|
|
|
| 25 |
|
| 26 |
+
class Category(BaseModel):
|
| 27 |
+
"""Category model for Firestore 'categories' collection."""
|
| 28 |
+
id: Optional[str] = Field(default=None)
|
| 29 |
+
name: str
|
| 30 |
+
description: Optional[str] = None
|
| 31 |
+
user_id: str
|
| 32 |
+
created_at: datetime = Field(default_factory=datetime.utcnow)
|
| 33 |
|
|
|
|
|
|
|
| 34 |
|
| 35 |
+
class Note(BaseModel):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 36 |
"""
|
| 37 |
+
Note model for Firestore 'notes' collection.
|
|
|
|
| 38 |
"""
|
| 39 |
+
id: Optional[str] = Field(default=None)
|
| 40 |
+
user_id: str
|
| 41 |
+
video_url: str
|
| 42 |
+
video_title: str
|
| 43 |
+
summary_content: str
|
| 44 |
+
category: Optional[str] = "Uncategorized"
|
| 45 |
+
created_at: datetime = Field(default_factory=datetime.utcnow)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
src/{ai_modules/recommendation → recommendation}/README.md
RENAMED
|
File without changes
|
src/{ai_modules/categorization → recommendation}/__init__.py
RENAMED
|
File without changes
|
src/{ai_modules/recommendation → recommendation}/recommender.py
RENAMED
|
File without changes
|
src/{ai_modules/summarization → summarization}/README.md
RENAMED
|
File without changes
|