Ahmed Mostafa commited on
Commit
6405808
·
1 Parent(s): d74863e

server v2

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .env.example +0 -99
  2. .gitattributes +0 -35
  3. .gitignore +40 -0
  4. .python-version +1 -0
  5. Dockerfile +3 -2
  6. README.md +0 -16
  7. check_models.py +24 -0
  8. examples/sample_output.md +105 -0
  9. get_ffmpeg.py +48 -0
  10. outputs/.gitkeep +2 -0
  11. packages.txt +0 -1
  12. pyproject.toml +34 -0
  13. requirements.txt +1 -0
  14. run.py +1 -1
  15. src/__pycache__/__init__.cpython-312.pyc +0 -0
  16. src/__pycache__/__init__.cpython-314.pyc +0 -0
  17. src/ai_modules/README.md +0 -137
  18. src/ai_modules/__pycache__/__init__.cpython-312.pyc +0 -0
  19. src/ai_modules/__pycache__/__init__.cpython-314.pyc +0 -0
  20. src/ai_modules/categorization/__pycache__/__init__.cpython-312.pyc +0 -0
  21. src/ai_modules/categorization/__pycache__/categorizer.cpython-312.pyc +0 -0
  22. src/ai_modules/summarization/__pycache__/__init__.cpython-312.pyc +0 -0
  23. src/ai_modules/summarization/__pycache__/__init__.cpython-314.pyc +0 -0
  24. src/ai_modules/summarization/__pycache__/note_generator.cpython-312.pyc +0 -0
  25. src/ai_modules/summarization/__pycache__/note_generator.cpython-314.pyc +0 -0
  26. src/ai_modules/summarization/__pycache__/schemas.cpython-312.pyc +0 -0
  27. src/ai_modules/transcription/__init__.py +0 -0
  28. src/ai_modules/transcription/__pycache__/__init__.cpython-312.pyc +0 -0
  29. src/ai_modules/transcription/__pycache__/__init__.cpython-314.pyc +0 -0
  30. src/ai_modules/transcription/__pycache__/audio_downloader.cpython-312.pyc +0 -0
  31. src/ai_modules/transcription/__pycache__/audio_downloader.cpython-314.pyc +0 -0
  32. src/ai_modules/transcription/__pycache__/whisper_transcriber.cpython-312.pyc +0 -0
  33. src/ai_modules/transcription/__pycache__/whisper_transcriber.cpython-314.pyc +0 -0
  34. src/api/__pycache__/main.cpython-312.pyc +0 -0
  35. src/api/__pycache__/main.cpython-314.pyc +0 -0
  36. src/api/auth_routes.py +56 -43
  37. src/api/main.py +19 -18
  38. src/api/notes_routes.py +66 -57
  39. src/auth/__pycache__/dependencies.cpython-312.pyc +0 -0
  40. src/auth/dependencies.py +23 -29
  41. src/{ai_modules/categorization → categorization}/README.md +0 -0
  42. src/{ai_modules → categorization}/__init__.py +0 -0
  43. src/{ai_modules/categorization → categorization}/categorizer.py +0 -0
  44. src/db/__pycache__/models.cpython-312.pyc +0 -0
  45. src/db/firebase.py +53 -0
  46. src/db/models.py +29 -36
  47. src/{ai_modules/recommendation → recommendation}/README.md +0 -0
  48. src/{ai_modules/categorization → recommendation}/__init__.py +0 -0
  49. src/{ai_modules/recommendation → recommendation}/recommender.py +0 -0
  50. 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
- ![Python](https://img.shields.io/badge/python-3.8+-blue.svg)
14
- ![License](https://img.shields.io/badge/license-MIT-green.svg)
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.database import get_session
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: int
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": 1,
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, session: AsyncSession = Depends(get_session)
89
  ):
90
  """
91
- Register a new user.
92
  """
93
- # Check if email or username already exists
94
- statement = select(User).where(
95
- (User.email == signup_data.email) | (User.username == signup_data.username)
96
- )
97
- result = await session.exec(statement)
98
- existing_user = result.first()
99
 
100
- if existing_user:
 
 
 
 
 
 
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
- new_user = User(
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
- statement = select(User).where(User.username == form_data.username)
145
- result = await session.exec(statement)
146
- user = result.first()
147
 
148
  # If not found by username, try finding by email
149
- if not user:
150
- statement = select(User).where(User.email == form_data.username)
151
- result = await session.exec(statement)
152
- user = result.first()
153
 
154
  # Verify user exists and password is correct
155
- if not user or not verify_password(form_data.password, user.password_hash):
 
 
 
 
 
 
 
 
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": user.username}, expires_delta=access_token_expires
166
  )
167
 
168
- logger.info(f"User logged in: {user.username}")
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.ai_modules.transcription.audio_downloader import YouTubeDownloader
12
- from src.ai_modules.transcription.whisper_transcriber import WhisperTranscriber
13
- from src.ai_modules.summarization.note_generator import NoteGenerator
14
  from src.utils.logger import setup_logger
15
- from src.db.database import create_db_and_tables, async_engine
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 database tables...")
53
- await create_db_and_tables()
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: int
106
  ):
