Changeset c68150f
- Timestamp:
- 10/27/22 17:35:03 (2 years ago)
- Branches:
- main
- Children:
- 3b6962d
- Parents:
- 8d83180
- Files:
-
- 6 added
- 1 deleted
- 30 edited
Legend:
- Unmodified
- Added
- Removed
-
reactapp/src/App.js
r8d83180 rc68150f 12 12 import AuthApi from "./api/AuthApi"; 13 13 import Cookies from "js-cookie"; 14 import axios from "./api/axios";15 import JSOG from "jsog";16 14 import NotFound from "./Pages/NotFound"; 15 import Topic from "./Pages/Topic"; 17 16 18 17 export default function App() { 19 18 const [auth, setAuth] = useState(false); 20 const [user, setUser] = useState(null);21 const [userLoaded, setUserLoaded] = useState(false);22 19 const variableAuth = useMemo(() => ({ auth, setAuth }), [auth]); 23 20 const [authLoaded, setAuthLoaded] = useState(false); 24 25 const fetchUser = async () => {26 try {27 const response = await axios.get(28 "http://192.168.0.17:8080/secure/currentUser",29 { withCredentials: true }30 );31 var cyclicGraph = await response.data;32 var jsogStructure = JSOG.encode(cyclicGraph);33 cyclicGraph = JSOG.decode(jsogStructure);34 setUser(cyclicGraph);35 setUserLoaded(true);36 } catch (error) {37 console.log("Fetching error", error);38 }39 };40 21 41 22 const readCookie = async () => { … … 43 24 if (session) { 44 25 setAuth(true); 45 fetchUser();46 26 } else { 47 27 setAuth(false); … … 51 31 52 32 useEffect(() => { 33 document.title = "profesori.mk"; 53 34 readCookie(); 54 35 }, []); … … 69 50 <BrowserRouter> 70 51 <Routes> 71 <Route 72 path="/" 73 element={<Home user={user} userLoaded={userLoaded} />} 74 > 52 <Route path="/" element={<Home />}> 75 53 <Route path="login" element={<Login />}></Route> 76 54 <Route path="registration" element={<Registration />}></Route> 77 55 <Route path="professor"> 78 <Route 79 path=":professorId" 80 element={<Professor user={user} userLoaded={userLoaded} />} 81 /> 56 <Route path=":professorId" element={<Professor />} /> 82 57 </Route> 83 58 <Route path="university/:universityId" element={<University />} /> 84 59 <Route path="faculty/:facultyId" element={<Faculty />} /> 85 60 <Route path="subject/:subjectId" element={<Subject />} /> 61 <Route path="topic/:topicId" element={<Topic />} /> 86 62 <Route path="search" element={<SearchResults />}></Route> 87 63 <Route 88 64 path="user_dashboard" 89 65 element={ 90 <ProtectedRoute auth={auth}> 91 {<UserDashboard user={user} userLoaded={userLoaded} />} 92 </ProtectedRoute> 66 <ProtectedRoute auth={auth}>{<UserDashboard />}</ProtectedRoute> 93 67 } 94 68 ></Route> -
reactapp/src/Components/OpinionTree.js
r8d83180 rc68150f 3 3 OpinionCardContent, 4 4 OpinionCardContentTime, 5 OpinionCardContentTitle,6 5 OpinionReplyCard, 7 6 OpinionReplyCardContent, … … 14 13 import AuthApi from "../api/AuthApi"; 15 14 import { useNavigate } from "react-router-dom"; 16 import { useContext, useState } from "react"; 15 import { useContext, useState, useEffect } from "react"; 16 import JSOG from "jsog"; 17 17 import { 18 18 Modal, … … 26 26 import axios from "../api/axios"; 27 27 28 function OpinionTree({ professor , user, userLoaded}) {28 function OpinionTree({ professor }) { 29 29 var renderedOpinionIds = []; 30 30 var postCount; // za da ne go pokazuva ispod postot 31 31 32 const { auth, setAuth } = useContext(AuthApi); 32 33 let navigate = useNavigate(); 33 const { auth, setAuth } = useContext(AuthApi); 34 35 let [replyModalDisplay, setReplyModalDisplay] = useState("none"); 34 35 const [replyModalDisplay, setReplyModalDisplay] = useState("none"); 36 36 const [replyContent, setReplyContent] = useState(""); 37 38 37 const [postForModal, setPostForModal] = useState(null); 38 const [user, setUser] = useState(null); 39 const [loadedUser, setLoadedUser] = useState(false); 40 const [fetchError, setFetchError] = useState(false); 41 const [errorMessage, setErrorMessage] = useState(""); 42 43 useEffect(() => { 44 const url = `http://192.168.0.17:8080/secure/currentUser`; 45 46 const fetchUser = async () => { 47 try { 48 const response = await axios.get(url, { withCredentials: true }); 49 var cyclicGraph = await response.data; 50 var jsogStructure = JSOG.encode(cyclicGraph); 51 cyclicGraph = JSOG.decode(jsogStructure); 52 setUser(cyclicGraph); 53 setLoadedUser(true); 54 } catch (error) { 55 setFetchError(true); 56 } 57 }; 58 59 if (auth) fetchUser(); 60 }, []); 39 61 40 62 const handleLike = async (post) => { 41 if ( 42 auth && 43 userLoaded && 44 !post.likes.some((e) => e.id === user.user.id) && 45 !post.dislikes.some((e) => e.id === user.user.id) 46 ) { 47 const response = await axios( 48 `http://192.168.0.17:8080/secure/professor/${professor.professorId}/upvoteOpinion/${post.postId}`, 49 { 50 method: "get", 51 withCredentials: true, 52 } 53 ); 54 55 window.location.reload(false); 63 if (auth) { 64 if ( 65 loadedUser && 66 user && 67 !post.votes.some((e) => e.user.id === user.id) 68 ) { 69 const response = await axios( 70 `http://192.168.0.17:8080/secure/upvoteOpinion/${post.postId}`, 71 { 72 method: "get", 73 withCredentials: true, 74 } 75 ); 76 window.location.reload(false); 77 } else { 78 return; 79 } 56 80 } else { 57 return;81 navigate("/login"); 58 82 } 59 83 }; 60 84 61 85 const handleDislike = async (post) => { 62 if ( 63 auth && 64 auth && 65 userLoaded && 66 !post.likes.some((e) => e.id === user.user.id) && 67 !post.dislikes.some((e) => e.id === user.user.id) 68 ) { 69 const response = await axios( 70 `http://192.168.0.17:8080/secure/professor/${professor.professorId}/downvoteOpinion/${post.postId}`, 71 { 72 method: "get", 73 withCredentials: true, 74 } 75 ); 76 77 window.location.reload(false); 86 if (auth) { 87 if ( 88 loadedUser && 89 user && 90 !post.votes.some((e) => e.user.id === user.id) 91 ) { 92 const response = await axios( 93 `http://192.168.0.17:8080/secure/downvoteOpinion/${post.postId}`, 94 { 95 method: "get", 96 withCredentials: true, 97 } 98 ); 99 100 window.location.reload(false); 101 } else { 102 return; 103 } 78 104 } else { 79 return;105 navigate("/login"); 80 106 } 81 107 }; … … 101 127 e.preventDefault(); 102 128 103 const response = await axios( 104 `http://192.168.0.17:8080/secure/professor/${professor.professorId}/replyToOpinion/${postId}`, 105 { 106 method: "post", 107 body: { 108 content: replyContent, 109 }, 110 withCredentials: true, 111 } 112 ); 113 114 window.location.reload(false); 115 //console.log(response); 129 if (!replyContent.length < 1) { 130 const response = await axios( 131 `http://192.168.0.17:8080/secure/professor/${professor.professorId}/replyToOpinion/${postId}`, 132 { 133 method: "post", 134 data: { 135 content: replyContent, 136 }, 137 withCredentials: true, 138 } 139 ); 140 setErrorMessage(""); 141 window.location.reload(false); 142 } else { 143 setErrorMessage("Полето за содржина не смее да биде празно"); 144 } 116 145 }; 117 146 … … 123 152 <OpinionReplyCard indent={replyIndent + "px"}> 124 153 <OpinionReplyCardContent> 125 <p >126 <a href= "#">{child.author.username}</a> му реплицирал на{" "}127 {parentPostAuthorUsername}154 <p style={{ fontStyle: "italic", marginBottom: "10px" }}> 155 <a href={"/user/" + child.author.id}>{child.author.username}</a>{" "} 156 му реплицирал на {parentPostAuthorUsername} 128 157 </p> 129 <p>{child.content}</p> 158 <p style={{ marginBottom: "10px", maxWidth: "90%" }}> 159 {child.content} 160 </p> 130 161 <OpinionReplyCardContentTime> 131 162 {dateConverter( … … 133 164 )} 134 165 </OpinionReplyCardContentTime> 135 {auth && userLoaded && user.user.id !== child.author.id && ( 136 <> 137 <StyledFontAwesomeIcon 138 icon={solid("thumbs-up")} 139 right={50 + "px"} 140 color={ 141 child.likes.some((e) => e.id === user.user.id) 166 167 <div 168 style={{ 169 display: 170 !auth || (auth && loadedUser && user.id !== child.author.id) 171 ? "block" 172 : "none", 173 }} 174 > 175 <StyledFontAwesomeIcon 176 icon={solid("thumbs-up")} 177 right={50 + "px"} 178 color={ 179 auth && loadedUser && user 180 ? child.votes.some( 181 (e) => e.vote === "UPVOTE" && e.user.id === user.id 182 ) 142 183 ? "greenyellow" 143 184 : "darkgrey" 144 } 145 onClick={() => handleLike(child)} 146 /> 147 <VoteCount right={50 + "px"}>{child.likes.length}</VoteCount> 148 <StyledFontAwesomeIcon 149 icon={solid("thumbs-down")} 150 right={10 + "px"} 151 color={ 152 child.dislikes.some((e) => e.id === user.user.id) 185 : "darkgrey" 186 } 187 onClick={() => handleLike(child)} 188 /> 189 190 <VoteCount right={50 + "px"}> 191 {child.votes.filter((v) => v.vote === "UPVOTE").length} 192 </VoteCount> 193 194 <StyledFontAwesomeIcon 195 icon={solid("thumbs-down")} 196 right={10 + "px"} 197 color={ 198 auth && loadedUser && user 199 ? child.votes.some( 200 (e) => e.vote === "DOWNVOTE" && e.user.id === user.id 201 ) 153 202 ? "indianred" 154 203 : "darkgrey" 155 } 156 onClick={() => handleDislike(child)} 157 /> 158 <VoteCount right={10 + "px"}>{child.dislikes.length}</VoteCount> 159 <StyledFontAwesomeIcon 160 icon={solid("reply")} 161 right={90 + "px"} 162 color="darkgrey" 163 onClick={() => handleReply(child)} 164 /> 165 </> 166 )} 204 : "darkgrey" 205 } 206 onClick={() => handleDislike(child)} 207 /> 208 209 <VoteCount right={10 + "px"}> 210 {child.votes.filter((v) => v.vote === "DOWNVOTE").length} 211 </VoteCount> 212 213 <StyledFontAwesomeIcon 214 icon={solid("reply")} 215 right={90 + "px"} 216 color="darkgrey" 217 onClick={() => handleReply(child)} 218 /> 219 </div> 167 220 </OpinionReplyCardContent> 168 221 {child.children.map((childOfChild) => … … 187 240 <OpinionCard> 188 241 <OpinionCardContent> 189 <p> 190 <a href="#">{opinion.author.username}</a> напишал 242 <p style={{ fontStyle: "italic", marginBottom: "10px" }}> 243 <a href={"/user/" + opinion.author.id}> 244 {opinion.author.username} 245 </a>{" "} 246 напишал 191 247 </p> 192 <OpinionCardContentTitle> 193 {opinion.title} 194 </OpinionCardContentTitle> 195 <p>{opinion.content}</p> 248 <p style={{ marginBottom: "10px", maxWidth: "90%" }}> 249 {opinion.content} 250 </p> 196 251 <OpinionCardContentTime> 197 252 {dateConverter( … … 199 254 )} 200 255 </OpinionCardContentTime> 201 {auth && userLoaded && user.user.id !== opinion.author.id && ( 202 <> 203 <StyledFontAwesomeIcon 204 icon={solid("thumbs-up")} 205 right={50 + "px"} 206 color={ 207 opinion.likes.some((e) => e.id === user.user.id) 256 257 <div 258 style={{ 259 display: 260 !auth || 261 (auth && loadedUser && user.id !== opinion.author.id) 262 ? "block" 263 : "none", 264 }} 265 > 266 <StyledFontAwesomeIcon 267 icon={solid("thumbs-up")} 268 right={50 + "px"} 269 color={ 270 auth && loadedUser && user 271 ? opinion.votes.some( 272 (e) => 273 e.vote === "UPVOTE" && e.user.id === user.id 274 ) 208 275 ? "greenyellow" 209 276 : "darkgrey" 210 } 211 onClick={() => handleLike(opinion)} 212 /> 213 <VoteCount right={50 + "px"}> 214 {opinion.likes.length} 215 </VoteCount> 216 <StyledFontAwesomeIcon 217 icon={solid("thumbs-down")} 218 right={10 + "px"} 219 color={ 220 opinion.dislikes.some((e) => e.id === user.user.id) 277 : "darkgrey" 278 } 279 onClick={() => handleLike(opinion)} 280 /> 281 282 <VoteCount right={50 + "px"}> 283 {opinion.votes.filter((v) => v.vote === "UPVOTE").length} 284 </VoteCount> 285 286 <StyledFontAwesomeIcon 287 icon={solid("thumbs-down")} 288 right={10 + "px"} 289 color={ 290 auth && loadedUser && user 291 ? opinion.votes.some( 292 (e) => 293 e.vote === "DOWNVOTE" && e.user.id === user.id 294 ) 221 295 ? "indianred" 222 296 : "darkgrey" 223 } 224 onClick={() => handleDislike(opinion)} 225 /> 226 <VoteCount right={10 + "px"}> 227 {opinion.dislikes.length} 228 </VoteCount> 229 <StyledFontAwesomeIcon 230 icon={solid("reply")} 231 right={90 + "px"} 232 color="darkgrey" 233 onClick={() => handleReply(opinion)} 234 /> 235 </> 236 )} 297 : "darkgrey" 298 } 299 onClick={() => handleDislike(opinion)} 300 /> 301 302 <VoteCount right={10 + "px"}> 303 { 304 opinion.votes.filter((v) => v.vote === "DOWNVOTE") 305 .length 306 } 307 </VoteCount> 308 309 <StyledFontAwesomeIcon 310 icon={solid("reply")} 311 right={90 + "px"} 312 color="darkgrey" 313 onClick={() => handleReply(opinion)} 314 /> 315 </div> 237 316 </OpinionCardContent> 238 317 {opinion.children.map((child) => … … 243 322 ); 244 323 } 324 return null; 245 325 })} 246 326 {postForModal && ( … … 266 346 </label> 267 347 </ModalBody> 348 <p 349 style={{ color: "red", marginLeft: "15px", marginTop: "10px" }} 350 > 351 {errorMessage} 352 </p> 268 353 <ModalFooter type="submit">РЕПЛИЦИРАЈ</ModalFooter> 269 354 </form> -
reactapp/src/Components/Styled/EntityList.style.js
r8d83180 rc68150f 18 18 export const EntityParam = styled.p` 19 19 position: absolute; 20 right: 30px;20 right: ${(props) => props.right}; 21 21 top: 10px; 22 22 font-style: italic; -
reactapp/src/Components/Styled/Modal.style.js
r8d83180 rc68150f 18 18 float: right; 19 19 font-weight: bold; 20 box-shadow: 2px 1px 10px #aaaaaa; 20 21 `; 21 22 -
reactapp/src/Components/Styled/OpinionCard.style.js
r8d83180 rc68150f 18 18 } 19 19 position: relative; 20 overflow-wrap: break-word; 20 21 `; 21 22 … … 55 56 display: block; 56 57 position: absolute; 58 height: 20px; 57 59 top: 50%; 58 60 transform: translateY(-50%); … … 60 62 transition: 0.5s; 61 63 &:hover { 62 color: ${(props) => props.color};64 opacity: 0.5; 63 65 cursor: pointer; 64 66 } -
reactapp/src/Components/SubjectsAccordion.js
r8d83180 rc68150f 27 27 <SubjectsAccordionDiv height={height} opacity={opacity}> 28 28 <EntityUl> 29 {props.content.map((item) => { 30 let totalPosts = item.threads.length; 31 return ( 32 <EntityLi key={item.subjectName} bgcolor="cornsilk"> 33 <a href={"/subject/" + item.subjectId}>{item.subjectName}</a> 34 <EntityParam> 35 {totalPosts !== 1 ? ( 36 totalPosts !== 0 ? ( 37 <span 38 style={{ 39 fontWeight: "normal", 40 opacity: totalPosts === 0 ? "0.5" : "1", 41 }} 42 > 29 {props.content && 30 props.content.map((item) => { 31 let totalPosts = item.threads.length; 32 return ( 33 <EntityLi key={item.subjectName} bgcolor="cornsilk"> 34 <a href={"/subject/" + item.subjectId}>{item.subjectName}</a> 35 <EntityParam right="30px"> 36 {totalPosts !== 1 ? ( 37 totalPosts !== 0 ? ( 43 38 <span 44 39 style={{ 45 fontWeight: " bold",40 fontWeight: "normal", 46 41 opacity: totalPosts === 0 ? "0.5" : "1", 47 42 }} 48 43 > 49 {totalPosts}50 </span>{" "}51 мислења52 </span>53 ) : (54 <span55 style={{56 fontWeight: "normal",57 opacity: totalPosts === 0 ? "0.5" : "1",58 }}59 >44 <span 45 style={{ 46 fontWeight: "bold", 47 opacity: totalPosts === 0 ? "0.5" : "1", 48 }} 49 > 50 {totalPosts} 51 </span>{" "} 52 мислења 53 </span> 54 ) : ( 60 55 <span 61 56 style={{ 62 fontWeight: " bold",57 fontWeight: "normal", 63 58 opacity: totalPosts === 0 ? "0.5" : "1", 64 59 }} 65 60 > 66 {totalPosts} 67 </span>{" "} 68 мислења 61 <span 62 style={{ 63 fontWeight: "bold", 64 opacity: totalPosts === 0 ? "0.5" : "1", 65 }} 66 > 67 {totalPosts} 68 </span>{" "} 69 мислења 70 </span> 71 ) 72 ) : ( 73 <span style={{ fontWeight: "normal" }}> 74 <span style={{ fontWeight: "bold" }}>{totalPosts}</span>{" "} 75 мислење 69 76 </span> 70 ) 71 ) : ( 72 <span style={{ fontWeight: "normal" }}> 73 <span style={{ fontWeight: "bold" }}>{totalPosts}</span>{" "} 74 мислење 75 </span> 76 )} 77 </EntityParam> 78 </EntityLi> 79 ); 80 })} 77 )} 78 </EntityParam> 79 </EntityLi> 80 ); 81 })} 81 82 </EntityUl> 82 83 </SubjectsAccordionDiv> -
reactapp/src/Components/UserHeader.js
r8d83180 rc68150f 4 4 import Logout from "./Logout"; 5 5 6 function UserHeader({ user, userLoaded }) { 7 return userLoaded ? ( 6 function UserHeader({}) { 7 const [user, setUser] = useState(null); 8 const [loadedUser, setLoadedUser] = useState(false); 9 const [fetchError, setFetchError] = useState(false); 10 11 useEffect(() => { 12 const url = `http://192.168.0.17:8080/secure/currentUser`; 13 14 const fetchUser = async () => { 15 try { 16 const response = await axios.get(url, { withCredentials: true }); 17 var cyclicGraph = await response.data; 18 var jsogStructure = JSOG.encode(cyclicGraph); 19 cyclicGraph = JSOG.decode(jsogStructure); 20 setUser(cyclicGraph); 21 setLoadedUser(true); 22 } catch (error) { 23 setFetchError(true); 24 } 25 }; 26 27 fetchUser(); 28 }, []); 29 30 return loadedUser ? ( 8 31 <div style={{ float: "left", marginTop: 20, marginLeft: 40 }}> 9 32 Најавен/а: <a href="/user_dashboard">{user.username}</a> <Logout />{" "} -
reactapp/src/Pages/Faculty.js
r8d83180 rc68150f 60 60 }, [params.facultyId]); 61 61 62 return loadedProfessors ? (62 return loadedProfessors && professors.length != 0 ? ( 63 63 entityType === 0 ? ( 64 64 <> … … 114 114 {professor.professorName} 115 115 </a> 116 <EntityParam >116 <EntityParam right="30px"> 117 117 {totalPosts !== 1 ? ( 118 118 totalPosts !== 0 ? ( … … 168 168 <> 169 169 <CurrentPageNav> 170 »{" "} 170 171 <a 171 172 href={ … … 175 176 {professors[0].faculty.university.universityName} 176 177 </a>{" "} 177 /<a href="#">{professors[0].faculty.facultyName}</a>178 » <a href="#">{professors[0].faculty.facultyName}</a> 178 179 </CurrentPageNav> 179 180 <ProfessorCard> … … 221 222 ) 222 223 ) 223 ) : !fetchError ? (224 ) : !fetchError && !loadedProfessors ? ( 224 225 <div> 225 226 <p style={{ marginTop: "140px" }}>се вчитува...</p> -
reactapp/src/Pages/Home.js
r8d83180 rc68150f 24 24 </a>{" "} 25 25 <Search /> 26 {auth && <UserHeader user={user} userLoaded={userLoaded}/>}26 {auth && <UserHeader />} 27 27 <div style={{ marginTop: "140px" }}></div> 28 28 <Outlet /> -
reactapp/src/Pages/Login.js
r8d83180 rc68150f 35 35 if (!response.request.responseURL.includes("error")) { 36 36 // ako NE redirektira na /login?error 37 Cookies.set("JSESSIONID", response.data.sessionId); 37 var in30Minutes = 1 / 48; 38 Cookies.set("JSESSIONID", response.data.sessionId, { 39 expires: in30Minutes, 40 }); 38 41 setAuth(true); 39 42 setErrMsg(""); -
reactapp/src/Pages/Professor.js
r8d83180 rc68150f 25 25 import { CurrentPageNav } from "../Components/Styled/Main.style"; 26 26 27 function Professor( user, userLoaded) {27 function Professor() { 28 28 let params = useParams(); 29 let navigate = useNavigate(); 29 30 30 let [professor, setProfessor] = useState(null);31 let [loaded, setLoaded] = useState(null);32 let [postModalDisplay, setPostModalDisplay] = useState("none"); 33 let navigate = useNavigate();31 const [professor, setProfessor] = useState(null); 32 const [loadedProfessor, setLoadedProfessor] = useState(false); 33 34 const [postModalDisplay, setPostModalDisplay] = useState("none"); 34 35 const { auth, setAuth } = useContext(AuthApi); 35 const [postTitle, setPostTitle] = useState("");36 36 const [postContent, setPostContent] = useState(""); 37 37 const [fetchError, setFetchError] = useState(false); 38 const [errorMessage, setErrorMessage] = useState(""); 38 39 39 40 useEffect(() => { 40 41 const url = `http://192.168.0.17:8080/public/professor/${params.professorId}`; 41 42 42 const fetch Data= async () => {43 const fetchProfessor = async () => { 43 44 try { 44 45 const response = await fetch(url); … … 47 48 cyclicGraph = JSOG.decode(jsogStructure); 48 49 setProfessor(cyclicGraph); 49 setLoaded (true);50 setLoadedProfessor(true); 50 51 } catch (error) { 51 52 setFetchError(true); … … 53 54 }; 54 55 55 fetch Data();56 fetchProfessor(); 56 57 }, [params.professorId]); 57 58 … … 71 72 e.preventDefault(); 72 73 73 const response = await axios( 74 `http://192.168.0.17:8080/secure/professor/${professor.professorId}/addOpinion`, 75 { 76 method: "post", 77 data: { 78 title: postTitle, 79 content: postContent, 80 }, 81 withCredentials: true, 82 } 83 ); 84 85 window.location.reload(false); 86 }; 87 88 const handleTitleChange = (e) => { 89 setPostTitle(e.target.value); 74 if (!postContent.length < 1) { 75 const response = await axios( 76 `http://192.168.0.17:8080/secure/professor/${params.professorId}/addOpinion`, 77 { 78 method: "post", 79 data: { 80 content: postContent, 81 }, 82 withCredentials: true, 83 } 84 ); 85 setErrorMessage(""); 86 window.location.reload(false); 87 } else { 88 setErrorMessage("Полето за содржина не смее да биде празно"); 89 } 90 90 }; 91 91 … … 94 94 }; 95 95 96 if (loaded ) {96 if (loadedProfessor) { 97 97 return ( 98 98 <div> … … 120 120 </div> 121 121 </ProfessorCard> 122 <div style={{ height: "20px", marginBottom: " 30px" }}>122 <div style={{ height: "20px", marginBottom: "50px" }}> 123 123 <h3 124 124 style={{ … … 146 146 <form onSubmit={handlePostSubmit}> 147 147 <ModalBody> 148 <label htmlFor="title">149 <b>Наслов</b>:150 <ModalInput151 id="title"152 type="text"153 value={postTitle}154 onChange={handleTitleChange}155 />156 </label>157 148 <label htmlFor="content"> 158 149 <b>Содржина</b>: … … 166 157 </label> 167 158 </ModalBody> 159 <p 160 style={{ color: "red", marginLeft: "15px", marginTop: "10px" }} 161 > 162 {errorMessage} 163 </p> 168 164 <ModalFooter type="submit">ОБЈАВИ</ModalFooter> 169 165 </form> … … 172 168 173 169 <div className="opinionTree"> 174 <OpinionTree 175 professor={professor} 176 user={user} 177 userLoaded={userLoaded} 178 /> 170 <OpinionTree professor={professor} /> 179 171 </div> 180 172 <Outlet /> -
reactapp/src/Pages/Registration.js
r8d83180 rc68150f 1 import React, { useRef, useState, useEffect } from "react";1 import React, { useRef, useState, useEffect, useContext } from "react"; 2 2 import axios from "../api/axios"; 3 3 import { … … 7 7 RequiredAsterisk, 8 8 } from "../Components/Styled/Login.style"; 9 import { Navigate } from "react-router-dom"; 10 import AuthApi from "../api/AuthApi"; 9 11 const REGISTRATION_URL = "/registration"; 10 12 11 13 const Registration = () => { 14 const { auth, setAuth } = useContext(AuthApi); 12 15 const userRef = useRef(); 13 16 const errRef = useRef(); … … 50 53 useState(0); 51 54 52 return registrationSuccessful === false ? ( 55 return auth ? ( 56 <Navigate to="/user_dashboard" /> 57 ) : registrationSuccessful === false ? ( 53 58 <div 54 59 style={{ -
reactapp/src/Pages/Subject.js
r8d83180 rc68150f 1 import React from "react"; 2 import { useParams } from "react-router-dom"; 1 import React, { useState, useEffect, useContext } from "react"; 2 import { useNavigate, useParams } from "react-router-dom"; 3 import JSOG from "jsog"; 4 import { Outlet } from "react-router-dom"; 5 import { CurrentPageNav } from "../Components/Styled/Main.style"; 6 import { 7 ProfessorCard, 8 ProfessorCardDetails, 9 ProfessorCardName, 10 ProfessorCardSeparator, 11 } from "../Components/Styled/ProfessorCard.style"; 12 import AuthApi from "../api/AuthApi"; 13 import { AddOpinionButton } from "../Components/Styled/Modal.style"; 14 import { 15 EntityLi, 16 EntityUl, 17 EntityParam, 18 } from "../Components/Styled/EntityList.style"; 19 import { 20 Modal, 21 ModalBody, 22 ModalClose, 23 ModalContent, 24 ModalFooter, 25 ModalHeader, 26 ModalInput, 27 ModalTextarea, 28 } from "../Components/Styled/Modal.style"; 29 import axios from "../api/axios"; 3 30 4 31 const Subject = () => { 5 32 let params = useParams(); 6 return <div>Subject {params.subjectId}</div>; 33 let navigate = useNavigate(); 34 35 const { auth, setAuth } = useContext(AuthApi); 36 const [subject, setSubject] = useState(null); 37 const [loaded, setLoaded] = useState(false); 38 const [fetchError, setFetchError] = useState(false); 39 var totalTopics = 0; 40 var topics = []; 41 42 const [topicModalDisplay, setTopicModalDisplay] = useState("none"); 43 const [topicTitle, setTopicTitle] = useState(""); 44 const [topicContent, setTopicContent] = useState(""); 45 const [errorMessage, setErrorMessage] = useState(""); 46 47 useEffect(() => { 48 const url = `http://192.168.0.17:8080/public/subject/${params.subjectId}`; 49 50 const fetchData = async () => { 51 try { 52 const response = await fetch(url); 53 let cyclicGraph = await response.json(); 54 let jsogStructure = JSOG.encode(cyclicGraph); 55 cyclicGraph = JSOG.decode(jsogStructure); 56 setSubject(cyclicGraph); 57 setLoaded(true); 58 } catch (error) { 59 setFetchError(true); 60 } 61 }; 62 63 fetchData(); 64 }, [params.subjectId]); 65 66 const handleAddTopicButtonClick = () => { 67 if (auth) { 68 setTopicModalDisplay("block"); 69 } else { 70 navigate("/login"); 71 } 72 }; 73 74 const handleModalCloseClick = () => { 75 setTopicModalDisplay("none"); 76 }; 77 78 const handleTopicSubmit = async (e) => { 79 e.preventDefault(); 80 81 if (!topicTitle.length < 1 && !topicContent.length < 1) { 82 const response = await axios( 83 `http://192.168.0.17:8080/secure/subject/${params.subjectId}/addThread`, 84 { 85 method: "post", 86 data: { 87 title: topicTitle, 88 content: topicContent, 89 }, 90 withCredentials: true, 91 } 92 ); 93 setErrorMessage(""); 94 window.location.reload(false); 95 } else { 96 setErrorMessage("Полињата за наслов и содржина не смеат да бидат празни"); 97 } 98 }; 99 100 const handleContentChange = (e) => { 101 setTopicContent(e.target.value); 102 }; 103 104 const handleTitleChange = (e) => { 105 setTopicTitle(e.target.value); 106 }; 107 108 return loaded ? ( 109 <> 110 <CurrentPageNav> 111 »{" "} 112 <a 113 href={ 114 "/university/" + 115 subject.studyProgramme.faculty.university.universityId 116 } 117 > 118 {subject.studyProgramme.faculty.university.universityName} 119 </a>{" "} 120 »{" "} 121 <a href={"/faculty/" + subject.studyProgramme.faculty.facultyId}> 122 {subject.studyProgramme.faculty.facultyName} 123 </a>{" "} 124 » <a href="#">{subject.subjectName}</a> 125 </CurrentPageNav> 126 <ProfessorCard> 127 <ProfessorCardName>{subject.subjectName}</ProfessorCardName> 128 <ProfessorCardSeparator /> 129 <div style={{ marginTop: "10px" }}> 130 <ProfessorCardDetails fontSize="20px"> 131 {subject.studyProgramme.studyProgrammeName} ( 132 {subject.studyProgramme.cycle} 133 {"."} 134 {"циклус"}) 135 </ProfessorCardDetails> 136 <ProfessorCardDetails fontSize="20px"> 137 {subject.studyProgramme.faculty.facultyName} 138 </ProfessorCardDetails> 139 <ProfessorCardDetails fontSize="15px"> 140 {subject.studyProgramme.faculty.university.universityName} 141 </ProfessorCardDetails> 142 </div> 143 </ProfessorCard> 144 <div style={{ height: "20px", marginBottom: "50px" }}> 145 <h3 146 style={{ 147 float: "left", 148 }} 149 > 150 {subject.threads.map((thread) => { 151 if (thread.parent === null) { 152 totalTopics++; 153 topics.push(thread); 154 } 155 })} 156 {totalTopics} {totalTopics !== 1 ? "теми" : "тема"} 157 </h3> 158 {auth && ( 159 <AddOpinionButton onClick={handleAddTopicButtonClick}> 160 Отвори тема 161 </AddOpinionButton> 162 )} 163 </div> 164 <Modal display={topicModalDisplay}> 165 <ModalContent> 166 <ModalHeader> 167 <ModalClose onClick={handleModalCloseClick}>×</ModalClose> 168 <h3 style={{ marginTop: "5px" }}> 169 Тема во врска со {subject.subjectName} 170 </h3> 171 </ModalHeader> 172 <form onSubmit={handleTopicSubmit}> 173 <ModalBody> 174 <label htmlFor="title"> 175 <b>Наслов</b>: 176 <ModalInput 177 id="title" 178 value={topicTitle} 179 onChange={handleTitleChange} 180 /> 181 </label> 182 <label htmlFor="content"> 183 <b>Содржина</b>: 184 <ModalTextarea 185 id="content" 186 rows="8" 187 cols="100" 188 value={topicContent} 189 onChange={handleContentChange} 190 /> 191 </label> 192 </ModalBody> 193 <p style={{ color: "red", marginLeft: "15px", marginTop: "10px" }}> 194 {errorMessage} 195 </p> 196 <ModalFooter type="submit">ОБЈАВИ</ModalFooter> 197 </form> 198 </ModalContent> 199 </Modal> 200 <div key={subject.subjectId}> 201 {topics.map((topic) => { 202 var numReplies = topic.children.length; 203 return ( 204 <EntityUl key={topic.postId}> 205 <EntityLi bgcolor="cornsilk"> 206 <a href={"/topic/" + topic.postId}>{topic.title}</a> 207 <EntityParam right="30px"> 208 <span style={{ fontWeight: "normal" }}> 209 отворил:{" "} 210 <a href={"/user/" + topic.author.id}> 211 {topic.author.username} 212 </a> 213 </span> 214 <span style={{ fontStyle: "normal" }}>,</span>{" "} 215 {numReplies !== 1 ? ( 216 numReplies !== 0 ? ( 217 <span 218 style={{ 219 fontWeight: "normal", 220 opacity: numReplies === 0 ? "0.5" : "1", 221 }} 222 > 223 <span 224 style={{ 225 fontWeight: "bold", 226 opacity: numReplies === 0 ? "0.5" : "1", 227 }} 228 > 229 {numReplies} 230 </span>{" "} 231 реплики 232 </span> 233 ) : ( 234 <span 235 style={{ 236 fontWeight: "normal", 237 opacity: numReplies === 0 ? "0.5" : "1", 238 }} 239 > 240 <span 241 style={{ 242 fontWeight: "bold", 243 opacity: numReplies === 0 ? "0.5" : "1", 244 }} 245 > 246 {numReplies} 247 </span>{" "} 248 реплики 249 </span> 250 ) 251 ) : ( 252 <span style={{ fontWeight: "normal" }}> 253 <span style={{ fontWeight: "bold" }}>{numReplies}</span>{" "} 254 реплика 255 </span> 256 )} 257 </EntityParam> 258 </EntityLi> 259 </EntityUl> 260 ); 261 })} 262 </div> 263 </> 264 ) : !fetchError ? ( 265 <div> 266 <p style={{ marginTop: "140px" }}>се вчитува...</p> 267 <Outlet /> 268 </div> 269 ) : ( 270 <div style={{ marginTop: "140px" }}> 271 <h1 style={{ textAlign: "center" }}>Страницата не е пронајдена.</h1> 272 </div> 273 ); 7 274 }; 8 275 -
reactapp/src/Pages/University.js
r8d83180 rc68150f 18 18 let params = useParams(); 19 19 const [loaded, setLoaded] = useState(false); 20 const [faculties, setFaculties] = useState( false);20 const [faculties, setFaculties] = useState(null); 21 21 const [fetchError, setFetchError] = useState(false); 22 22 … … 39 39 }, [params.universityId]); 40 40 41 return loaded ? (41 return loaded && !fetchError && faculties.length !== 0 ? ( 42 42 <> 43 43 <CurrentPageNav> … … 74 74 {faculty.facultyName} 75 75 </a> 76 <EntityParam >76 <EntityParam right="30px"> 77 77 {totalSections}{" "} 78 78 {totalSections !== 1 ? ( … … 103 103 </div> 104 104 </> 105 ) : !fetchError ? (105 ) : !fetchError && !loaded ? ( 106 106 <div> 107 107 <p style={{ marginTop: "140px" }}>се вчитува...</p> -
reactapp/src/Pages/UserDashboard.js
r8d83180 rc68150f 1 import React, { useEffect } from "react";1 import React, { useEffect, useState, useContext } from "react"; 2 2 import { 3 3 OpinionCard, 4 4 OpinionCardContent, 5 5 OpinionCardContentTime, 6 OpinionCardContentTitle,7 6 } from "../Components/Styled/OpinionCard.style"; 8 7 import { … … 11 10 } from "../Components/Styled/UserDetails.style"; 12 11 import { dateConverter } from "../Util/dateConverter"; 12 import axios from "../api/axios"; 13 import JSOG from "jsog"; 14 import AuthApi from "../api/AuthApi"; 13 15 14 function UserDashboard({ user, userLoaded }) { 16 function UserDashboard() { 17 const { auth, setAuth } = useContext(AuthApi); 18 19 const [user, setUser] = useState(null); 20 const [loadedUser, setLoadedUser] = useState(false); 21 const [fetchError, setFetchError] = useState(false); 22 15 23 useEffect(() => { 16 const timer = setTimeout(() => { 17 if (user === null) window.location.reload(false); 18 }, 3000); 19 return () => clearTimeout(timer); 24 const url = `http://192.168.0.17:8080/secure/currentUser`; 25 26 const fetchUser = async () => { 27 try { 28 const response = await axios.get(url, { withCredentials: true }); 29 var cyclicGraph = await response.data; 30 var jsogStructure = JSOG.encode(cyclicGraph); 31 cyclicGraph = JSOG.decode(jsogStructure); 32 setUser(cyclicGraph); 33 setLoadedUser(true); 34 } catch (error) { 35 setFetchError(true); 36 } 37 }; 38 39 if (auth) fetchUser(); 20 40 }, []); 21 41 22 return userLoaded ? ( 42 // useEffect(() => { 43 // const timer = setTimeout(() => { 44 // if (user === null) window.location.reload(false); <---- :-) 45 // }, 3000); 46 // return () => clearTimeout(timer); 47 // }, []); 48 49 function findParentThread(post) { 50 if (post.parent === null) return post; 51 return findParentThread(post.parent); 52 } 53 54 return loadedUser ? ( 23 55 <> 24 56 <h3>Кориснички податоци:</h3> … … 49 81 <OpinionCard> 50 82 <OpinionCardContent> 51 <p> 52 Во дискусија за{" "} 53 <a href={"/professor/" + post.targetProfessor.professorId}> 54 {post.targetProfessor.professorName} 55 </a> 83 <p style={{ fontStyle: "italic", marginBottom: "10px" }}> 84 во дискусија за{" "} 85 {post.targetProfessor !== undefined ? ( 86 <a href={"/professor/" + post.targetProfessor.professorId}> 87 {post.targetProfessor.professorName} 88 </a> 89 ) : ( 90 <a 91 href={ 92 post.parent === null 93 ? "/topic/" + post.postId 94 : "/topic/" + findParentThread(post).postId 95 } 96 > 97 {post.targetSubject.subjectName} 98 </a> 99 )} 56 100 </p> 57 <OpinionCardContentTitle>{post.title}</OpinionCardContentTitle> 58 <p>{post.content}</p> 101 <p style={{ marginBottom: "10px" }}>{post.content}</p> 59 102 <OpinionCardContentTime> 60 103 {dateConverter( -
springapp/pom.xml
r8d83180 rc68150f 30 30 <artifactId>spring-boot-starter-web</artifactId> 31 31 </dependency> 32 33 32 <dependency> 34 33 <groupId>org.postgresql</groupId> … … 63 62 <scope>compile</scope> 64 63 </dependency> 64 65 65 </dependencies> 66 66 -
springapp/src/main/java/mk/profesori/springapp/Controller/PublicController.java
r8d83180 rc68150f 18 18 import mk.profesori.springapp.Model.Professor; 19 19 import mk.profesori.springapp.Model.StudyProgramme; 20 import mk.profesori.springapp.Model.Subject; 20 21 import mk.profesori.springapp.Model.University; 22 import mk.profesori.springapp.Model._Thread; 21 23 import mk.profesori.springapp.Service.MainService; 22 24 23 25 @RestController 24 26 @RequestMapping("/public") 25 @CrossOrigin(origins = { "http://192.168.0.17:3000", "http://192.168.0. 28:3000" })27 @CrossOrigin(origins = { "http://192.168.0.17:3000", "http://192.168.0.39:3000" }) 26 28 public class PublicController { 27 29 … … 98 100 } 99 101 102 @RequestMapping(value = "/subject/{subjectId}", method = RequestMethod.GET) 103 public Subject getSubjectById(@PathVariable Long subjectId) { 104 return mainService.getSubjectById(subjectId); // vrakja predmet spored id 105 } 106 107 @RequestMapping(value = "/thread/{postId}", method = RequestMethod.GET) 108 public _Thread getThreadById(@PathVariable Long postId) { 109 return mainService.get_ThreadById(postId); // vrakja thread (tema) spored id 110 } 111 100 112 @RequestMapping(value = "/loginSuccessRegular", method = RequestMethod.GET) 101 113 public Map<String, String> loginSuccessRegular(@RequestParam String sessionId) { -
springapp/src/main/java/mk/profesori/springapp/Controller/SecureController.java
r8d83180 rc68150f 1 1 package mk.profesori.springapp.Controller; 2 2 3 import java.util.Collections;4 import java.util.Map;5 6 import org.springframework.beans.factory.annotation.Autowired;3 import com.fasterxml.jackson.databind.node.ObjectNode; 4 import mk.profesori.springapp.Model.CustomUserDetails; 5 import mk.profesori.springapp.Service.CustomUserDetailsService; 6 import mk.profesori.springapp.Service.MainService; 7 7 import org.springframework.security.core.Authentication; 8 8 import org.springframework.security.core.annotation.CurrentSecurityContext; 9 9 import org.springframework.security.core.context.SecurityContext; 10 10 import org.springframework.security.core.userdetails.UserDetails; 11 import org.springframework.web.bind.annotation.CrossOrigin; 12 import org.springframework.web.bind.annotation.PathVariable; 13 import org.springframework.web.bind.annotation.RequestBody; 14 import org.springframework.web.bind.annotation.RequestMapping; 15 import org.springframework.web.bind.annotation.RequestMethod; 16 import org.springframework.web.bind.annotation.RequestParam; 17 import org.springframework.web.bind.annotation.RestController; 18 19 import com.fasterxml.jackson.databind.node.ObjectNode; 20 21 import mk.profesori.springapp.Model.CustomUserDetails; 22 import mk.profesori.springapp.Service.CustomUserDetailsService; 23 import mk.profesori.springapp.Service.MainService; 11 import org.springframework.web.bind.annotation.*; 24 12 25 13 @RestController 26 14 @RequestMapping("/secure") 27 @CrossOrigin(origins = { "http://192.168.0.17:3000", "http://192.168.0. 28:3000" })15 @CrossOrigin(origins = { "http://192.168.0.17:3000", "http://192.168.0.39:3000" }) 28 16 public class SecureController { 29 17 30 @Autowired 31 private MainService mainService; 32 @Autowired 18 private final MainService mainService; 19 final 33 20 CustomUserDetailsService customUserDetailsService; 21 22 public SecureController(MainService mainService, CustomUserDetailsService customUserDetailsService) { 23 this.mainService = mainService; 24 this.customUserDetailsService = customUserDetailsService; 25 } 34 26 35 27 @RequestMapping(value = "/professor/{professorId}/addOpinion", method = RequestMethod.POST) … … 39 31 Authentication authentication = context.getAuthentication(); 40 32 41 if (authentication != null && authentication.getPrincipal() instanceof CustomUserDetails) { 42 CustomUserDetails currentUser = (CustomUserDetails) authentication.getPrincipal(); 43 String title = objectNode.get("title").asText(); 33 if (authentication != null && authentication.getPrincipal() instanceof CustomUserDetails currentUser) { 44 34 String content = objectNode.get("content").asText(); 45 mainService.addOpinion( title,content, professorId, currentUser);35 mainService.addOpinion(content, professorId, currentUser); 46 36 } 47 37 } … … 53 43 Authentication authentication = context.getAuthentication(); 54 44 55 if (authentication != null && authentication.getPrincipal() instanceof CustomUserDetails) { 56 CustomUserDetails currentUser = (CustomUserDetails) authentication.getPrincipal(); 45 if (authentication != null && authentication.getPrincipal() instanceof CustomUserDetails currentUser) { 57 46 String content = objectNode.get("content").asText(); 58 47 mainService.replyToOpinion(content, professorId, postId, currentUser); 48 } 49 } 50 51 @RequestMapping(value = "/subject/{subjectId}/addThread", method = RequestMethod.POST) 52 public void addThread(@RequestBody ObjectNode objectNode, @PathVariable Long subjectId, 53 @CurrentSecurityContext SecurityContext context) { 54 55 Authentication authentication = context.getAuthentication(); 56 57 if (authentication != null && authentication.getPrincipal() instanceof CustomUserDetails currentUser) { 58 String title = objectNode.get("title").asText(); 59 String content = objectNode.get("content").asText(); 60 mainService.addThread(title, content, subjectId, currentUser); 61 } 62 } 63 64 @RequestMapping(value = "/subject/{subjectId}/replyToThread/{postId}", method = RequestMethod.POST) 65 public void replyToThread(@RequestBody ObjectNode objectNode, @PathVariable Long subjectId, 66 @PathVariable Long postId, @CurrentSecurityContext SecurityContext context) { 67 68 Authentication authentication = context.getAuthentication(); 69 70 if (authentication != null && authentication.getPrincipal() instanceof CustomUserDetails currentUser) { 71 String content = objectNode.get("content").asText(); 72 mainService.replyToThread(content, subjectId, postId, currentUser); 59 73 } 60 74 } … … 64 78 65 79 Authentication authentication = context.getAuthentication(); 66 if (authentication != null && authentication.getPrincipal() instanceof CustomUserDetails) { 67 CustomUserDetails currentUser = (CustomUserDetails) authentication.getPrincipal(); 80 if (authentication != null && authentication.getPrincipal() instanceof CustomUserDetails currentUser) { 68 81 return customUserDetailsService.loadUserByUsername(currentUser.getEmail()); 69 82 } … … 72 85 } 73 86 74 @RequestMapping(value = "/professor/{professorId}/upvoteOpinion/{postId}", method = RequestMethod.GET) 75 public void upvoteOpinion(@PathVariable Long professorId, 76 @PathVariable Long postId, @CurrentSecurityContext SecurityContext context) { 87 @RequestMapping(value = "/upvoteOpinion/{postId}", method = RequestMethod.GET) 88 public void upvoteOpinion(@PathVariable Long postId, @CurrentSecurityContext SecurityContext context) { 77 89 78 90 Authentication authentication = context.getAuthentication(); 79 91 80 if (authentication != null && authentication.getPrincipal() instanceof CustomUserDetails) { 81 CustomUserDetails currentUser = (CustomUserDetails) authentication.getPrincipal(); 92 if (authentication != null && authentication.getPrincipal() instanceof CustomUserDetails currentUser) { 82 93 mainService.upvoteOpinion(postId, currentUser); 83 94 } 84 95 } 85 96 86 @RequestMapping(value = "/professor/{professorId}/downvoteOpinion/{postId}", method = RequestMethod.GET) 87 public void downvoteOpinion(@PathVariable Long professorId, 88 @PathVariable Long postId, @CurrentSecurityContext SecurityContext context) { 97 @RequestMapping(value = "/downvoteOpinion/{postId}", method = RequestMethod.GET) 98 public void downvoteOpinion(@PathVariable Long postId, @CurrentSecurityContext SecurityContext context) { 89 99 90 100 Authentication authentication = context.getAuthentication(); 91 101 92 if (authentication != null && authentication.getPrincipal() instanceof CustomUserDetails) { 93 CustomUserDetails currentUser = (CustomUserDetails) authentication.getPrincipal(); 102 if (authentication != null && authentication.getPrincipal() instanceof CustomUserDetails currentUser) { 94 103 mainService.downvoteOpinion(postId, currentUser); 95 104 } 96 105 } 97 106 107 @RequestMapping(value = "/upvoteThread/{postId}", method = RequestMethod.GET) 108 public void upvoteThread(@PathVariable Long postId, @CurrentSecurityContext SecurityContext context) { 109 110 Authentication authentication = context.getAuthentication(); 111 112 if (authentication != null && authentication.getPrincipal() instanceof CustomUserDetails currentUser) { 113 mainService.upvote_Thread(postId, currentUser); 114 } 115 } 116 117 @RequestMapping(value = "/downvoteThread/{postId}", method = RequestMethod.GET) 118 public void downvoteThread(@PathVariable Long postId, @CurrentSecurityContext SecurityContext context) { 119 120 Authentication authentication = context.getAuthentication(); 121 122 if (authentication != null && authentication.getPrincipal() instanceof CustomUserDetails currentUser) { 123 mainService.downvote_Thread(postId, currentUser); 124 } 125 } 126 98 127 } -
springapp/src/main/java/mk/profesori/springapp/Model/ConfirmationToken.java
r8d83180 rc68150f 12 12 import javax.persistence.SequenceGenerator; 13 13 14 import com.fasterxml.jackson.annotation.JsonIdentityInfo; 15 import com.voodoodyne.jackson.jsog.JSOGGenerator; 16 14 17 import lombok.Getter; 15 18 import lombok.NoArgsConstructor; … … 20 23 @NoArgsConstructor 21 24 @Entity 25 @JsonIdentityInfo(generator = JSOGGenerator.class) 22 26 public class ConfirmationToken { 23 27 24 28 @Id 25 29 @SequenceGenerator(name = "confirmation_token_sequence", sequenceName = "confirmation_token_sequence", allocationSize = 1) … … 42 46 private CustomUserDetails customUserDetails; 43 47 44 public ConfirmationToken(String token, LocalDateTime createdAt, LocalDateTime expiredAt, 45 CustomUserDetails customUserDetails) {46 48 public ConfirmationToken(String token, LocalDateTime createdAt, LocalDateTime expiredAt, 49 CustomUserDetails customUserDetails) { 50 47 51 this.token = token; 48 52 this.createdAt = createdAt; -
springapp/src/main/java/mk/profesori/springapp/Model/CustomUserDetails.java
r8d83180 rc68150f 1 1 package mk.profesori.springapp.Model; 2 2 3 import java.util.ArrayList; 4 import java.util.Collection; 5 import java.util.Collections; 6 import java.util.HashSet; 7 import java.util.List; 8 import java.util.Set; 9 10 import javax.persistence.CascadeType; 11 import javax.persistence.Entity; 12 import javax.persistence.EnumType; 13 import javax.persistence.Enumerated; 14 import javax.persistence.FetchType; 15 import javax.persistence.GeneratedValue; 16 import javax.persistence.GenerationType; 17 import javax.persistence.Id; 18 import javax.persistence.JoinTable; 19 import javax.persistence.JoinColumn; 20 import javax.persistence.ManyToMany; 21 import javax.persistence.OneToMany; 22 import javax.persistence.SequenceGenerator; 23 3 import com.fasterxml.jackson.annotation.JsonIdentityInfo; 4 import com.voodoodyne.jackson.jsog.JSOGGenerator; 5 import lombok.EqualsAndHashCode; 6 import lombok.Getter; 7 import lombok.NoArgsConstructor; 8 import lombok.Setter; 24 9 import org.springframework.security.core.GrantedAuthority; 25 10 import org.springframework.security.core.authority.SimpleGrantedAuthority; 26 11 import org.springframework.security.core.userdetails.UserDetails; 27 12 28 import com.fasterxml.jackson.annotation.JsonIdentityInfo; 29 import com.voodoodyne.jackson.jsog.JSOGGenerator; 30 31 import lombok.EqualsAndHashCode; 32 import lombok.Getter; 33 import lombok.NoArgsConstructor; 34 import lombok.Setter; 13 import javax.persistence.*; 14 import java.util.Collection; 15 import java.util.Collections; 16 import java.util.HashSet; 17 import java.util.Set; 35 18 36 19 @Getter … … 59 42 private Set<Post> authoredPosts = new HashSet<>(); 60 43 private Integer karma = 0; 61 @ManyToMany(fetch = FetchType.EAGER) 62 @JoinTable(name = "post_like", joinColumns = @JoinColumn(name = "custom_user_details_id"), inverseJoinColumns = @JoinColumn(name = "post_id")) 63 Set<Post> likedPosts; 64 @ManyToMany(fetch = FetchType.EAGER) 65 @JoinTable(name = "post_dislike", joinColumns = @JoinColumn(name = "custom_user_details_id"), inverseJoinColumns = @JoinColumn(name = "post_id")) 66 Set<Post> dislikedPosts; 44 45 public Set<PostVote> getVotes() { 46 return votes; 47 } 48 49 public void setVotes(Set<PostVote> votes) { 50 this.votes = votes; 51 } 52 53 @OneToMany(mappedBy = "user") 54 private Set<PostVote> votes = new HashSet<>(); 67 55 68 56 public CustomUserDetails(String fullName, String username, String email, String password, UserRole userRole) { … … 122 110 } 123 111 124 public Set<Post> getLikedPosts() { 125 return this.likedPosts; 112 @Override 113 public String toString() { 114 return this.id.toString(); 126 115 } 127 116 128 public void setLikedPosts(Set<Post> likedPosts) {129 this.likedPosts = likedPosts;130 }131 132 public Set<Post> getDislikedPosts() {133 return this.dislikedPosts;134 }135 136 public void setDislikedPosts(Set<Post> dislikedPosts) {137 this.likedPosts = dislikedPosts;138 }139 117 } -
springapp/src/main/java/mk/profesori/springapp/Model/Post.java
r8d83180 rc68150f 3 3 import java.time.LocalDateTime; 4 4 import java.util.ArrayList; 5 import java.util.HashSet; 5 6 import java.util.List; 6 7 import java.util.Set; … … 55 56 56 57 @ManyToOne 57 @JoinColumn(name = "parent_post_id" , nullable = true)58 @JoinColumn(name = "parent_post_id") 58 59 private Post parent; 60 61 @OneToMany(mappedBy = "post") 62 private Set<PostVote> votes = new HashSet<>(); 63 64 public Set<PostVote> getVotes() { 65 return votes; 66 } 67 68 public void setVotes(Set<PostVote> votes) { 69 this.votes = votes; 70 } 59 71 60 72 @OneToMany(mappedBy = "parent", cascade = CascadeType.ALL) 61 73 private List<Post> children = new ArrayList<>(); 62 63 @ManyToMany(mappedBy = "likedPosts")64 Set<CustomUserDetails> likes;65 66 @ManyToMany(mappedBy = "dislikedPosts")67 Set<CustomUserDetails> dislikes;68 74 69 75 // getters and setters … … 132 138 } 133 139 134 public Set<CustomUserDetails> getLikes() {135 return likes;136 }137 138 public void setLikes(Set<CustomUserDetails> likes) {139 this.likes = likes;140 }141 142 public Set<CustomUserDetails> getDislikes() {143 return dislikes;144 }145 146 public void setDislikes(Set<CustomUserDetails> dislikes) {147 this.dislikes = dislikes;148 }149 150 140 // konstruktor so parent (koga e reply) 151 141 public Post(String title, String content, CustomUserDetails author, LocalDateTime timePosted, … … 174 164 } 175 165 166 @Override 167 public String toString() { 168 return this.postId.toString(); 169 } 170 176 171 } -
springapp/src/main/java/mk/profesori/springapp/Model/StudyProgramme.java
r8d83180 rc68150f 12 12 import javax.persistence.ManyToOne; 13 13 import javax.persistence.OneToMany; 14 import javax.persistence.OneToOne;15 14 import javax.persistence.Table; 16 15 … … 41 40 private Set<Subject> subjects = new HashSet<>(); 42 41 43 @OneToOne(mappedBy = "relatedStudyProgramme")44 private Section relatedSection;45 46 42 // getters 47 43 public Long getStudyProgrammeId() { … … 65 61 } 66 62 67 public Section getRelatedSection() {68 return relatedSection;69 }70 63 } -
springapp/src/main/java/mk/profesori/springapp/Model/Subject.java
r8d83180 rc68150f 15 15 16 16 import com.fasterxml.jackson.annotation.JsonIdentityInfo; 17 import com. fasterxml.jackson.annotation.ObjectIdGenerators;17 import com.voodoodyne.jackson.jsog.JSOGGenerator; 18 18 19 19 @Entity 20 20 @Table(name = "subject") 21 @JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "subjectId")21 @JsonIdentityInfo(generator = JSOGGenerator.class) 22 22 public class Subject { 23 23 -
springapp/src/main/java/mk/profesori/springapp/Model/University.java
r8d83180 rc68150f 15 15 16 16 import com.fasterxml.jackson.annotation.JsonIdentityInfo; 17 import com. fasterxml.jackson.annotation.ObjectIdGenerators;17 import com.voodoodyne.jackson.jsog.JSOGGenerator; 18 18 19 19 @Entity 20 20 @Table(name = "university") 21 @JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "universityId")21 @JsonIdentityInfo(generator = JSOGGenerator.class) 22 22 public class University { 23 23 -
springapp/src/main/java/mk/profesori/springapp/Model/_Thread.java
r8d83180 rc68150f 2 2 3 3 import java.time.LocalDateTime; 4 import java.util.ArrayList; 4 5 5 import java.util.List; 6 6 7 import javax.persistence.Column;8 7 import javax.persistence.DiscriminatorValue; 9 import javax.persistence.ElementCollection; 8 10 9 import javax.persistence.Entity; 11 10 import javax.persistence.JoinColumn; 12 11 import javax.persistence.ManyToOne; 13 12 13 import lombok.NoArgsConstructor; 14 14 15 @Entity 15 16 @DiscriminatorValue("thread") 17 @NoArgsConstructor 16 18 public class _Thread extends Post { 17 18 @Column(name = "tags") // unused19 @ElementCollection20 private List<String> tags = new ArrayList<>();21 22 @ManyToOne23 @JoinColumn(name = "section_id")24 private Section parentSection;25 19 26 20 @ManyToOne … … 31 25 public _Thread(String title, String content, CustomUserDetails author, LocalDateTime timePosted, 32 26 LocalDateTime timeLastEdited, 33 Post parent, List<Post> children, S ection parentSection, Subject targetSubject) {27 Post parent, List<Post> children, Subject targetSubject) { 34 28 super(title, content, author, timePosted, timeLastEdited, parent, children); 35 this.parentSection = parentSection;36 29 this.targetSubject = targetSubject; 37 30 } … … 39 32 // konstruktor bez parent (koga NE e reply) 40 33 public _Thread(String title, String content, CustomUserDetails author, LocalDateTime timePosted, 41 LocalDateTime timeLastEdited, List<Post> children, S ection parentSection, Subject targetSubject) {34 LocalDateTime timeLastEdited, List<Post> children, Subject targetSubject) { 42 35 super(title, content, author, timePosted, timeLastEdited, children); 43 this.parentSection = parentSection;44 36 this.targetSubject = targetSubject; 45 }46 47 // getters48 public List<String> getTags() {49 return tags;50 }51 52 public Section getParentSection() {53 return parentSection;54 37 } 55 38 -
springapp/src/main/java/mk/profesori/springapp/Repository/OpinionRepository.java
r8d83180 rc68150f 1 1 package mk.profesori.springapp.Repository; 2 2 3 import mk.profesori.springapp.Model.Opinion; 3 4 import org.springframework.data.repository.CrudRepository; 4 5 import org.springframework.stereotype.Repository; 5 6 6 import mk.profesori.springapp.Model.Opinion;7 8 7 @Repository 9 public interface OpinionRepository extends CrudRepository<Opinion, 10 public Opinion findByPostId(Long id);8 public interface OpinionRepository extends CrudRepository<Opinion,Long> { 9 Opinion findByPostId(Long postId); 11 10 } -
springapp/src/main/java/mk/profesori/springapp/Security/SecurityConfiguration.java
r8d83180 rc68150f 37 37 @Override 38 38 public void addCorsMappings(CorsRegistry registry) { 39 registry.addMapping("/**").allowedOrigins("http://192.168.0.17:3000", "http://192.168.0. 28:3000")39 registry.addMapping("/**").allowedOrigins("http://192.168.0.17:3000", "http://192.168.0.39:3000") 40 40 .allowCredentials(true); 41 41 } -
springapp/src/main/java/mk/profesori/springapp/Service/EmailValidator.java
r8d83180 rc68150f 13 13 public boolean test(String email) { 14 14 15 String regex = "^(.+)@(.+)$"; 15 String regex = "^(.+)@(.+)$"; //TODO bilosto@@ ->> illegal address exception od mailsender 16 16 17 17 Pattern pattern = Pattern.compile(regex); -
springapp/src/main/java/mk/profesori/springapp/Service/MainService.java
r8d83180 rc68150f 1 1 package mk.profesori.springapp.Service; 2 3 import mk.profesori.springapp.Model.*; 4 import mk.profesori.springapp.Repository.*; 5 import org.springframework.stereotype.Service; 2 6 3 7 import java.util.ArrayList; 4 8 import java.util.List; 5 9 6 import org.springframework.beans.factory.annotation.Autowired;7 import org.springframework.stereotype.Service;8 9 import mk.profesori.springapp.Repository.CityRepository;10 import mk.profesori.springapp.Repository.FacultyRepository;11 import mk.profesori.springapp.Repository.OpinionRepository;12 import mk.profesori.springapp.Repository.ProfessorRepository;13 import mk.profesori.springapp.Repository.StudyProgrammeRepository;14 import mk.profesori.springapp.Repository.UniversityRepository;15 import mk.profesori.springapp.Repository.UserRepository;16 import mk.profesori.springapp.Model.City;17 import mk.profesori.springapp.Model.CustomUserDetails;18 import mk.profesori.springapp.Model.Faculty;19 import mk.profesori.springapp.Model.Opinion;20 import mk.profesori.springapp.Model.Professor;21 import mk.profesori.springapp.Model.StudyProgramme;22 import mk.profesori.springapp.Model.University;23 24 10 @Service 25 11 public class MainService { 26 12 27 @Autowired 28 private ProfessorRepository professorRepository; 29 @Autowired 30 private StudyProgrammeRepository studyProgrammeRepository; 31 @Autowired 32 private FacultyRepository facultyRepository; 33 @Autowired 34 private UniversityRepository universityRepository; 35 @Autowired 36 private CityRepository cityRepository; 37 @Autowired 38 private OpinionRepository opinionRepository; 39 @Autowired 40 private UserRepository userRepository; 13 private final ProfessorRepository professorRepository; 14 private final StudyProgrammeRepository studyProgrammeRepository; 15 private final FacultyRepository facultyRepository; 16 private final UniversityRepository universityRepository; 17 private final CityRepository cityRepository; 18 private final OpinionRepository opinionRepository; 19 private final _ThreadRepository _threadRepository; 20 private final SubjectRepository subjectRepository; 21 private final PostVoteRepository postVoteRepository; 22 private final UserRepository userRepository; 23 24 public MainService(ProfessorRepository professorRepository, StudyProgrammeRepository studyProgrammeRepository, FacultyRepository facultyRepository, UniversityRepository universityRepository, CityRepository cityRepository, OpinionRepository opinionRepository, _ThreadRepository _threadRepository, SubjectRepository subjectRepository, PostVoteRepository postVoteRepository, UserRepository userRepository) { 25 this.professorRepository = professorRepository; 26 this.studyProgrammeRepository = studyProgrammeRepository; 27 this.facultyRepository = facultyRepository; 28 this.universityRepository = universityRepository; 29 this.cityRepository = cityRepository; 30 this.opinionRepository = opinionRepository; 31 this._threadRepository = _threadRepository; 32 this.subjectRepository = subjectRepository; 33 this.postVoteRepository = postVoteRepository; 34 this.userRepository = userRepository; 35 } 41 36 42 37 public List<Professor> getAllProfessors() { 43 38 44 List<Professor> list = new ArrayList<>(); 45 professorRepository.findAll().forEach(list::add); 46 return list; 39 return new ArrayList<>(professorRepository.findAll()); 47 40 } 48 41 … … 56 49 Faculty faculty = facultyRepository.findByFacultyId(facultyId); 57 50 58 List<Professor> list = new ArrayList<>(); 59 professorRepository.findByFaculty(faculty).forEach(list::add); 60 return list; 51 return new ArrayList<>(professorRepository.findByFaculty(faculty)); 61 52 } 62 53 63 54 public List<Professor> getProfessorsByNameContains(String contained) { 64 List<Professor> list = new ArrayList<>(); 65 professorRepository.findByProfessorNameContainingIgnoreCase(contained).forEach(list::add); 66 return list; 55 return new ArrayList<>(professorRepository.findByProfessorNameContainingIgnoreCase(contained)); 67 56 } 68 57 69 58 public List<StudyProgramme> getAllStudyProgrammes() { 70 59 71 List<StudyProgramme> list = new ArrayList<>(); 72 studyProgrammeRepository.findAll().forEach(list::add); 73 return list; 60 return new ArrayList<>(studyProgrammeRepository.findAll()); 74 61 } 75 62 … … 83 70 Faculty faculty = facultyRepository.findByFacultyId(facultyId); 84 71 85 List<StudyProgramme> list = new ArrayList<>(); 86 studyProgrammeRepository.findByFaculty(faculty).forEach(list::add); 87 return list; 72 return new ArrayList<>(studyProgrammeRepository.findByFaculty(faculty)); 88 73 } 89 74 90 75 public List<Faculty> getAllFaculties() { 91 List<Faculty> list = new ArrayList<>(); 92 facultyRepository.findAll().forEach(list::add); 93 return list; 76 return new ArrayList<>(facultyRepository.findAll()); 94 77 } 95 78 … … 102 85 University university = universityRepository.findByUniversityId(universityId); 103 86 104 List<Faculty> list = new ArrayList<>(); 105 facultyRepository.findByUniversity(university).forEach(list::add); 106 return list; 87 return new ArrayList<>(facultyRepository.findByUniversity(university)); 107 88 } 108 89 109 90 public List<University> getAllUniversities() { 110 List<University> list = new ArrayList<>(); 111 universityRepository.findAll().forEach(list::add); 112 return list; 91 return new ArrayList<>(universityRepository.findAll()); 113 92 } 114 93 … … 121 100 City city = cityRepository.findByCityId(cityId); 122 101 123 List<University> list = new ArrayList<>(); 124 universityRepository.findByCity(city).forEach(list::add); 125 return list; 102 return new ArrayList<>(universityRepository.findByCity(city)); 126 103 } 127 104 128 105 public List<City> getAllCities() { 129 List<City> list = new ArrayList<>(); 130 cityRepository.findAll().forEach(list::add); 131 return list; 106 return new ArrayList<>(cityRepository.findAll()); 132 107 } 133 108 … … 136 111 } 137 112 138 public void addOpinion(String title, Stringcontent, Long professorId, CustomUserDetails currentUser) {113 public void addOpinion(String content, Long professorId, CustomUserDetails currentUser) { 139 114 140 115 Professor targetProfessor = professorRepository.findByProfessorId(professorId); 141 116 142 Opinion opinionToAdd = new Opinion( title, content, currentUser, null, null,117 Opinion opinionToAdd = new Opinion(null, content, currentUser, null, null, 143 118 null, targetProfessor); 144 119 … … 155 130 opinionRepository.save(opinionToAdd); 156 131 132 //mozda ne treba 157 133 targetOpinion.getChildren().add(opinionToAdd); 158 134 opinionRepository.save(targetOpinion); 159 135 } 160 136 161 public void upvoteOpinion(Long postId, CustomUserDetails currentUser) {162 Opinion targetOpinion = opinionRepository.findByPostId(postId);137 public void addThread(String title, String content, Long subjectId, CustomUserDetails currentUser) { 138 Subject targetSubject = subjectRepository.findBySubjectId(subjectId); 163 139 164 if (!targetOpinion.getLikes().contains(currentUser)) { 165 166 targetOpinion.getLikes().add(currentUser); 167 // opinionRepository.save(targetOpinion); 168 169 targetOpinion.getAuthor().setKarma(targetOpinion.getAuthor().getKarma() + 1); 170 userRepository.save(targetOpinion.getAuthor()); 171 172 currentUser.getLikedPosts().add(targetOpinion); 173 userRepository.save(currentUser); 174 } 140 _Thread _threadToAdd = new _Thread(title, content, currentUser, null, null, null, targetSubject); 141 _threadRepository.save(_threadToAdd); 175 142 } 176 143 144 public void replyToThread(String content, Long subjectId, Long postId, CustomUserDetails currentUser) { 145 Subject targetSubject = subjectRepository.findBySubjectId(subjectId); 146 _Thread target_Thread = _threadRepository.findByPostId(postId); 147 148 _Thread _threadToAdd = new _Thread(null, content, currentUser, null, null, target_Thread, null, targetSubject); 149 _threadRepository.save(_threadToAdd); 150 151 //mozda ne treba 152 target_Thread.getChildren().add(_threadToAdd); 153 _threadRepository.save(target_Thread); 154 } 155 156 public Subject getSubjectById(Long subjectId) { 157 return subjectRepository.findBySubjectId(subjectId); 158 } 159 160 public _Thread get_ThreadById(Long postId) { 161 return _threadRepository.findByPostId(postId); 162 } 163 164 public void upvoteOpinion(Long postId, CustomUserDetails currentUser) { 165 Post targetPost = opinionRepository.findByPostId(postId); 166 PostVote voteToAdd = new PostVote(currentUser, targetPost, VoteType.UPVOTE); 167 postVoteRepository.save(voteToAdd); 168 targetPost.getAuthor().setKarma(targetPost.getAuthor().getKarma()+1); 169 userRepository.save(targetPost.getAuthor()); 170 } 177 171 public void downvoteOpinion(Long postId, CustomUserDetails currentUser) { 178 Opinion targetOpinion = opinionRepository.findByPostId(postId); 172 Post targetPost = opinionRepository.findByPostId(postId); 173 PostVote voteToAdd = new PostVote(currentUser, targetPost, VoteType.DOWNVOTE); 174 postVoteRepository.save(voteToAdd); 175 targetPost.getAuthor().setKarma(targetPost.getAuthor().getKarma()-1); 176 userRepository.save(targetPost.getAuthor()); 177 } 179 178 180 if (!targetOpinion.getDislikes().contains(currentUser)) { 179 public void upvote_Thread(Long postId, CustomUserDetails currentUser) { 180 Post targetPost = _threadRepository.findByPostId(postId); 181 PostVote voteToAdd = new PostVote(currentUser, targetPost, VoteType.UPVOTE); 182 postVoteRepository.save(voteToAdd); 183 targetPost.getAuthor().setKarma(targetPost.getAuthor().getKarma()+1); 184 userRepository.save(targetPost.getAuthor()); 181 185 182 targetOpinion.getDislikes().add(currentUser); 183 // opinionRepository.save(targetOpinion); 184 185 targetOpinion.getAuthor().setKarma(targetOpinion.getAuthor().getKarma() - 1); 186 userRepository.save(targetOpinion.getAuthor()); 187 188 currentUser.getDislikedPosts().add(targetOpinion); 189 userRepository.save(currentUser); 190 } 186 } 187 public void downvote_Thread(Long postId, CustomUserDetails currentUser) { 188 Post targetPost = _threadRepository.findByPostId(postId); 189 PostVote voteToAdd = new PostVote(currentUser, targetPost, VoteType.DOWNVOTE); 190 postVoteRepository.save(voteToAdd); 191 targetPost.getAuthor().setKarma(targetPost.getAuthor().getKarma()-1); 192 userRepository.save(targetPost.getAuthor()); 191 193 } 192 194 } -
springapp/src/main/resources/application.properties
r8d83180 rc68150f 4 4 spring.datasource.password=1win7337 5 5 spring.jpa.hibernate.ddl-auto=update 6 spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect 6 7 spring.jpa.show-sql=false 7 spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect8 8 spring.jpa.properties.hibernate.format_sql=true 9 9 server.address=192.168.0.17 10 spring.mail.host=192.168.0. 2810 spring.mail.host=192.168.0.39 11 11 spring.mail.username=mailuser 12 12 spring.mail.password=mailpass
Note:
See TracChangeset
for help on using the changeset viewer.