Changeset 9bf1f8d


Ignore:
Timestamp:
01/20/23 22:57:18 (16 months ago)
Author:
viktor <viktor@…>
Branches:
main
Children:
8dffe02
Parents:
4abf55a
Message:

prefinal

Files:
5 added
10 deleted
48 edited
2 moved

Legend:

Unmodified
Added
Removed
  • reactapp/public/index.html

    r4abf55a r9bf1f8d  
    33  <head>
    44    <meta charset="utf-8" />
    5     <link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
     5    <link rel="icon" href="%PUBLIC_URL%/favicon.ico?v=2" />
    66    <meta name="viewport" content="width=device-width, initial-scale=1" />
    77    <meta name="theme-color" content="#000000" />
  • reactapp/src/App.js

    r4abf55a r9bf1f8d  
    44import Registration from "./Pages/Registration";
    55import { BrowserRouter, Routes, Route, Navigate } from "react-router-dom";
    6 import Home from "./Pages/Home";
     6import Root from "./Components/Root";
    77import UserDashboard from "./Pages/UserDashboard";
    88import Subject from "./Pages/Subject";
     
    1515import Topic from "./Pages/Topic";
    1616import LoadingSpinner from "./Components/Styled/LoadingSpinner.style";
     17import Home from "./Pages/Home";
     18import PublicUserProfile from "./Pages/PublicUserProfile";
    1719
    1820export default function App() {
     
    5153      <BrowserRouter>
    5254        <Routes>
    53           <Route path="/" element={<Home />}>
     55          <Route path="/" element={<Root />}>
     56            <Route path="/" element={<Home/>}/>
    5457            <Route path="login" element={<Login />}></Route>
    5558            <Route path="registration" element={<Registration />}></Route>
     
    5861            </Route>
    5962            <Route path="university/:universityId" element={<University />} />
     63            <Route path="user/:userId" element={<PublicUserProfile />} />
    6064            <Route path="faculty/:facultyId" element={<Faculty />} />
    6165            <Route path="subject/:subjectId" element={<Subject />} />
  • reactapp/src/Components/Logout.js

    r4abf55a r9bf1f8d  
    33import Cookies from "js-cookie";
    44import { LogoutButton } from "./Styled/UserDetails.style";
    5 import { Navigate } from "react-router-dom";
    65
    76function Logout() {
     
    1312  };
    1413
    15   return <LogoutButton onClick={handleLogout}>Одјави се</LogoutButton>;
     14  return <LogoutButton onClick={handleLogout} style={{color:"black"}}>Одјави се</LogoutButton>;
    1615}
    1716
  • reactapp/src/Components/OpinionTree.js

    r4abf55a r9bf1f8d  
    2626import axios from "../api/axios";
    2727
    28 function OpinionTree({ professor }) {
     28const OpinionTree = ({professor, relatedOpinions}) => {
    2929  var renderedOpinionIds = [];
    3030  var postCount; // za da ne go pokazuva ispod postot
     
    3535  const [replyModalDisplay, setReplyModalDisplay] = useState("none");
    3636  const [replyContent, setReplyContent] = useState("");
    37   const [postForModal, setPostForModal] = useState(null);
     37  const [postForReplyModal, setPostForReplyModal] = useState(null);
     38  const [reportModalDisplay, setReportModalDisplay] = useState("none");
     39  const [reportContent, setReportContent] = useState("")
     40  const [postForReportModal, setPostForReportModal] = useState(null);
     41
    3842  const [user, setUser] = useState(null);
    3943  const [loadedUser, setLoadedUser] = useState(false);
     
    4246
    4347  useEffect(() => {
    44     const url = `http://192.168.0.29:8080/secure/currentUser`;
     48    const url = `http://192.168.1.254:8080/secure/currentUser`;
    4549
    4650    const fetchUser = async () => {
     
    6872      ) {
    6973        const response = await axios(
    70           `http://192.168.0.29:8080/secure/upvoteOpinion/${post.postId}`,
     74          `http://192.168.1.254:8080/secure/upvoteOpinion/${post.postId}`,
    7175          {
    7276            method: "get",
     
    9195      ) {
    9296        const response = await axios(
    93           `http://192.168.0.29:8080/secure/downvoteOpinion/${post.postId}`,
     97          `http://192.168.1.254:8080/secure/downvoteOpinion/${post.postId}`,
    9498          {
    9599            method: "get",
     
    110114    if (auth) {
    111115      setReplyModalDisplay("block");
    112       setPostForModal(opinion);
     116      setPostForReplyModal(opinion);
    113117      document.body.style.overflowY = "hidden";
    114118    } else {
     
    116120    }
    117121  };
     122
     123  const handleReport = (opinion) => {
     124    if (auth) {
     125      setReportModalDisplay("block");
     126      setPostForReportModal(opinion);
     127      document.body.style.overflowY = "hidden";
     128    } else {
     129      navigate("/login");
     130    }
     131  }
    118132
    119133  const handleModalCloseClick = () => {
    120134    setReplyModalDisplay("none");
     135    setReportModalDisplay("none");
    121136    document.body.style.overflowY = "auto";
    122137  };
    123138
    124   const handleContentChange = (e) => {
     139  const handleReplyContentChange = (e) => {
    125140    setReplyContent(e.target.value);
     141  };
     142
     143  const handleReportContentChange = (e) => {
     144    setReportContent(e.target.value);
    126145  };
    127146
     
    131150    if (!replyContent.length < 1) {
    132151      const response = await axios(
    133         `http://192.168.0.29:8080/secure/professor/${professor.professorId}/replyToOpinion/${postId}`,
     152        `http://192.168.1.254:8080/secure/professor/${professor.professorId}/replyToOpinion/${postId}`,
    134153        {
    135154          method: "post",
     
    147166  };
    148167
     168  const handleReportSubmit = async (e, postId) => {
     169    e.preventDefault();
     170
     171    if (!reportContent.length < 1) {
     172      const response = await axios(
     173          `http://192.168.1.254:8080/secure/reportOpinion/${postId}`,
     174          {
     175            method: "post",
     176            data: {
     177              description: reportContent,
     178            },
     179            withCredentials: true,
     180          }
     181      );
     182      setErrorMessage("");
     183      window.location.reload();
     184    } else {
     185      setErrorMessage("Полето за содржина не смее да биде празно");
     186    }
     187  };
     188
    149189  function displayChildPosts(child, parentPostAuthorUsername, replyIndent) {
    150190    if (child == null) return;
    151     postCount = renderedOpinionIds.push(child.postId);
     191    if (!renderedOpinionIds.includes(child.postId)) {postCount = renderedOpinionIds.push(child.postId);}
    152192    return (
    153193      <div key={child.postId}>
     
    158198              му реплицирал на {parentPostAuthorUsername}
    159199            </p>
    160             <p style={{ marginBottom: "10px", maxWidth: "90%" }}>
     200            <p style={{ marginBottom: "10px", maxWidth: "85%" }}>
    161201              {child.content}
    162202            </p>
     
    228268                onClick={() => handleReply(child)}
    229269              />
     270
     271              <StyledFontAwesomeIcon
     272                  icon={solid("flag")}
     273                  right={130 + "px"}
     274                  color="darkgrey"
     275                  onClick={() => handleReport(child)}
     276              />
    230277            </div>
    231278          </OpinionReplyCardContent>
     
    244291  return (
    245292    <div className="opinionTree">
    246       {professor.relatedOpinions.map((opinion) => {
    247         if (!renderedOpinionIds.includes(opinion.postId)) {
     293      {relatedOpinions.map((opinion) => {
     294        if (!renderedOpinionIds.includes(opinion.postId) && opinion.parent === null) {
    248295          postCount = renderedOpinionIds.push(opinion.postId);
    249296          return (
     
    257304                    напишал
    258305                  </p>
    259                   <p style={{ marginBottom: "10px", maxWidth: "90%" }}>
     306                  <p style={{ marginBottom: "10px", maxWidth: "85%" }}>
    260307                    {opinion.content}
    261308                  </p>
     
    335382                      onClick={() => handleReply(opinion)}
    336383                    />
     384
     385                    <StyledFontAwesomeIcon
     386                        icon={solid("flag")}
     387                        right={130 + "px"}
     388                        color="darkgrey"
     389                        onClick={() => handleReport(opinion)}
     390                    />
    337391                  </div>
    338392                </OpinionCardContent>
     
    346400        return null;
    347401      })}
    348       {postForModal && (
     402      {postForReplyModal && (
    349403        <Modal display={replyModalDisplay}>
    350404          <ModalContent>
     
    352406              <ModalClose onClick={handleModalCloseClick}>&times;</ModalClose>
    353407              <h3 style={{ marginTop: "5px" }}>
    354                 Реплика на {postForModal.author.username}
     408                Реплика на {postForReplyModal.author.username}
    355409              </h3>
    356410            </ModalHeader>
    357             <form onSubmit={(e) => handleReplySubmit(e, postForModal.postId)}>
     411            <form onSubmit={(e) => handleReplySubmit(e, postForReplyModal.postId)}>
    358412              <ModalBody>
    359413                <label htmlFor="content">
     
    364418                    cols="100"
    365419                    value={replyContent}
    366                     onChange={handleContentChange}
     420                    onChange={handleReplyContentChange}
    367421                    spellCheck={false}
    368422                  />
     
    379433        </Modal>
    380434      )}
     435      {postForReportModal && (
     436          <Modal display={reportModalDisplay}>
     437            <ModalContent>
     438              <ModalHeader>
     439                <ModalClose onClick={handleModalCloseClick}>&times;</ModalClose>
     440                <h3 style={{ marginTop: "5px" }}>
     441                  Пријава за мислење #{postForReportModal.postId}
     442                </h3>
     443              </ModalHeader>
     444              <form onSubmit={(e) => handleReportSubmit(e, postForReportModal.postId)}>
     445                <ModalBody>
     446                  <label htmlFor="content">
     447                    <b>Наведете причина</b>:
     448                    <ModalTextarea
     449                        id="content"
     450                        rows="8"
     451                        cols="100"
     452                        value={reportContent}
     453                        onChange={handleReportContentChange}
     454                        spellCheck={false}
     455                    />
     456                  </label>
     457                </ModalBody>
     458                <p
     459                    style={{ color: "red", marginLeft: "15px", marginTop: "10px" }}
     460                >
     461                  {errorMessage}
     462                </p>
     463                <ModalFooter type="submit">ПРИЈАВИ</ModalFooter>
     464              </form>
     465            </ModalContent>
     466          </Modal>
     467      )}
    381468    </div>
    382469  );
  • reactapp/src/Components/Search.js

    r4abf55a r9bf1f8d  
    1313  const [query, setQuery] = useState("");
    1414  const [professors, setProfessors] = useState([]);
     15  const [subjects, setSubjects] = useState([]);
    1516
    1617  useEffect(() => {
    17     const url = `http://192.168.0.29:8080/public/professors/nameContains/${transliterate(
    18       query
    19     )}`;
    20 
    2118    const fetchData = async () => {
    2219      try {
    23         const response = await fetch(url);
    24         var cyclicGraph = await response.json();
    25         var jsogStructure = JSOG.encode(cyclicGraph);
    26         cyclicGraph = JSOG.decode(jsogStructure);
    27         setProfessors(cyclicGraph);
     20        Promise.all([fetch(`http://192.168.1.254:8080/public/professors/nameContains/${transliterate(query)}`),
     21          fetch(`http://192.168.1.254:8080/public/subjects/nameContains/${transliterate(query)}`)])
     22            .then(([resProfessors, resSubjects]) => Promise.all([resProfessors.json(), resSubjects.json()]))
     23            .then(([dataProfessors, dataSubjects]) => {
     24              let cyclicGraph1 = dataProfessors;
     25              let jsogStructure1 = JSOG.encode(cyclicGraph1);
     26              cyclicGraph1 = JSOG.decode(jsogStructure1);
     27              setProfessors(cyclicGraph1);
     28
     29              let cyclicGraph2 = dataSubjects;
     30              let jsogStructure2 = JSOG.encode(cyclicGraph2);
     31              cyclicGraph2 = JSOG.decode(jsogStructure2);
     32              setSubjects(cyclicGraph2);
     33            })
    2834      } catch (error) {
    2935        console.log("Fetching error", error);
     
    3137    };
    3238
    33     if (query.length > 2) fetchData();
     39    if (query.length > 3) fetchData();
    3440  }, [query]);
    3541
     
    3945    if (event.key === "Enter") {
    4046      setExpanded(false);
    41       navigate("/search", { state: professors });
     47      navigate("/search", { state: professors.concat(subjects) });
    4248    }
    4349  };
     
    7177      <SearchDropdown
    7278        display={
    73           query.length > 2 && professors.length > 0 && expanded
     79          query.length > 3 && (professors.length > 0 || subjects.length > 0) && expanded
    7480            ? "block"
    7581            : "none"
    7682        }
    7783      >
    78         {query.length > 2 &&
    79           professors.slice(0, 7).map((professor) => (
     84        {query.length > 3 &&
     85          professors.concat(subjects).slice(0, 7).map((match) => (
    8086            <SearchResult
    81               key={professor.professorId}
     87              key={match.professorId !== undefined ? match.professorId : match.subjectId}
    8288              onMouseDown={(event) => {
    8389                event.preventDefault();
     
    8692              margin="0"
    8793            >
    88               <SearchResultLink href={"/professor/" + professor.professorId}>
     94              <SearchResultLink href={`/${match.professorId !== undefined ? 'professor': 'subject'}/` + `${match.professorId !== undefined ? match.professorId : match.subjectId}`}>
    8995                <SearchResultText weight="bold" size="medium">
    90                   {professor.professorName}
     96                  {match.professorId !== undefined ? match.professorName : match.subjectName}
    9197                </SearchResultText>
    9298                <SearchResultText weight="normal" size="er">
    93                   {professor.faculty.facultyName}
     99                  {match.professorId !== undefined ? match.faculty.facultyName : match.studyProgramme.faculty.facultyName}
    94100                </SearchResultText>
    95101              </SearchResultLink>
  • reactapp/src/Components/Styled/Main.style.js

    r4abf55a r9bf1f8d  
    2525export const CurrentPageNav = styled.div`
    2626  font-style: italic;
    27   font-size: 14px;
     27  font-size: 16px;
     28  font-weight: bold;
    2829`;
  • reactapp/src/Components/Styled/Search.style.js

    r4abf55a r9bf1f8d  
    33
    44export const SearchBox = styled.input`
    5   width: 350px;
     5  width: 450px;
    66  box-sizing: border-box;
    77  border: 2px solid #ccc;
     
    2323  padding: 12px 16px;
    2424  z-index: 1;
    25   width: 350px;
     25  width: 450px;
    2626  padding: 0;
    2727`;
  • reactapp/src/Components/SubjectsAccordion.js

    r4abf55a r9bf1f8d  
    66} from "./Styled/SubjectsAccordion.style.";
    77import { EntityLi, EntityUl, EntityParam } from "./Styled/EntityList.style";
     8import JSOG from "jsog";
    89
    910const SubjectsAccordion = (props) => {
    1011  const [height, setHeight] = useState("0");
    1112  const [opacity, setOpacity] = useState(0);
     13
     14  const [subjects, setSubjects] = useState(null);
     15  const [loadedSubjects, setLoadedSubjects] = useState(false);
     16  const [counts, setCounts] = useState(null);
     17  const [loadedCounts, setLoadedCounts] = useState(false);
     18
     19  const [fetchError, setFetchError] = useState(false);
    1220  const handleClick = () => {
    1321    if (height === "0") {
    1422      setHeight("auto");
    1523      setOpacity(1);
     24      try {
     25        Promise.all([fetch(`http://192.168.1.254:8080/public/subjects?studyProgrammeId=${props.title.studyProgrammeId}`),
     26          fetch(`http://192.168.1.254:8080/public/study_programme/${props.title.studyProgrammeId}/threadCountForEachSubject`)])
     27            .then(([resSubjects, counts]) => Promise.all([resSubjects.json(), counts.json()]))
     28            .then(([dataSubjects, dataCounts]) => {
     29              let cyclicGraph1 = dataSubjects;
     30              let jsogStructure1 = JSOG.encode(cyclicGraph1);
     31              cyclicGraph1 = JSOG.decode(jsogStructure1);
     32              setSubjects(cyclicGraph1);
     33              setLoadedSubjects(true);
     34
     35              let cyclicGraph2 = dataCounts;
     36              let jsogStructure2 = JSOG.encode(cyclicGraph2);
     37              cyclicGraph2 = JSOG.decode(jsogStructure2);
     38              setCounts(cyclicGraph2);
     39              setLoadedCounts(true);
     40            })
     41
     42      } catch (error) {
     43        setFetchError(true);
     44      }
    1645    } else {
    1746      setHeight("0");
     
    2756      <SubjectsAccordionDiv height={height} opacity={opacity}>
    2857        <EntityUl>
    29           {props.content &&
    30             props.content.map((item) => {
    31               let totalPosts = item.threads.length;
     58          {loadedSubjects &&
     59            subjects.map((item, idx) => {
     60              let totalPosts = parseInt(counts[idx].split(",")[1]);
    3261              return (
    3362                <EntityLi key={item.subjectName} bgcolor="cornsilk">
  • reactapp/src/Components/UserHeader.js

    r4abf55a r9bf1f8d  
    1111
    1212  useEffect(() => {
    13     const url = `http://192.168.0.29:8080/secure/currentUser`;
     13    const url = `http://192.168.1.254:8080/secure/currentUser`;
    1414
    1515    const fetchUser = async () => {
     
    3030
    3131  return loadedUser ? (
    32     <div style={{ float: "left", marginTop: 20, marginLeft: 40 }}>
     32    <div style={{ float: "left", marginTop: 20, marginLeft: 10 }}>
    3333      Најавен/а: <a href="/user_dashboard">{user.username}</a> <Logout />{" "}
    3434    </div>
    3535  ) : (
    36     <div style={{ float: "left", marginTop: 25, marginLeft: 60 }}>
     36    <div style={{ float: "left", marginTop: 25, marginLeft: 10 }}>
    3737      <LoadingSpinner/>
    3838    </div>
  • reactapp/src/Pages/Faculty.js

    r4abf55a r9bf1f8d  
    2020const Faculty = () => {
    2121  let params = useParams();
     22
     23  const [professors, setProfessors] = useState(null);
     24  const [studyProgrammes, setStudyProgrammes] = useState(null);
     25  const [professorOpinionCount, setProfessorOpinionCount] = useState(null);
    2226  const [loadedProfessors, setLoadedProfessors] = useState(false);
    2327  const [loadedStudyProgrammes, setLoadedStudyProgrammes] = useState(false);
    24   const [professors, setProfessors] = useState(null);
    25   const [studyProgrammes, setStudyProgrammes] = useState(null);
     28  const [loadedProfessorOpinionCount, setLoadedProfessorOpinionCount] = useState(false);
     29
    2630  const [fetchError, setFetchError] = useState(false);
    2731  const [entityType, setEntityType] = useState(0);
    2832
    2933  useEffect(() => {
    30     const urlProfessors = `http://192.168.0.29:8080/public/professors?facultyId=${params.facultyId}`;
    31     const urlStudyProgrammes = `http://192.168.0.29:8080/public/study_programmes?facultyId=${params.facultyId}`;
    32 
    33     const fetchDataProfessors = async () => {
     34    const fetchProfessors = async () => {
    3435      try {
    35         const response = await fetch(urlProfessors);
     36        const response = await fetch(`http://192.168.1.254:8080/public/professors?facultyId=${params.facultyId}`);
    3637        let cyclicGraph = await response.json();
    3738        let jsogStructure = JSOG.encode(cyclicGraph);
     
    4445    };
    4546
    46     const fetchDataStudyProgrammes = async () => {
     47    const fetchStudyProgrammes = async () => {
    4748      try {
    48         const response2 = await fetch(urlStudyProgrammes);
     49        const response2 = await fetch(`http://192.168.1.254:8080/public/study_programmes?facultyId=${params.facultyId}`);
    4950        let cyclicGraph2 = await response2.json();
    5051        let jsogStructure2 = JSOG.encode(cyclicGraph2);
     
    5758    };
    5859
    59     fetchDataProfessors();
    60     fetchDataStudyProgrammes();
     60    const fetchProfessorOpinionCount = async () => {
     61      try {
     62        const response3 = await fetch(`http://192.168.1.254:8080/public/faculty/${params.facultyId}/opinionCountForEachProfessor`);
     63        let cyclicGraph3 = await response3.json();
     64        let jsogStructure3 = JSOG.encode(cyclicGraph3);
     65        cyclicGraph3 = JSOG.decode(jsogStructure3);
     66        setProfessorOpinionCount(cyclicGraph3);
     67        setLoadedProfessorOpinionCount(true);
     68      } catch (error) {
     69        setFetchError(true);
     70      }
     71    }
     72
     73    fetchProfessors();
     74    fetchStudyProgrammes();
     75    fetchProfessorOpinionCount();
    6176  }, [params.facultyId]);
    6277
    63   return loadedProfessors && professors.length != 0 ? (
     78  return loadedProfessors && professors.length !== 0 ? (
    6479    entityType === 0 ? (
    6580      <>
     
    106121        </div>
    107122        <div key={params.facultyId}>
    108           {professors.map((professor) => {
    109             let totalPosts = professor.relatedOpinions.length;
    110 
     123          {professors.map((professor, idx) => {
     124            let totalPosts = loadedProfessorOpinionCount ? parseInt(professorOpinionCount[idx].split(",")[1]) : 0;
    111125            return (
    112126              <EntityUl key={professor.professorId}>
     
    215229                  key={studyProgramme.studyProgrammeId}
    216230                  title={studyProgramme}
    217                   content={studyProgramme.subjects}
    218231                ></SubjectsAccordion>
    219232              );
  • reactapp/src/Pages/Home.js

    r4abf55a r9bf1f8d  
    1 import React, { useContext } from "react";
    2 import { MainWrapper, MainTitle } from "../Components/Styled/Main.style";
    3 import { Outlet } from "react-router-dom";
    4 import Search from "../Components/Search";
    5 import UserHeader from "../Components/UserHeader";
    6 import AuthApi from "../api/AuthApi";
     1import React, {useEffect, useState} from 'react';
     2import JSOG from "jsog";
     3import {OpinionCard, OpinionCardContent, OpinionCardContentTime} from "../Components/Styled/OpinionCard.style";
     4import LoadingSpinner from "../Components/Styled/LoadingSpinner.style";
     5import {dateConverter} from "../Util/dateConverter";
     6import {findParentThread} from "../Util/findParentThread";
     7import {CurrentPageNav} from "../Components/Styled/Main.style";
    78
    8 function Home({ user, userLoaded }) {
    9   const { auth, setAuth } = useContext(AuthApi);
     9const Home = () => {
     10    const [latestOpinions, setLatestOpinions] = useState(null);
     11    const [loadedLatestOpinions, setLoadedLatestOpinions] = useState(false);
     12    const [latestThreads, setLatestThreads] = useState(null);
     13    const [loadedLatestThreads, setLoadedLatestThreads] = useState(false);
    1014
    11   return (
    12     <MainWrapper>
    13       <style>
    14         @import
    15         url('https://fonts.googleapis.com/css2?family=Roboto+Mono:ital,wght@0,400;0,500;0,700;1,400;1,500;1,700&display=swap');
    16       </style>
    17       <style>
    18         {
    19           "body { background-color: papayawhip;} * {margin: 0; padding: 0; box-sizing: border-box;}"
    20         }
    21       </style>
    22       <a href="/">
    23         <MainTitle>profesori.mk</MainTitle>
    24       </a>{" "}
    25       <Search />
    26       {auth && <UserHeader />}
    27       <div style={{ marginTop: "140px" }}></div>
    28       <Outlet />
    29     </MainWrapper>
    30   );
    31 }
     15    useEffect(() => {
     16        Promise.all([fetch(`http://192.168.1.254:8080/public/latest10opinions`),
     17            fetch(`http://192.168.1.254:8080/public/latest10threads`)])
     18            .then(([resOpinions, resThreads]) => Promise.all([resOpinions.json(), resThreads.json()]))
     19            .then(([dataOpinions, dataThreads]) => {
     20                let cyclicGraph1 = dataOpinions;
     21                let jsogStructure1 = JSOG.encode(cyclicGraph1);
     22                cyclicGraph1 = JSOG.decode(jsogStructure1);
     23                setLatestOpinions(cyclicGraph1);
     24                setLoadedLatestOpinions(true);
     25
     26                let cyclicGraph2 = dataThreads;
     27                let jsogStructure2 = JSOG.encode(cyclicGraph2);
     28                cyclicGraph2 = JSOG.decode(jsogStructure2);
     29                setLatestThreads(cyclicGraph2);
     30                setLoadedLatestThreads(true);
     31            })
     32
     33    }, []);
     34
     35    return (
     36        <>
     37            <CurrentPageNav>
     38                &#187;{" "}
     39                <a href={"/university/1"}>
     40                    Универзитет „Св. Кирил и Методиј“
     41                </a>
     42            </CurrentPageNav>
     43        <div style={{display:"grid", gridTemplateColumns:"1fr 1fr", gap:"40px", marginTop:"60px"}}>
     44
     45        <div id="latestOpinions" style={{gridColumn:"1", paddingLeft:"20px"}}>
     46            <h2 style={{fontWeight:"normal", marginBottom:"30px"}}>Последни мислења за <span style={{fontWeight:"bold"}}>професори</span></h2>
     47            {loadedLatestOpinions ? latestOpinions.slice(5).map(opinion => {
     48                opinion.timePosted = undefined;
     49                return <OpinionCard key={opinion.postId}>
     50                    <OpinionCardContent>
     51                        <p style={{marginBottom:"10px"}}>
     52                        во дискусија за{" "}
     53                        <a href={"/professor/" + opinion.targetProfessor.professorId}>
     54                                    {opinion.targetProfessor.professorName}
     55                        </a>
     56                        </p>
     57                        <p style={{ fontStyle: "italic", marginBottom: "10px" }}>
     58                            <a href={"/user/" + opinion.author.id}>
     59                                {opinion.author.username}
     60                            </a>{" "}
     61                            напишал
     62                        </p>
     63                        <p style={{ marginBottom: "10px", maxWidth: "90%" }}>
     64                            {opinion.content}
     65                        </p>
     66                        {new Date(opinion.timePosted).setMilliseconds(0) === new Date(opinion.timeLastEdited).setMilliseconds(0) ? (
     67                            <OpinionCardContentTime>
     68                                {dateConverter(
     69                                    new Date(opinion.timePosted).toString().slice(4, -43)
     70                                )} <span style={{fontStyle:"normal",color:"blue"}}>#{opinion.postId}</span>
     71                            </OpinionCardContentTime>
     72                        ) : (
     73                            <OpinionCardContentTime>
     74                                {dateConverter(
     75                                    new Date(opinion.timeLastEdited)
     76                                        .toString()
     77                                        .slice(4, -43)
     78                                )}{" "} <span style={{fontStyle:"normal",color:"blue"}}>#{opinion.postId}</span>{" "}
     79                                (едитирано од модератор)
     80                            </OpinionCardContentTime>
     81                        )}
     82                    </OpinionCardContent>
     83                </OpinionCard>
     84            }) : <LoadingSpinner/>}
     85        </div>
     86
     87    <div id="latestThreads" style={{gridColumn:"2", paddingRight:"20px"}}>
     88        <h2 style={{fontWeight:"normal", marginBottom:"30px"}}>Последни мислења за <span style={{fontWeight:"bold"}}>предмети</span></h2>
     89        {loadedLatestThreads ? latestThreads.slice(5).map(thread => {
     90            return <OpinionCard key={thread.postId}>
     91                <OpinionCardContent>
     92                    <p style={{marginBottom:"10px"}}>
     93                        во дискусија за{" "}
     94                        <a href={
     95                                thread.parent === null
     96                                    ? "/topic/" + thread.postId
     97                                    : "/topic/" + findParentThread(thread).postId
     98                            }
     99                        > {thread.targetSubject.subjectName}
     100                        </a>
     101                    </p>
     102                    <p style={{ fontStyle: "italic", marginBottom: "10px" }}>
     103                        <a href={"/user/" + thread.author.id}>
     104                            {thread.author.username}
     105                        </a>{" "}
     106                        напишал
     107                    </p>
     108                    <p style={{ marginBottom: "10px", maxWidth: "90%" }}>
     109                        {thread.content}
     110                    </p>
     111                    {new Date(thread.timePosted).setMilliseconds(0) === new Date(thread.timeLastEdited).setMilliseconds(0) ? (
     112                        <OpinionCardContentTime>
     113                            {dateConverter(
     114                                new Date(thread.timePosted).toString().slice(4, -43)
     115                            )} <span style={{fontStyle:"normal",color:"blue"}}>#{thread.postId}</span>
     116                        </OpinionCardContentTime>
     117                    ) : (
     118                        <OpinionCardContentTime>
     119                            {dateConverter(
     120                                new Date(thread.timeLastEdited)
     121                                    .toString()
     122                                    .slice(4, -43)
     123                            )}{" "} <span style={{fontStyle:"normal",color:"blue"}}>#{thread.postId}</span>{" "}
     124                            (едитирано од модератор)
     125                        </OpinionCardContentTime>
     126                    )}
     127                </OpinionCardContent>
     128            </OpinionCard>
     129        }) : null}
     130    </div>
     131        </div>
     132        </>
     133    );
     134};
    32135
    33136export default Home;
  • reactapp/src/Pages/Professor.js

    r4abf55a r9bf1f8d  
    3232  const [professor, setProfessor] = useState(null);
    3333  const [loadedProfessor, setLoadedProfessor] = useState(false);
     34  const [relatedOpinions, setRelatedOpinions] = useState(null);
     35  const [loadedRelatedOpinions, setLoadedRelatedOpinions] = useState(false);
    3436
    3537  const [postModalDisplay, setPostModalDisplay] = useState("none");
     
    4042
    4143  useEffect(() => {
    42     const url = `http://192.168.0.29:8080/public/professor/${params.professorId}`;
     44     Promise.all([fetch(`http://192.168.1.254:8080/public/professor/${params.professorId}`),
     45     fetch(`http://192.168.1.254:8080/public/professor/${params.professorId}/relatedOpinions`)])
     46        .then(([resProfessor, resRelatedOpinions]) => Promise.all([resProfessor.json(), resRelatedOpinions.json()]))
     47        .then(([dataProfessor, dataRelatedOpinions]) => {
     48          let cyclicGraph1 = dataProfessor;
     49          let jsogStructure1 = JSOG.encode(cyclicGraph1);
     50          cyclicGraph1 = JSOG.decode(jsogStructure1);
     51          setProfessor(cyclicGraph1);
     52          setLoadedProfessor(true);
    4353
    44     const fetchProfessor = async () => {
    45       try {
    46         const response = await fetch(url);
    47         var cyclicGraph = await response.json();
    48         var jsogStructure = JSOG.encode(cyclicGraph);
    49         cyclicGraph = JSOG.decode(jsogStructure);
    50         setProfessor(cyclicGraph);
    51         setLoadedProfessor(true);
    52       } catch (error) {
    53         setFetchError(true);
    54       }
    55     };
     54          let cyclicGraph2 = dataRelatedOpinions;
     55          let jsogStructure2 = JSOG.encode(cyclicGraph2);
     56          cyclicGraph2 = JSOG.decode(jsogStructure2);
     57          setRelatedOpinions(cyclicGraph2);
     58          setLoadedRelatedOpinions(true);
     59        })
    5660
    57     fetchProfessor();
    58   }, [params.professorId]);
     61  }, []);
    5962
    6063  const handleAddOpinionButtonClick = () => {
     
    7780    if (!postContent.length < 1) {
    7881      const response = await axios(
    79         `http://192.168.0.29:8080/secure/professor/${params.professorId}/addOpinion`,
     82        `http://192.168.1.254:8080/secure/professor/${params.professorId}/addOpinion`,
    8083        {
    8184          method: "post",
     
    129132            }}
    130133          >
    131             {professor.relatedOpinions.length}{" "}
    132             {professor.relatedOpinions.length !== 1 ? "мислења" : "мислење"}
     134            {relatedOpinions.length}{" "}
     135            {relatedOpinions.length !== 1 ? "мислења" : "мислење"}
    133136          </h3>
    134137          {auth && (
     
    172175
    173176        <div className="opinionTree">
    174           <OpinionTree professor={professor} />
     177          <OpinionTree professor={professor} relatedOpinions={relatedOpinions}/>
    175178        </div>
    176179        <Outlet />
  • reactapp/src/Pages/SearchResults.js

    r4abf55a r9bf1f8d  
    1414    <SearchResultsWrapper>
    1515      <h3 style={{ marginBottom: "30px" }}>Резултати од пребарувањето:</h3>
    16       {location.state.map((professor) => (
    17         <SearchResult key={professor.professorId} margin="10px">
    18           <SearchResultLink href={"/professor/" + professor.professorId}>
     16      {location.state.map((match) => (
     17        <SearchResult key={match.professorId !== undefined ? match.professorId : match.subjectId} margin="10px">
     18          <SearchResultLink href={`/${match.professorId !== undefined ? 'professor': 'subject'}/` + `${match.professorId !== undefined ? match.professorId : match.subjectId}`}>
    1919            <SearchResultText weight="bold" size="medium">
    20               {professor.professorName}
     20              {match.professorId !== undefined ? match.professorName : match.subjectName}
    2121            </SearchResultText>
    2222            <SearchResultText weight="normal" size="er">
    23               {professor.faculty.facultyName}
     23              {match.professorId !== undefined ? match.faculty.facultyName : match.studyProgramme.faculty.facultyName}
    2424            </SearchResultText>
    2525          </SearchResultLink>
  • reactapp/src/Pages/Subject.js

    r4abf55a r9bf1f8d  
    3333  let params = useParams();
    3434  let navigate = useNavigate();
    35 
    3635  const { auth, setAuth } = useContext(AuthApi);
     36
    3737  const [subject, setSubject] = useState(null);
    38   const [loaded, setLoaded] = useState(false);
     38  const [loadedSubject, setLoadedSubject] = useState(false);
     39  const [threads, setThreads] = useState(null);
     40  const [loadedThreads, setLoadedThreads] = useState(false);
    3941  const [fetchError, setFetchError] = useState(false);
     42
    4043  var totalTopics = 0;
    4144  var topics = [];
     
    4750
    4851  useEffect(() => {
    49     const url = `http://192.168.0.29:8080/public/subject/${params.subjectId}`;
    50 
    51     const fetchData = async () => {
    52       try {
    53         const response = await fetch(url);
    54         let cyclicGraph = await response.json();
    55         let jsogStructure = JSOG.encode(cyclicGraph);
    56         cyclicGraph = JSOG.decode(jsogStructure);
    57         setSubject(cyclicGraph);
    58         setLoaded(true);
    59       } catch (error) {
    60         setFetchError(true);
    61       }
    62     };
    63 
    64     fetchData();
    65   }, [params.subjectId]);
     52    Promise.all([fetch(`http://192.168.1.254:8080/public/subject/${params.subjectId}`),
     53                        fetch(`http://192.168.1.254:8080/public/subject/${params.subjectId}/threads`)])
     54        .then(([resSubject, resThreads]) => Promise.all([resSubject.json(), resThreads.json()]))
     55        .then(([dataSubject, dataThreads]) => {
     56          let cyclicGraph1 = dataSubject;
     57          let jsogStructure1 = JSOG.encode(cyclicGraph1);
     58          cyclicGraph1 = JSOG.decode(jsogStructure1);
     59          setSubject(cyclicGraph1);
     60          setLoadedSubject(true);
     61
     62          let cyclicGraph2 = dataThreads;
     63          let jsogStructure2 = JSOG.encode(cyclicGraph2);
     64          cyclicGraph2 = JSOG.decode(jsogStructure2);
     65          setThreads(cyclicGraph2);
     66          setLoadedThreads(true);
     67        })
     68
     69  }, []);
    6670
    6771  const handleAddTopicButtonClick = () => {
     
    8488    if (!topicTitle.length < 1 && !topicContent.length < 1) {
    8589      const response = await axios(
    86         `http://192.168.0.29:8080/secure/subject/${params.subjectId}/addThread`,
     90        `http://192.168.1.254:8080/secure/subject/${params.subjectId}/addThread`,
    8791        {
    8892          method: "post",
     
    109113  };
    110114
    111   return loaded ? (
     115  return loadedSubject ? (
    112116    <>
    113117      <CurrentPageNav>
     
    151155          }}
    152156        >
    153           {subject.threads.map((thread) => {
     157          {threads.map((thread) => {
    154158            if (thread.parent === null) {
    155159              totalTopics++;
     
    210214            <EntityUl key={topic.postId}>
    211215              <EntityLi bgcolor="cornsilk">
    212                 <a href={"/topic/" + topic.postId}>{topic.title}</a>
     216                <a href={"/topic/" + topic.postId}>{topic.title.length >= 99 ? topic.title.slice(0,98) + "..." : topic.title}</a>
    213217                <EntityParam right="30px">
    214218                  <span style={{ fontWeight: "normal" }}>
  • reactapp/src/Pages/Topic.js

    r4abf55a r9bf1f8d  
    4343  const [postModalDisplay, setPostModalDisplay] = useState("none");
    4444  const [postContent, setPostContent] = useState("");
     45
    4546  const [replyModalDisplay, setReplyModalDisplay] = useState("none");
    4647  const [replyContent, setReplyContent] = useState("");
    47   const [postForModal, setPostForModal] = useState(null);
     48  const [postForReplyModal, setPostForReplyModal] = useState(null);
     49
     50  const [reportModalDisplay, setReportModalDisplay] = useState("none");
     51  const [reportContent, setReportContent] = useState("");
     52  const [postForReportModal, setPostForReportModal] = useState(null);
     53
    4854  const [errorMessage, setErrorMessage] = useState("");
    4955
    5056  useEffect(() => {
    51     const url1 = `http://192.168.0.29:8080/public/thread/${params.topicId}`;
    52     const url2 = `http://192.168.0.29:8080/secure/currentUser`;
     57    const url1 = `http://192.168.1.254:8080/public/thread/${params.topicId}`;
     58    const url2 = `http://192.168.1.254:8080/secure/currentUser`;
    5359
    5460    const fetchTopic = async () => {
     
    7985
    8086    fetchTopic().then(fetchUser);
    81   }, []);
     87  }, [loadedThread]);
    8288
    8389  const handleReply = (post) => {
    8490    if (auth) {
    8591      setReplyModalDisplay("block");
    86       setPostForModal(post);
     92      setPostForReplyModal(post);
     93      document.body.style.overflowY = "hidden";
     94    } else {
     95      navigate("/login");
     96    }
     97  };
     98
     99  const handleReport = (post) => {
     100    if (auth) {
     101      setReportModalDisplay("block");
     102      setPostForReportModal(post);
    87103      document.body.style.overflowY = "hidden";
    88104    } else {
     
    95111  };
    96112
     113  const handleReportContentChange = (e) => {
     114    e.preventDefault();
     115    setReportContent(e.target.value);
     116  };
     117
    97118  const handleReplySubmit = async (e, postId) => {
    98119    e.preventDefault();
     
    100121    if (!replyContent.length < 1) {
    101122      const response = await axios(
    102         `http://192.168.0.29:8080/secure/subject/${thread.targetSubject.subjectId}/replyToThread/${postId}`,
     123        `http://192.168.1.254:8080/secure/subject/${thread.targetSubject.subjectId}/replyToThread/${postId}`,
    103124        {
    104125          method: "post",
     
    116137  };
    117138
     139  const handleReportSubmit = async (e, postId) => {
     140    e.preventDefault();
     141
     142    if (!reportContent.length < 1) {
     143      const response = await axios(
     144          `http://192.168.1.254:8080/secure/reportThread/${postId}`,
     145          {
     146            method: "post",
     147            data: {
     148              description: reportContent,
     149            },
     150            withCredentials: true,
     151          }
     152      );
     153      setErrorMessage("");
     154      window.location.reload();
     155    } else {
     156      setErrorMessage("Полето за содржина не смее да биде празно");
     157    }
     158  };
     159
    118160  const handleAddOpinionButtonClick = () => {
    119161    if (auth) {
     
    129171    if (!postContent.length < 1) {
    130172      const response = await axios(
    131         `http://192.168.0.29:8080/secure/subject/${thread.targetSubject.subjectId}/replyToThread/${params.topicId}`,
     173        `http://192.168.1.254:8080/secure/subject/${thread.targetSubject.subjectId}/replyToThread/${params.topicId}`,
    132174        {
    133175          method: "post",
     
    147189    setPostModalDisplay("none");
    148190    setReplyModalDisplay("none");
     191    setReportModalDisplay("none");
    149192    document.body.style.overflowY = "auto";
    150193  };
     
    161204      ) {
    162205        const response = await axios(
    163           `http://192.168.0.29:8080/secure/upvoteThread/${post.postId}`,
     206          `http://192.168.1.254:8080/secure/upvoteThread/${post.postId}`,
    164207          {
    165208            method: "get",
     
    184227      ) {
    185228        const response = await axios(
    186           `http://192.168.0.29:8080/secure/downvoteThread/${post.postId}`,
     229          `http://192.168.1.254:8080/secure/downvoteThread/${post.postId}`,
    187230          {
    188231            method: "get",
     
    280323                color="darkgrey"
    281324                onClick={() => handleReply(child)}
     325              />
     326
     327              <StyledFontAwesomeIcon
     328                  icon={solid("flag")}
     329                  right={130 + "px"}
     330                  color="darkgrey"
     331                  onClick={() => handleReport(child)}
    282332              />
    283333            </div>
     
    393443          >
    394444            <StyledFontAwesomeIcon
     445                icon={solid("flag")}
     446                right={90 + "px"}
     447                color="darkgrey"
     448                onClick={() => handleReport(thread)}
     449            />
     450            <StyledFontAwesomeIcon
    395451              icon={solid("thumbs-up")}
    396452              right={50 + "px"}
     
    517573                  onClick={() => handleReply(directChild)}
    518574                />
     575
     576                <StyledFontAwesomeIcon
     577                    icon={solid("flag")}
     578                    right={130 + "px"}
     579                    color="darkgrey"
     580                    onClick={() => handleReport(directChild)}
     581                />
    519582              </div>
    520583            </OpinionCardContent>
     
    525588        );
    526589      })}
    527       {postForModal && (
     590      {postForReplyModal && (
    528591        <Modal display={replyModalDisplay}>
    529592          <ModalContent>
     
    531594              <ModalClose onClick={handleModalCloseClick}>&times;</ModalClose>
    532595              <h3 style={{ marginTop: "5px" }}>
    533                 Реплика на {postForModal.author.username}
     596                Реплика на {postForReplyModal.author.username}
    534597              </h3>
    535598            </ModalHeader>
    536             <form onSubmit={(e) => handleReplySubmit(e, postForModal.postId)}>
     599            <form onSubmit={(e) => handleReplySubmit(e, postForReplyModal.postId)}>
    537600              <ModalBody>
    538601                <label htmlFor="content">
     
    557620        </Modal>
    558621      )}
     622      {postForReportModal && (
     623          <Modal display={reportModalDisplay}>
     624            <ModalContent>
     625              <ModalHeader>
     626                <ModalClose onClick={handleModalCloseClick}>&times;</ModalClose>
     627                <h3 style={{ marginTop: "5px" }}>
     628                  Пријава за мислење #{postForReportModal.postId}
     629                </h3>
     630              </ModalHeader>
     631              <form onSubmit={(e) => handleReportSubmit(e, postForReportModal.postId)}>
     632                <ModalBody>
     633                  <label htmlFor="content">
     634                    <b>Наведете причина</b>:
     635                    <ModalTextarea
     636                        id="content"
     637                        rows="8"
     638                        cols="100"
     639                        value={reportContent}
     640                        onChange={handleReportContentChange}
     641                    />
     642                  </label>
     643                </ModalBody>
     644                <p
     645                    style={{ color: "red", marginLeft: "15px", marginTop: "10px" }}
     646                >
     647                  {errorMessage}
     648                </p>
     649                <ModalFooter type="submit">ПРИЈАВИ</ModalFooter>
     650              </form>
     651            </ModalContent>
     652          </Modal>
     653      )}
    559654    </>
    560655  ) : !fetchError && !loadedThread ? (
  • reactapp/src/Pages/University.js

    r4abf55a r9bf1f8d  
    1818const University = () => {
    1919  let params = useParams();
    20   const [loaded, setLoaded] = useState(false);
     20
    2121  const [faculties, setFaculties] = useState(null);
     22  const [loadedFaculties, setLoadedFaculties] = useState(false);
     23
     24  const [counts, setCounts] = useState(null);
     25  const [loadedCounts, setLoadedCounts] = useState(false);
     26
    2227  const [fetchError, setFetchError] = useState(false);
    2328
    2429  useEffect(() => {
    25     const url = `http://192.168.0.29:8080/public/faculties?universityId=${params.universityId}`;
     30        Promise.all([fetch(`http://192.168.1.254:8080/public/faculties?universityId=${params.universityId}`),
     31        fetch(`http://192.168.1.254:8080/public/university/${params.universityId}/sectionAndPostCount`)])
     32            .then(([resFaculties, counts]) => Promise.all([resFaculties.json(), counts.json()]))
     33            .then(([dataFaculties, dataCounts]) => {
     34                let cyclicGraph1 = dataFaculties;
     35                let jsogStructure1 = JSOG.encode(cyclicGraph1);
     36                cyclicGraph1 = JSOG.decode(jsogStructure1);
     37                setFaculties(cyclicGraph1);
     38                setLoadedFaculties(true);
    2639
    27     const fetchData = async () => {
    28       try {
    29         const response = await fetch(url);
    30         var cyclicGraph = await response.json();
    31         var jsogStructure = JSOG.encode(cyclicGraph);
    32         cyclicGraph = JSOG.decode(jsogStructure);
    33         setFaculties(cyclicGraph);
    34         setLoaded(true);
    35       } catch (error) {
    36         setFetchError(true);
    37       }
    38     };
    39     fetchData();
    40   }, [params.universityId]);
     40                let cyclicGraph2 = dataCounts;
     41                let jsogStructure2 = JSOG.encode(cyclicGraph2);
     42                cyclicGraph2 = JSOG.decode(jsogStructure2);
     43                setCounts(cyclicGraph2);
     44                setLoadedCounts(true);
     45            })
    4146
    42   return loaded && !fetchError && faculties.length !== 0 ? (
     47  }, []);
     48
     49  return loadedFaculties && !fetchError && faculties.length !== 0 ? (
    4350    <>
    4451      <CurrentPageNav>
     
    5562      </ProfessorCard>
    5663      <div key={params.universityId}>
    57         {faculties.map((faculty) => {
    58           let totalPosts = 0;
    59           let totalSections = 0;
    60           faculty.professors.map((professor) => {
    61             totalPosts += professor.relatedOpinions.length;
    62             totalSections++;
    63           });
    64           faculty.studyProgrammes.map((studyProgramme) => {
    65             studyProgramme.subjects.map((subject) => {
    66               totalPosts += subject.threads.length;
    67               totalSections++;
    68             });
    69           });
    70 
     64        {faculties.map((faculty, idx) => {
     65          let totalPosts = parseInt(counts[idx].split(",")[2]);
     66          let totalSections = parseInt(counts[idx].split(",")[1]);
     67          console.log(counts)
    7168          return (
    7269            <EntityUl key={faculty.facultyId}>
     
    104101      </div>
    105102    </>
    106   ) : !fetchError && !loaded ? (
     103  ) : !fetchError && !loadedFaculties ? (
    107104    <div>
    108105      <LoadingSpinner style={{ marginTop: "140px" }}/>
  • reactapp/src/Pages/UserDashboard.js

    r4abf55a r9bf1f8d  
    2727} from "../Components/Styled/Modal.style";
    2828import LoadingSpinner from "../Components/Styled/LoadingSpinner.style";
     29import {findParentThread} from "../Util/findParentThread";
    2930
    3031function UserDashboard() {
     
    3334  const [user, setUser] = useState(null);
    3435  const [loadedUser, setLoadedUser] = useState(false);
     36  const [authoredPosts, setAuthoredPosts] = useState(null);
     37  const [loadedAuthoredPosts, setLoadedAuthoredPosts] = useState(false);
     38
    3539  const [fetchError, setFetchError] = useState(false);
    3640
     
    4549  const [newPostContent, setNewPostContent] = useState("");
    4650  const [newOpinionTargetProfessorId, setNewOpinionTargetProfessorId] = useState("");
    47   const [newOpinionTargetProfessor, setNewOpinionTargetProfessor] = useState(null);
    48   const [loadedNewProfessor,setLoadedNewProfessor] = useState(false);
    4951  const [newParentPostId, setNewParentPostId] = useState("-1");
    5052
     
    5254  const [newParentThreadId,setNewParentThreadId] = useState("-1");
    5355  const [newTargetSubjectId, setNewTargetSubjectId] = useState("");
    54   const [newTargetSubject, setNewTargetSubject] = useState(null);
    55   const [loadedNewSubject, setLoadedNewSubject] = useState(null);
     56
    5657
    5758  const [markResolved, setMarkResolved] = useState(false);
     
    8384  }, [reportForModal]);
    8485
    85   const[loadingProf, setLoadingProf] = useState(false);
     86  const [newOpinionTargetProfessor, setNewOpinionTargetProfessor] = useState(null);
     87  const [loadedNewProfessor,setLoadedNewProfessor] = useState(false);
     88  const [newProfessorRelatedOpinions, setNewProfessorRelatedOpinions] = useState(null);
     89  const [loadedNewProfessorRelatedOpinions, setLoadedNewProfessorRelatedOpinions] = useState(false);
     90  const [loadingProf, setLoadingProf] = useState(false);
    8691
    8792  const handleNewTargetProfessorChange = async (e) => {
     
    9095    if (newOpinionTargetProfessorId!=="") {
    9196        try {
    92           const response = await axios.get(`http://192.168.0.29:8080/public/professor/${newOpinionTargetProfessorId}`, {withCredentials: true});
    93           let cyclicGraph = await response.data;
    94           var jsogStructure = JSOG.encode(cyclicGraph);
    95           cyclicGraph = JSOG.decode(jsogStructure);
    96           setNewOpinionTargetProfessor(cyclicGraph);
    97           setLoadedNewProfessor(true);
    98           setLoadingProf(false);
     97          Promise.all([fetch(`http://192.168.1.254:8080/public/professor/${newOpinionTargetProfessorId}`),
     98            fetch(`http://192.168.1.254:8080/public/professor/${newOpinionTargetProfessorId}/relatedOpinions`)])
     99              .then(([resNewOpinionTargetProfessor, resNewProfessorRelatedOpinions]) => Promise.all([resNewOpinionTargetProfessor.json(), resNewProfessorRelatedOpinions.json()]))
     100              .then(([dataNewOpinionTargetProfessor, dataNewProfessorRelatedOpinions]) => {
     101                let cyclicGraph1 = dataNewOpinionTargetProfessor;
     102                var jsogStructure1 = JSOG.encode(cyclicGraph1);
     103                cyclicGraph1 = JSOG.decode(jsogStructure1);
     104                setNewOpinionTargetProfessor(cyclicGraph1);
     105                setLoadedNewProfessor(true);
     106
     107                let cyclicGraph2 = dataNewProfessorRelatedOpinions;
     108                var jsogStructure2 = JSOG.encode(cyclicGraph2);
     109                cyclicGraph2 = JSOG.decode(jsogStructure2);
     110                setNewProfessorRelatedOpinions(cyclicGraph2);
     111                setLoadedNewProfessorRelatedOpinions(true);
     112
     113                setLoadingProf(false);
     114              })
    99115        } catch (error) {
    100116          setFetchError(true);
     
    103119  }
    104120
    105   const[loadingSubj, setLoadingSubj] = useState(false);
     121  const [newTargetSubject, setNewTargetSubject] = useState(null);
     122  const [loadedNewSubject, setLoadedNewSubject] = useState(false);
     123  const [newTargetSubjectThreads, setNewTargetSubjectThreads] = useState(null);
     124  const [loadedNewSubjectThreads, setLoadedNewSubjectThreads] = useState(false);
     125  const [loadingSubj, setLoadingSubj] = useState(false);
    106126
    107127  const handleNewTargetSubjectChange = async (e) => {
     
    110130    if (newTargetSubjectId!=="") {
    111131      try {
    112         const response = await axios.get(`http://192.168.0.29:8080/public/subject/${newTargetSubjectId}`, {withCredentials: true});
    113         let cyclicGraph = await response.data;
    114         var jsogStructure = JSOG.encode(cyclicGraph);
    115         cyclicGraph = JSOG.decode(jsogStructure);
    116         setNewTargetSubject(cyclicGraph);
    117         setLoadedNewSubject(true);
    118         setLoadingSubj(false);
    119       } catch (error) {
    120         setFetchError(true);
    121       }
     132        Promise.all([fetch(`http://192.168.1.254:8080/public/subject/${newTargetSubjectId}`),
     133        fetch(`http://192.168.1.254:8080/public/subject/${newTargetSubjectId}/threads`)])
     134            .then(([resNewTargetSubject, resNewTargetSubjectThreads]) => Promise.all([resNewTargetSubject.json(), resNewTargetSubjectThreads.json()]))
     135            .then(([dataNewTargetSubject, dataNewTargetSubjectThreads]) => {
     136              let cyclicGraph1 = dataNewTargetSubject;
     137              var jsogStructure1 = JSOG.encode(cyclicGraph1);
     138              cyclicGraph1 = JSOG.decode(jsogStructure1);
     139              setNewTargetSubject(cyclicGraph1);
     140              setLoadedNewSubject(true);
     141
     142              let cyclicGraph2 = dataNewTargetSubjectThreads;
     143              var jsogStructure2 = JSOG.encode(cyclicGraph2);
     144              cyclicGraph2 = JSOG.decode(jsogStructure2);
     145              setNewTargetSubjectThreads(cyclicGraph2);
     146              setLoadedNewSubjectThreads(true);
     147
     148              setLoadingSubj(false);
     149            })
     150        } catch (error) {
     151            setFetchError(true);
     152        }
    122153    }
    123154  }
    124155
    125156  useEffect(() => {
    126     const url1 = `http://192.168.0.29:8080/secure/currentUser`;
    127     const url2 = `http://192.168.0.29:8080/secure/getAllPostReports`;
    128 
    129     const fetchUser = async () => {
     157    const fetchUser = () => {
    130158      try {
    131159        if(!loadedUser) {
    132           const response = await axios.get(url1, {withCredentials: true});
    133           let cyclicGraph = await response.data;
    134           var jsogStructure = JSOG.encode(cyclicGraph);
    135           cyclicGraph = JSOG.decode(jsogStructure);
    136           setUser(cyclicGraph);
    137           setLoadedUser(true);
     160          Promise.all([axios.get(`http://192.168.1.254:8080/secure/currentUser`, {withCredentials:true}),
     161            axios.get(`http://192.168.1.254:8080/secure/currentUser/posts`, {withCredentials:true})])
     162              .then(([resUser, resAuthoredPosts]) => Promise.all([resUser.data, resAuthoredPosts.data]))
     163              .then(([dataUser, dataAuthoredPosts]) => {
     164                let cyclicGraph1 = dataUser;
     165                let jsogStructure1 = JSOG.encode(cyclicGraph1);
     166                cyclicGraph1 = JSOG.decode(jsogStructure1);
     167                setUser(cyclicGraph1);
     168                setLoadedUser(true);
     169
     170                let cyclicGraph2 = dataAuthoredPosts;
     171                let jsogStructure2 = JSOG.encode(cyclicGraph2);
     172                cyclicGraph2 = JSOG.decode(jsogStructure2);
     173                setAuthoredPosts(cyclicGraph2);
     174                setLoadedAuthoredPosts(true);
     175              })
    138176        }
    139         if(user.userRole==='MODERATOR')fetchPostReports();
     177        if(user.userRole==='MODERATOR') fetchPostReports();
    140178      } catch (error) {
    141179        setFetchError(true);
     
    145183    const fetchPostReports = async () => {
    146184      try {
    147         const response = await axios.get(url2, {withCredentials: true});
     185        const response = await axios.get(`http://192.168.1.254:8080/secure/getAllPostReports`, {withCredentials: true});
    148186        var cyclicGraph = await response.data;
    149187        var jsogStructure = JSOG.encode(cyclicGraph);
     
    160198  }, [user]);
    161199
    162   // useEffect(() => {
    163   //   const timer = setTimeout(() => {
    164   //     if (user === null) window.location.reload(); <---- :-)
    165   //   }, 3000);
    166   //   return () => clearTimeout(timer);
    167   // }, []);
    168 
    169   function findParentThread(post) {
    170     if (post.parent === null) return post;
    171     return findParentThread(post.parent);
    172   }
    173 
    174200  const handleEdit = async (e) => {
    175201    e.preventDefault();
    176202    try {
    177203      if(reportForModal.post !== null && reportForModal.post.targetProfessor !== undefined) {
    178         await axios(`http://192.168.0.29:8080/secure/updateOpinion/${reportForModal.post.postId}`,
     204        await axios(`http://192.168.1.254:8080/secure/updateOpinion/${reportForModal.post.postId}`,
    179205            {
    180206              method: "put",
     
    186212              withCredentials: true,
    187213            })
     214        await axios(`http://192.168.1.254:8080/secure/markReportResolved/${reportForModal.postReportId}/${markResolved ? `resolve` : `open`}`,{
     215          method: "get",
     216          withCredentials: true
     217        })
    188218      } else if(reportForModal.post !== null && reportForModal.post.targetProfessor === undefined) {
    189         await axios(`http://192.168.0.29:8080/secure/updateThread/${reportForModal.post.postId}`,
     219        await axios(`http://192.168.1.254:8080/secure/updateThread/${reportForModal.post.postId}`,
    190220            {
    191221              method: "put",
     
    199229            })
    200230      }
    201         await axios(`http://192.168.0.29:8080/secure/markReportResolved/${reportForModal.postReportId}/${markResolved ? `resolve` : `open`}`,{
     231      await axios(`http://192.168.1.254:8080/secure/markReportResolved/${reportForModal.postReportId}/${markResolved ? `resolve` : `open`}`,{
    202232          method: "get",
    203233          withCredentials: true
    204         })
     234      })
    205235    } catch (error) {
    206236      setFetchError(true);
     
    213243    try {
    214244      if(reportForModal.post !== null && reportForModal.post.targetProfessor !== undefined) {
    215         var response = await axios(`http://192.168.0.29:8080/secure/updateOpinion/${reportForModal.post.postId}`,
     245        var response = await axios(`http://192.168.1.254:8080/secure/updateOpinion/${reportForModal.post.postId}`,
    216246            {
    217247              method: "put",
     
    223253              withCredentials: true,
    224254            })
     255        await axios(`http://192.168.1.254:8080/secure/markReportResolved/${reportForModal.postReportId}/${markResolved ? `resolve` : `open`}`,{
     256          method: "get",
     257          withCredentials: true
     258        })
    225259      } else if(reportForModal.post !== null && reportForModal.post.targetProfessor === undefined) {
    226         var response = await axios(`http://192.168.0.29:8080/secure/updateThread/${reportForModal.post.postId}`,
     260        var response = await axios(`http://192.168.1.254:8080/secure/updateThread/${reportForModal.post.postId}`,
    227261            {
    228262              method: "put",
     
    236270            })
    237271      }
    238       await axios(`http://192.168.0.29:8080/secure/markReportResolved/${reportForModal.postReportId}/${markResolved ? `resolve` : `open`}`,{
     272      await axios(`http://192.168.1.254:8080/secure/markReportResolved/${reportForModal.postReportId}/${markResolved ? `resolve` : `open`}`,{
    239273        method: "get",
    240274        withCredentials: true
     
    251285    try {
    252286      if(reportForModal.post !== null && reportForModal.post.targetProfessor !== undefined) {
    253         await axios(`http://192.168.0.29:8080/secure/deleteOpinion/${reportForModal.post.postId}`,
     287        await axios(`http://192.168.1.254:8080/secure/deleteOpinion/${reportForModal.post.postId}`,
    254288            {
    255289              method: "delete",
     
    258292        window.location.reload();
    259293      } else if(reportForModal.post !== null && reportForModal.post.targetProfessor === undefined) {
    260         await axios(`http://192.168.0.29:8080/secure/deleteThread/${reportForModal.post.postId}`,
     294        await axios(`http://192.168.1.254:8080/secure/deleteThread/${reportForModal.post.postId}`,
    261295            {
    262296              method: "delete",
     
    312346      })}
    313347      </EntityUl>
    314       {user.authoredPosts.length > 0 ? (
     348      {authoredPosts.length > 0 ? (
    315349        <h3 style={{ marginBottom: "10px", marginTop:"30px" }}>Ваши мислења:</h3>
    316350      ) : (
    317351        <h3 style={{ marginBottom: "10px" }}>Немате објавени мислења</h3>
    318352      )}
    319       {user.authoredPosts.map((post) => {
     353      {authoredPosts.map((post) => {
    320354        return (
    321355          <div key={post.postId}>
     
    527561                                    во која треба да биде преместено мислењето:</p>
    528562                                    <div style={{marginTop:"15px"}}>
    529                                       <label>
    530563                                        <ModalInput
    531564                                            value={newOpinionTargetProfessorId}
     
    542575                                        <select value={newParentPostId} onChange={e => setNewParentPostId(e.target.value)} style={{width:"280px", display:"block", padding:"5px",marginBottom:"5px", fontFamily: "Roboto Mono, monospace"}}>
    543576                                          <option value="-1">Постави како самостојно мислење</option>
    544                                           {newOpinionTargetProfessor.relatedOpinions.filter((opinion)=>opinion.postId!==reportForModal.post.postId).map((opinion) => {
     577                                          {newProfessorRelatedOpinions.filter((opinion)=>opinion.postId!==reportForModal.post.postId).map((opinion) => {
    545578                                            return <option key={opinion.postId} value={opinion.postId}>{opinion.postId}</option>})
    546579                                          }
    547580                                        </select>}
    548581                                        <br/>
    549                                         <input
    550                                             type="checkbox"
    551                                             defaultChecked={reportForModal.resolved}
    552                                             onChange={handleMarkResolved}
    553                                         />
    554                                         <span style={{marginLeft:"10px", fontWeight:"bold"}}>Означи како разрешено</span>
    555                                       </label>
     582                                        <label>
     583                                          <input
     584                                              type="checkbox"
     585                                              onChange={handleMarkResolved}
     586                                          />
     587                                          <span style={{marginLeft:"10px", fontWeight:"bold"}}>Означи како разрешено</span>
     588                                        </label>
    556589                                    </div>
    557590                                {errMsg!=="" && <p style={{color:"red", display:"flex", justifyContent:"space-around"}}>{errMsg}</p>}
     
    578611                                        <select value={newParentThreadId} onChange={e => setNewParentThreadId(e.target.value)} style={{width:"370px", display:"block", padding:"5px",marginBottom:"5px", fontFamily: "Roboto Mono, monospace"}}>
    579612                                          <option value="-1">Постави како самостојно мислење (нова тема)</option>
    580                                           {newTargetSubject.threads.filter((thread)=>thread.postId!==reportForModal.post.postId).map((thread) => {
     613                                          {newTargetSubjectThreads.filter((thread)=>thread.postId!==reportForModal.post.postId).map((thread) => {
    581614                                            return <option key={thread.postId} value={thread.postId}>{thread.postId}</option>})
    582615                                          }
  • reactapp/src/api/axios.js

    r4abf55a r9bf1f8d  
    22
    33export default axios.create({
    4   baseURL: "http://192.168.0.29:8080",
     4  baseURL: "http://192.168.1.254:8080",
    55});
  • springapp/src/main/java/mk/profesori/springapp/Controller/PublicController.java

    r4abf55a r9bf1f8d  
    11package mk.profesori.springapp.Controller;
     2
     3import mk.profesori.springapp.Model.*;
     4import mk.profesori.springapp.Service.MainService;
     5import org.springframework.beans.factory.annotation.Autowired;
     6import org.springframework.web.bind.annotation.*;
    27
    38import java.util.Collections;
     
    611import java.util.Optional;
    712
    8 import org.springframework.beans.factory.annotation.Autowired;
    9 import org.springframework.web.bind.annotation.CrossOrigin;
    10 import org.springframework.web.bind.annotation.PathVariable;
    11 import org.springframework.web.bind.annotation.RequestMapping;
    12 import org.springframework.web.bind.annotation.RequestMethod;
    13 import org.springframework.web.bind.annotation.RequestParam;
    14 import org.springframework.web.bind.annotation.RestController;
    15 
    16 import mk.profesori.springapp.Model.City;
    17 import mk.profesori.springapp.Model.Faculty;
    18 import mk.profesori.springapp.Model.Professor;
    19 import mk.profesori.springapp.Model.StudyProgramme;
    20 import mk.profesori.springapp.Model.Subject;
    21 import mk.profesori.springapp.Model.University;
    22 import mk.profesori.springapp.Model._Thread;
    23 import mk.profesori.springapp.Service.MainService;
    24 
    2513@RestController
    2614@RequestMapping("/public")
    27 @CrossOrigin(origins = { "http://192.168.0.29:3000", "http://192.168.0.28:3000" })
     15@CrossOrigin(origins = { "http://192.168.1.254:3000", "http://192.168.0.28:3000" })
    2816public class PublicController {
    2917
     
    4230    @RequestMapping(value = "/professors/nameContains/{contained}", method = RequestMethod.GET)
    4331    public List<Professor> getProfessorsByNameContains(@PathVariable String contained) {
    44         return mainService.getProfessorsByNameContains(contained); // vrakja profesori sto sodrzat "contained" vo
    45                                                                    // professorName
     32        return mainService.getProfessorsByNameContains(contained); // vrakja profesori sto sodrzat "contained" vo imeto
     33
     34    }
     35
     36    @RequestMapping(value = "/subjects/nameContains/{contained}", method = RequestMethod.GET)
     37    public List<Subject> getSubjectsByNameContains(@PathVariable String contained) {
     38        return mainService.getSubjectsByNameContains(contained); // vrakja predmeti sto sodrzat "contained" vo imeto
    4639    }
    4740
     
    4942    public Professor getProfessorById(@PathVariable Long professorId) {
    5043        return mainService.getProfessorById(professorId); // vrakja profesor spored id
     44    }
     45
     46    @RequestMapping(value="/professor/{professorId}/relatedOpinions", method = RequestMethod.GET)
     47    public List<Opinion> getRelatedOpinions(@PathVariable Long professorId) {
     48        return mainService.getRelatedOpinions(professorId);
    5149    }
    5250
     
    105103    }
    106104
     105    @RequestMapping(value = "/subjects", method = RequestMethod.GET)
     106    public List<Subject> getSubjectsByStudyProgramme(@RequestParam Long studyProgrammeId) {
     107        return mainService.getSubjectsByStudyProgramme(studyProgrammeId);
     108    }
     109
    107110    @RequestMapping(value = "/thread/{postId}", method = RequestMethod.GET)
    108111    public _Thread getThreadById(@PathVariable Long postId) {
     
    119122        return Collections.singletonMap("sessionId", sessionId);
    120123    }
     124
     125    @RequestMapping(value = "/latest10opinions", method = RequestMethod.GET)
     126    public List<Opinion> latest10opinions() {
     127        return mainService.getLatest10Opinions();
     128    }
     129
     130    @RequestMapping(value = "/latest10threads", method = RequestMethod.GET)
     131    public List<_Thread> latest10threads() {
     132        return mainService.getLatest10Threads();
     133    }
     134
     135    @RequestMapping(value = "/subject/{id}/threads")
     136    public List<_Thread> getThreadsBySubject(@PathVariable Long id) {
     137        return mainService.getThreadsBySubject(id);
     138    }
     139
     140    @RequestMapping(value = "/university/{id}/sectionAndPostCount")
     141    public List<String> getUniversitySectionCount(@PathVariable Long id) {
     142        return mainService.getUniversitySectionAndPostCount();
     143    }
     144
     145    @RequestMapping(value = "/faculty/{id}/opinionCountForEachProfessor")
     146    public List<String> getOpinionCountForEachProfessorInFaculty(@PathVariable Long id) {
     147        return mainService.getOpinionCountForEachProfessorInFaculty(id);
     148    }
     149
     150    @RequestMapping(value = "/study_programme/{id}/threadCountForEachSubject")
     151    public List<String> getThreadCountForEachSubjectInStudyProgramme(@PathVariable Long id) {
     152        return mainService.getThreadCountForEachSubjectInStudyProgramme(id);
     153    }
     154
     155    @RequestMapping(value = "/user/{id}")
     156    public CustomUserDetails getPublicUserProfile(@PathVariable Long id) {
     157        return mainService.getPublicUserProfile(id);
     158    }
     159
    121160}
  • springapp/src/main/java/mk/profesori/springapp/Controller/SecureController.java

    r4abf55a r9bf1f8d  
    33import com.fasterxml.jackson.databind.node.ObjectNode;
    44import mk.profesori.springapp.Model.CustomUserDetails;
     5import mk.profesori.springapp.Model.Post;
    56import mk.profesori.springapp.Model.PostReport;
    67import mk.profesori.springapp.Model.UserRole;
    78import mk.profesori.springapp.Service.CustomUserDetailsService;
    8 import mk.profesori.springapp.Service.DisallowedOperationException;
    9 import mk.profesori.springapp.Service.IncompatiblePostId;
     9import mk.profesori.springapp.Service.Exception.DisallowedOperationException;
     10import mk.profesori.springapp.Service.Exception.IncompatiblePostId;
    1011import mk.profesori.springapp.Service.MainService;
    1112import org.apache.tomcat.websocket.AuthenticationException;
     
    1718
    1819import java.util.List;
     20import java.util.Set;
    1921
    2022@RestController
    2123@RequestMapping("/secure")
    22 @CrossOrigin(origins = { "http://192.168.0.29:3000", "http://192.168.0.28:3000" })
     24@CrossOrigin(origins = { "http://192.168.1.254:3000", "http://192.168.0.28:3000" })
    2325public class SecureController {
    2426
     
    7779        if (authentication != null && authentication.getPrincipal() instanceof CustomUserDetails currentUser) {
    7880            return customUserDetailsService.loadUserByUsername(currentUser.getEmail());
     81        }
     82        return null;
     83    }
     84
     85    @RequestMapping(value = "/currentUser/posts", method = RequestMethod.GET)
     86    public Set<Post> getPostsByUser(@CurrentSecurityContext SecurityContext context) {
     87        Authentication authentication = context.getAuthentication();
     88        if (authentication != null && authentication.getPrincipal() instanceof CustomUserDetails currentUser) {
     89            return mainService.getPostsByUser(currentUser.getEmail());
    7990        }
    8091        return null;
  • springapp/src/main/java/mk/profesori/springapp/Model/City.java

    r4abf55a r9bf1f8d  
    11package mk.profesori.springapp.Model;
    22
     3import com.fasterxml.jackson.annotation.JsonIdentityInfo;
     4import com.voodoodyne.jackson.jsog.JSOGGenerator;
     5import lombok.Data;
     6
     7import javax.persistence.*;
    38import java.util.HashSet;
    49import java.util.Set;
    510
    6 import javax.persistence.Column;
    7 import javax.persistence.Entity;
    8 import javax.persistence.GeneratedValue;
    9 import javax.persistence.GenerationType;
    10 import javax.persistence.Id;
    11 import javax.persistence.OneToMany;
    12 import javax.persistence.Table;
    13 
    14 import com.fasterxml.jackson.annotation.JsonIdentityInfo;
    15 import com.fasterxml.jackson.annotation.ObjectIdGenerators;
    16 
    1711@Entity
     12@Data
    1813@Table(name = "city")
    19 @JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "cityId")
     14@JsonIdentityInfo(generator = JSOGGenerator.class)
    2015public class City {
    2116
     
    2823    private String cityName;
    2924
     25    @Transient
    3026    @OneToMany(mappedBy = "city")
    3127    private Set<University> universities = new HashSet<>();
    3228
    33     // getters
    34     public Long getCityId() {
    35         return cityId;
    36     }
    37 
    38     public String getCityName() {
    39         return cityName;
    40     }
    41 
    42     public Set<University> getUniversities() {
    43         return universities;
    44     }
    45 
    4629}
  • springapp/src/main/java/mk/profesori/springapp/Model/CustomUserDetails.java

    r4abf55a r9bf1f8d  
    22
    33import com.fasterxml.jackson.annotation.JsonIdentityInfo;
     4import com.fasterxml.jackson.annotation.JsonIgnore;
    45import com.voodoodyne.jackson.jsog.JSOGGenerator;
    56import lombok.EqualsAndHashCode;
     
    3132    private String fullName;
    3233    private String username;
     34    @JsonIgnore
    3335    private String email;
    34     private String password; // TODO
     36    @JsonIgnore
     37    private String password;
     38    /*  UseCases
     39        го уредува својот кориснички профил
     40        го верификува својот кориснички профил todo...
     41    */
    3542    @Enumerated(EnumType.STRING)
    3643    private UserRole userRole;
    3744    private Boolean locked = false;
    3845    private Boolean enabled = false;
    39     @OneToMany(mappedBy = "customUserDetails", fetch = FetchType.EAGER, orphanRemoval = true)
     46    @Transient
     47    @OneToMany(mappedBy = "customUserDetails", orphanRemoval = true)
    4048    private Set<ConfirmationToken> confirmationTokens = new HashSet<>();
    41     @OneToMany(mappedBy = "author", fetch = FetchType.EAGER, orphanRemoval = true)
     49    @Transient
     50    @OneToMany(mappedBy = "author", orphanRemoval = true)
    4251    private Set<Post> authoredPosts = new HashSet<>();
    4352    private Integer karma = 0;
    4453
     54    @Transient
    4555    @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)
    4656    private Set<PostVote> votes = new HashSet<>();
    4757
     58    @Transient
    4859    @OneToMany(mappedBy = "user", cascade = {CascadeType.PERSIST})
    4960    private Set<PostReport> reportsSubmitted = new HashSet<>();
     
    99110    }
    100111
    101     public Set<Post> getAuthoredPosts() {
    102         return this.authoredPosts;
    103     }
    104 
    105     public Integer getKarma() {
    106         return this.karma;
    107     }
    108 
    109     public void setKarma(Integer karma) {
    110         this.karma = karma;
    111     }
    112 
    113112    @Override
    114113    public String toString() {
    115114        return this.id.toString();
    116115    }
    117 
    118     public void setLocked(Boolean locked) {
    119         this.locked = locked;
    120     }
    121 
    122     public Set<PostVote> getVotes() {
    123         return votes;
    124     }
    125 
    126     public void setVotes(Set<PostVote> votes) {
    127         this.votes = votes;
    128     }
    129116}
  • springapp/src/main/java/mk/profesori/springapp/Model/Faculty.java

    r4abf55a r9bf1f8d  
    11package mk.profesori.springapp.Model;
    22
     3import com.fasterxml.jackson.annotation.JsonIdentityInfo;
     4import com.voodoodyne.jackson.jsog.JSOGGenerator;
     5import lombok.Data;
     6
     7import javax.persistence.*;
    38import java.util.HashSet;
    49import java.util.Set;
    510
    6 import javax.persistence.Column;
    7 import javax.persistence.Entity;
    8 import javax.persistence.GeneratedValue;
    9 import javax.persistence.GenerationType;
    10 import javax.persistence.Id;
    11 import javax.persistence.JoinColumn;
    12 import javax.persistence.ManyToOne;
    13 import javax.persistence.OneToMany;
    14 import javax.persistence.Table;
    15 
    16 import com.fasterxml.jackson.annotation.JsonIdentityInfo;
    17 import com.voodoodyne.jackson.jsog.JSOGGenerator;
    18 
    1911@Entity
     12@Data
    2013@Table(name = "faculty")
    2114@JsonIdentityInfo(generator = JSOGGenerator.class)
     
    3427    private University university;
    3528
     29    @Transient
    3630    @OneToMany(mappedBy = "faculty")
    3731    private Set<Professor> professors = new HashSet<>();
    3832
     33    @Transient
    3934    @OneToMany(mappedBy = "faculty")
    4035    private Set<StudyProgramme> studyProgrammes = new HashSet<>();
    4136
    42     // getters
    43     public Long getFacultyId() {
    44         return facultyId;
    45     }
    46 
    47     public String getFacultyName() {
    48         return facultyName;
    49     }
    50 
    51     public University getUniversity() {
    52         return university;
    53     }
    54 
    55     public Set<Professor> getProfessors() {
    56         return professors;
    57     }
    58 
    59     public Set<StudyProgramme> getStudyProgrammes() {
    60         return studyProgrammes;
    61     }
    62 
    6337}
  • springapp/src/main/java/mk/profesori/springapp/Model/Opinion.java

    r4abf55a r9bf1f8d  
    11package mk.profesori.springapp.Model;
    22
    3 import java.time.LocalDateTime;
    4 import java.util.List;
     3import lombok.Data;
     4import lombok.NoArgsConstructor;
    55
    66import javax.persistence.DiscriminatorValue;
     
    88import javax.persistence.JoinColumn;
    99import javax.persistence.ManyToOne;
    10 
    11 import lombok.NoArgsConstructor;
     10import java.time.LocalDateTime;
     11import java.util.List;
    1212
    1313@Entity
     14@Data
    1415@DiscriminatorValue("opinion")
    1516@NoArgsConstructor
     
    3536        this.targetProfessor = targetProfessor;
    3637    }
    37 
    38     // getters and setters
    39     public Professor getTargetProfessor() {
    40         return targetProfessor;
    41     }
    42     public void setTargetProfessor(Professor targetProfessor) {
    43         this.targetProfessor = targetProfessor;
    44     }
    45 
    4638}
  • springapp/src/main/java/mk/profesori/springapp/Model/Post.java

    r4abf55a r9bf1f8d  
    33import com.fasterxml.jackson.annotation.JsonIdentityInfo;
    44import com.voodoodyne.jackson.jsog.JSOGGenerator;
     5import lombok.Data;
    56import lombok.NoArgsConstructor;
    67
     
    1314
    1415@Entity(name = "post")
     16@Data
    1517@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
    1618@DiscriminatorColumn(name = "post_type", discriminatorType = DiscriminatorType.STRING)
     
    4446    private Post parent;
    4547
    46     @OneToMany(mappedBy = "post", cascade = CascadeType.ALL, orphanRemoval = true)
     48    @OneToMany(mappedBy = "post", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER)
    4749    private Set<PostVote> votes = new HashSet<>();
    4850
    49     @OneToMany(mappedBy = "post", cascade={CascadeType.PERSIST})
     51    @OneToMany(mappedBy = "post", cascade={CascadeType.PERSIST}, fetch = FetchType.EAGER)
    5052    private Set<PostReport> reports = new HashSet<>();
     53
    5154    @PreRemove
    5255    public void preRemove() {
     
    5659        });
    5760    }
    58 
    59 
    60     @OneToMany(mappedBy = "parent", cascade = CascadeType.ALL, orphanRemoval = true)
     61    @OneToMany(mappedBy = "parent", orphanRemoval = true, fetch = FetchType.EAGER)
    6162    private List<Post> children = new ArrayList<>();
    62 
    63     // getters and setters
    64     public Set<PostVote> getVotes() {
    65         return votes;
    66     }
    67 
    68     public void setVotes(Set<PostVote> votes) {
    69         this.votes = votes;
    70     }
    71     public Long getPostId() {
    72         return postId;
    73     }
    74 
    75     public void setPostId(Long postId) {
    76         this.postId = postId;
    77     }
    78 
    79     public String getTitle() {
    80         return title;
    81     }
    82 
    83     public void setTitle(String title) {
    84         this.title = title;
    85     }
    86 
    87     public String getContent() {
    88         return content;
    89     }
    90 
    91     public void setContent(String content) {
    92         this.content = content;
    93     }
    94 
    95     public CustomUserDetails getAuthor() {
    96         return author;
    97     }
    98 
    99     public void setAuthor(CustomUserDetails author) {
    100         this.author = author;
    101     }
    102 
    103     public LocalDateTime getTimePosted() {
    104         return timePosted;
    105     }
    106 
    107     public void setTimePosted(LocalDateTime timePosted) {
    108         this.timePosted = timePosted;
    109     }
    110 
    111     public LocalDateTime getTimeLastEdited() {
    112         return timeLastEdited;
    113     }
    114 
    115     public void setTimeLastEdited(LocalDateTime timeLastEdited) {
    116         this.timeLastEdited = timeLastEdited;
    117     }
    118 
    119     public Post getParent() {
    120         return parent;
    121     }
    122 
    123     public void setParent(Post parent) {
    124         this.parent = parent;
    125     }
    126 
    127     public List<Post> getChildren() {
    128         return children;
    129     }
    130 
    131     public void setChildren(List<Post> children) {
    132         this.children = children;
    133     }
    13463
    13564    // konstruktor so parent (koga e reply)
  • springapp/src/main/java/mk/profesori/springapp/Model/PostReport.java

    r4abf55a r9bf1f8d  
    11package mk.profesori.springapp.Model;
    22
     3import lombok.Data;
    34import lombok.NoArgsConstructor;
    45
     
    78
    89@Entity
     10@Data
    911@NoArgsConstructor
    1012public class PostReport {
     
    3840        this.resolved=false;
    3941    }
    40 
    41     public boolean isResolved() {
    42         return resolved;
    43     }
    44 
    45     public void setResolved(boolean resolved) {
    46         this.resolved = resolved;
    47     }
    48 
    49     public Long getPostReportId() {
    50         return postReportId;
    51     }
    52 
    53     public void setPostReportId(Long postReportId) {
    54         this.postReportId = postReportId;
    55     }
    56 
    57     public CustomUserDetails getUser() {
    58         return user;
    59     }
    60 
    61     public void setUser(CustomUserDetails user) {
    62         this.user = user;
    63     }
    64 
    65     public Post getPost() {
    66         return post;
    67     }
    68 
    69     public void setPost(Post post) {
    70         this.post = post;
    71     }
    72 
    73     public LocalDateTime getTime() {
    74         return time;
    75     }
    76 
    77     public void setTime(LocalDateTime time) {
    78         this.time = time;
    79     }
    80 
    81     public String getDescription() {
    82         return description;
    83     }
    84 
    85     public void setDescription(String description) {
    86         this.description = description;
    87     }
    8842}
  • springapp/src/main/java/mk/profesori/springapp/Model/PostVote.java

    r4abf55a r9bf1f8d  
    11package mk.profesori.springapp.Model;
    22
     3import lombok.Data;
    34import lombok.NoArgsConstructor;
    45
     
    78
    89@Entity
     10@Data
    911@NoArgsConstructor
    1012public class PostVote {
     
    3436        this.vote = vote;
    3537    }
    36 
    37     public Long getPostVoteId() {
    38         return postVoteId;
    39     }
    40 
    41     public void setPostVoteId(Long id) {
    42         this.postVoteId = id;
    43     }
    44 
    45     public CustomUserDetails getUser() {
    46         return user;
    47     }
    48 
    49     public void setUser(CustomUserDetails user) {
    50         this.user = user;
    51     }
    52 
    53     public Post getPost() {
    54         return post;
    55     }
    56 
    57     public void setPost(Post post) {
    58         this.post = post;
    59     }
    60 
    61     public LocalDateTime getTime() {
    62         return time;
    63     }
    64 
    65     public void setTime(LocalDateTime time) {
    66         this.time = time;
    67     }
    68 
    69     public VoteType getVote() {
    70         return vote;
    71     }
    72 
    73     public void setVote(VoteType vote) {
    74         this.vote = vote;
    75     }
    7638}
  • springapp/src/main/java/mk/profesori/springapp/Model/Professor.java

    r4abf55a r9bf1f8d  
    11package mk.profesori.springapp.Model;
    2 
    3 import java.util.ArrayList;
    4 import java.util.List;
    5 import javax.persistence.CascadeType;
    6 import javax.persistence.Column;
    7 import javax.persistence.Entity;
    8 import javax.persistence.GeneratedValue;
    9 import javax.persistence.GenerationType;
    10 import javax.persistence.Id;
    11 import javax.persistence.JoinColumn;
    12 import javax.persistence.ManyToOne;
    13 import javax.persistence.OneToMany;
    14 import javax.persistence.Table;
    152
    163import com.fasterxml.jackson.annotation.JsonIdentityInfo;
    174import com.voodoodyne.jackson.jsog.JSOGGenerator;
     5import lombok.Data;
     6
     7import javax.persistence.*;
     8import java.util.ArrayList;
     9import java.util.List;
    1810
    1911@Entity
     12@Data
    2013@Table(name = "professor")
    2114@JsonIdentityInfo(generator = JSOGGenerator.class)
     
    3427    private Faculty faculty;
    3528
     29    @Transient
    3630    @OneToMany(mappedBy = "targetProfessor", cascade = CascadeType.ALL)
    37     private List<Opinion> relatedOpinions = new ArrayList<Opinion>();
    38 
    39     // getters
    40     public Long getProfessorId() {
    41         return professorId;
    42     }
    43 
    44     public String getProfessorName() {
    45         return professorName;
    46     }
    47 
    48     public Faculty getFaculty() {
    49         return faculty;
    50     }
    51 
    52     public List<Opinion> getRelatedOpinions() {
    53         return relatedOpinions;
    54     }
     31    private List<Opinion> relatedOpinions = new ArrayList<>();
    5532}
  • springapp/src/main/java/mk/profesori/springapp/Model/StudyProgramme.java

    r4abf55a r9bf1f8d  
    11package mk.profesori.springapp.Model;
    22
     3import com.fasterxml.jackson.annotation.JsonIdentityInfo;
     4import com.voodoodyne.jackson.jsog.JSOGGenerator;
     5import lombok.Data;
     6
     7import javax.persistence.*;
    38import java.util.HashSet;
    49import java.util.Set;
    510
    6 import javax.persistence.Column;
    7 import javax.persistence.Entity;
    8 import javax.persistence.GeneratedValue;
    9 import javax.persistence.GenerationType;
    10 import javax.persistence.Id;
    11 import javax.persistence.JoinColumn;
    12 import javax.persistence.ManyToOne;
    13 import javax.persistence.OneToMany;
    14 import javax.persistence.Table;
    15 
    16 import com.fasterxml.jackson.annotation.JsonIdentityInfo;
    17 import com.voodoodyne.jackson.jsog.JSOGGenerator;
    18 
    1911@Entity
     12@Data
    2013@Table(name = "study_programme")
    2114@JsonIdentityInfo(generator = JSOGGenerator.class)
     
    3730    private Faculty faculty;
    3831
     32    @Transient
    3933    @OneToMany(mappedBy = "studyProgramme")
    4034    private Set<Subject> subjects = new HashSet<>();
    4135
    42     // getters
    43     public Long getStudyProgrammeId() {
    44         return studyProgrammeId;
    45     }
    46 
    47     public String getStudyProgrammeName() {
    48         return studyProgrammeName;
    49     }
    50 
    51     public int getCycle() {
    52         return cycle;
    53     }
    54 
    55     public Faculty getFaculty() {
    56         return faculty;
    57     }
    58 
    59     public Set<Subject> getSubjects() {
    60         return subjects;
    61     }
    62 
    6336}
  • springapp/src/main/java/mk/profesori/springapp/Model/Subject.java

    r4abf55a r9bf1f8d  
    11package mk.profesori.springapp.Model;
    2 
    3 import java.util.ArrayList;
    4 import java.util.List;
    5 import javax.persistence.CascadeType;
    6 import javax.persistence.Column;
    7 import javax.persistence.Entity;
    8 import javax.persistence.GeneratedValue;
    9 import javax.persistence.GenerationType;
    10 import javax.persistence.Id;
    11 import javax.persistence.JoinColumn;
    12 import javax.persistence.ManyToOne;
    13 import javax.persistence.OneToMany;
    14 import javax.persistence.Table;
    152
    163import com.fasterxml.jackson.annotation.JsonIdentityInfo;
    174import com.voodoodyne.jackson.jsog.JSOGGenerator;
     5import lombok.Data;
     6
     7import javax.persistence.*;
     8import java.util.ArrayList;
     9import java.util.List;
    1810
    1911@Entity
     12@Data
    2013@Table(name = "subject")
    2114@JsonIdentityInfo(generator = JSOGGenerator.class)
     
    3427    private StudyProgramme studyProgramme;
    3528
     29    @Transient
    3630    @OneToMany(mappedBy = "targetSubject", cascade = CascadeType.ALL)
    3731    private List<_Thread> threads = new ArrayList<>();
    3832
    39     // getters
    40     public Long getSubjectId() {
    41         return subjectId;
    42     }
    43 
    44     public String getSubjectName() {
    45         return subjectName;
    46     }
    47 
    48     public StudyProgramme getStudyProgramme() {
    49         return studyProgramme;
    50     }
    51 
    52     public List<_Thread> getThreads() {
    53         return threads;
    54     }
    55 
    5633}
  • springapp/src/main/java/mk/profesori/springapp/Model/University.java

    r4abf55a r9bf1f8d  
    11package mk.profesori.springapp.Model;
    22
     3import com.fasterxml.jackson.annotation.JsonIdentityInfo;
     4import com.voodoodyne.jackson.jsog.JSOGGenerator;
     5import lombok.Data;
     6
     7import javax.persistence.*;
    38import java.util.HashSet;
    49import java.util.Set;
    510
    6 import javax.persistence.Column;
    7 import javax.persistence.Entity;
    8 import javax.persistence.GeneratedValue;
    9 import javax.persistence.GenerationType;
    10 import javax.persistence.Id;
    11 import javax.persistence.JoinColumn;
    12 import javax.persistence.ManyToOne;
    13 import javax.persistence.OneToMany;
    14 import javax.persistence.Table;
    15 
    16 import com.fasterxml.jackson.annotation.JsonIdentityInfo;
    17 import com.voodoodyne.jackson.jsog.JSOGGenerator;
    18 
    1911@Entity
     12@Data
    2013@Table(name = "university")
    2114@JsonIdentityInfo(generator = JSOGGenerator.class)
     
    3427    private City city;
    3528
     29    @Transient
    3630    @OneToMany(mappedBy = "university")
    3731    private Set<Faculty> faculties = new HashSet<>();
    38 
    39     // getters
    40     public Long getUniversityId() {
    41         return universityId;
    42     }
    43 
    44     public String getUniversityName() {
    45         return universityName;
    46     }
    47 
    48     public City getCity() {
    49         return city;
    50     }
    51 
    52     public Set<Faculty> getFaculties() {
    53         return faculties;
    54     }
    55 
    5632}
  • springapp/src/main/java/mk/profesori/springapp/Model/_Thread.java

    r4abf55a r9bf1f8d  
    11package mk.profesori.springapp.Model;
    22
    3 import java.time.LocalDateTime;
    4 
    5 import java.util.List;
     3import lombok.Data;
     4import lombok.NoArgsConstructor;
    65
    76import javax.persistence.DiscriminatorValue;
    8 
    97import javax.persistence.Entity;
    108import javax.persistence.JoinColumn;
    119import javax.persistence.ManyToOne;
    12 
    13 import lombok.NoArgsConstructor;
     10import java.time.LocalDateTime;
     11import java.util.List;
    1412
    1513@Entity
     14@Data
    1615@DiscriminatorValue("thread")
    1716@NoArgsConstructor
     
    3635        this.targetSubject = targetSubject;
    3736    }
    38 
    39     public Subject getTargetSubject() {
    40         return targetSubject;
    41     }
    42 
    43     public void setTargetSubject(Subject targetSubject) {
    44         this.targetSubject = targetSubject;
    45     }
    4637}
  • springapp/src/main/java/mk/profesori/springapp/Repository/CityRepository.java

    r4abf55a r9bf1f8d  
    11package mk.profesori.springapp.Repository;
     2
     3import mk.profesori.springapp.Model.City;
     4import org.springframework.data.repository.CrudRepository;
     5import org.springframework.stereotype.Repository;
    26
    37import java.util.List;
    48
    5 import org.springframework.data.repository.CrudRepository;
    6 import org.springframework.stereotype.Repository;
    7 
    8 import mk.profesori.springapp.Model.City;
    9 
    109@Repository
    1110public interface CityRepository extends CrudRepository<City, Long>{
    12 
    1311    List<City> findAll();
    1412    City findByCityId(Long id);
  • springapp/src/main/java/mk/profesori/springapp/Repository/FacultyRepository.java

    r4abf55a r9bf1f8d  
    11package mk.profesori.springapp.Repository;
     2
     3import mk.profesori.springapp.Model.Faculty;
     4import mk.profesori.springapp.Model.University;
     5import org.springframework.data.jpa.repository.Query;
     6import org.springframework.data.repository.CrudRepository;
     7import org.springframework.data.repository.query.Param;
     8import org.springframework.stereotype.Repository;
    29
    310import java.util.List;
    411
    5 import org.springframework.data.repository.CrudRepository;
    6 import org.springframework.stereotype.Repository;
    7 
    8 import mk.profesori.springapp.Model.Faculty;
    9 import mk.profesori.springapp.Model.University;
    10 
    1112@Repository
    1213public interface FacultyRepository extends CrudRepository<Faculty, Long>{
    13 
    1414    List<Faculty> findAll();
    1515    Faculty findByFacultyId(Long id);
    1616    List<Faculty> findByUniversity(University university);
     17    @Query(value = "select professor.id, count(p.*)\n" +
     18            "from professor left join post p on professor.id = p.professor_id\n" +
     19            "where professor.faculty_id = :facultyId\n" +
     20            "group by professor.id\n" +
     21            "order by professor.id;", nativeQuery = true)
     22    List<String> getOpinionCountForEachProfessorInFaculty(@Param("facultyId") Long id);
    1723}
  • springapp/src/main/java/mk/profesori/springapp/Repository/OpinionRepository.java

    r4abf55a r9bf1f8d  
    22
    33import mk.profesori.springapp.Model.Opinion;
     4import mk.profesori.springapp.Model.Professor;
    45import org.springframework.data.repository.CrudRepository;
    56import org.springframework.stereotype.Repository;
     7
     8import java.util.List;
    69
    710@Repository
    811public interface OpinionRepository extends CrudRepository<Opinion,Long> {
    912    Opinion findByPostId(Long postId);
     13    List<Opinion> findByTargetProfessor(Professor p);
     14    List<Opinion> findTop10ByOrderByTimePosted();
    1015}
  • springapp/src/main/java/mk/profesori/springapp/Repository/PostVoteRepository.java

    r4abf55a r9bf1f8d  
    11package mk.profesori.springapp.Repository;
    22
     3import mk.profesori.springapp.Model.Post;
    34import mk.profesori.springapp.Model.PostVote;
    45import org.springframework.data.repository.CrudRepository;
    56import org.springframework.stereotype.Repository;
    67
     8import java.util.List;
     9
    710@Repository
    811public interface PostVoteRepository extends CrudRepository<PostVote,Long> {
    912    PostVote findByPostVoteId(Long id);
     13    List<PostVote> findByPost(Post post);
    1014}
  • springapp/src/main/java/mk/profesori/springapp/Repository/ProfessorRepository.java

    r4abf55a r9bf1f8d  
    11package mk.profesori.springapp.Repository;
    22
    3 import java.util.List;
    4 
     3import mk.profesori.springapp.Model.Faculty;
     4import mk.profesori.springapp.Model.Professor;
    55import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
    66import org.springframework.data.repository.CrudRepository;
    77import org.springframework.stereotype.Repository;
    88
    9 import mk.profesori.springapp.Model.Faculty;
    10 import mk.profesori.springapp.Model.Professor;
     9import java.util.List;
    1110
    1211@Repository
     
    1413
    1514    List<Professor> findAll();
    16 
    1715    Professor findByProfessorId(Long id);
    18 
    1916    List<Professor> findByFaculty(Faculty faculty);
    20 
    2117    List<Professor> findByProfessorNameContainingIgnoreCase(String name);
    2218}
  • springapp/src/main/java/mk/profesori/springapp/Repository/StudyProgrammeRepository.java

    r4abf55a r9bf1f8d  
    11package mk.profesori.springapp.Repository;
    2 
    3 import java.util.List;
    4 
    5 import org.springframework.data.repository.CrudRepository;
    6 import org.springframework.stereotype.Repository;
    72
    83import mk.profesori.springapp.Model.Faculty;
    94import mk.profesori.springapp.Model.StudyProgramme;
     5import org.springframework.data.jpa.repository.Query;
     6import org.springframework.data.repository.CrudRepository;
     7import org.springframework.data.repository.query.Param;
     8import org.springframework.stereotype.Repository;
     9
     10import java.util.List;
    1011
    1112
    1213@Repository
    1314public interface StudyProgrammeRepository extends CrudRepository<StudyProgramme, Long>{
    14    
    1515    List<StudyProgramme> findAll();
    1616    StudyProgramme findByStudyProgrammeId(Long id);
    1717    List<StudyProgramme> findByFaculty(Faculty faculty);
     18    @Query(value = "select subject.id, count(p.*)\n" +
     19            "from subject left join post p on subject.id = p.subject_id\n" +
     20            "where subject.study_programme_id = :studyProgrammeId\n" +
     21            "group by subject.id\n" +
     22            "order by subject.id;", nativeQuery = true)
     23    List<String> getThreadCountForEachSubjectInStudyProgramme(@Param("studyProgrammeId") Long id);
    1824}
  • springapp/src/main/java/mk/profesori/springapp/Repository/SubjectRepository.java

    r4abf55a r9bf1f8d  
    11package mk.profesori.springapp.Repository;
    22
     3import mk.profesori.springapp.Model.StudyProgramme;
     4import mk.profesori.springapp.Model.Subject;
    35import org.springframework.data.repository.CrudRepository;
    46import org.springframework.stereotype.Repository;
    57
    6 import mk.profesori.springapp.Model.Subject;
     8import java.util.List;
    79
    810@Repository
    911public interface SubjectRepository extends CrudRepository<Subject, Long> {
    1012    Subject findBySubjectId(Long id);
     13    List<Subject> findByStudyProgramme(StudyProgramme studyProgramme);
     14    List<Subject> findBySubjectNameContainingIgnoreCase(String name);
    1115}
  • springapp/src/main/java/mk/profesori/springapp/Repository/UniversityRepository.java

    r4abf55a r9bf1f8d  
    11package mk.profesori.springapp.Repository;
     2
     3import mk.profesori.springapp.Model.City;
     4import mk.profesori.springapp.Model.University;
     5import org.springframework.data.jpa.repository.Query;
     6import org.springframework.data.repository.CrudRepository;
     7import org.springframework.stereotype.Repository;
    28
    39import java.util.List;
    410
    5 import org.springframework.data.repository.CrudRepository;
    6 import org.springframework.stereotype.Repository;
    7 
    8 import mk.profesori.springapp.Model.City;
    9 import mk.profesori.springapp.Model.University;
    10 
    1111@Repository
    1212public interface UniversityRepository extends CrudRepository<University, Long> {
    13 
    1413    List<University> findAll();
    1514
    1615    University findByUniversityId(Long id);
    17 
    1816    List<University> findByCity(City city);
     17   
     18    @Query(value = "select facultyid, sectioncount, opinioncount from\n" +
     19            "(select faculty_id as facultyid, t1.subject_count+t2.professor_count as sectioncount\n" +
     20            "from (select sp.faculty_id, count(*) as subject_count\n" +
     21            "      from subject s join study_programme sp on s.study_programme_id = sp.id\n" +
     22            "      group by sp.faculty_id) as t1\n" +
     23            "         natural join\n" +
     24            "     (select count(*) as professor_count, f.id as faculty_id\n" +
     25            "      from professor p join faculty f on f.id = p.faculty_id\n" +
     26            "      group by f.id) as t2) as q1\n" +
     27            "natural join\n" +
     28            "(select facultyid, count1+count2 as opinioncount from\n" +
     29            "    (select pr.faculty_id as facultyid, count(*) as count1\n" +
     30            "    from post po join professor pr on po.professor_id = pr.id\n" +
     31            "    where professor_id is not null\n" +
     32            "    group by pr.faculty_id) as t3\n" +
     33            "    natural join\n" +
     34            "    (select sp.faculty_id as facultyid, count(*) as count2\n" +
     35            "    from post po join subject s on po.subject_id = s.id join study_programme sp on s.study_programme_id = sp.id\n" +
     36            "    where po.subject_id is not null\n" +
     37            "    group by sp.faculty_id) as t4) as q2;", nativeQuery = true)
     38    List<String> findSectionAndPostCount();
    1939}
  • springapp/src/main/java/mk/profesori/springapp/Repository/UserRepository.java

    r4abf55a r9bf1f8d  
    11package mk.profesori.springapp.Repository;
    22
    3 import java.util.Optional;
    4 
     3import mk.profesori.springapp.Model.CustomUserDetails;
    54import org.springframework.data.jpa.repository.JpaRepository;
    65import org.springframework.data.jpa.repository.Modifying;
     
    98import org.springframework.transaction.annotation.Transactional;
    109
    11 import mk.profesori.springapp.Model.CustomUserDetails;
     10import java.util.Optional;
    1211
    1312@Repository
     
    1615    Optional<CustomUserDetails> findByEmail(String email);
    1716    Optional<CustomUserDetails> findByUsername(String username);
     17    Optional<CustomUserDetails> findById(Long id);
    1818
    1919    @Transactional
  • springapp/src/main/java/mk/profesori/springapp/Repository/_ThreadRepository.java

    r4abf55a r9bf1f8d  
    11package mk.profesori.springapp.Repository;
    22
     3import mk.profesori.springapp.Model.Subject;
    34import mk.profesori.springapp.Model._Thread;
    45import org.springframework.data.repository.CrudRepository;
    56import org.springframework.stereotype.Repository;
    67
     8import java.util.List;
     9
    710@Repository
    811public interface _ThreadRepository extends CrudRepository<_Thread,Long> {
    912    _Thread findByPostId(Long postId);
     13    List<_Thread> findTop10ByOrderByTimePosted();
     14    List<_Thread> findByTargetSubject(Subject s);
    1015}
  • springapp/src/main/java/mk/profesori/springapp/Security/SecurityConfiguration.java

    r4abf55a r9bf1f8d  
    3737            @Override
    3838            public void addCorsMappings(CorsRegistry registry) {
    39                 registry.addMapping("/**").allowedOrigins("http://192.168.0.29:3000", "http://192.168.0.28:3000")
     39                registry.addMapping("/**").allowedOrigins("http://192.168.1.254:3000", "http://192.168.0.28:3000")
    4040                        .allowCredentials(true);
    4141            }
  • springapp/src/main/java/mk/profesori/springapp/Service/CustomUserDetailsService.java

    r4abf55a r9bf1f8d  
    11package mk.profesori.springapp.Service;
    22
    3 import java.time.LocalDateTime;
    4 import java.util.UUID;
    5 
     3import lombok.AllArgsConstructor;
     4import mk.profesori.springapp.Model.ConfirmationToken;
     5import mk.profesori.springapp.Model.CustomUserDetails;
     6import mk.profesori.springapp.Repository.UserRepository;
    67import org.springframework.beans.factory.annotation.Autowired;
    78import org.springframework.security.core.userdetails.UserDetails;
     
    1213import org.springframework.stereotype.Service;
    1314
    14 import lombok.AllArgsConstructor;
    15 import mk.profesori.springapp.Model.ConfirmationToken;
    16 import mk.profesori.springapp.Model.CustomUserDetails;
    17 import mk.profesori.springapp.Repository.UserRepository;
     15import java.time.LocalDateTime;
     16import java.util.UUID;
    1817
    1918@Service
  • springapp/src/main/java/mk/profesori/springapp/Service/Exception/DisallowedOperationException.java

    r4abf55a r9bf1f8d  
    1 package mk.profesori.springapp.Service;
     1package mk.profesori.springapp.Service.Exception;
    22
    33public class DisallowedOperationException extends RuntimeException{
  • springapp/src/main/java/mk/profesori/springapp/Service/Exception/IncompatiblePostId.java

    r4abf55a r9bf1f8d  
    1 package mk.profesori.springapp.Service;
     1package mk.profesori.springapp.Service.Exception;
    22
    33public class IncompatiblePostId extends RuntimeException{
  • springapp/src/main/java/mk/profesori/springapp/Service/MainService.java

    r4abf55a r9bf1f8d  
    33import mk.profesori.springapp.Model.*;
    44import mk.profesori.springapp.Repository.*;
     5import mk.profesori.springapp.Service.Exception.DisallowedOperationException;
     6import mk.profesori.springapp.Service.Exception.IncompatiblePostId;
    57import org.springframework.security.core.userdetails.UsernameNotFoundException;
    68import org.springframework.stereotype.Service;
     
    911import java.util.ArrayList;
    1012import java.util.List;
     13import java.util.Set;
    1114
    1215@Service
     
    2427    private final UserRepository userRepository;
    2528    private final PostReportRepository postReportRepository;
    26 
    27     public MainService(ProfessorRepository professorRepository, StudyProgrammeRepository studyProgrammeRepository, FacultyRepository facultyRepository, UniversityRepository universityRepository, CityRepository cityRepository, OpinionRepository opinionRepository, _ThreadRepository _threadRepository, SubjectRepository subjectRepository, PostVoteRepository postVoteRepository, UserRepository userRepository, PostReportRepository postReportRepository) {
     29    private final PostRepository postRepository;
     30
     31    public MainService(ProfessorRepository professorRepository, StudyProgrammeRepository studyProgrammeRepository, FacultyRepository facultyRepository, UniversityRepository universityRepository, CityRepository cityRepository, OpinionRepository opinionRepository, _ThreadRepository _threadRepository, SubjectRepository subjectRepository, PostVoteRepository postVoteRepository, UserRepository userRepository, PostReportRepository postReportRepository, PostRepository postRepository) {
    2832        this.professorRepository = professorRepository;
    2933        this.studyProgrammeRepository = studyProgrammeRepository;
     
    3741        this.userRepository = userRepository;
    3842        this.postReportRepository = postReportRepository;
     43        this.postRepository = postRepository;
    3944    }
    4045
     
    5863    public List<Professor> getProfessorsByNameContains(String contained) {
    5964        return new ArrayList<>(professorRepository.findByProfessorNameContainingIgnoreCase(contained));
     65    }
     66
     67    public List<Subject> getSubjectsByNameContains(String contained) {
     68        return new ArrayList<>(subjectRepository.findBySubjectNameContainingIgnoreCase(contained));
    6069    }
    6170
     
    200209    public void delete_Thread(Long postId) {_threadRepository.deleteById(postId);}
    201210
    202     public String updateOpinion(String newContent, Long newTargetProfessorId, Long newParentPostId, Long postId) {
     211    public void updateOpinion(String newContent, Long newTargetProfessorId, Long newParentPostId, Long postId) {
    203212        Opinion opinionToUpdate = opinionRepository.findByPostId(postId);
    204213
     
    218227        opinionToUpdate.setParent(newParentOpinion);
    219228
     229        propagateNewTargetProfessorToChildren(opinionToUpdate, newTargetProfessor);
     230
     231        opinionToUpdate.setTimeLastEdited(LocalDateTime.now());
     232        opinionRepository.save(opinionToUpdate);
     233    }
     234
     235    public void propagateNewTargetProfessorToChildren(Opinion opinionToUpdate, Professor newTargetProfessor) {
    220236        for(Post p : opinionToUpdate.getChildren()) {
     237            // direct children
    221238            Opinion o = (Opinion) p;
    222239            o.setTargetProfessor(newTargetProfessor);
     240            // ancestors
     241            if(o.getChildren().isEmpty()) return;
     242            propagateNewTargetProfessorToChildren(o, newTargetProfessor);
    223243        }
    224         opinionToUpdate.setTimeLastEdited(LocalDateTime.now());
    225         opinionRepository.save(opinionToUpdate);
    226         return null;
    227     }
    228 
    229     public String update_Thread(String newTitle, String newContent, Long newTargetSubjectId, Long newParentThreadId, Long postId) {
     244    }
     245
     246    public void update_Thread(String newTitle, String newContent, Long newTargetSubjectId, Long newParentThreadId, Long postId) {
    230247        _Thread _threadToUpdate = _threadRepository.findByPostId(postId);
    231248
     
    251268            }
    252269
    253          for(Post p : _threadToUpdate.getChildren()) {
    254                 _Thread t = (_Thread) p;
    255                 t.setTargetSubject(newTargetSubject);
    256             }
     270         propagateNewTargetSubjectToChildren(_threadToUpdate, newTargetSubject);
     271
    257272         _threadToUpdate.setTimeLastEdited(LocalDateTime.now());
    258273          _threadRepository.save(_threadToUpdate);
    259           return null;
     274    }
     275
     276    public void propagateNewTargetSubjectToChildren(_Thread _threadToUpdate, Subject newTargetSubject) {
     277        for(Post p : _threadToUpdate.getChildren()) {
     278            // direct children
     279            _Thread t = (_Thread) p;
     280            t.setTargetSubject(newTargetSubject);
     281            // ancestors
     282            if (t.getChildren().isEmpty()) return;
     283            propagateNewTargetSubjectToChildren(t, newTargetSubject);
     284        }
    260285    }
    261286
     
    306331        postReportRepository.save(reportToAdd);
    307332    }
     333
     334    public List<Opinion> getRelatedOpinions(Long professorId) {
     335        Professor p = professorRepository.findByProfessorId(professorId);
     336        return opinionRepository.findByTargetProfessor(p);
     337    }
     338
     339    public List<Opinion> getLatest10Opinions() {
     340        return opinionRepository.findTop10ByOrderByTimePosted();
     341    }
     342    public List<_Thread> getLatest10Threads() {
     343        return _threadRepository.findTop10ByOrderByTimePosted();
     344    }
     345
     346    public List<String> getUniversitySectionAndPostCount() {
     347        return universityRepository.findSectionAndPostCount();
     348    }
     349
     350    public List<Subject> getSubjectsByStudyProgramme(Long studyProgrammeId) {
     351        StudyProgramme sp = studyProgrammeRepository.findByStudyProgrammeId(studyProgrammeId);
     352        return subjectRepository.findByStudyProgramme(sp);
     353    }
     354
     355    public List<String> getOpinionCountForEachProfessorInFaculty(Long id) {
     356        return facultyRepository.getOpinionCountForEachProfessorInFaculty(id);
     357    }
     358
     359    public List<_Thread> getThreadsBySubject(Long id) {
     360        Subject s = subjectRepository.findBySubjectId(id);
     361        return _threadRepository.findByTargetSubject(s);
     362    }
     363
     364    public List<String> getThreadCountForEachSubjectInStudyProgramme(Long id) {
     365        return studyProgrammeRepository.getThreadCountForEachSubjectInStudyProgramme(id);
     366    }
     367
     368    public Set<Post> getPostsByUser(String email) {
     369        CustomUserDetails u = userRepository.findByEmail(email).orElseThrow(
     370                () -> new UsernameNotFoundException(String.format("User with email %s not found", email)));
     371        return postRepository.findByAuthor(u);
     372    }
     373
     374    public CustomUserDetails getPublicUserProfile(Long id) {
     375        return userRepository.findById(id).orElseThrow(
     376                () -> new UsernameNotFoundException(String.format("User with id %d not found", id)));
     377    }
    308378}
  • springapp/src/main/java/mk/profesori/springapp/Service/RegistrationService.java

    r4abf55a r9bf1f8d  
    4545                String tokenToResend = customUserDetailsService
    4646                        .createToken(userRepository.findByEmail(request.getEmail()).get());
    47                 String link = "http://192.168.0.29:8080/registration/confirm?token=" + tokenToResend;
     47                String link = "http://192.168.1.254:8080/registration/confirm?token=" + tokenToResend;
    4848                emailSender.send(request.getEmail(), emailSender.buildEmail(request.getUsername(), link));
    4949                return tokenToResend;
     
    6666                        UserRole.REGULAR));
    6767
    68         String link = "http://192.168.0.29:8080/registration/confirm?token=" + token;
     68        String link = "http://192.168.1.254:8080/registration/confirm?token=" + token;
    6969
    7070        emailSender.send(request.getEmail(), emailSender.buildEmail(request.getUsername(), link));
  • springapp/src/main/resources/application.properties

    r4abf55a r9bf1f8d  
    66spring.jpa.show-sql=false
    77spring.jpa.properties.hibernate.format_sql=true
    8 server.address=192.168.0.29
    9 spring.mail.host=localhost
     8server.address=192.168.1.254
     9spring.mail.host=192.168.1.254
    1010spring.mail.username=mailuser
    1111spring.mail.password=mailpass
    1212spring.mail.port=1025
     13spring.jpa.open-in-view=false
Note: See TracChangeset for help on using the changeset viewer.