FIX: General fixes and update to HomePage
This commit is contained in:
@@ -7,7 +7,7 @@
|
||||
<title>Team Software Project</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root" class="min-h-screen h-full bg-gradient-to-tr from-[#07001F] via-[#1D0085] to-[#CC00AF]"></div>
|
||||
<div id="root" class="min-h-screen h-full bg-gradient-to-tr from-[#2043ba] via-[#0026a6] to-[#63007a] overflow-x-hidden"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -21,7 +21,7 @@ export const Return: React.FC = () => {
|
||||
const sessionId = urlParams.get("session_id");
|
||||
|
||||
if (sessionId) {
|
||||
fetch(`${API_URL}/session-status?session_id=${sessionId}`)
|
||||
fetch(`/api/session-status?session_id=${sessionId}`)
|
||||
.then((res) => res.json())
|
||||
.then((data) => {
|
||||
setStatus(data.status);
|
||||
@@ -56,7 +56,7 @@ interface CheckoutFormProps {
|
||||
|
||||
const CheckoutForm: React.FC<CheckoutFormProps> = ({ onClose }) => {
|
||||
const fetchClientSecret = () => {
|
||||
return fetch(`${API_URL}/create-checkout-session`, {
|
||||
return fetch(`/api/create-checkout-session`, {
|
||||
method: "POST",
|
||||
})
|
||||
.then((res) => res.json())
|
||||
|
||||
@@ -19,40 +19,6 @@ interface ListRowProps {
|
||||
onClick: (itemId: number, itemName: string) => void;
|
||||
}
|
||||
|
||||
// Individual list entry component
|
||||
const ListItem: React.FC<ListItemProps> = ({
|
||||
type,
|
||||
title,
|
||||
streamer,
|
||||
viewers,
|
||||
thumbnail,
|
||||
onItemClick,
|
||||
}) => {
|
||||
return (
|
||||
<div
|
||||
className="flex flex-col bg-gray-800 rounded-lg overflow-hidden cursor-pointer hover:bg-gray-700 transition-colors"
|
||||
onClick={onItemClick}
|
||||
>
|
||||
<div className="relative w-full pt-[56.25%] ">
|
||||
{thumbnail ? (
|
||||
<img
|
||||
src={thumbnail}
|
||||
alt={title}
|
||||
className="absolute top-0 left-0 w-full h-full object-cover"
|
||||
/>
|
||||
) : (
|
||||
<div className="absolute top-0 left-0 w-full h-full bg-gray-600" />
|
||||
)}
|
||||
</div>
|
||||
<div className="p-3 bg-white">
|
||||
<h3 className="font-semibold text-lg">{title}</h3>
|
||||
{type === "stream" && <p className="text-red-600">{streamer}</p>}
|
||||
<p className="text-sm text-white">{viewers} viewers</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// Row of entries
|
||||
const ListRow: React.FC<ListRowProps> = ({
|
||||
title,
|
||||
@@ -62,10 +28,10 @@ const ListRow: React.FC<ListRowProps> = ({
|
||||
extraClasses="",
|
||||
}) => {
|
||||
return (
|
||||
<div className={`flex flex-col space-y-4 py-6 px-5 mx-2 mt-5 rounded-md ${extraClasses}`}>
|
||||
<div className={`flex flex-col space-y-4 py-6 bg-black/50 text-white px-5 mx-2 mt-5 rounded-[1.5rem] ${extraClasses}`}>
|
||||
<div className="space-y-1">
|
||||
<h2 className="text-2xl font-bold text-white">{title}</h2>
|
||||
<p className="text-white">{description}</p>
|
||||
<h2 className="text-2xl font-bold">{title}</h2>
|
||||
<p>{description}</p>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4">
|
||||
{items.map((item) => (
|
||||
@@ -85,4 +51,38 @@ const ListRow: React.FC<ListRowProps> = ({
|
||||
);
|
||||
};
|
||||
|
||||
// Individual list entry component
|
||||
const ListItem: React.FC<ListItemProps> = ({
|
||||
type,
|
||||
title,
|
||||
streamer,
|
||||
viewers,
|
||||
thumbnail,
|
||||
onItemClick,
|
||||
}) => {
|
||||
return (
|
||||
<div
|
||||
className="flex flex-col bg-purple-900 rounded-lg overflow-hidden cursor-pointer hover:bg-pink-700 hover:scale-105 transition-all"
|
||||
onClick={onItemClick}
|
||||
>
|
||||
<div className="relative w-full pt-[56.25%] ">
|
||||
{thumbnail ? (
|
||||
<img
|
||||
src={thumbnail}
|
||||
alt={title}
|
||||
className="absolute top-0 left-0 w-full h-full object-cover"
|
||||
/>
|
||||
) : (
|
||||
<div className="absolute top-0 left-0 w-full h-full bg-gray-600" />
|
||||
)}
|
||||
</div>
|
||||
<div className="p-3">
|
||||
<h3 className="font-semibold text-lg text-center">{title}</h3>
|
||||
{type === "stream" && <p className="font-bold">{streamer}</p>}
|
||||
<p className="text-sm text-gray-300">{viewers} viewers</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ListRow;
|
||||
|
||||
@@ -23,6 +23,7 @@ const Navbar: React.FC<NavbarProps> = ({
|
||||
const [showAuthModal, setShowAuthModal] = useState(false);
|
||||
const { isLoggedIn } = useAuth();
|
||||
const [showSideBar, setShowSideBar] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (showAuthModal) {
|
||||
document.body.style.overflow = "hidden";
|
||||
|
||||
@@ -12,7 +12,7 @@ const StreamerRoute: React.FC = () => {
|
||||
useEffect(() => {
|
||||
const checkStreamStatus = async () => {
|
||||
try {
|
||||
const response = await fetch(`/api/streamer/${streamerName}/status`);
|
||||
const response = await fetch(`/api/user/${streamerName}/status`);
|
||||
const data = await response.json();
|
||||
setIsLive(Boolean(data.is_live));
|
||||
setStreamId(data.most_recent_stream);
|
||||
@@ -44,7 +44,7 @@ const StreamerRoute: React.FC = () => {
|
||||
|
||||
// streamId=0 is a special case for the streamer's latest stream
|
||||
return isLive ? (
|
||||
<VideoPage streamId={streamId} />
|
||||
<VideoPage streamerId={streamId} />
|
||||
) : streamerName ? (
|
||||
<UserPage />
|
||||
) : (
|
||||
|
||||
@@ -34,7 +34,7 @@ export function StreamsProvider({ children }: { children: React.ReactNode }) {
|
||||
const { isLoggedIn } = useAuth();
|
||||
|
||||
const fetch_url = isLoggedIn
|
||||
? ["/api/streams/recommended", "/api/categories/following"]
|
||||
? ["/api/streams/recommended", "/api/categories/recommended"]
|
||||
: ["/api/streams/popular/4", "/api/categories/popular/4"];
|
||||
|
||||
useEffect(() => {
|
||||
@@ -44,7 +44,7 @@ export function StreamsProvider({ children }: { children: React.ReactNode }) {
|
||||
.then((data: StreamItem[]) => {
|
||||
const extractedData: StreamItem[] = data.map((stream: any) => ({
|
||||
type: "stream",
|
||||
id: stream.stream_id,
|
||||
id: stream.user_id,
|
||||
title: stream.title,
|
||||
streamer: stream.username,
|
||||
viewers: stream.num_viewers,
|
||||
|
||||
@@ -12,8 +12,8 @@ const HomePage: React.FC<HomePageProps> = ({ variant = "default" }) => {
|
||||
const { featuredStreams, featuredCategories } = useStreams();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const handleStreamClick = (streamId: number, streamerName: string) => {
|
||||
console.log(`Navigating to stream ${streamId}`);
|
||||
const handleStreamClick = (streamerId: number, streamerName: string) => {
|
||||
console.log(`Navigating to stream by user ${streamerId}`);
|
||||
navigate(`/${streamerName}`);
|
||||
};
|
||||
|
||||
@@ -43,7 +43,7 @@ const HomePage: React.FC<HomePageProps> = ({ variant = "default" }) => {
|
||||
}
|
||||
items={featuredStreams}
|
||||
onClick={handleStreamClick}
|
||||
extraClasses="border border-gray-700 bg-[#FF7F50]/80"
|
||||
extraClasses="bg-red-950/60"
|
||||
/>
|
||||
|
||||
{/* If Personalised_HomePage, display Categories the logged-in user follows. Else, trending categories. */}
|
||||
@@ -61,7 +61,7 @@ const HomePage: React.FC<HomePageProps> = ({ variant = "default" }) => {
|
||||
}
|
||||
items={featuredCategories}
|
||||
onClick={handleCategoryClick}
|
||||
extraClasses="border border-gray-700 bg-[#5AFF75]/80"
|
||||
extraClasses="bg-green-950/60"
|
||||
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import Navbar from "../components/Layout/Navbar";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { useAuth } from "../context/AuthContext";
|
||||
|
||||
interface UserProfileData {
|
||||
username: string;
|
||||
@@ -11,19 +12,28 @@ interface UserProfileData {
|
||||
|
||||
const UserPage: React.FC = () => {
|
||||
const [profileData, setProfileData] = useState<UserProfileData | null>(null);
|
||||
const { username: loggedInUsername } = useAuth();
|
||||
const { username } = useParams();
|
||||
let userPageVariant = "user";
|
||||
|
||||
let setUserPageVariant = (currentStream: string) => {
|
||||
if (username === loggedInUsername) userPageVariant = "personal";
|
||||
else if (currentStream) userPageVariant = "streamer";
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
// Fetch user profile data
|
||||
fetch(`/api/get_streamer_data/${username}`)
|
||||
fetch(`/api/user/${username}`)
|
||||
.then((res) => res.json())
|
||||
.then((data) => {
|
||||
setProfileData({
|
||||
username: data.username,
|
||||
bio: data.bio || "This user hasn't written a bio yet.",
|
||||
followerCount: data.num_followering || 0,
|
||||
followerCount: data.num_followers || 0,
|
||||
isPartnered: data.isPartnered || false,
|
||||
});
|
||||
|
||||
setUserPageVariant(data.current_stream_title);
|
||||
})
|
||||
.catch((err) => console.error("Error fetching profile data:", err));
|
||||
}, [username]);
|
||||
@@ -42,9 +52,12 @@ const UserPage: React.FC = () => {
|
||||
<div className="mx-auto px-4 py-8">
|
||||
<div className="grid grid-cols-3 gap-8">
|
||||
{/* Profile Section - Left Third */}
|
||||
<div className="col-span-1 bg-gray-800 rounded-lg p-6 shadow-lg">
|
||||
{/* Profile Picture */}
|
||||
<div
|
||||
id="profile"
|
||||
className="col-span-1 bg-gray-800 rounded-lg p-6 shadow-lg"
|
||||
>
|
||||
<div className="flex flex-col items-center">
|
||||
{/* Profile Picture */}
|
||||
<div className="relative w-48 h-48 rounded-full overflow-hidden mb-6">
|
||||
<img
|
||||
src="/images/monkey.png"
|
||||
@@ -65,9 +78,11 @@ const UserPage: React.FC = () => {
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Username & Follower Count */}
|
||||
<h1 className="text-3xl font-bold mb-2">
|
||||
{profileData.username}
|
||||
</h1>
|
||||
<small className="text-green-400" >{userPageVariant.toUpperCase()}</small>
|
||||
|
||||
<div className="flex items-center space-x-2 mb-6">
|
||||
<span className="text-gray-400">
|
||||
@@ -111,7 +126,10 @@ const UserPage: React.FC = () => {
|
||||
</div>
|
||||
|
||||
{/* Content Section */}
|
||||
<div className="col-span-2 bg-gray-800 rounded-lg p-6 flex flex-col">
|
||||
<div
|
||||
id="content"
|
||||
className="col-span-2 bg-gray-800 rounded-lg p-6 flex flex-col"
|
||||
>
|
||||
<h2 className="text-2xl font-bold mb-4">Past Broadcasts</h2>
|
||||
<div className="text-gray-400 flex h-full rounded-none">
|
||||
No past broadcasts found
|
||||
|
||||
@@ -8,23 +8,21 @@ import VideoPlayer from "../components/Video/VideoPlayer";
|
||||
import { SocketProvider } from "../context/SocketContext";
|
||||
|
||||
interface VideoPageProps {
|
||||
streamId: number;
|
||||
streamerId: number;
|
||||
}
|
||||
|
||||
interface StreamDataProps {
|
||||
streamId: number;
|
||||
streamTitle: string;
|
||||
streamerName: string;
|
||||
streamerId: number;
|
||||
startTime: string;
|
||||
categoryName: string;
|
||||
}
|
||||
|
||||
const VideoPage: React.FC<VideoPageProps> = ({ streamId }) => {
|
||||
const VideoPage: React.FC<VideoPageProps> = ({ streamerId }) => {
|
||||
const { isLoggedIn } = useAuth();
|
||||
const { streamerName } = useParams<{ streamerName: string }>();
|
||||
const [streamData, setStreamData] = useState<StreamDataProps>();
|
||||
const [viewerCount, setViewerCount] = useState(1000000);
|
||||
const [viewerCount, setViewerCount] = useState(0);
|
||||
const [isChatOpen, setIsChatOpen] = useState(true);
|
||||
// const [showCheckout, setShowCheckout] = useState(false);
|
||||
// const showReturn = window.location.search.includes("session_id");
|
||||
@@ -44,11 +42,7 @@ const VideoPage: React.FC<VideoPageProps> = ({ streamId }) => {
|
||||
// }, [showCheckout]);
|
||||
useEffect(() => {
|
||||
// Fetch stream data for this streamer
|
||||
fetch(
|
||||
`/api/get_stream_data/${streamerName}${
|
||||
streamId == 0 ? "" : `/${streamId}`
|
||||
}`
|
||||
).then((res) => {
|
||||
fetch(`/api/streams/${streamerId}/data`).then((res) => {
|
||||
if (!res.ok) {
|
||||
console.error("Failed to load stream data:", res.statusText);
|
||||
}
|
||||
@@ -57,9 +51,7 @@ const VideoPage: React.FC<VideoPageProps> = ({ streamId }) => {
|
||||
.then((data) => {
|
||||
// Transform snake_case to camelCase
|
||||
const transformedData: StreamDataProps = {
|
||||
streamId: streamId,
|
||||
streamerName: data.username,
|
||||
streamerId: data.user_id,
|
||||
streamTitle: data.title,
|
||||
startTime: data.start_time,
|
||||
categoryName: data.category_name,
|
||||
@@ -70,7 +62,7 @@ const VideoPage: React.FC<VideoPageProps> = ({ streamId }) => {
|
||||
console.error("Error fetching stream data:", error);
|
||||
});
|
||||
});
|
||||
}, [streamId, streamerName]);
|
||||
}, [streamerId]);
|
||||
|
||||
const toggleChat = () => {
|
||||
setIsChatOpen((prev) => !prev);
|
||||
@@ -88,7 +80,7 @@ const VideoPage: React.FC<VideoPageProps> = ({ streamId }) => {
|
||||
} grid-rows-[auto_1fr] bg-gray-900 h-full grid-cols-[auto_25vw] transition-all`}
|
||||
>
|
||||
<div className="relative">
|
||||
<VideoPlayer streamId={streamId} />
|
||||
<VideoPlayer streamId={streamerId} />
|
||||
</div>
|
||||
|
||||
<ToggleButton
|
||||
@@ -99,7 +91,10 @@ const VideoPage: React.FC<VideoPageProps> = ({ streamId }) => {
|
||||
{isChatOpen ? "Hide Chat" : "Show Chat"}
|
||||
</ToggleButton>
|
||||
|
||||
<ChatPanel streamId={streamId} onViewerCountChange={(count: number) => setViewerCount(count)} />
|
||||
<ChatPanel
|
||||
streamId={streamerId}
|
||||
onViewerCountChange={(count: number) => setViewerCount(count)}
|
||||
/>
|
||||
|
||||
<div
|
||||
id="stream-info"
|
||||
|
||||
@@ -11,7 +11,7 @@ export default {
|
||||
|
||||
animation: {
|
||||
moving_text_colour: "moving_text_colour 6s ease-in-out infinite alternate",
|
||||
moving_bg: 'moving_bg 200s linear infinite',
|
||||
moving_bg: 'moving_bg 50s linear infinite',
|
||||
'border-spin': 'border-spin linear infinite',
|
||||
floating: "floating 30s linear infinite",
|
||||
burnIn: 'burnIn 1s ease-out',
|
||||
@@ -26,13 +26,13 @@ export default {
|
||||
moving_text_colour: {
|
||||
"0%": { backgroundPosition: "0% 50%" },
|
||||
"100%": { backgroundPosition: "100% 50%" },
|
||||
|
||||
moving_bg: {
|
||||
'0%': { backgroundPosition: '0% 0%' },
|
||||
'100%': { backgroundPosition: '100% 0%' },
|
||||
}
|
||||
},
|
||||
|
||||
moving_bg: {
|
||||
'0%': { backgroundPosition: '0% 0%' },
|
||||
'100%': { backgroundPosition: '-1280px 1280px' },
|
||||
},
|
||||
|
||||
floating: {
|
||||
'0%': { transform: 'translate(0px, -5px) rotateX(0deg) rotateY(0deg)' },
|
||||
'5%': { transform: 'translate(-3px, -5.5px) rotateX(-0.35deg) rotateY(-0.55deg)' },
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
from flask import Flask
|
||||
from flask_session import Session
|
||||
from flask_cors import CORS
|
||||
from blueprints.utils import logged_in_user, record_time
|
||||
from blueprints.utils import logged_in_user
|
||||
from blueprints.errorhandlers import register_error_handlers
|
||||
# from flask_wtf.csrf import CSRFProtect, generate_csrf
|
||||
|
||||
@@ -48,7 +48,6 @@ def create_app():
|
||||
|
||||
Session(app)
|
||||
app.before_request(logged_in_user)
|
||||
app.after_request(record_time)
|
||||
|
||||
# adds in error handlers
|
||||
register_error_handlers(app)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from flask import Blueprint, session, jsonify, request, redirect, abort
|
||||
from flask import Blueprint, session, jsonify, request, redirect
|
||||
from utils.stream_utils import *
|
||||
from utils.recommendation_utils import *
|
||||
from utils.user_utils import get_user_id
|
||||
@@ -45,15 +45,13 @@ def get_recommended_streams() -> list[dict]:
|
||||
streams = get_streams_based_on_category(category)
|
||||
return streams
|
||||
|
||||
@stream_bp.route('/streams/<string:streamer_username>/data')
|
||||
def get_stream(streamer_username):
|
||||
@stream_bp.route('/streams/<int:streamer_id>/data')
|
||||
def get_stream_data(streamer_id):
|
||||
"""
|
||||
Returns a streamer's most recent stream data
|
||||
Returns a streamer's current stream data
|
||||
"""
|
||||
|
||||
user_id = get_user_id(streamer_username)
|
||||
|
||||
return jsonify(get_most_recent_stream(user_id))
|
||||
return jsonify(get_current_stream_data(streamer_id))
|
||||
|
||||
|
||||
## Category Routes
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from flask import Blueprint, jsonify, session, abort, abort
|
||||
from flask import Blueprint, jsonify, session
|
||||
from utils.user_utils import *
|
||||
from blueprints.utils import login_required
|
||||
from blueprints.email import send_email, forgot_password_body
|
||||
@@ -10,14 +10,14 @@ r = redis.from_url(redis_url, decode_responses=True)
|
||||
user_bp = Blueprint("user", __name__)
|
||||
|
||||
@user_bp.route('/user/<string:username>')
|
||||
def get_user_data_(username):
|
||||
def get_user_data(username):
|
||||
"""
|
||||
Returns a given user's data
|
||||
"""
|
||||
user_id = get_user_id(username)
|
||||
if not user_id:
|
||||
abort(404)
|
||||
data = get_user_data(user_id)
|
||||
jsonify({"error": "User not found from username"}), 404
|
||||
data = get_user(user_id)
|
||||
return jsonify(data)
|
||||
|
||||
## Subscription Routes
|
||||
@@ -127,5 +127,5 @@ def user_reset_password(token, new_password):
|
||||
if response:
|
||||
return 200
|
||||
else:
|
||||
abort(500)
|
||||
return abort(404)
|
||||
return jsonify({"error": "Failed to reset password"}), 500
|
||||
return jsonify({"error": "Invalid token"}), 400
|
||||
@@ -1,25 +1,14 @@
|
||||
from flask import redirect, url_for, request, g, session
|
||||
from functools import wraps
|
||||
from re import match
|
||||
from time import time
|
||||
|
||||
def logged_in_user():
|
||||
"""
|
||||
Validator to make sure a user is logged in.
|
||||
"""
|
||||
g.start_time = time()
|
||||
g.user = session.get("username", None)
|
||||
print(f"Path: {request.path}, session username: {g.user}", flush=True)
|
||||
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):
|
||||
"""
|
||||
Add at start of routes where users need to be logged in to access.
|
||||
|
||||
@@ -94,18 +94,6 @@ INSERT INTO chat (stream_id, chatter_id, message) VALUES
|
||||
(1, 2, 'Woah, cannot believe that');
|
||||
|
||||
|
||||
SELECT * FROM users;
|
||||
SELECT * FROM follows;
|
||||
SELECT * FROM user_preferences;
|
||||
SELECT * FROM subscribes;
|
||||
SELECT * FROM categories;
|
||||
SELECT * FROM streams;
|
||||
SELECT * FROM chat;
|
||||
SELECT * FROM tags;
|
||||
SELECT * FROM stream_tags;
|
||||
|
||||
-- To see all tables in the database
|
||||
SELECT name FROM sqlite_master WHERE type='table';
|
||||
|
||||
SELECT users.user_id, streams.title, streams.num_viewers, users.username
|
||||
FROM streams JOIN users
|
||||
@@ -133,4 +121,17 @@ INSERT INTO followed_categories (user_id, category_id) VALUES
|
||||
|
||||
INSERT INTO subscribes (user_id, subscribed_id, since, expires) VALUES
|
||||
(7, 1, '2024-08-30', '2025-02-28 12:00:00'),
|
||||
(7, 2, '2024-08-30', '2025-02-15');
|
||||
(7, 2, '2024-08-30', '2025-02-15');
|
||||
|
||||
SELECT * FROM users;
|
||||
SELECT * FROM follows;
|
||||
SELECT * FROM user_preferences;
|
||||
SELECT * FROM subscribes;
|
||||
SELECT * FROM categories;
|
||||
SELECT * FROM streams;
|
||||
SELECT * FROM chat;
|
||||
SELECT * FROM tags;
|
||||
SELECT * FROM stream_tags;
|
||||
|
||||
-- To see all tables in the database
|
||||
SELECT name FROM sqlite_master WHERE type='table';
|
||||
|
||||
@@ -34,7 +34,7 @@ def get_followed_live_streams(user_id: int) -> Optional[List[dict]]:
|
||||
""", (user_id,))
|
||||
return live_streams
|
||||
|
||||
def get_most_recent_stream(user_id: int) -> Optional[dict]:
|
||||
def get_current_stream_data(user_id: int) -> Optional[dict]:
|
||||
"""
|
||||
Returns data of the most recent stream by a streamer
|
||||
"""
|
||||
|
||||
@@ -195,7 +195,7 @@ def get_followed_streamers(user_id: int) -> Optional[List[dict]]:
|
||||
""", (user_id,))
|
||||
return followed_streamers
|
||||
|
||||
def get_user_data(user_id: int) -> Optional[dict]:
|
||||
def get_user(user_id: int) -> Optional[dict]:
|
||||
"""
|
||||
Returns username, bio, number of followers, and if user is partnered from user_id
|
||||
"""
|
||||
|
||||
Reference in New Issue
Block a user