summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorA Farzat <a@farzat.xyz>2025-10-08 15:26:43 +0300
committerA Farzat <a@farzat.xyz>2025-10-08 15:26:43 +0300
commit05607dc845a1dd111fd393478acf084ca3532081 (patch)
tree4c8cb78e591d9f76983cb68eee265fef85addd1f
parentd93a91867baff153e0d57655996726914f042888 (diff)
downloadcsca5028-05607dc845a1dd111fd393478acf084ca3532081.tar.gz
csca5028-05607dc845a1dd111fd393478acf084ca3532081.zip
Add the ability to add subscriptions
-rw-r--r--front-end/src/App.css136
-rw-r--r--front-end/src/App.jsx128
-rw-r--r--wsgi/__init__.py14
3 files changed, 277 insertions, 1 deletions
diff --git a/front-end/src/App.css b/front-end/src/App.css
index 0220950..19cddd2 100644
--- a/front-end/src/App.css
+++ b/front-end/src/App.css
@@ -92,6 +92,142 @@ body {
color: #74b9ff;
}
+.subscription-section {
+ max-width: 600px;
+ margin: 1.5rem auto 0;
+}
+
+.add-subscription-toggle {
+ padding: 0.6rem 1.2rem;
+ border: 1px solid rgba(255, 255, 255, 0.3);
+ border-radius: 20px;
+ background: rgba(255, 255, 255, 0.15);
+ color: white;
+ cursor: pointer;
+ font-size: 0.9rem;
+ transition: all 0.3s ease;
+}
+
+.add-subscription-toggle:hover {
+ background: rgba(255, 255, 255, 0.25);
+ transform: translateY(-1px);
+}
+
+.subscription-form-container {
+ margin-top: 1.5rem;
+ padding: 1.5rem;
+ background: rgba(255, 255, 255, 0.1);
+ border-radius: 15px;
+ backdrop-filter: blur(10px);
+ border: 1px solid rgba(255, 255, 255, 0.2);
+}
+
+.subscription-form h3 {
+ color: white;
+ margin-bottom: 1.5rem;
+ text-align: center;
+ font-size: 1.3rem;
+}
+
+.form-group {
+ margin-bottom: 1.5rem;
+ text-align: left;
+}
+
+.form-group label {
+ display: block;
+ color: white;
+ margin-bottom: 0.5rem;
+ font-weight: bold;
+}
+
+.subscription-input {
+ width: 100%;
+ padding: 0.8rem 1rem;
+ border: none;
+ border-radius: 10px;
+ background: rgba(255, 255, 255, 0.9);
+ font-size: 1rem;
+ box-sizing: border-box;
+}
+
+.subscription-input:focus {
+ outline: none;
+ box-shadow: 0 0 0 2px rgba(116, 185, 255, 0.5);
+}
+
+.interval-help {
+ display: block;
+ color: rgba(255, 255, 255, 0.7);
+ font-size: 0.8rem;
+ margin-top: 0.3rem;
+}
+
+.subscription-error {
+ color: #ff6b6b;
+ background: rgba(255, 107, 107, 0.1);
+ padding: 0.8rem;
+ border-radius: 8px;
+ border-left: 3px solid #ff6b6b;
+ margin-bottom: 1rem;
+ font-size: 0.9rem;
+}
+
+.subscription-success {
+ color: #51cf66;
+ background: rgba(81, 207, 102, 0.1);
+ padding: 0.8rem;
+ border-radius: 8px;
+ border-left: 3px solid #51cf66;
+ margin-bottom: 1rem;
+ font-size: 0.9rem;
+}
+
+.form-actions {
+ text-align: center;
+}
+
+.submit-subscription {
+ padding: 0.8rem 2rem;
+ border: none;
+ border-radius: 20px;
+ background: #51cf66;
+ color: white;
+ font-weight: bold;
+ cursor: pointer;
+ transition: all 0.3s ease;
+ font-size: 1rem;
+}
+
+.submit-subscription:hover:not(:disabled) {
+ background: #69db7c;
+ transform: translateY(-2px);
+ box-shadow: 0 4px 15px rgba(81, 207, 102, 0.3);
+}
+
+.submit-subscription:disabled {
+ background: rgba(255, 255, 255, 0.3);
+ cursor: not-allowed;
+ transform: none;
+ box-shadow: none;
+}
+
+/* Responsive design updates */
+@media (max-width: 768px) {
+ .subscription-form-container {
+ padding: 1rem;
+ margin-top: 1rem;
+ }
+
+ .subscription-form h3 {
+ font-size: 1.1rem;
+ }
+
+ .submit-subscription {
+ width: 100%;
+ }
+}
+
.main-content {
padding: 2rem;
max-width: 1200px;
diff --git a/front-end/src/App.jsx b/front-end/src/App.jsx
index 92d0adf..4215650 100644
--- a/front-end/src/App.jsx
+++ b/front-end/src/App.jsx
@@ -13,6 +13,12 @@ function App() {
const [error, setError] = useState(null);
const [selectedChannelId, setSelectedChannelId] = useState('');
const [expandedDescriptions, setExpandedDescriptions] = useState({});
+ const [showAddSubscription, setShowAddSubscription] = useState(false);
+ const [subscriptionUrl, setSubscriptionUrl] = useState('');
+ const [timeBetweenFetches, setTimeBetweenFetches] = useState(300); // Default 5 minutes
+ const [addingSubscription, setAddingSubscription] = useState(false);
+ const [subscriptionError, setSubscriptionError] = useState(null);
+ const [subscriptionSuccess, setSubscriptionSuccess] = useState(null);
const fetchChannels = async () => {
try {
@@ -138,6 +144,59 @@ function App() {
}
};
+ const handleAddSubscription = async (e) => {
+ e.preventDefault();
+
+ if (!subscriptionUrl.trim()) {
+ setSubscriptionError('Please enter a YouTube channel/playlist URL');
+ return;
+ }
+
+ try {
+ setAddingSubscription(true);
+ setSubscriptionError(null);
+ setSubscriptionSuccess(null);
+
+ const formData = new FormData();
+ formData.append('url', subscriptionUrl.trim());
+ formData.append('time_between_fetches', timeBetweenFetches.toString());
+
+ const response = await axios.post(`${API_BASE_URL}/add-sub/`, formData, {
+ headers: {
+ 'Content-Type': 'multipart/form-data',
+ },
+ });
+
+ if (response.data) {
+ setSubscriptionSuccess('Subscription added successfully!');
+ setSubscriptionUrl('');
+ setTimeBetweenFetches(300);
+ setShowAddSubscription(false);
+
+ // Refresh the subs list to include the new subscription
+ fetchChannels();
+ } else {
+ throw new Error(response.data.error || 'Failed to add subscription');
+ }
+ } catch (err) {
+ console.error('Error adding subscription:', err);
+ setSubscriptionError(
+ err.response?.data?.error ||
+ err.message ||
+ 'Failed to add subscription. Please check the URL and try again.'
+ );
+ } finally {
+ setAddingSubscription(false);
+ }
+ };
+
+ const resetSubscriptionForm = () => {
+ setSubscriptionUrl('');
+ setTimeBetweenFetches(300);
+ setSubscriptionError(null);
+ setSubscriptionSuccess(null);
+ };
+
return (
<div className="App">
<header className="App-header">
@@ -189,6 +248,75 @@ function App() {
</div>
)}
</div>
+ <div className="subscription-section">
+ <button
+ onClick={() => {
+ setShowAddSubscription(!showAddSubscription);
+ resetSubscriptionForm();
+ }}
+ className="add-subscription-toggle"
+ >
+ {showAddSubscription ? '✕ Cancel' : '+ Add Subscription'}
+ </button>
+
+ {showAddSubscription && (
+ <div className="subscription-form-container">
+ <form onSubmit={handleAddSubscription} className="subscription-form">
+ <h3>Add New Subscription</h3>
+
+ <div className="form-group">
+ <label htmlFor="subscription-url">YouTube Channel URL:</label>
+ <input
+ id="subscription-url"
+ type="url"
+ value={subscriptionUrl}
+ onChange={(e) => setSubscriptionUrl(e.target.value)}
+ placeholder="https://www.youtube.com/channel/UCxxxxxxxxxxxxxxxxxx"
+ className="subscription-input"
+ disabled={addingSubscription}
+ />
+ </div>
+
+ <div className="form-group">
+ <label htmlFor="time-between-fetches">
+ Fetch Interval (seconds):
+ </label>
+ <input
+ id="time-between-fetches"
+ type="number"
+ value={timeBetweenFetches}
+ onChange={(e) => setTimeBetweenFetches(parseInt(e.target.value) || 300)}
+ min="60"
+ max="86400"
+ className="subscription-input"
+ disabled={addingSubscription}
+ />
+ <small className="interval-help">
+ How often to check for new videos (default: 300s = 5 minutes)
+ </small>
+ </div>
+
+ {subscriptionError && (
+ <div className="subscription-error">{subscriptionError}</div>
+ )}
+
+ {subscriptionSuccess && (
+ <div className="subscription-success">{subscriptionSuccess}</div>
+ )}
+
+ <div className="form-actions">
+ <button
+ type="submit"
+ className="submit-subscription"
+ disabled={addingSubscription || !subscriptionUrl.trim()}
+ >
+ {addingSubscription ? 'Adding...' : 'Add Subscription'}
+ </button>
+ </div>
+ </form>
+ </div>
+ )}
+ </div>
</header>
<main className="main-content">
diff --git a/wsgi/__init__.py b/wsgi/__init__.py
index d029c12..e467f82 100644
--- a/wsgi/__init__.py
+++ b/wsgi/__init__.py
@@ -2,9 +2,10 @@ from typing import Any, Dict, List
from components.database import subscriptions
from components.subscriptions.main import Subscription
from components.videos import VideoTuple
-from flask import Flask
+from flask import Flask, request
from flask_cors import CORS
+from components.extractor.extract_sub_info import get_sub_info_from_yt_url
from .utils import vid_dicts_from_tuple_list, sub_info_from_dict
app = Flask(__name__)
@@ -25,3 +26,14 @@ def sub_dict(id: str) -> Dict[str, Any]:
@app.route("/subs-info")
def subs_info() -> List[Dict[str, Any]]:
return [sub_info_from_dict(sub_dict) for sub_dict in subscriptions.find()]
+
+@app.post("/add-sub/")
+def add_sub() -> Dict[str, Any]:
+ sub_info = get_sub_info_from_yt_url(request.form["url"])
+ sub = Subscription(
+ _id=sub_info["id"],
+ link=sub_info["link"],
+ time_between_fetches=int(request.form["time_between_fetches"]),
+ )
+ sub.insert()
+ return sub_info_from_dict(sub.asdict())