Patch: Tidy up of style code and fix to authentication logic
Feat: Added ability to access user's username through AuthContext
This commit is contained in:
@@ -15,7 +15,7 @@ services:
|
|||||||
build:
|
build:
|
||||||
context: ./web_server
|
context: ./web_server
|
||||||
ports:
|
ports:
|
||||||
- "5000"
|
- "5000:5000"
|
||||||
networks:
|
networks:
|
||||||
- app_network
|
- app_network
|
||||||
env_file:
|
env_file:
|
||||||
|
|||||||
Binary file not shown.
@@ -2,18 +2,20 @@ import { useState, useEffect } from "react";
|
|||||||
import { AuthContext } from "./context/AuthContext";
|
import { AuthContext } from "./context/AuthContext";
|
||||||
import { StreamsProvider } from "./context/StreamsContext";
|
import { StreamsProvider } from "./context/StreamsContext";
|
||||||
import { BrowserRouter, Routes, Route } from "react-router-dom";
|
import { BrowserRouter, Routes, Route } from "react-router-dom";
|
||||||
import HomePage, { PersonalisedHomePage } from "./pages/HomePage";
|
import HomePage from "./pages/HomePage";
|
||||||
import StreamerRoute from "./components/Stream/StreamerRoute";
|
import StreamerRoute from "./components/Stream/StreamerRoute";
|
||||||
import NotFoundPage from "./pages/NotFoundPage";
|
import NotFoundPage from "./pages/NotFoundPage";
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
const [isLoggedIn, setIsLoggedIn] = useState(false);
|
const [isLoggedIn, setIsLoggedIn] = useState(false);
|
||||||
|
const [username, setUsername] = useState<string | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetch("/api/get_login_status")
|
fetch("/api/get_login_status")
|
||||||
.then((response) => response.json())
|
.then((response) => response.json())
|
||||||
.then((loggedIn) => {
|
.then((data) => {
|
||||||
setIsLoggedIn(loggedIn);
|
setIsLoggedIn(data.status);
|
||||||
|
setUsername(data.username);
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error("Error fetching login status:", error);
|
console.error("Error fetching login status:", error);
|
||||||
@@ -22,13 +24,13 @@ function App() {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AuthContext.Provider value={{ isLoggedIn, setIsLoggedIn }}>
|
<AuthContext.Provider value={{ isLoggedIn, username, setIsLoggedIn, setUsername }}>
|
||||||
<StreamsProvider>
|
<StreamsProvider>
|
||||||
<BrowserRouter>
|
<BrowserRouter>
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route
|
<Route
|
||||||
path="/"
|
path="/"
|
||||||
element={isLoggedIn ? <PersonalisedHomePage /> : <HomePage />}
|
element={isLoggedIn ? <HomePage variant="personalised" /> : <HomePage />}
|
||||||
/>
|
/>
|
||||||
<Route path="/:streamerName" element={<StreamerRoute />} />
|
<Route path="/:streamerName" element={<StreamerRoute />} />
|
||||||
|
|
||||||
|
|||||||
@@ -20,25 +20,6 @@
|
|||||||
background: #555;
|
background: #555;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bg-repeat {
|
|
||||||
animation: moving_bg 200s linear infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (prefers-reduced-motion: reduce) {
|
|
||||||
.bg-repeat {
|
|
||||||
animation: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes moving_bg {
|
|
||||||
0% {
|
|
||||||
background-position: 0% 0%;
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
background-position: 100% 0%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
:root {
|
:root {
|
||||||
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
|
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
|
||||||
|
|||||||
@@ -7,14 +7,14 @@ interface LogoProps {
|
|||||||
|
|
||||||
const Logo: React.FC<LogoProps> = ({ variant = "default" }) => {
|
const Logo: React.FC<LogoProps> = ({ variant = "default" }) => {
|
||||||
const gradient =
|
const gradient =
|
||||||
"bg-gradient-to-br from-yellow-400 via-red-500 to-indigo-500 text-transparent bg-clip-text group-hover:mx-1 transition-all";
|
"text-transparent group-hover:mx-1 transition-all";
|
||||||
return (
|
return (
|
||||||
<Link to="/" className="cursor-pointer">
|
<Link to="/" className="cursor-pointer">
|
||||||
<div id="logo" className={`group py-3 text-center font-bold hover:scale-110 transition-all ${variant === "home" ? "text-[12vh]" : "text-[4vh]"}`}>
|
<div id="logo" className={`group py-3 text-center font-bold hover:scale-110 transition-all ${variant === "home" ? "text-[12vh]" : "text-[4vh]"}`}>
|
||||||
<h6 className="text-sm bg-gradient-to-br from-blue-400 via-green-500 to-indigo-500 font-black text-transparent bg-clip-text">
|
<h6 className="text-sm bg-gradient-to-br from-blue-400 via-green-500 to-indigo-500 font-black text-transparent bg-clip-text">
|
||||||
Go on, have a...
|
Go on, have a...
|
||||||
</h6>
|
</h6>
|
||||||
<div className="flex w-fit min-w-[30vw] justify-center leading-none transition-all">
|
<div className="flex w-fit min-w-[30vw] bg-logo bg-clip-text animate-moving_text_colour bg-[length:300%_300%] justify-center leading-none transition-all">
|
||||||
<span className={gradient}>G</span>
|
<span className={gradient}>G</span>
|
||||||
<span className={gradient}>A</span>
|
<span className={gradient}>A</span>
|
||||||
<span className={gradient}>N</span>
|
<span className={gradient}>N</span>
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
const Name = () => {
|
|
||||||
return (
|
|
||||||
<div id="logo" className="text-center">
|
|
||||||
<span className="text-7xl font-bold italic bg-agog bg-clip-text text-transparent leading-none p-1 hover:scale-110 transition-all hover:animate-agog bg-[length:300%_300%]">
|
|
||||||
AGOG
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Name;
|
|
||||||
@@ -26,7 +26,7 @@ const StreamerRoute: React.FC = () => {
|
|||||||
checkStreamStatus();
|
checkStreamStatus();
|
||||||
|
|
||||||
// Poll for live status changes
|
// Poll for live status changes
|
||||||
const interval = setInterval(checkStreamStatus, 90000); // Check every 90 seconds
|
const interval = setInterval(checkStreamStatus, 1000); // Check every 90 seconds
|
||||||
|
|
||||||
return () => clearInterval(interval);
|
return () => clearInterval(interval);
|
||||||
}, [streamerName]);
|
}, [streamerName]);
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import React, { useState, useEffect, useRef } from "react";
|
import React, { useState, useEffect, useRef } from "react";
|
||||||
import { io, Socket } from "socket.io-client";
|
import { io, Socket } from "socket.io-client";
|
||||||
import Input from "../Layout/Input";
|
import Input from "../Layout/Input";
|
||||||
|
import { useAuth } from "../../context/AuthContext";
|
||||||
|
|
||||||
interface ChatMessage {
|
interface ChatMessage {
|
||||||
chatter_id: string;
|
chatter_id: string;
|
||||||
@@ -10,21 +11,21 @@ interface ChatMessage {
|
|||||||
|
|
||||||
interface ChatPanelProps {
|
interface ChatPanelProps {
|
||||||
streamId: number;
|
streamId: number;
|
||||||
chatterId?: string; // Optional as user might not be logged in
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const ChatPanel: React.FC<ChatPanelProps> = ({ streamId, chatterId }) => {
|
const ChatPanel: React.FC<ChatPanelProps> = ({ streamId }) => {
|
||||||
const [messages, setMessages] = useState<ChatMessage[]>([]);
|
const [messages, setMessages] = useState<ChatMessage[]>([]);
|
||||||
const [inputMessage, setInputMessage] = useState("");
|
const [inputMessage, setInputMessage] = useState("");
|
||||||
const [socket, setSocket] = useState<Socket | null>(null);
|
const [socket, setSocket] = useState<Socket | null>(null);
|
||||||
const chatContainerRef = useRef<HTMLDivElement>(null);
|
const chatContainerRef = useRef<HTMLDivElement>(null);
|
||||||
|
const { isLoggedIn, username } = useAuth();
|
||||||
|
|
||||||
// Initialize socket connection
|
// Initialize socket connection
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const newSocket = io("/", {
|
const newSocket = io("/", {
|
||||||
path: "/api/socket.io",
|
path: "/api/socket.io",
|
||||||
withCredentials: true
|
withCredentials: true
|
||||||
}); // Make sure this matches your backend URL
|
});
|
||||||
setSocket(newSocket);
|
setSocket(newSocket);
|
||||||
|
|
||||||
newSocket.on("connect", () => {
|
newSocket.on("connect", () => {
|
||||||
@@ -37,6 +38,14 @@ const ChatPanel: React.FC<ChatPanelProps> = ({ streamId, chatterId }) => {
|
|||||||
setMessages(prev => [...prev, data]);
|
setMessages(prev => [...prev, data]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
newSocket.on("connect_error", (error) => {
|
||||||
|
console.error("Socket connection error:", error);
|
||||||
|
});
|
||||||
|
|
||||||
|
newSocket.on("connect_timeout", () => {
|
||||||
|
console.error("Socket connection timeout");
|
||||||
|
});
|
||||||
|
|
||||||
newSocket.on("error", (error) => {
|
newSocket.on("error", (error) => {
|
||||||
console.error("Socket error:", error);
|
console.error("Socket error:", error);
|
||||||
});
|
});
|
||||||
@@ -74,10 +83,12 @@ const ChatPanel: React.FC<ChatPanelProps> = ({ streamId, chatterId }) => {
|
|||||||
}, [messages]);
|
}, [messages]);
|
||||||
|
|
||||||
const sendChat = () => {
|
const sendChat = () => {
|
||||||
if (!inputMessage.trim() || !chatterId || !socket) return;
|
if (!inputMessage.trim() || !socket) {
|
||||||
|
console.log("No message to send or socket not initialized!");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
socket.emit("send_message", {
|
socket.emit("send_message", {
|
||||||
chatter_id: chatterId,
|
|
||||||
stream_id: streamId,
|
stream_id: streamId,
|
||||||
message: inputMessage.trim()
|
message: inputMessage.trim()
|
||||||
});
|
});
|
||||||
@@ -106,7 +117,7 @@ const ChatPanel: React.FC<ChatPanelProps> = ({ streamId, chatterId }) => {
|
|||||||
<span className="text-gray-400 text-sm">
|
<span className="text-gray-400 text-sm">
|
||||||
{new Date(msg.time_sent).toLocaleTimeString()}
|
{new Date(msg.time_sent).toLocaleTimeString()}
|
||||||
</span>
|
</span>
|
||||||
<span className={`font-bold ${msg.chatter_id === chatterId ? "text-blue-400" : "text-green-400"}`}> {msg.chatter_id}: </span>
|
<span className={`font-bold ${msg.chatter_id === username ? "text-blue-400" : "text-green-400"}`}> {msg.chatter_id}: </span>
|
||||||
<span>{msg.message}</span>
|
<span>{msg.message}</span>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
@@ -118,13 +129,13 @@ const ChatPanel: React.FC<ChatPanelProps> = ({ streamId, chatterId }) => {
|
|||||||
value={inputMessage}
|
value={inputMessage}
|
||||||
onChange={(e) => setInputMessage(e.target.value)}
|
onChange={(e) => setInputMessage(e.target.value)}
|
||||||
onKeyDown={handleKeyPress}
|
onKeyDown={handleKeyPress}
|
||||||
placeholder={chatterId ? "Type a message..." : "Login to chat"}
|
placeholder={isLoggedIn ? "Type a message..." : "Login to chat"}
|
||||||
disabled={!chatterId}
|
disabled={!isLoggedIn}
|
||||||
extraClasses="flex-grow disabled:cursor-not-allowed"
|
extraClasses="flex-grow disabled:cursor-not-allowed"
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
onClick={sendChat}
|
onClick={sendChat}
|
||||||
disabled={!chatterId}
|
disabled={!isLoggedIn}
|
||||||
className="px-4 py-2 bg-purple-600 text-white rounded-lg hover:bg-purple-700 disabled:opacity-50 disabled:cursor-not-allowed"
|
className="px-4 py-2 bg-purple-600 text-white rounded-lg hover:bg-purple-700 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||||
>
|
>
|
||||||
Send
|
Send
|
||||||
|
|||||||
@@ -2,7 +2,9 @@ import { createContext, useContext } from "react";
|
|||||||
|
|
||||||
interface AuthContextType {
|
interface AuthContextType {
|
||||||
isLoggedIn: boolean;
|
isLoggedIn: boolean;
|
||||||
|
username: string | null;
|
||||||
setIsLoggedIn: (value: boolean) => void;
|
setIsLoggedIn: (value: boolean) => void;
|
||||||
|
setUsername: (value: string | null) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const AuthContext = createContext<AuthContextType | undefined>(
|
export const AuthContext = createContext<AuthContextType | undefined>(
|
||||||
|
|||||||
@@ -3,9 +3,12 @@ import Navbar from "../components/Layout/Navbar";
|
|||||||
import StreamListRow from "../components/Layout/StreamListRow";
|
import StreamListRow from "../components/Layout/StreamListRow";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { useStreams } from "../context/StreamsContext";
|
import { useStreams } from "../context/StreamsContext";
|
||||||
import Name from "../components/Layout/Name";
|
|
||||||
|
|
||||||
const HomePage: React.FC = () => {
|
interface HomePageProps {
|
||||||
|
variant?: "default" | "personalised";
|
||||||
|
}
|
||||||
|
|
||||||
|
const HomePage: React.FC<HomePageProps> = ({ variant = "default" }) => {
|
||||||
const { featuredStreams, featuredCategories } = useStreams();
|
const { featuredStreams, featuredCategories } = useStreams();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
@@ -17,54 +20,22 @@ const HomePage: React.FC = () => {
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
id="home-page"
|
id="home-page"
|
||||||
className="bg-repeat"
|
className="animate-moving_bg"
|
||||||
style={{ backgroundImage: "url(/images/background-pattern.svg)" }}
|
style={{ backgroundImage: "url(/images/background-pattern.svg)" }}
|
||||||
>
|
>
|
||||||
<Navbar variant="home" />
|
<Navbar variant="home" />
|
||||||
<Name></Name>
|
|
||||||
|
{/*//TODO Extract StreamListRow away, to ListRow so that it makes sense for categories to be there also */}
|
||||||
|
|
||||||
<StreamListRow
|
<StreamListRow
|
||||||
title="Live Now"
|
title={"Live Now" + (variant === "personalised" ? " - Recommended" : "")}
|
||||||
description="Streamers that are currently live"
|
description={variant === "personalised" ? "We think you might like these streams - Streamers recommended for you" : "Streamers that are currently live"}
|
||||||
streams={featuredStreams}
|
streams={featuredStreams}
|
||||||
onStreamClick={handleStreamClick}
|
onStreamClick={handleStreamClick}
|
||||||
/>
|
/>
|
||||||
<StreamListRow
|
<StreamListRow
|
||||||
title="Trending Categories"
|
title={variant === "personalised" ? "Followed Categories" : "Trending Categories"}
|
||||||
description="Categories that have been 'popping off' lately"
|
description={variant === "personalised" ? "Current streams from your followed categories" : "Categories that have been 'popping off' lately"}
|
||||||
streams={featuredCategories}
|
|
||||||
onStreamClick={() => {}} //TODO
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const PersonalisedHomePage: React.FC = () => {
|
|
||||||
const { featuredStreams, featuredCategories } = useStreams();
|
|
||||||
const navigate = useNavigate();
|
|
||||||
|
|
||||||
const handleStreamClick = (streamId: number, streamerName: string) => {
|
|
||||||
console.log(`Navigating to ${streamId}`);
|
|
||||||
navigate(`/${streamerName}`);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
id="personalised-home-page"
|
|
||||||
className="bg-repeat"
|
|
||||||
style={{ backgroundImage: "url(/images/background-pattern.svg)" }}
|
|
||||||
>
|
|
||||||
<Navbar variant="home" />
|
|
||||||
{/*//TODO Extract StreamListRow away to ListRow so that it makes sense for categories to be there also */}
|
|
||||||
<StreamListRow
|
|
||||||
title="Live Now - Recommended"
|
|
||||||
description="We think you might like these streams - Streamers recommended for you"
|
|
||||||
streams={featuredStreams}
|
|
||||||
onStreamClick={handleStreamClick}
|
|
||||||
/>
|
|
||||||
<StreamListRow
|
|
||||||
title="Followed Categories"
|
|
||||||
description="Current streams from your followed categories"
|
|
||||||
streams={featuredCategories}
|
streams={featuredCategories}
|
||||||
onStreamClick={() => {}} //TODO
|
onStreamClick={() => {}} //TODO
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ const VideoPage: React.FC<VideoPageProps> = ({ streamId }) => {
|
|||||||
<VideoPlayer streamId={streamId} />
|
<VideoPlayer streamId={streamId} />
|
||||||
|
|
||||||
{isLoggedIn ? (
|
{isLoggedIn ? (
|
||||||
<ChatPanel streamId={streamId} chatterId="chatter-man" />
|
<ChatPanel streamId={streamId} />
|
||||||
) : (
|
) : (
|
||||||
<ChatPanel streamId={streamId} />
|
<ChatPanel streamId={streamId} />
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -6,20 +6,27 @@ export default {
|
|||||||
],
|
],
|
||||||
theme: {
|
theme: {
|
||||||
extend: {
|
extend: {
|
||||||
|
|
||||||
|
animation: {
|
||||||
|
moving_text_colour: "moving_text_colour 6s ease-in-out infinite alternate",
|
||||||
|
moving_bg: 'moving_bg 200s linear infinite'
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
backgroundImage: {
|
||||||
|
logo: "linear-gradient(45deg, #60A5FA, #8B5CF6, #EC4899, #FACC15,#60A5FA, #8B5CF6, #EC4899, #FACC15)",
|
||||||
|
},
|
||||||
|
|
||||||
keyframes: {
|
keyframes: {
|
||||||
agog: {
|
moving_text_colour: {
|
||||||
"0%": { backgroundPosition: "0% 50%" },
|
"0%": { backgroundPosition: "0% 50%" },
|
||||||
"100%": { backgroundPosition: "100% 50%" },
|
"100%": { backgroundPosition: "100% 50%" },
|
||||||
},
|
},
|
||||||
},
|
moving_bg: {
|
||||||
|
'0%': { backgroundPosition: '0% 0%' },
|
||||||
animation: {
|
'100%': { backgroundPosition: '100% 0%' }
|
||||||
agog: "agog 6s linear infinite",
|
}
|
||||||
},
|
}
|
||||||
|
|
||||||
backgroundImage: {
|
|
||||||
agog: "linear-gradient(to right, #60A5FA, #8B5CF6, #EC4899, #FACC15,#60A5FA, #8B5CF6, #EC4899, #FACC15)",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
|
|||||||
@@ -25,13 +25,10 @@ def signup():
|
|||||||
|
|
||||||
# Validation - ensure all fields exist, users cannot have an empty field
|
# Validation - ensure all fields exist, users cannot have an empty field
|
||||||
if not all([username, email, password]):
|
if not all([username, email, password]):
|
||||||
fields = ["username", "email", "password"]
|
error_fields = get_error_fields([username, email, password]), #!←← find the error_fields, to highlight them in red to the user on the frontend
|
||||||
for x in fields:
|
|
||||||
if not [username, email, password][fields.index(x)]:
|
|
||||||
fields.remove(x)
|
|
||||||
return jsonify({
|
return jsonify({
|
||||||
"account_created": False,
|
"account_created": False,
|
||||||
"error_fields": fields,
|
"error_fields": error_fields,
|
||||||
"message": "Missing required fields"
|
"message": "Missing required fields"
|
||||||
}), 400
|
}), 400
|
||||||
|
|
||||||
@@ -41,9 +38,10 @@ def signup():
|
|||||||
email = sanitizer(email, "email")
|
email = sanitizer(email, "email")
|
||||||
password = sanitizer(password, "password")
|
password = sanitizer(password, "password")
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
|
error_fields = get_error_fields([username, email, password])
|
||||||
return jsonify({
|
return jsonify({
|
||||||
"account_created": False,
|
"account_created": False,
|
||||||
"error_fields": fields,
|
"error_fields": error_fields,
|
||||||
"message": "Invalid input received"
|
"message": "Invalid input received"
|
||||||
}), 400
|
}), 400
|
||||||
|
|
||||||
@@ -204,3 +202,10 @@ def logout() -> dict:
|
|||||||
"""
|
"""
|
||||||
session.clear()
|
session.clear()
|
||||||
return {"logged_in": False}
|
return {"logged_in": False}
|
||||||
|
|
||||||
|
def get_error_fields(values: list):
|
||||||
|
fields = ["username", "email", "password"]
|
||||||
|
for x in fields:
|
||||||
|
if not values[fields.index(x)]:
|
||||||
|
fields.remove(x)
|
||||||
|
return fields
|
||||||
@@ -9,6 +9,7 @@ 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 ---------------------->
|
||||||
# TODO: Add a route that deletes all chat logs when the stream is finished
|
# TODO: Add a route that deletes all chat logs when the stream is finished
|
||||||
|
|
||||||
|
|
||||||
@socketio.on("connect")
|
@socketio.on("connect")
|
||||||
def handle_connection() -> None:
|
def handle_connection() -> None:
|
||||||
"""
|
"""
|
||||||
@@ -16,6 +17,7 @@ def handle_connection() -> None:
|
|||||||
"""
|
"""
|
||||||
print("Client Connected") # Confirmation connect has been made
|
print("Client Connected") # Confirmation connect has been made
|
||||||
|
|
||||||
|
|
||||||
@socketio.on("join")
|
@socketio.on("join")
|
||||||
def handle_join(data) -> None:
|
def handle_join(data) -> None:
|
||||||
"""
|
"""
|
||||||
@@ -26,6 +28,7 @@ def handle_join(data) -> None:
|
|||||||
join_room(stream_id)
|
join_room(stream_id)
|
||||||
emit("status", {"message": f"Welcome to the chat, stream_id: {stream_id}"}, room=stream_id)
|
emit("status", {"message": f"Welcome to the chat, stream_id: {stream_id}"}, room=stream_id)
|
||||||
|
|
||||||
|
|
||||||
@socketio.on("leave")
|
@socketio.on("leave")
|
||||||
def handle_leave(data) -> None:
|
def handle_leave(data) -> None:
|
||||||
"""
|
"""
|
||||||
@@ -36,11 +39,12 @@ def handle_leave(data) -> None:
|
|||||||
leave_room(stream_id)
|
leave_room(stream_id)
|
||||||
emit("status", {"message": f"user left room {stream_id}"}, room=stream_id)
|
emit("status", {"message": f"user left room {stream_id}"}, room=stream_id)
|
||||||
|
|
||||||
|
|
||||||
@chat_bp.route("/chat/<int:stream_id>")
|
@chat_bp.route("/chat/<int:stream_id>")
|
||||||
def get_past_chat(stream_id: int):
|
def get_past_chat(stream_id: int):
|
||||||
"""
|
"""
|
||||||
Returns a JSON object to be passed to the server.
|
Returns a JSON object to be passed to the server.
|
||||||
|
|
||||||
Output structure in the following format: `{chatter_id: message}` for all chats.
|
Output structure in the following format: `{chatter_id: message}` for all chats.
|
||||||
|
|
||||||
Ran once when a user first logs into a stream to get the most recent 50 chat messages.
|
Ran once when a user first logs into a stream to get the most recent 50 chat messages.
|
||||||
@@ -49,7 +53,7 @@ def get_past_chat(stream_id: int):
|
|||||||
# Connect to the database
|
# Connect to the database
|
||||||
db = Database()
|
db = Database()
|
||||||
cursor = db.create_connection()
|
cursor = db.create_connection()
|
||||||
|
|
||||||
# fetched in format: [(chatter_id, message, time_sent)]
|
# fetched in format: [(chatter_id, message, time_sent)]
|
||||||
all_chats = cursor.execute("""
|
all_chats = cursor.execute("""
|
||||||
SELECT *
|
SELECT *
|
||||||
@@ -62,13 +66,15 @@ def get_past_chat(stream_id: int):
|
|||||||
)
|
)
|
||||||
ORDER BY time_sent ASC;""", (stream_id,)).fetchall()
|
ORDER BY time_sent ASC;""", (stream_id,)).fetchall()
|
||||||
db.close_connection()
|
db.close_connection()
|
||||||
|
|
||||||
# Create JSON output of chat_history to pass through NGINX proxy
|
# Create JSON output of chat_history to pass through NGINX proxy
|
||||||
chat_history = [{"chatter_id": chat[0], "message": chat[1], "time_sent": chat[2]} for chat in all_chats]
|
chat_history = [{"chatter_id": chat[0], "message": chat[1],
|
||||||
|
"time_sent": chat[2]} for chat in all_chats]
|
||||||
|
|
||||||
# Pass the chat history to the proxy
|
# Pass the chat history to the proxy
|
||||||
return jsonify({"chat_history": chat_history}), 200
|
return jsonify({"chat_history": chat_history}), 200
|
||||||
|
|
||||||
|
|
||||||
@socketio.on("send_message")
|
@socketio.on("send_message")
|
||||||
def send_chat(data) -> None:
|
def send_chat(data) -> None:
|
||||||
"""
|
"""
|
||||||
@@ -84,7 +90,7 @@ def send_chat(data) -> None:
|
|||||||
if not all([chatter_id, message, stream_id]):
|
if not all([chatter_id, message, stream_id]):
|
||||||
emit("error", {"error": "Unable to send a chat"}, broadcast=False)
|
emit("error", {"error": "Unable to send a chat"}, broadcast=False)
|
||||||
return
|
return
|
||||||
|
|
||||||
# Save chat information to database so other users can see
|
# Save chat information to database so other users can see
|
||||||
db = Database()
|
db = Database()
|
||||||
cursor = db.create_connection()
|
cursor = db.create_connection()
|
||||||
@@ -96,7 +102,7 @@ def send_chat(data) -> None:
|
|||||||
|
|
||||||
# Send the chat message to the client so it can be displayed
|
# Send the chat message to the client so it can be displayed
|
||||||
emit("new_message", {
|
emit("new_message", {
|
||||||
"chatter_id":chatter_id,
|
"chatter_id": chatter_id,
|
||||||
"message":message,
|
"message": message,
|
||||||
"time_sent": datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
"time_sent": datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||||
}, room=stream_id)
|
}, room=stream_id)
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ from utils.user_utils import is_subscribed, is_following, subscription_expiratio
|
|||||||
|
|
||||||
user_bp = Blueprint("user", __name__)
|
user_bp = Blueprint("user", __name__)
|
||||||
|
|
||||||
|
|
||||||
@user_bp.route('/is_subscribed/<int:user_id>/<int:streamer_id>')
|
@user_bp.route('/is_subscribed/<int:user_id>/<int:streamer_id>')
|
||||||
def user_subscribed(user_id: int, streamer_id: int):
|
def user_subscribed(user_id: int, streamer_id: int):
|
||||||
"""
|
"""
|
||||||
@@ -19,7 +20,7 @@ def user_following(user_id: int, streamer_id: int):
|
|||||||
"""
|
"""
|
||||||
if is_following(user_id, streamer_id):
|
if is_following(user_id, streamer_id):
|
||||||
return jsonify({"following": True})
|
return jsonify({"following": True})
|
||||||
return jsonify({"following": False})
|
return jsonify({"following": False})
|
||||||
|
|
||||||
|
|
||||||
@user_bp.route('/subscription_remaining/<int:user_id>/<int:streamer_id>')
|
@user_bp.route('/subscription_remaining/<int:user_id>/<int:streamer_id>')
|
||||||
@@ -28,7 +29,7 @@ def user_subscription_expiration(user_id: int, streamer_id: int):
|
|||||||
Returns remaining time until subscription expiration
|
Returns remaining time until subscription expiration
|
||||||
"""
|
"""
|
||||||
remaining_time = subscription_expiration(user_id, streamer_id)
|
remaining_time = subscription_expiration(user_id, streamer_id)
|
||||||
|
|
||||||
return jsonify({"remaining_time": remaining_time})
|
return jsonify({"remaining_time": remaining_time})
|
||||||
|
|
||||||
@user_bp.route('/get_login_status')
|
@user_bp.route('/get_login_status')
|
||||||
@@ -36,7 +37,9 @@ def get_login_status():
|
|||||||
"""
|
"""
|
||||||
Returns whether the user is logged in or not
|
Returns whether the user is logged in or not
|
||||||
"""
|
"""
|
||||||
return jsonify(session.get("username") is not None)
|
username = session.get("username")
|
||||||
|
return jsonify({'status': username is not None, 'username': username})
|
||||||
|
|
||||||
|
|
||||||
@user_bp.route('/authenticate_user')
|
@user_bp.route('/authenticate_user')
|
||||||
def authenticate_user() -> dict:
|
def authenticate_user() -> dict:
|
||||||
@@ -45,6 +48,7 @@ def authenticate_user() -> dict:
|
|||||||
"""
|
"""
|
||||||
return {"authenticated": True}
|
return {"authenticated": True}
|
||||||
|
|
||||||
|
|
||||||
@user_bp.route('/forgot_password', methods=['POST'])
|
@user_bp.route('/forgot_password', methods=['POST'])
|
||||||
def forgot_password():
|
def forgot_password():
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ Flask==3.1.0
|
|||||||
Flask-Session==0.8.0
|
Flask-Session==0.8.0
|
||||||
Flask-WTF==1.2.2
|
Flask-WTF==1.2.2
|
||||||
Flask_CORS==5.0.0
|
Flask_CORS==5.0.0
|
||||||
|
flask-socketio==5.5.1
|
||||||
python-dotenv==1.0.1
|
python-dotenv==1.0.1
|
||||||
idna==3.10
|
idna==3.10
|
||||||
itsdangerous==2.2.0
|
itsdangerous==2.2.0
|
||||||
@@ -20,5 +21,4 @@ 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
|
||||||
flask-socketio==5.5.1
|
|
||||||
Reference in New Issue
Block a user