107
  audio_file = None
108
  try:
@@ -127,16 +126,18 @@ async def process_video_and_save(
127
  video_info["duration"],
128
  )
129
 
130
- async with AsyncSession(async_engine) as session:
131
- new_note = Note(
132
- user_id=user_id,
133
- video_url=youtube_url,
134
- video_title=video_info["title"],
135
- summary_content=final_notes,
136
- )
137
- session.add(new_note)
138
- await session.commit()
139
- await session.refresh(new_note)
 
 
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.database import get_session
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: int
49
  video_url: str
50
  video_title: str
51
  summary_text: str
52
  video_duration: Optional[int]
53
  language: str
54
- user_id: int
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: int,
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
- statement = select(Note).where(Note.id == note_id, Note.user_id == current_user.id)
143
- result = await session.exec(statement)
144
- note = result.first()
 
 
 
145
 
146
- if not note:
147
  raise HTTPException(status_code=404, detail="Note not found")
148
 
 
 
 
 
149
  return NoteResponse(
150
- id=note.id,
151
- video_url=note.video_url,
152
- video_title=note.video_title,
153
- summary_text=note.summary_content,
154
  video_duration=None,
155
  language="en",
156
- user_id=note.user_id,
157
- category=note.category,
158
- created_at=str(note.created_at),
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
- statement = (
171
- select(Note)
172
- .where(Note.user_id == current_user.id)
173
- .order_by(Note.created_at.desc())
 
 
 
 
 
174
  )
175
- result = await session.exec(statement)
176
- notes = result.all()
177
 
178
  return [
179
  NoteResponse(
180
- id=n.id,
181
- video_url=n.video_url,
182
- video_title=n.video_title,
183
- summary_text=n.summary_content,
184
- video_duration=None, # Update if stored
185
- language="en", # Default
186
- user_id=n.user_id,
187
- category=n.category,
188
- created_at=str(n.created_at),
189
  )
190
- for n in notes
 
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
- new_note = Note(
204
- video_url=str(note_data.video_url),
205
- video_title=note_data.video_title,
206
- summary_content=note_data.summary_text,
207
- user_id=current_user.id,
208
- category=category,
209
- )
210
- session.add(new_note)
211
- await session.commit()
212
- await session.refresh(new_note)
 
 
 
 
 
213
  return NoteResponse(
214
- id=new_note.id,
215
- video_url=new_note.video_url,
216
- video_title=new_note.video_title,
217
- summary_text=new_note.summary_content,
218
  video_duration=None,
219
  language="en",
220
- user_id=new_note.user_id,
221
- category=new_note.category,
222
- created_at=str(new_note.created_at),
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.database import get_session
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), session: AsyncSession = Depends(get_session)
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 database
60
- statement = select(User).where(User.username == username)
61
- result = await session.exec(statement)
62
- user = result.first()
63
-
64
- if user is None:
 
 
 
 
 
 
 
 
 
 
 
65
  raise credentials_exception
66
 
67
- return user
 
 
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 sqlmodel import SQLModel, Field, Relationship
9
 
10
 
11
- class User(SQLModel, table=True):
12
- __tablename__ = "users"
 
 
 
 
 
 
 
 
13
 
14
- id: Optional[int] = Field(default=None, primary_key=True)
15
- email: str = Field(unique=True, index=True, max_length=255, nullable=False)
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
- id: Optional[int] = Field(default=None, primary_key=True)
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 synchronized with Supabase schema.
39
- Removed content_json and keywords to prevent UndefinedColumnError.
40
  """
41
-
42
- __tablename__ = "notes"
43
-
44
- id: Optional[int] = Field(default=None, primary_key=True)
45
- user_id: int = Field(foreign_key="users.id", index=True, nullable=False)
46
- video_url: str = Field(index=True, max_length=500, nullable=False)
47
- video_title: str = Field(max_length=500, nullable=False)
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