MAJOR Fix: Resolved API Request Delays;
Feat: Added a dev test account to users for expedited login; Refactor: Improve socket connection handling and add logging for API request duration & update to ListRow key generation for improved uniqueness; Feat: Made it so streams with no set thumbnail use their category's thumbnail; Minor Fix: Corrections to db recommendation methods;
This commit is contained in:
BIN
frontend/public/images/thumbnails/categories/education.webp
Normal file
BIN
frontend/public/images/thumbnails/categories/education.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 30 KiB |
@@ -68,7 +68,7 @@ const ListRow: React.FC<ListRowProps> = ({
|
|||||||
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4">
|
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4">
|
||||||
{items.map((item) => (
|
{items.map((item) => (
|
||||||
<ListItem
|
<ListItem
|
||||||
key={item.id}
|
key={`${item.type}-${item.id}`}
|
||||||
id={item.id}
|
id={item.id}
|
||||||
type={item.type}
|
type={item.type}
|
||||||
title={item.title}
|
title={item.title}
|
||||||
|
|||||||
@@ -23,8 +23,17 @@ const ChatPanel: React.FC<ChatPanelProps> = ({ streamId }) => {
|
|||||||
// Join chat room when component mounts
|
// Join chat room when component mounts
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (socket && isConnected) {
|
if (socket && isConnected) {
|
||||||
|
// Join chat room
|
||||||
socket.emit("join", { stream_id: streamId });
|
socket.emit("join", { stream_id: streamId });
|
||||||
|
|
||||||
|
// Handle beforeunload event
|
||||||
|
const handleBeforeUnload = () => {
|
||||||
|
socket.emit("leave", { stream_id: streamId });
|
||||||
|
socket.disconnect();
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener("beforeunload", handleBeforeUnload);
|
||||||
|
|
||||||
// Load initial chat history
|
// Load initial chat history
|
||||||
fetch(`/api/chat/${streamId}`)
|
fetch(`/api/chat/${streamId}`)
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
@@ -45,9 +54,11 @@ const ChatPanel: React.FC<ChatPanelProps> = ({ streamId }) => {
|
|||||||
setMessages((prev) => [...prev, data]);
|
setMessages((prev) => [...prev, data]);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Cleanup
|
// Cleanup function
|
||||||
return () => {
|
return () => {
|
||||||
|
window.removeEventListener("beforeunload", handleBeforeUnload);
|
||||||
socket.emit("leave", { stream_id: streamId });
|
socket.emit("leave", { stream_id: streamId });
|
||||||
|
socket.disconnect();
|
||||||
socket.off("new_message");
|
socket.off("new_message");
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React, { createContext, useContext, useEffect, useState } from 'react';
|
import React, { createContext, useContext, useEffect, useRef, useState } from "react";
|
||||||
import { io, Socket } from 'socket.io-client';
|
import { io, Socket } from "socket.io-client";
|
||||||
|
|
||||||
interface SocketContextType {
|
interface SocketContextType {
|
||||||
socket: Socket | null;
|
socket: Socket | null;
|
||||||
@@ -8,39 +8,92 @@ interface SocketContextType {
|
|||||||
|
|
||||||
const SocketContext = createContext<SocketContextType | undefined>(undefined);
|
const SocketContext = createContext<SocketContextType | undefined>(undefined);
|
||||||
|
|
||||||
export const SocketProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
export const SocketProvider: React.FC<{ children: React.ReactNode }> = ({
|
||||||
|
children,
|
||||||
|
}) => {
|
||||||
const [socket, setSocket] = useState<Socket | null>(null);
|
const [socket, setSocket] = useState<Socket | null>(null);
|
||||||
const [isConnected, setIsConnected] = useState(false);
|
const [isConnected, setIsConnected] = useState(false);
|
||||||
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
|
const socketRef = useRef<Socket | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const newSocket = io("http://localhost:8080", {
|
console.log("Start of useEffect");
|
||||||
path: "/socket.io/",
|
|
||||||
|
// Check if we already have a socket instance
|
||||||
|
if (socketRef.current) {
|
||||||
|
console.log("Socket already exists, closing existing socket");
|
||||||
|
socketRef.current.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("Creating new socket connection");
|
||||||
|
const newSocket = io('http://localhost:8080', {
|
||||||
|
path: '/socket.io/',
|
||||||
|
transports: ['websocket', 'polling'],
|
||||||
withCredentials: true,
|
withCredentials: true,
|
||||||
transports: ['websocket'],
|
reconnectionDelay: 1000,
|
||||||
upgrade: false
|
reconnectionDelayMax: 5000,
|
||||||
|
reconnectionAttempts: 5,
|
||||||
|
timeout: 5000
|
||||||
});
|
});
|
||||||
|
|
||||||
newSocket.on('connect', () => {
|
socketRef.current = newSocket;
|
||||||
console.log('Socket connected!');
|
|
||||||
setIsConnected(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
newSocket.on('connect_error', (error) => {
|
|
||||||
console.error('Socket connection error:', error);
|
|
||||||
});
|
|
||||||
|
|
||||||
newSocket.on('disconnect', () => {
|
|
||||||
console.log('Socket disconnected!');
|
|
||||||
setIsConnected(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
setSocket(newSocket);
|
setSocket(newSocket);
|
||||||
|
|
||||||
|
newSocket.on("connect", () => {
|
||||||
|
console.log("Socket connected!");
|
||||||
|
setIsConnected(true);
|
||||||
|
setIsLoading(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
newSocket.on("reconnect_attempt", (attemptNumber) => {
|
||||||
|
console.log(`Reconnecting... Attempt ${attemptNumber}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
newSocket.on("reconnect_error", (error) => {
|
||||||
|
console.error("Reconnection error:", error);
|
||||||
|
});
|
||||||
|
|
||||||
|
newSocket.on("reconnect", (attemptNumber) => {
|
||||||
|
console.log(`Reconnected after ${attemptNumber} attempts!`);
|
||||||
|
});
|
||||||
|
|
||||||
|
newSocket.on("reconnect_failed", () => {
|
||||||
|
console.error("Reconnection failed. Please refresh the page.");
|
||||||
|
});
|
||||||
|
|
||||||
|
newSocket.on("connect_error", (error) => {
|
||||||
|
console.error("Socket connection error:", error);
|
||||||
|
setIsLoading(false);
|
||||||
|
if (newSocket) newSocket.disconnect();
|
||||||
|
newSocket.connect();
|
||||||
|
});
|
||||||
|
|
||||||
|
newSocket.on("disconnect", (reason) => {
|
||||||
|
console.log(
|
||||||
|
"Socket disconnected! Reason: " + reason + " - Attempting reconnect..."
|
||||||
|
);
|
||||||
|
setIsConnected(false);
|
||||||
|
newSocket.connect();
|
||||||
|
});
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
newSocket.close();
|
if (socketRef.current) {
|
||||||
|
console.log("Cleaning up socket connection...");
|
||||||
|
socketRef.current.disconnect();
|
||||||
|
socketRef.current.close();
|
||||||
|
socketRef.current = null;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
return (
|
||||||
|
<div className="h-screen w-screen flex items-center justify-center">
|
||||||
|
<div className="text-4xl text-white">Connecting to socket...</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SocketContext.Provider value={{ socket, isConnected }}>
|
<SocketContext.Provider value={{ socket, isConnected }}>
|
||||||
{children}
|
{children}
|
||||||
@@ -51,7 +104,7 @@ export const SocketProvider: React.FC<{ children: React.ReactNode }> = ({ childr
|
|||||||
export const useSocket = () => {
|
export const useSocket = () => {
|
||||||
const context = useContext(SocketContext);
|
const context = useContext(SocketContext);
|
||||||
if (context === undefined) {
|
if (context === undefined) {
|
||||||
throw new Error('useSocket must be used within a SocketProvider');
|
throw new Error("useSocket must be used within a SocketProvider");
|
||||||
}
|
}
|
||||||
return context;
|
return context;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -48,8 +48,15 @@ export function StreamsProvider({ children }: { children: React.ReactNode }) {
|
|||||||
title: stream.title,
|
title: stream.title,
|
||||||
streamer: stream.username,
|
streamer: stream.username,
|
||||||
viewers: stream.num_viewers,
|
viewers: stream.num_viewers,
|
||||||
thumbnail: stream.thumbnail,
|
thumbnail:
|
||||||
|
stream.thumbnail ||
|
||||||
|
`/images/thumbnails/categories/${stream.category_name
|
||||||
|
.toLowerCase()
|
||||||
|
.replace(/ /g, "_")}.webp`,
|
||||||
|
category: stream.category_name,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
console.log(extractedData);
|
||||||
setFeaturedStreams(extractedData);
|
setFeaturedStreams(extractedData);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -57,15 +64,15 @@ export function StreamsProvider({ children }: { children: React.ReactNode }) {
|
|||||||
fetch(fetch_url[1])
|
fetch(fetch_url[1])
|
||||||
.then((response) => response.json())
|
.then((response) => response.json())
|
||||||
.then((data: CategoryItem[]) => {
|
.then((data: CategoryItem[]) => {
|
||||||
const extractedData: CategoryItem[] = data.map(
|
const extractedData: CategoryItem[] = data.map((category: any) => ({
|
||||||
(category: any) => ({
|
type: "category",
|
||||||
type: "category",
|
id: category.category_id,
|
||||||
id: category.category_id,
|
title: category.category_name,
|
||||||
title: category.category_name,
|
viewers: category.num_viewers,
|
||||||
viewers: category.num_viewers,
|
thumbnail: `/images/thumbnails/categories/${category.category_name
|
||||||
thumbnail: `/images/thumbnails/categories/${category.category_name.toLowerCase().replace(/ /g, "_")}.webp`
|
.toLowerCase()
|
||||||
})
|
.replace(/ /g, "_")}.webp`,
|
||||||
);
|
}));
|
||||||
console.log(extractedData);
|
console.log(extractedData);
|
||||||
setFeaturedCategories(extractedData);
|
setFeaturedCategories(extractedData);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import './assets/styles/index.css'
|
|||||||
import App from './App.tsx'
|
import App from './App.tsx'
|
||||||
|
|
||||||
createRoot(document.getElementById('root')!).render(
|
createRoot(document.getElementById('root')!).render(
|
||||||
<StrictMode>
|
// <StrictMode>
|
||||||
<App />
|
<App />
|
||||||
</StrictMode>,
|
// </StrictMode>,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -23,24 +23,24 @@ interface StreamDataProps {
|
|||||||
|
|
||||||
const VideoPage: React.FC<VideoPageProps> = ({ streamId }) => {
|
const VideoPage: React.FC<VideoPageProps> = ({ streamId }) => {
|
||||||
const { isLoggedIn } = useAuth();
|
const { isLoggedIn } = useAuth();
|
||||||
const [showCheckout, setShowCheckout] = useState(false);
|
// const [showCheckout, setShowCheckout] = useState(false);
|
||||||
const showReturn = window.location.search.includes("session_id");
|
const showReturn = window.location.search.includes("session_id");
|
||||||
const { streamerName } = useParams<{ streamerName: string }>();
|
const { streamerName } = useParams<{ streamerName: string }>();
|
||||||
const [streamData, setStreamData] = useState<StreamDataProps>();
|
const [streamData, setStreamData] = useState<StreamDataProps>();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
useEffect(() => {
|
// useEffect(() => {
|
||||||
// Prevent scrolling when checkout is open
|
// // Prevent scrolling when checkout is open
|
||||||
if (showCheckout) {
|
// if (showCheckout) {
|
||||||
document.body.style.overflow = "hidden";
|
// document.body.style.overflow = "hidden";
|
||||||
} else {
|
// } else {
|
||||||
document.body.style.overflow = "unset";
|
// document.body.style.overflow = "unset";
|
||||||
}
|
// }
|
||||||
// Cleanup function to ensure overflow is restored when component unmounts
|
// // Cleanup function to ensure overflow is restored when component unmounts
|
||||||
return () => {
|
// return () => {
|
||||||
document.body.style.overflow = "unset";
|
// document.body.style.overflow = "unset";
|
||||||
};
|
// };
|
||||||
}, [showCheckout]);
|
// }, [showCheckout]);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Fetch stream data for this streamer
|
// Fetch stream data for this streamer
|
||||||
fetch(
|
fetch(
|
||||||
@@ -83,7 +83,7 @@ const VideoPage: React.FC<VideoPageProps> = ({ streamId }) => {
|
|||||||
>
|
>
|
||||||
{isLoggedIn && (
|
{isLoggedIn && (
|
||||||
<Button
|
<Button
|
||||||
onClick={() => setShowCheckout(true)}
|
// onClick={() => setShowCheckout(true)}
|
||||||
extraClasses="mx-auto mb-4"
|
extraClasses="mx-auto mb-4"
|
||||||
>
|
>
|
||||||
Payment Screen Test
|
Payment Screen Test
|
||||||
@@ -92,8 +92,8 @@ const VideoPage: React.FC<VideoPageProps> = ({ streamId }) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{showCheckout && <CheckoutForm onClose={() => setShowCheckout(false)} />}
|
{/* {showCheckout && <CheckoutForm onClose={() => setShowCheckout(false)} />} */}
|
||||||
{showReturn && <Return />}
|
{/* {showReturn && <Return />} */}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -43,6 +43,23 @@ http {
|
|||||||
listen 8080;
|
listen 8080;
|
||||||
root /var/www;
|
root /var/www;
|
||||||
|
|
||||||
|
location /api/ {
|
||||||
|
rewrite ^/api/(.*)$ /$1 break;
|
||||||
|
proxy_pass http://web_server:5000; # flask-app is the name of the Flask container in docker-compose
|
||||||
|
}
|
||||||
|
|
||||||
|
location /socket.io/ {
|
||||||
|
proxy_pass http://web_server:5000/socket.io/;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection "upgrade";
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_cache_bypass $http_upgrade;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
# The MPEG-TS video chunks are stored in /tmp/hls
|
# The MPEG-TS video chunks are stored in /tmp/hls
|
||||||
location ~ ^/stream/user/(.+\.ts)$ {
|
location ~ ^/stream/user/(.+\.ts)$ {
|
||||||
alias /tmp/hls/$1;
|
alias /tmp/hls/$1;
|
||||||
@@ -59,19 +76,6 @@ http {
|
|||||||
expires -1d;
|
expires -1d;
|
||||||
}
|
}
|
||||||
|
|
||||||
location /api/ {
|
|
||||||
rewrite ^/api/(.*)$ /$1 break;
|
|
||||||
proxy_pass http://web_server:5000; # flask-app is the name of the Flask container in docker-compose
|
|
||||||
}
|
|
||||||
|
|
||||||
location /socket.io/ {
|
|
||||||
proxy_pass http://web_server:5000/socket.io/;
|
|
||||||
proxy_http_version 1.1;
|
|
||||||
proxy_buffering off;
|
|
||||||
proxy_set_header Upgrade $http_upgrade;
|
|
||||||
proxy_set_header Connection "Upgrade";
|
|
||||||
}
|
|
||||||
|
|
||||||
location / {
|
location / {
|
||||||
proxy_pass http://frontend:5173; # frontend is the name of the React container in docker-compose
|
proxy_pass http://frontend:5173; # frontend is the name of the React container in docker-compose
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,4 +16,4 @@ COPY . .
|
|||||||
ENV FLASK_APP=blueprints.__init__
|
ENV FLASK_APP=blueprints.__init__
|
||||||
ENV FLASK_DEBUG=True
|
ENV FLASK_DEBUG=True
|
||||||
|
|
||||||
CMD ["gunicorn", "-b", "0.0.0.0:5000", "blueprints.__init__:create_app()"]
|
CMD ["python", "-c", "from blueprints.socket import socketio; from blueprints.__init__ import create_app; app = create_app(); socketio.run(app, host='0.0.0.0', port=5000, debug=True)"]
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
from flask import Flask
|
from flask import Flask
|
||||||
from flask_session import Session
|
from flask_session import Session
|
||||||
from flask_cors import CORS
|
from flask_cors import CORS
|
||||||
from blueprints.utils import logged_in_user
|
from blueprints.utils import logged_in_user, record_time
|
||||||
from blueprints.errorhandlers import register_error_handlers
|
from blueprints.errorhandlers import register_error_handlers
|
||||||
# from flask_wtf.csrf import CSRFProtect, generate_csrf
|
# from flask_wtf.csrf import CSRFProtect, generate_csrf
|
||||||
|
|
||||||
@@ -9,7 +9,8 @@ from blueprints.authentication import auth_bp
|
|||||||
from blueprints.stripe import stripe_bp
|
from blueprints.stripe import stripe_bp
|
||||||
from blueprints.user import user_bp
|
from blueprints.user import user_bp
|
||||||
from blueprints.streams import stream_bp
|
from blueprints.streams import stream_bp
|
||||||
from blueprints.chat import chat_bp, socketio
|
from blueprints.chat import chat_bp
|
||||||
|
from blueprints.socket import socketio
|
||||||
|
|
||||||
from os import getenv
|
from os import getenv
|
||||||
|
|
||||||
@@ -29,8 +30,11 @@ def create_app():
|
|||||||
CORS(app, supports_credentials=True)
|
CORS(app, supports_credentials=True)
|
||||||
# csrf.init_app(app)
|
# csrf.init_app(app)
|
||||||
|
|
||||||
|
socketio.init_app(app)
|
||||||
|
|
||||||
Session(app)
|
Session(app)
|
||||||
app.before_request(logged_in_user)
|
app.before_request(logged_in_user)
|
||||||
|
app.after_request(record_time)
|
||||||
|
|
||||||
# adds in error handlers
|
# adds in error handlers
|
||||||
register_error_handlers(app)
|
register_error_handlers(app)
|
||||||
@@ -48,7 +52,6 @@ def create_app():
|
|||||||
app.register_blueprint(stream_bp)
|
app.register_blueprint(stream_bp)
|
||||||
app.register_blueprint(chat_bp)
|
app.register_blueprint(chat_bp)
|
||||||
|
|
||||||
# Tell sockets where the initialisation app is
|
socketio.init_app(app)
|
||||||
socketio.init_app(app, cors_allowed_origins="*")
|
|
||||||
|
|
||||||
return app
|
return app
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
from flask import Blueprint, jsonify, session
|
from flask import Blueprint, jsonify, session
|
||||||
from database.database import Database
|
from database.database import Database
|
||||||
from flask_socketio import SocketIO, emit, join_room, leave_room
|
from .socket import socketio
|
||||||
|
from flask_socketio import emit, join_room, leave_room
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from flask_socketio import SocketIO
|
|
||||||
|
|
||||||
chat_bp = Blueprint("chat", __name__)
|
chat_bp = Blueprint("chat", __name__)
|
||||||
socketio = SocketIO()
|
|
||||||
|
|
||||||
# <---------------------- ROUTES NEEDS TO BE CHANGED TO VIDEO OR DELETED AS DEEMED APPROPRIATE ---------------------->
|
# <---------------------- ROUTES NEEDS TO BE CHANGED TO VIDEO OR DELETED AS DEEMED APPROPRIATE ---------------------->
|
||||||
|
|
||||||
|
|||||||
3
web_server/blueprints/socket.py
Normal file
3
web_server/blueprints/socket.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
from flask_socketio import SocketIO
|
||||||
|
|
||||||
|
socketio = SocketIO(cors_allowed_origins="*", async_mode='gevent', logger=True, engineio_logger=True)
|
||||||
@@ -120,7 +120,7 @@ def get_following_categories_streams():
|
|||||||
"""
|
"""
|
||||||
Returns popular streams in categories which the user followed
|
Returns popular streams in categories which the user followed
|
||||||
"""
|
"""
|
||||||
streams = followed_categories_recommendations()
|
streams = followed_categories_recommendations(get_user_id(session.get('username')))
|
||||||
return jsonify(streams)
|
return jsonify(streams)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,24 @@
|
|||||||
from flask import redirect, url_for, request, g, session
|
from flask import redirect, url_for, request, g, session
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
from re import match
|
from re import match
|
||||||
|
from time import time
|
||||||
|
|
||||||
def logged_in_user():
|
def logged_in_user():
|
||||||
"""
|
"""
|
||||||
Validator to make sure a user is logged in.
|
Validator to make sure a user is logged in.
|
||||||
"""
|
"""
|
||||||
|
g.start_time = time()
|
||||||
g.user = session.get("username", None)
|
g.user = session.get("username", None)
|
||||||
g.admin = session.get("username", None)
|
g.admin = session.get("username", None)
|
||||||
|
|
||||||
|
def record_time(response):
|
||||||
|
if hasattr(g, 'start_time'):
|
||||||
|
time_taken = time() - g.start_time
|
||||||
|
print(f"Request to {request.endpoint} took {time_taken:.4f} seconds", flush=True)
|
||||||
|
else:
|
||||||
|
print("No start time found", flush=True)
|
||||||
|
return response
|
||||||
|
|
||||||
def login_required(view):
|
def login_required(view):
|
||||||
"""
|
"""
|
||||||
Add at start of routes where users need to be logged in to access.
|
Add at start of routes where users need to be logged in to access.
|
||||||
|
|||||||
Binary file not shown.
@@ -47,7 +47,8 @@ INSERT INTO subscribes (user_id, subscribed_id, since, expires) VALUES
|
|||||||
(5, 105, '2024-08-30', '2025-02-28');
|
(5, 105, '2024-08-30', '2025-02-28');
|
||||||
|
|
||||||
INSERT INTO users (username, password, email, num_followers, stream_key, is_partnered, bio) VALUES
|
INSERT INTO users (username, password, email, num_followers, stream_key, is_partnered, bio) VALUES
|
||||||
('GamerDude2', 'password123', 'gamerdude3@gmail.com', 3200, '7890', 0, 'Streaming my gaming adventures!');
|
('GamerDude2', 'password123', 'gamerdude3@gmail.com', 3200, '7890', 0, 'Streaming my gaming adventures!'),
|
||||||
|
('dev', 'scrypt:32768:8:1$avr94c5cplosNUDc$f2ba0738080facada51a1ed370bf869199e121e547fe64a7094ef0330b5db2ab7fff87700898729977f4cd24f17c17b9e8c0c93e7241dcdf9aa522d5d1732626', 'dev@gmail.com', 1, '8080', 0, 'A test account to save that tedious signup each time!');
|
||||||
|
|
||||||
INSERT INTO chat (stream_id, chatter_id, message) VALUES
|
INSERT INTO chat (stream_id, chatter_id, message) VALUES
|
||||||
(1, 'Susan', 'Hey Every, loving the stream'),
|
(1, 'Susan', 'Hey Every, loving the stream'),
|
||||||
@@ -67,17 +68,4 @@ SELECT * FROM stream_tags;
|
|||||||
-- To see all tables in the database
|
-- To see all tables in the database
|
||||||
SELECT name FROM sqlite_master WHERE type='table';
|
SELECT name FROM sqlite_master WHERE type='table';
|
||||||
|
|
||||||
|
INSERT INTO users
|
||||||
SELECT isLive FROM streams WHERE user_id = '5';
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
SELECT *
|
|
||||||
FROM (
|
|
||||||
SELECT chatter_id, message, time_sent
|
|
||||||
FROM chat
|
|
||||||
WHERE stream_id = 1
|
|
||||||
ORDER BY time_sent DESC
|
|
||||||
LIMIT 50
|
|
||||||
)
|
|
||||||
ORDER BY time_sent ASC
|
|
||||||
@@ -1,6 +1,3 @@
|
|||||||
-- View all tables in the database
|
|
||||||
SELECT name FROM sqlite_master WHERE type='table';
|
|
||||||
|
|
||||||
DROP TABLE IF EXISTS users;
|
DROP TABLE IF EXISTS users;
|
||||||
CREATE TABLE users
|
CREATE TABLE users
|
||||||
(
|
(
|
||||||
|
|||||||
@@ -21,4 +21,6 @@ typing_extensions==4.12.2
|
|||||||
urllib3==2.3.0
|
urllib3==2.3.0
|
||||||
Werkzeug==3.1.3
|
Werkzeug==3.1.3
|
||||||
WTForms==3.2.1
|
WTForms==3.2.1
|
||||||
Gunicorn==20.1.0
|
Gunicorn==20.1.0
|
||||||
|
gevent>=22.10.2
|
||||||
|
gevent-websocket
|
||||||
@@ -15,20 +15,22 @@ def user_recommendation_category(user_id: int) -> Optional[int]:
|
|||||||
""", (user_id,))
|
""", (user_id,))
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def followed_categories_recommendations(user_id: int):
|
#TODO Needs to be reworked to get categories instead of streams of categories (below can be done in another function - get_streams_by_category)
|
||||||
|
def followed_categories_recommendations(user_id : int):
|
||||||
"""
|
"""
|
||||||
Returns top 25 streams given a users category following
|
Returns top 25 streams given a users category following
|
||||||
"""
|
"""
|
||||||
with Database() as db:
|
with Database() as db:
|
||||||
categories = db.fetchall("""
|
categories = db.fetchall("""
|
||||||
SELECT users.user_id, title, username, num_viewers, category_name
|
SELECT user_id, title, num_viewers, categories.category_name
|
||||||
FROM streams
|
FROM streams
|
||||||
WHERE category_id IN (SELECT category_id FROM categories WHERE user_id = ?)
|
JOIN categories ON streams.category_id = categories.category_id
|
||||||
ORDER BY num_viewers DESC
|
WHERE category_id IN (SELECT category_id FROM categories WHERE user_id = ?)
|
||||||
LIMIT 25;
|
ORDER BY num_viewers DESC
|
||||||
""", (user_id,))
|
LIMIT 25; """, (user_id,))
|
||||||
return categories
|
return categories
|
||||||
|
|
||||||
|
#TODO Needs to be reworked to get categories instead of streams of categories
|
||||||
def recommendations_based_on_category(category_id: int) -> Optional[List[Tuple[int, str, int]]]:
|
def recommendations_based_on_category(category_id: int) -> Optional[List[Tuple[int, str, int]]]:
|
||||||
"""
|
"""
|
||||||
Queries stream database to get top 25 most viewed streams based on given category and returns
|
Queries stream database to get top 25 most viewed streams based on given category and returns
|
||||||
@@ -36,14 +38,14 @@ def recommendations_based_on_category(category_id: int) -> Optional[List[Tuple[i
|
|||||||
"""
|
"""
|
||||||
with Database() as db:
|
with Database() as db:
|
||||||
data = db.fetchall("""
|
data = db.fetchall("""
|
||||||
SELECT users.user_id, title, username, num_viewers, category_name
|
SELECT streams.category_id, streams.user_id, streams.title, users.username, streams.num_viewers, categories.category_name
|
||||||
FROM streams
|
FROM streams
|
||||||
JOIN users ON users.user_id = streams.user_id
|
JOIN users ON users.user_id = streams.user_id
|
||||||
JOIN categories ON streams.category_id = categories.category_id
|
JOIN categories ON streams.category_id = categories.category_id
|
||||||
WHERE categories.category_id = ?
|
WHERE categories.category_id = ?
|
||||||
ORDER BY num_viewers DESC
|
ORDER BY num_viewers DESC
|
||||||
LIMIT 25
|
LIMIT 25
|
||||||
""", (category_id,))
|
""", (category_id,))
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def default_recommendations():
|
def default_recommendations():
|
||||||
@@ -53,13 +55,13 @@ def default_recommendations():
|
|||||||
"""
|
"""
|
||||||
with Database() as db:
|
with Database() as db:
|
||||||
data = db.fetchall("""
|
data = db.fetchall("""
|
||||||
SELECT users.user_id, title, username, num_viewers, category_name
|
SELECT stream_id, users.user_id, title, username, num_viewers, category_name
|
||||||
FROM streams
|
FROM streams
|
||||||
JOIN users ON users.user_id = streams.user_id
|
JOIN users ON users.user_id = streams.user_id
|
||||||
JOIN categories ON streams.category_id = categories.category_id
|
JOIN categories ON streams.category_id = categories.category_id
|
||||||
ORDER BY num_viewers DESC
|
ORDER BY num_viewers DESC
|
||||||
LIMIT 25;
|
LIMIT 25;
|
||||||
""")
|
""")
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def category_recommendations():
|
def category_recommendations():
|
||||||
|
|||||||
Reference in New Issue
Block a user