├── .gitignore ├── DummyFiles ├── CN-SDN.pdf ├── GradAlgoNotes.pdf └── IntroToMachineLearning.pdf ├── LICENSE ├── README.md ├── backend ├── README.md ├── api │ ├── __init__.py │ ├── admin.py │ ├── apps.py │ ├── migrations │ │ ├── 0001_initial.py │ │ ├── 0002_course_professor_semester_topic_peeruser_last_poll_and_more.py │ │ ├── 0003_file_course_alter_file_original_author_and_more.py │ │ ├── 0004_course_number_alter_course_name.py │ │ ├── 0005_file_created_at.py │ │ ├── 0006_file_points_peeruser_points.py │ │ ├── 0007_remove_file_points_file_downvotes_file_upvotes.py │ │ ├── 0008_userreport.py │ │ ├── 0009_add_course_data.py │ │ ├── 0010_add_professors.py │ │ ├── 0011_auto_20240421_0406.py │ │ ├── 0012_auto_20240422_1349.py │ │ └── __init__.py │ ├── models.py │ ├── serializers.py │ ├── tests.py │ ├── urls.py │ ├── user_login.py │ ├── utils │ │ ├── JSON_inputs │ │ │ ├── courses.json │ │ │ └── faculty_list.json │ │ ├── __init__.py │ │ └── get_client_ip.py │ ├── views.py │ └── views │ │ └── filters.py ├── manage.py ├── peer_notes │ ├── __init__.py │ ├── asgi.py │ ├── settings.py │ ├── urls.py │ ├── views.py │ └── wsgi.py ├── requirements.txt └── server.py ├── file_peer ├── .gitignore ├── peer_service.py └── uploads │ ├── 1_abcd.txt │ └── 2_bcdef.txt ├── frontend ├── .eslintrc.cjs ├── .gitignore ├── README.md ├── index.html ├── package-lock.json ├── package.json ├── public │ ├── PeerNotes.png │ └── vite.svg ├── src │ ├── App.tsx │ ├── assets │ │ ├── file.svg │ │ └── react.svg │ ├── contexts │ │ └── session.ts │ ├── hooks │ │ ├── sessionRedirect.ts │ │ └── usePoll.ts │ ├── index.css │ ├── main.tsx │ ├── screens │ │ ├── Login.tsx │ │ ├── MainSearch.tsx │ │ ├── RegisterFile.tsx │ │ ├── Results.tsx │ │ └── Signup.tsx │ ├── styles │ │ ├── App.css │ │ ├── Login.css │ │ ├── MainScreenWrapper.css │ │ ├── RegisterFile.css │ │ └── Results.module.css │ ├── types │ │ └── types.ts │ ├── utils │ │ └── getAuthHeaders.ts │ └── vite-env.d.ts ├── tsconfig.json ├── tsconfig.node.json └── vite.config.ts └── test1.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | cover/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | .pybuilder/ 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | # For a library or package, you might want to ignore these files since the code is 87 | # intended to run in multiple environments; otherwise, check them in: 88 | # .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # poetry 98 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 99 | # This is especially recommended for binary packages to ensure reproducibility, and is more 100 | # commonly ignored for libraries. 101 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 102 | #poetry.lock 103 | 104 | # pdm 105 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 106 | #pdm.lock 107 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 108 | # in version control. 109 | # https://pdm.fming.dev/#use-with-ide 110 | .pdm.toml 111 | 112 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 113 | __pypackages__/ 114 | 115 | # Celery stuff 116 | celerybeat-schedule 117 | celerybeat.pid 118 | 119 | # SageMath parsed files 120 | *.sage.py 121 | 122 | # Environments 123 | .env 124 | .venv 125 | env/ 126 | venv/ 127 | ENV/ 128 | env.bak/ 129 | venv.bak/ 130 | 131 | # Spyder project settings 132 | .spyderproject 133 | .spyproject 134 | 135 | # Rope project settings 136 | .ropeproject 137 | 138 | # mkdocs documentation 139 | /site 140 | 141 | # mypy 142 | .mypy_cache/ 143 | .dmypy.json 144 | dmypy.json 145 | 146 | # Pyre type checker 147 | .pyre/ 148 | 149 | # pytype static type analyzer 150 | .pytype/ 151 | 152 | # Cython debug symbols 153 | cython_debug/ 154 | 155 | # PyCharm 156 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 157 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 158 | # and can be added to the global gitignore or merged into this file. For a more nuclear 159 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 160 | #.idea/ 161 | 162 | # Misc 163 | .DS_STORE 164 | -------------------------------------------------------------------------------- /DummyFiles/CN-SDN.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AmanTahiliani/PeerNotes/46a4574f62d46a0110f9324f7d0e23b3c8f3f9bc/DummyFiles/CN-SDN.pdf -------------------------------------------------------------------------------- /DummyFiles/GradAlgoNotes.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AmanTahiliani/PeerNotes/46a4574f62d46a0110f9324f7d0e23b3c8f3f9bc/DummyFiles/GradAlgoNotes.pdf -------------------------------------------------------------------------------- /DummyFiles/IntroToMachineLearning.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AmanTahiliani/PeerNotes/46a4574f62d46a0110f9324f7d0e23b3c8f3f9bc/DummyFiles/IntroToMachineLearning.pdf -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PeerNotes (In Progress) 2 | Repository for a course project of CS4675/CS6675 at Georgia Institute of Technology. 3 | 4 | ## Advisory for Contributors 5 | 11 | 12 | ## Contributing to the Codebase 13 | 1. Create a new branch: 14 | ```bash 15 | git checkout -b 16 | ``` 17 | 2. Make your changes. 18 | 19 | 3. Use black to format your code: (With virtual environment activated) 20 | ```bash 21 | black . 22 | ``` 23 | 24 | 4. Make your changes and commit them: 25 | ```bash 26 | git add . 27 | git commit -m "Your commit message" 28 | ``` 29 | 5. Push your changes to the remote repository: 30 | ```bash 31 | git push origin 32 | ``` 33 | 6. Create a Pull Request on GitHub. 34 | 35 | 36 | ## Getting Started 37 | 38 | # Frontend (ReactJs) 39 | For our frontend we are using React + Vite + TypeScript! 40 | 41 | - **React** -> JavaScript Framework for creating reactive user interfaces. 42 | - **Vite** -> Development environment / build tool. Gives us access to features like hot reload, bundling, and plugins. 43 | - **TypeScript** -> A superset of JavaScript allowing for static types. 44 | 45 | Here's how to set up + run the frontend environment: 46 | 1. Download and install [Node.js](https://nodejs.org/en/download) v18+ 47 | Check your node version: 48 | ```sh 49 | node -v 50 | ``` 51 | 2. Clone the repo using Git 52 | 3. Install dependencies 53 | ```sh 54 | cd frontend 55 | npm install 56 | ``` 57 | 4. Start the development server 58 | ```sh 59 | npm run dev 60 | ``` 61 | 5. Visit http://localhost:5173 62 | 63 | 64 | # Backend 65 | # Central Server (Django) 66 | 67 | This is the backend for the Django project. 68 | 69 | 70 | ## Setup 71 | 72 | 1. Open a new terminal and cd into the backend directory: 73 | ```bash 74 | cd backend 75 | ``` 76 | 77 | 2. Create a virtual environment: 78 | ```bash 79 | python3 -m venv venv 80 | ``` 81 | 82 | 3. Activate the virtual environment: 83 | - For macOS/Linux: 84 | ```bash 85 | source venv/bin/activate 86 | ``` 87 | - For Windows: 88 | ```bash 89 | venv\Scripts\activate 90 | ``` 91 | 92 | 4. Install the project dependencies: 93 | ```bash 94 | pip install -r requirements.txt 95 | ``` 96 | 97 | ### Running the Server 98 | 99 | To start the Django server, run the following command: 100 | ```bash 101 | python3 manage.py runserver 102 | ``` 103 | 104 | By default, the server will run on `http://localhost:8000/`. 105 | 106 | ### Admin Panel 107 | In order to access the admin panel, you need to create a superuser. To do this, run the following command: 108 | ```bash 109 | python3 manage.py createsuperuser 110 | ``` 111 | 112 | Then, you can access the admin panel by visiting `http://localhost:8000/admin/` and logging in with the superuser credentials. 113 | 114 | # Intermediate Proxy Service (Flask) 115 | 116 | This is the service which runs on the client side and is responsible for handling the peer to peer connections and file transfers along with getting the IPs of the machine. In order to run this, please do the following: 117 | 118 | 1. Open a new terminal and cd into the file_peer directory: 119 | ```bash 120 | cd file_peer 121 | ``` 122 | 2. Make sure that the venv that we used for the backend service is activated 123 | 124 | 3. Run the flask server using the following command: 125 | ```bash 126 | python3 peer_service.py 127 | ``` 128 | 129 | 130 | 131 | 132 | ## Further Configurations: 133 | - You would need to set up the central server with a domain and change the domain within the client code. (It is currently hardcoded with IP.) 134 | - Make sure all the servers are running within the same network as this solution is featured to be restricted to within a network by design. -------------------------------------------------------------------------------- /backend/README.md: -------------------------------------------------------------------------------- 1 | # Django Backend 2 | 3 | This is the backend for the Django project. 4 | 5 | ## Setup 6 | 7 | 1. Create a virtual environment: 8 | ```bash 9 | python3 -m venv venv 10 | ``` 11 | 12 | 2. Activate the virtual environment: 13 | - For macOS/Linux: 14 | ```bash 15 | source venv/bin/activate 16 | ``` 17 | - For Windows: 18 | ```bash 19 | venv\Scripts\activate 20 | ``` 21 | 22 | 3. Install the project dependencies: 23 | ```bash 24 | pip install -r requirements.txt 25 | ``` 26 | 27 | ## Running the Server 28 | 29 | To start the Django server, run the following command: 30 | ```bash 31 | python3 manage.py runserver 32 | ``` 33 | 34 | By default, the server will run on `http://localhost:8000/`. 35 | 36 | ## Admin Panel 37 | In order to access the admin panel, you need to create a superuser. To do this, run the following command: 38 | ```bash 39 | python3 manage.py createsuperuser 40 | ``` 41 | 42 | Then, you can access the admin panel by visiting `http://localhost:8000/admin/` and logging in with the superuser credentials. 43 | 44 | ## Contributing to the Codebase 45 | 1. Create a new branch: 46 | ```bash 47 | git checkout -b 48 | ``` 49 | 2. Make your changes. 50 | 51 | 3. Use black to format your code: 52 | ```bash 53 | black . 54 | ``` 55 | 56 | 4. Make your changes and commit them: 57 | ```bash 58 | git add . 59 | git commit -m "Your commit message" 60 | ``` 61 | 5. Push your changes to the remote repository: 62 | ```bash 63 | git push origin 64 | ``` 65 | 6. Create a Pull Request on GitHub. 66 | 67 | 68 | -------------------------------------------------------------------------------- /backend/api/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AmanTahiliani/PeerNotes/46a4574f62d46a0110f9324f7d0e23b3c8f3f9bc/backend/api/__init__.py -------------------------------------------------------------------------------- /backend/api/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from api.models import PeerUser, Semester, Professor, Course, Topic, File 3 | 4 | # Register your models here. 5 | admin.site.register(PeerUser) 6 | admin.site.register(Topic) 7 | admin.site.register(Professor) 8 | admin.site.register(Course) 9 | admin.site.register(File) 10 | admin.site.register(Semester) 11 | -------------------------------------------------------------------------------- /backend/api/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class ApiConfig(AppConfig): 5 | default_auto_field = "django.db.models.BigAutoField" 6 | name = "api" 7 | -------------------------------------------------------------------------------- /backend/api/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.11 on 2024-03-25 17:37 2 | 3 | import django.contrib.auth.models 4 | import django.contrib.auth.validators 5 | from django.db import migrations, models 6 | import django.utils.timezone 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | initial = True 12 | 13 | dependencies = [ 14 | ("auth", "0012_alter_user_first_name_max_length"), 15 | ] 16 | 17 | operations = [ 18 | migrations.CreateModel( 19 | name="PeerUser", 20 | fields=[ 21 | ( 22 | "id", 23 | models.BigAutoField( 24 | auto_created=True, 25 | primary_key=True, 26 | serialize=False, 27 | verbose_name="ID", 28 | ), 29 | ), 30 | ("password", models.CharField(max_length=128, verbose_name="password")), 31 | ( 32 | "last_login", 33 | models.DateTimeField( 34 | blank=True, null=True, verbose_name="last login" 35 | ), 36 | ), 37 | ( 38 | "is_superuser", 39 | models.BooleanField( 40 | default=False, 41 | help_text="Designates that this user has all permissions without explicitly assigning them.", 42 | verbose_name="superuser status", 43 | ), 44 | ), 45 | ( 46 | "username", 47 | models.CharField( 48 | error_messages={ 49 | "unique": "A user with that username already exists." 50 | }, 51 | help_text="Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.", 52 | max_length=150, 53 | unique=True, 54 | validators=[ 55 | django.contrib.auth.validators.UnicodeUsernameValidator() 56 | ], 57 | verbose_name="username", 58 | ), 59 | ), 60 | ( 61 | "first_name", 62 | models.CharField( 63 | blank=True, max_length=150, verbose_name="first name" 64 | ), 65 | ), 66 | ( 67 | "last_name", 68 | models.CharField( 69 | blank=True, max_length=150, verbose_name="last name" 70 | ), 71 | ), 72 | ( 73 | "email", 74 | models.EmailField( 75 | blank=True, max_length=254, verbose_name="email address" 76 | ), 77 | ), 78 | ( 79 | "is_staff", 80 | models.BooleanField( 81 | default=False, 82 | help_text="Designates whether the user can log into this admin site.", 83 | verbose_name="staff status", 84 | ), 85 | ), 86 | ( 87 | "is_active", 88 | models.BooleanField( 89 | default=True, 90 | help_text="Designates whether this user should be treated as active. Unselect this instead of deleting accounts.", 91 | verbose_name="active", 92 | ), 93 | ), 94 | ( 95 | "date_joined", 96 | models.DateTimeField( 97 | default=django.utils.timezone.now, verbose_name="date joined" 98 | ), 99 | ), 100 | ("ip_address", models.GenericIPAddressField(blank=True, null=True)), 101 | ( 102 | "groups", 103 | models.ManyToManyField( 104 | blank=True, 105 | help_text="The groups this user belongs to. A user will get all permissions granted to each of their groups.", 106 | related_name="user_set", 107 | related_query_name="user", 108 | to="auth.group", 109 | verbose_name="groups", 110 | ), 111 | ), 112 | ( 113 | "user_permissions", 114 | models.ManyToManyField( 115 | blank=True, 116 | help_text="Specific permissions for this user.", 117 | related_name="user_set", 118 | related_query_name="user", 119 | to="auth.permission", 120 | verbose_name="user permissions", 121 | ), 122 | ), 123 | ], 124 | options={ 125 | "verbose_name": "user", 126 | "verbose_name_plural": "users", 127 | "abstract": False, 128 | }, 129 | managers=[ 130 | ("objects", django.contrib.auth.models.UserManager()), 131 | ], 132 | ), 133 | ] 134 | -------------------------------------------------------------------------------- /backend/api/migrations/0002_course_professor_semester_topic_peeruser_last_poll_and_more.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.11 on 2024-03-26 04:40 2 | 3 | from django.conf import settings 4 | from django.db import migrations, models 5 | import django.db.models.deletion 6 | import django.utils.timezone 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | dependencies = [ 12 | ("api", "0001_initial"), 13 | ] 14 | 15 | operations = [ 16 | migrations.CreateModel( 17 | name="Course", 18 | fields=[ 19 | ( 20 | "id", 21 | models.BigAutoField( 22 | auto_created=True, 23 | primary_key=True, 24 | serialize=False, 25 | verbose_name="ID", 26 | ), 27 | ), 28 | ("name", models.CharField(max_length=20)), 29 | ], 30 | ), 31 | migrations.CreateModel( 32 | name="Professor", 33 | fields=[ 34 | ( 35 | "id", 36 | models.BigAutoField( 37 | auto_created=True, 38 | primary_key=True, 39 | serialize=False, 40 | verbose_name="ID", 41 | ), 42 | ), 43 | ("name", models.CharField(max_length=100)), 44 | ], 45 | ), 46 | migrations.CreateModel( 47 | name="Semester", 48 | fields=[ 49 | ( 50 | "id", 51 | models.BigAutoField( 52 | auto_created=True, 53 | primary_key=True, 54 | serialize=False, 55 | verbose_name="ID", 56 | ), 57 | ), 58 | ("name", models.CharField(max_length=20)), 59 | ], 60 | ), 61 | migrations.CreateModel( 62 | name="Topic", 63 | fields=[ 64 | ( 65 | "id", 66 | models.BigAutoField( 67 | auto_created=True, 68 | primary_key=True, 69 | serialize=False, 70 | verbose_name="ID", 71 | ), 72 | ), 73 | ("name", models.CharField(max_length=100)), 74 | ("description", models.TextField(blank=True)), 75 | ], 76 | ), 77 | migrations.AddField( 78 | model_name="peeruser", 79 | name="last_poll", 80 | field=models.DateTimeField( 81 | auto_now_add=True, default=django.utils.timezone.now 82 | ), 83 | preserve_default=False, 84 | ), 85 | migrations.CreateModel( 86 | name="File", 87 | fields=[ 88 | ( 89 | "id", 90 | models.BigAutoField( 91 | auto_created=True, 92 | primary_key=True, 93 | serialize=False, 94 | verbose_name="ID", 95 | ), 96 | ), 97 | ("filename", models.CharField(max_length=200)), 98 | ( 99 | "original_author", 100 | models.ForeignKey( 101 | null=True, 102 | on_delete=django.db.models.deletion.SET_NULL, 103 | to=settings.AUTH_USER_MODEL, 104 | ), 105 | ), 106 | ( 107 | "peer_users", 108 | models.ManyToManyField( 109 | related_name="shared_files", to=settings.AUTH_USER_MODEL 110 | ), 111 | ), 112 | ( 113 | "professor", 114 | models.ForeignKey( 115 | null=True, 116 | on_delete=django.db.models.deletion.SET_NULL, 117 | to="api.professor", 118 | ), 119 | ), 120 | ( 121 | "semester", 122 | models.ForeignKey( 123 | null=True, 124 | on_delete=django.db.models.deletion.SET_NULL, 125 | to="api.semester", 126 | ), 127 | ), 128 | ( 129 | "topic", 130 | models.ForeignKey( 131 | null=True, 132 | on_delete=django.db.models.deletion.SET_NULL, 133 | to="api.topic", 134 | ), 135 | ), 136 | ], 137 | ), 138 | ] 139 | -------------------------------------------------------------------------------- /backend/api/migrations/0003_file_course_alter_file_original_author_and_more.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.11 on 2024-03-26 16:30 2 | 3 | from django.conf import settings 4 | from django.db import migrations, models 5 | import django.db.models.deletion 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ("api", "0002_course_professor_semester_topic_peeruser_last_poll_and_more"), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name="file", 17 | name="course", 18 | field=models.ForeignKey( 19 | null=True, 20 | on_delete=django.db.models.deletion.SET_NULL, 21 | related_name="files", 22 | to="api.course", 23 | ), 24 | ), 25 | migrations.AlterField( 26 | model_name="file", 27 | name="original_author", 28 | field=models.ForeignKey( 29 | null=True, 30 | on_delete=django.db.models.deletion.SET_NULL, 31 | related_name="owned_files", 32 | to=settings.AUTH_USER_MODEL, 33 | ), 34 | ), 35 | migrations.AlterField( 36 | model_name="file", 37 | name="professor", 38 | field=models.ForeignKey( 39 | null=True, 40 | on_delete=django.db.models.deletion.SET_NULL, 41 | related_name="files", 42 | to="api.professor", 43 | ), 44 | ), 45 | migrations.AlterField( 46 | model_name="file", 47 | name="semester", 48 | field=models.ForeignKey( 49 | null=True, 50 | on_delete=django.db.models.deletion.SET_NULL, 51 | related_name="files", 52 | to="api.semester", 53 | ), 54 | ), 55 | migrations.AlterField( 56 | model_name="file", 57 | name="topic", 58 | field=models.ForeignKey( 59 | null=True, 60 | on_delete=django.db.models.deletion.SET_NULL, 61 | related_name="files", 62 | to="api.topic", 63 | ), 64 | ), 65 | ] 66 | -------------------------------------------------------------------------------- /backend/api/migrations/0004_course_number_alter_course_name.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.11 on 2024-03-26 17:52 2 | 3 | from django.db import migrations, models 4 | import django.utils.timezone 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ("api", "0003_file_course_alter_file_original_author_and_more"), 11 | ] 12 | 13 | operations = [ 14 | migrations.AddField( 15 | model_name="course", 16 | name="number", 17 | field=models.CharField( 18 | default=django.utils.timezone.now, max_length=20, unique=True 19 | ), 20 | preserve_default=False, 21 | ), 22 | migrations.AlterField( 23 | model_name="course", 24 | name="name", 25 | field=models.CharField(blank=True, max_length=40), 26 | ), 27 | ] 28 | -------------------------------------------------------------------------------- /backend/api/migrations/0005_file_created_at.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.11 on 2024-03-26 23:14 2 | 3 | from django.db import migrations, models 4 | import django.utils.timezone 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ("api", "0004_course_number_alter_course_name"), 11 | ] 12 | 13 | operations = [ 14 | migrations.AddField( 15 | model_name="file", 16 | name="created_at", 17 | field=models.DateTimeField( 18 | auto_now_add=True, default=django.utils.timezone.now 19 | ), 20 | preserve_default=False, 21 | ), 22 | ] 23 | -------------------------------------------------------------------------------- /backend/api/migrations/0006_file_points_peeruser_points.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.11 on 2024-04-12 20:24 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ("api", "0005_file_created_at"), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name="file", 15 | name="points", 16 | field=models.IntegerField(default=0), 17 | ), 18 | migrations.AddField( 19 | model_name="peeruser", 20 | name="points", 21 | field=models.IntegerField(default=0), 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /backend/api/migrations/0007_remove_file_points_file_downvotes_file_upvotes.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.11 on 2024-04-12 21:47 2 | 3 | from django.conf import settings 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ("api", "0006_file_points_peeruser_points"), 11 | ] 12 | 13 | operations = [ 14 | migrations.RemoveField( 15 | model_name="file", 16 | name="points", 17 | ), 18 | migrations.AddField( 19 | model_name="file", 20 | name="downvotes", 21 | field=models.ManyToManyField( 22 | related_name="downvoted_file", to=settings.AUTH_USER_MODEL 23 | ), 24 | ), 25 | migrations.AddField( 26 | model_name="file", 27 | name="upvotes", 28 | field=models.ManyToManyField( 29 | related_name="upvoted_file", to=settings.AUTH_USER_MODEL 30 | ), 31 | ), 32 | ] 33 | -------------------------------------------------------------------------------- /backend/api/migrations/0008_userreport.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.11 on 2024-04-20 23:10 2 | 3 | from django.conf import settings 4 | from django.db import migrations, models 5 | import django.db.models.deletion 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ("api", "0007_remove_file_points_file_downvotes_file_upvotes"), 12 | ] 13 | 14 | operations = [ 15 | migrations.CreateModel( 16 | name="UserReport", 17 | fields=[ 18 | ( 19 | "id", 20 | models.BigAutoField( 21 | auto_created=True, 22 | primary_key=True, 23 | serialize=False, 24 | verbose_name="ID", 25 | ), 26 | ), 27 | ("description", models.TextField(max_length=300)), 28 | ( 29 | "file", 30 | models.ForeignKey( 31 | null=True, 32 | on_delete=django.db.models.deletion.CASCADE, 33 | related_name="file_report", 34 | to="api.file", 35 | ), 36 | ), 37 | ( 38 | "reporting_user", 39 | models.ForeignKey( 40 | null=True, 41 | on_delete=django.db.models.deletion.SET_NULL, 42 | related_name="user_report_generated", 43 | to=settings.AUTH_USER_MODEL, 44 | ), 45 | ), 46 | ( 47 | "user", 48 | models.ForeignKey( 49 | null=True, 50 | on_delete=django.db.models.deletion.SET_NULL, 51 | related_name="user_report", 52 | to=settings.AUTH_USER_MODEL, 53 | ), 54 | ), 55 | ], 56 | ), 57 | ] 58 | -------------------------------------------------------------------------------- /backend/api/migrations/0009_add_course_data.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.11 on 2024-04-21 03:17 2 | 3 | from django.db import migrations 4 | import os 5 | 6 | import json 7 | 8 | 9 | def load_data_from_json(apps, schema_editor): 10 | Course = apps.get_model("api", "Course") 11 | json_file = os.path.abspath( 12 | os.path.join( 13 | os.path.dirname(__file__), "..", "utils", "JSON_inputs", "courses.json" 14 | ) 15 | ) 16 | 17 | with open(json_file, "r") as f: 18 | data = json.load(f) 19 | 20 | for item in data: 21 | try: 22 | Course.objects.create( 23 | name=item["name"], 24 | number=item["number"], 25 | ) 26 | except Exception as e: 27 | print("Error:", str(e)) 28 | 29 | 30 | class Migration(migrations.Migration): 31 | 32 | dependencies = [ 33 | ("api", "0008_userreport"), 34 | ] 35 | 36 | operations = [ 37 | migrations.RunPython(load_data_from_json), 38 | ] 39 | -------------------------------------------------------------------------------- /backend/api/migrations/0010_add_professors.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.11 on 2024-04-21 04:01 2 | 3 | from django.db import migrations 4 | import os 5 | 6 | import json 7 | 8 | 9 | def load_data_from_json(apps, schema_editor): 10 | Professor = apps.get_model("api", "Professor") 11 | json_file = os.path.abspath( 12 | os.path.join( 13 | os.path.dirname(__file__), "..", "utils", "JSON_inputs", "faculty_list.json" 14 | ) 15 | ) 16 | 17 | with open(json_file, "r") as f: 18 | data = json.load(f) 19 | 20 | for item in data: 21 | try: 22 | Professor.objects.create( 23 | name=item["name"], 24 | ) 25 | except Exception as e: 26 | print("Error:", str(e)) 27 | 28 | 29 | class Migration(migrations.Migration): 30 | 31 | dependencies = [ 32 | ("api", "0009_add_course_data"), 33 | ] 34 | 35 | operations = [ 36 | migrations.RunPython(load_data_from_json), 37 | ] 38 | -------------------------------------------------------------------------------- /backend/api/migrations/0011_auto_20240421_0406.py: -------------------------------------------------------------------------------- 1 | from django.db import migrations 2 | import os 3 | 4 | import json 5 | 6 | 7 | def load_data_from_json(apps, schema_editor): 8 | Semester = apps.get_model("api", "Semester") 9 | Semester.objects.all().delete() 10 | for year in range(2015, 2024): 11 | for sem in ["Spring", "Summer", "Fall"]: 12 | try: 13 | Semester.objects.create( 14 | name=f"{sem} {year}", 15 | ) 16 | except Exception as e: 17 | print("Error:", str(e)) 18 | 19 | 20 | class Migration(migrations.Migration): 21 | 22 | dependencies = [ 23 | ("api", "0010_add_professors"), 24 | ] 25 | operations = [ 26 | migrations.RunPython(load_data_from_json), 27 | ] 28 | -------------------------------------------------------------------------------- /backend/api/migrations/0012_auto_20240422_1349.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.2.11 on 2024-04-22 13:49 2 | 3 | from django.db import migrations 4 | 5 | 6 | def load_data_from_json(apps, schema_editor): 7 | topics = [ 8 | "Introduction to Computer Science", 9 | "Programming Fundamentals", 10 | "Data Structures", 11 | "Algorithms", 12 | "Computer Architecture", 13 | "Operating Systems", 14 | "Database Systems", 15 | "Networking", 16 | "Software Engineering", 17 | "Artificial Intelligence", 18 | "Theory of Computation", 19 | "Compiler Design", 20 | "Distributed Systems", 21 | "Computer Graphics", 22 | "Cryptography", 23 | "Parallel and Concurrent Programming", 24 | "Human-Computer Interaction", 25 | "Computer Vision", 26 | "Web Technologies", 27 | "Embedded Systems", 28 | "Data Mining and Analytics", 29 | "Software Quality Assurance", 30 | "Ethical and Social Implications of Computing", 31 | "Advanced Topics", 32 | "Automata Theory", 33 | "Formal Languages and Automata", 34 | "Computational Complexity", 35 | "Graph Theory", 36 | "Computational Geometry", 37 | "Numerical Analysis", 38 | "Machine Learning", 39 | "Deep Learning", 40 | "Natural Language Processing", 41 | "Reinforcement Learning", 42 | "Computer Security", 43 | "Network Security", 44 | "Information Retrieval", 45 | "Cloud Computing", 46 | "IoT (Internet of Things)", 47 | "Virtual Reality", 48 | "Augmented Reality", 49 | "Mobile Application Development", 50 | "Game Development", 51 | "Computer Animation", 52 | "Operating System Design", 53 | "Real-Time Systems", 54 | "Concurrency Control", 55 | "Transaction Processing", 56 | "Data Warehousing", 57 | "Big Data Technologies", 58 | "Data Science", 59 | "Business Intelligence", 60 | "Quantum Computing", 61 | "Bioinformatics", 62 | "Computational Biology", 63 | "Robotics", 64 | "Computer Ethics", 65 | "Privacy and Security in Computing", 66 | "Digital Forensics", 67 | "Cryptocurrency and Blockchain", 68 | "Social Network Analysis", 69 | "Cloud Security", 70 | "Edge Computing", 71 | "Compiler Optimization", 72 | "High-Performance Computing", 73 | "Grid Computing", 74 | "Fuzzy Logic and Systems", 75 | "Expert Systems", 76 | "Pattern Recognition", 77 | "Knowledge Representation and Reasoning", 78 | "Semantic Web", 79 | "Computer Algebra Systems", 80 | "Geographic Information Systems", 81 | "Wireless Sensor Networks", 82 | "Biometric Authentication", 83 | "Internet Security Protocols", 84 | "Software Testing and Debugging", 85 | "Agile Methodologies", 86 | "DevOps", 87 | "Continuous Integration and Continuous Deployment (CI/CD)", 88 | "Model-Driven Engineering", 89 | "User Interface Design", 90 | "Accessibility in Computing", 91 | "User Experience (UX) Design", 92 | "Game Theory", 93 | "Social Computing", 94 | "Computational Sociology", 95 | "Computational Linguistics", 96 | "Music Information Retrieval", 97 | "Ethics in Artificial Intelligence", 98 | "Algorithmic Game Theory", 99 | "Behavioral Economics and Computing", 100 | "Neuroinformatics", 101 | "Health Informatics", 102 | "Geometric Modeling", 103 | "Scientific Computing", 104 | "Information Theory", 105 | "System Modeling and Simulation", 106 | "Decision Support Systems", 107 | "Database Security and Privacy", 108 | "Knowledge Management Systems", 109 | "Information Visualization", 110 | "Geospatial Data Analysis", 111 | "Enterprise Architecture", 112 | "Supply Chain Management Systems", 113 | "Data Integration and Interoperability", 114 | "Software Reuse and Component-Based Development", 115 | "Legal and Regulatory Issues in Computing", 116 | "Information Assurance", 117 | "Systems Analysis and Design", 118 | "E-commerce Systems", 119 | "E-learning Systems", 120 | "Human-Robot Interaction", 121 | "Biologically Inspired Computing", 122 | "Social Robotics", 123 | "Humanoid Robotics", 124 | "Computational Neuroscience", 125 | "Affective Computing", 126 | "Explainable AI", 127 | "Robot Ethics", 128 | "Cyber-Physical Systems", 129 | "Internet of Medical Things (IoMT)", 130 | "Quantum Information Science", 131 | "Quantum Cryptography", 132 | "Quantum Algorithms", 133 | "Quantum Machine Learning", 134 | "Quantum Error Correction", 135 | "Quantum Networking", 136 | ] 137 | 138 | Topic = apps.get_model("api", "Topic") 139 | Topic.objects.all().delete() 140 | for topic in topics: 141 | try: 142 | Topic.objects.create( 143 | name=topic, 144 | ) 145 | except Exception as e: 146 | print("Error:", str(e)) 147 | 148 | 149 | class Migration(migrations.Migration): 150 | 151 | dependencies = [ 152 | ("api", "0011_auto_20240421_0406"), 153 | ] 154 | 155 | operations = [ 156 | migrations.RunPython(load_data_from_json), 157 | ] 158 | -------------------------------------------------------------------------------- /backend/api/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AmanTahiliani/PeerNotes/46a4574f62d46a0110f9324f7d0e23b3c8f3f9bc/backend/api/migrations/__init__.py -------------------------------------------------------------------------------- /backend/api/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from django.contrib.auth.models import AbstractUser 3 | from django.db import models 4 | 5 | 6 | class PeerUser(AbstractUser): 7 | ip_address = models.GenericIPAddressField(blank=True, null=True) 8 | last_poll = models.DateTimeField(auto_now_add=True) 9 | points = models.IntegerField(default=0, blank=False) 10 | 11 | 12 | class Topic(models.Model): 13 | name = models.CharField(max_length=100, blank=False, null=False) 14 | description = models.TextField(blank=True) 15 | 16 | def __str__(self) -> str: 17 | return self.name 18 | 19 | 20 | class Professor(models.Model): 21 | name = models.CharField(max_length=100, blank=False, null=False) 22 | 23 | def __str__(self) -> str: 24 | return self.name 25 | 26 | 27 | class Semester(models.Model): 28 | name = models.CharField(max_length=20, blank=False, null=False) 29 | 30 | def __str__(self) -> str: 31 | return self.name 32 | 33 | 34 | class Course(models.Model): 35 | name = models.CharField(max_length=40, blank=True) 36 | number = models.CharField(max_length=20, blank=False, null=False, unique=True) 37 | 38 | def __str__(self) -> str: 39 | return self.number + f" ({self.name})" 40 | 41 | 42 | class File(models.Model): 43 | filename = models.CharField(max_length=200, blank=False, null=False) 44 | original_author = models.ForeignKey( 45 | PeerUser, on_delete=models.SET_NULL, null=True, related_name="owned_files" 46 | ) 47 | peer_users = models.ManyToManyField(PeerUser, related_name="shared_files") 48 | topic = models.ForeignKey( 49 | Topic, on_delete=models.SET_NULL, null=True, related_name="files" 50 | ) 51 | professor = models.ForeignKey( 52 | Professor, on_delete=models.SET_NULL, null=True, related_name="files" 53 | ) 54 | semester = models.ForeignKey( 55 | Semester, on_delete=models.SET_NULL, null=True, related_name="files" 56 | ) 57 | course = models.ForeignKey( 58 | Course, on_delete=models.SET_NULL, null=True, related_name="files" 59 | ) 60 | created_at = models.DateTimeField(auto_now_add=True) 61 | upvotes = models.ManyToManyField(PeerUser, related_name="upvoted_file") 62 | downvotes = models.ManyToManyField(PeerUser, related_name="downvoted_file") 63 | 64 | @property 65 | def points(self): 66 | return self.upvotes.count() - self.downvotes.count() 67 | 68 | def __str__(self): 69 | return self.filename 70 | 71 | 72 | class UserReport(models.Model): 73 | user = models.ForeignKey( 74 | PeerUser, on_delete=models.SET_NULL, null=True, related_name="user_report" 75 | ) 76 | reporting_user = models.ForeignKey( 77 | PeerUser, 78 | on_delete=models.SET_NULL, 79 | null=True, 80 | related_name="user_report_generated", 81 | ) 82 | description = models.TextField(blank=False, null=False, max_length=300) 83 | file = models.ForeignKey( 84 | File, on_delete=models.CASCADE, null=True, related_name="file_report" 85 | ) 86 | 87 | def __str__(self) -> str: 88 | return f"{self.user}:{self.file}:{self.reporting_user}" 89 | -------------------------------------------------------------------------------- /backend/api/serializers.py: -------------------------------------------------------------------------------- 1 | from rest_framework import serializers 2 | from .models import PeerUser, Topic, Semester, Professor, Course, File 3 | 4 | 5 | class LoginSerializer(serializers.Serializer): 6 | username = serializers.CharField() 7 | password = serializers.CharField() 8 | 9 | 10 | class UserSerializer(serializers.ModelSerializer): 11 | class Meta: 12 | model = PeerUser 13 | fields = ["id", "username", "email", "ip_address", "points"] 14 | 15 | 16 | class TopicSerializer(serializers.ModelSerializer): 17 | class Meta: 18 | model = Topic 19 | fields = "__all__" 20 | 21 | 22 | class ProfessorSerializer(serializers.ModelSerializer): 23 | class Meta: 24 | model = Professor 25 | fields = "__all__" 26 | 27 | 28 | class SemesterSerializer(serializers.ModelSerializer): 29 | class Meta: 30 | model = Semester 31 | fields = "__all__" 32 | 33 | 34 | class CourseSerializer(serializers.ModelSerializer): 35 | class Meta: 36 | model = Course 37 | fields = "__all__" 38 | 39 | 40 | class FileSerializer(serializers.ModelSerializer): 41 | original_author = UserSerializer() 42 | peer_users = UserSerializer(many=True) 43 | topic = TopicSerializer() 44 | professor = ProfessorSerializer() 45 | semester = SemesterSerializer() 46 | course = CourseSerializer() 47 | 48 | class Meta: 49 | model = File 50 | fields = [ 51 | "id", 52 | "filename", 53 | "points", 54 | "original_author", 55 | "peer_users", 56 | "topic", 57 | "professor", 58 | "semester", 59 | "course", 60 | "created_at", 61 | "upvotes", 62 | "downvotes", 63 | ] 64 | -------------------------------------------------------------------------------- /backend/api/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /backend/api/urls.py: -------------------------------------------------------------------------------- 1 | """ 2 | URL configuration for peer_notes project. 3 | 4 | The `urlpatterns` list routes URLs to views. For more information please see: 5 | https://docs.djangoproject.com/en/4.2/topics/http/urls/ 6 | Examples: 7 | Function views 8 | 1. Add an import: from my_app import views 9 | 2. Add a URL to urlpatterns: path('', views.home, name='home') 10 | Class-based views 11 | 1. Add an import: from other_app.views import Home 12 | 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') 13 | Including another URLconf 14 | 1. Import the include() function: from django.urls import include, path 15 | 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) 16 | """ 17 | 18 | from django.urls import path 19 | from api.user_login import LoginView, SignupView, PollOnlineView 20 | from api import views 21 | 22 | urlpatterns = [ 23 | path("api/login/", LoginView.as_view(), name="login"), 24 | path("api/signup/", SignupView.as_view(), name="signup"), 25 | path("api/poll/", PollOnlineView.as_view(), name="poll"), 26 | path("api/topics/", views.TopicListCreateAPIView.as_view(), name="topic-list"), 27 | path( 28 | "api/topics//", views.TopicDetailAPIView.as_view(), name="topic-detail" 29 | ), 30 | path( 31 | "api/professors/", 32 | views.ProfessorListCreateAPIView.as_view(), 33 | name="professor-list", 34 | ), 35 | path( 36 | "api/professors//", 37 | views.ProfessorDetailAPIView.as_view(), 38 | name="professor-detail", 39 | ), 40 | path( 41 | "api/semesters/", 42 | views.SemesterListCreateAPIView.as_view(), 43 | name="semester-list", 44 | ), 45 | path( 46 | "api/semesters//", 47 | views.SemesterDetailAPIView.as_view(), 48 | name="semester-detail", 49 | ), 50 | path("api/courses/", views.CourseListCreateAPIView.as_view(), name="course-list"), 51 | path( 52 | "api/courses//", 53 | views.CourseDetailAPIView.as_view(), 54 | name="course-detail", 55 | ), 56 | path("api/files/", views.FileListCreateAPIView.as_view(), name="file-list"), 57 | path( 58 | "api/files//upvote/", 59 | views.UpvoteFile.as_view(), 60 | name="upvote-file", 61 | ), 62 | path( 63 | "api/files//downvote/", 64 | views.DownvoteFile.as_view(), 65 | name="downvote-file", 66 | ), 67 | path( 68 | "api/files//add-peer/", 69 | views.AddPeerToFile.as_view(), 70 | name="add-peer", 71 | ), 72 | path("api/files//", views.FileDetailAPIView.as_view(), name="file-detail"), 73 | path("api/report_user/", views.ReportUserView.as_view(), name="report-user"), 74 | path("api/register/", views.RegisterFile.as_view(), name="file-register"), 75 | path("api/files/filter/", views.FileFilterView.as_view(), name="file-filter-view"), 76 | path("api/get-peer-files/", views.UserFilesView.as_view(), name="user-files-view"), 77 | ] 78 | -------------------------------------------------------------------------------- /backend/api/user_login.py: -------------------------------------------------------------------------------- 1 | from api.serializers import LoginSerializer, UserSerializer 2 | from django.contrib.auth import authenticate, get_user_model 3 | from rest_framework import status 4 | from rest_framework.authtoken.models import Token 5 | from rest_framework.response import Response 6 | from rest_framework.views import APIView 7 | from rest_framework.authentication import TokenAuthentication 8 | from rest_framework.permissions import IsAuthenticated 9 | from api.utils.get_client_ip import get_client_ip, PollMiddleware 10 | from django.utils import timezone 11 | 12 | 13 | class LoginView(APIView): 14 | def post(self, request): 15 | serializer = LoginSerializer(data=request.data) 16 | if serializer.is_valid(): 17 | username = serializer.validated_data["username"] 18 | password = serializer.validated_data["password"] 19 | user = authenticate(username=username, password=password) 20 | if user: 21 | token, _ = Token.objects.get_or_create(user=user) 22 | user.last_poll = timezone.now() 23 | response = Response({"token": token.key}, status=status.HTTP_200_OK) 24 | response.set_cookie("token", token.key) 25 | return response 26 | else: 27 | return Response( 28 | {"error": "Invalid credentials"}, 29 | status=status.HTTP_401_UNAUTHORIZED, 30 | ) 31 | else: 32 | return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) 33 | 34 | 35 | class SignupView(APIView): 36 | def post(self, request): 37 | serializer = UserSerializer(data=request.data) 38 | if serializer.is_valid(): 39 | print(serializer.validated_data) 40 | username = serializer.validated_data["username"] 41 | email = serializer.validated_data["email"] 42 | password = request.data["password"] 43 | ip_address = get_client_ip(request) 44 | print(email, username, password) 45 | if get_user_model().objects.filter(username=username).exists(): 46 | return Response( 47 | {"error": "Username already exists"}, 48 | status=status.HTTP_400_BAD_REQUEST, 49 | ) 50 | 51 | user = get_user_model().objects.create_user( 52 | username=username, email=email, password=password, ip_address=ip_address 53 | ) 54 | 55 | return Response( 56 | {"message": "User created successfully"}, status=status.HTTP_201_CREATED 57 | ) 58 | else: 59 | return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) 60 | 61 | 62 | class PollOnlineView(APIView): 63 | authentication_classes = [TokenAuthentication] 64 | permission_classes = [IsAuthenticated] 65 | 66 | def post(self, request): 67 | try: 68 | user = request.user 69 | try: 70 | data = request.data 71 | ip_address = data["ip"] 72 | print("Local IP found in request") 73 | except Exception as e: 74 | ip_address = get_client_ip(request) 75 | print("Using public IP") 76 | user.ip_address = ip_address 77 | user.last_poll = timezone.now() 78 | user.save() 79 | return Response( 80 | { 81 | "Message": "IP address updated successfully", 82 | "username": user.username, 83 | }, 84 | status=status.HTTP_200_OK, 85 | ) 86 | except Exception as e: 87 | return Response({"error": str(e)}, status=status.HTTP_400_BAD_REQUEST) 88 | -------------------------------------------------------------------------------- /backend/api/utils/JSON_inputs/courses.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "number": "CS 1100", 4 | "name": "Freshman Leap Seminar" 5 | }, 6 | { 7 | "number": "CS 1171", 8 | "name": "Computing in MATLAB" 9 | }, 10 | { 11 | "number": "CS 1301", 12 | "name": "Intro to Computing" 13 | }, 14 | { 15 | "number": "CS 1301R", 16 | "name": "CS 1301 Recitation" 17 | }, 18 | { 19 | "number": "CS 1315", 20 | "name": "Intro Media Computation" 21 | }, 22 | { 23 | "number": "CS 1315R", 24 | "name": "CS 1315 Recitation" 25 | }, 26 | { 27 | "number": "CS 1316", 28 | "name": "Rep Structure & Behavior" 29 | }, 30 | { 31 | "number": "CS 1331", 32 | "name": "Intro-Object Orient Prog" 33 | }, 34 | { 35 | "number": "CS 1331R", 36 | "name": "CS 1331 Recitation" 37 | }, 38 | { 39 | "number": "CS 1332", 40 | "name": "Data Struct & Algorithms" 41 | }, 42 | { 43 | "number": "CS 1332R", 44 | "name": "CS 1332 Recitation" 45 | }, 46 | { 47 | "number": "CS 1371", 48 | "name": "Computing for Engineers" 49 | }, 50 | { 51 | "number": "CS 1371R", 52 | "name": "CS 1371 Recitation" 53 | }, 54 | { 55 | "number": "CS 1372", 56 | "name": "Program Design for Engrs" 57 | }, 58 | { 59 | "number": "CS 1801", 60 | "name": "Special Topics" 61 | }, 62 | { 63 | "number": "CS 1802", 64 | "name": "Special Topics" 65 | }, 66 | { 67 | "number": "CS 1803", 68 | "name": "Special Topics" 69 | }, 70 | { 71 | "number": "CS 1804", 72 | "name": "Special Topics" 73 | }, 74 | { 75 | "number": "CS 1805", 76 | "name": "Special Topics" 77 | }, 78 | { 79 | "number": "CS 1XXX", 80 | "name": "Computer Sci Elective" 81 | }, 82 | { 83 | "number": "CS 2050", 84 | "name": "Intro Discrete Math CS" 85 | }, 86 | { 87 | "number": "CS 2050R", 88 | "name": "CS 2050 Recitation" 89 | }, 90 | { 91 | "number": "CS 2051", 92 | "name": "Honors Discrete Math CS" 93 | }, 94 | { 95 | "number": "CS 2110", 96 | "name": "Computer Organiz&Program" 97 | }, 98 | { 99 | "number": "CS 2200", 100 | "name": "Systems and Networks" 101 | }, 102 | { 103 | "number": "CS 2261", 104 | "name": "Media Device Architectur" 105 | }, 106 | { 107 | "number": "CS 2316", 108 | "name": "Data Input/Manipulation" 109 | }, 110 | { 111 | "number": "CS 2316R", 112 | "name": "CS 2316 Recitation" 113 | }, 114 | { 115 | "number": "CS 2335", 116 | "name": "Software Practicum" 117 | }, 118 | { 119 | "number": "CS 2340", 120 | "name": "Objects and Design" 121 | }, 122 | { 123 | "number": "CS 2345", 124 | "name": "Adv Practical O-O Prog" 125 | }, 126 | { 127 | "number": "CS 2600", 128 | "name": "Knowledge Rep & Process" 129 | }, 130 | { 131 | "number": "CS 2698", 132 | "name": "Research Assistantship" 133 | }, 134 | { 135 | "number": "CS 2699", 136 | "name": "Undergraduate Research" 137 | }, 138 | { 139 | "number": "CS 2701", 140 | "name": "Startup Lab" 141 | }, 142 | { 143 | "number": "CS 2701R", 144 | "name": "CS 2701 Recitation" 145 | }, 146 | { 147 | "number": "CS 2801", 148 | "name": "Special Topics" 149 | }, 150 | { 151 | "number": "CS 2802", 152 | "name": "Special Topics" 153 | }, 154 | { 155 | "number": "CS 2803", 156 | "name": "Special Topics" 157 | }, 158 | { 159 | "number": "CS 2804", 160 | "name": "Special Topics" 161 | }, 162 | { 163 | "number": "CS 2805", 164 | "name": "Special Topics" 165 | }, 166 | { 167 | "number": "CS 2XXX", 168 | "name": "Computer Sci Elective" 169 | }, 170 | { 171 | "number": "CS 3001", 172 | "name": "Computing & Society" 173 | }, 174 | { 175 | "number": "CS 3101", 176 | "name": "Comp Sci Ventures" 177 | }, 178 | { 179 | "number": "CS 3210", 180 | "name": "Design-Operating Systems" 181 | }, 182 | { 183 | "number": "CS 3220", 184 | "name": "Processor Design" 185 | }, 186 | { 187 | "number": "CS 3240", 188 | "name": "Languages and Computation" 189 | }, 190 | { 191 | "number": "CS 3251", 192 | "name": "Computer Networking I" 193 | }, 194 | { 195 | "number": "CS 3300", 196 | "name": "Intro to Software Engr" 197 | }, 198 | { 199 | "number": "CS 3311", 200 | "name": "Project Design" 201 | }, 202 | { 203 | "number": "CS 3312", 204 | "name": "Project Implementation" 205 | }, 206 | { 207 | "number": "CS 3451", 208 | "name": "Computer Graphics" 209 | }, 210 | { 211 | "number": "CS 3510", 212 | "name": "Dsgn&Analysis-Algorithms" 213 | }, 214 | { 215 | "number": "CS 3511", 216 | "name": "Algorithms Honors" 217 | }, 218 | { 219 | "number": "CS 3600", 220 | "name": "Intro-Artificial Intell" 221 | }, 222 | { 223 | "number": "CS 3630", 224 | "name": "Intro-Perception&Robotic" 225 | }, 226 | { 227 | "number": "CS 3651", 228 | "name": "Prototyping Intelligent Device" 229 | }, 230 | { 231 | "number": "CS 3651R", 232 | "name": "CS 3651 Recitation" 233 | }, 234 | { 235 | "number": "CS 3743", 236 | "name": "Emerging Technologies" 237 | }, 238 | { 239 | "number": "CS 3744", 240 | "name": "Mangn Prod Serv Tech Dev" 241 | }, 242 | { 243 | "number": "CS 3750", 244 | "name": "User Interface Design" 245 | }, 246 | { 247 | "number": "CS 3751", 248 | "name": "Intro UI Design" 249 | }, 250 | { 251 | "number": "CS 3790", 252 | "name": "Intro-Cognitive Science" 253 | }, 254 | { 255 | "number": "CS 3801", 256 | "name": "Special Topics" 257 | }, 258 | { 259 | "number": "CS 3802", 260 | "name": "Special Topics" 261 | }, 262 | { 263 | "number": "CS 3803", 264 | "name": "Special Topics" 265 | }, 266 | { 267 | "number": "CS 3804", 268 | "name": "Special Topics" 269 | }, 270 | { 271 | "number": "CS 3805", 272 | "name": "Special Topics" 273 | }, 274 | { 275 | "number": "CS 3873", 276 | "name": "Special Topics" 277 | }, 278 | { 279 | "number": "CS 3XXX", 280 | "name": "Computer Sci" 281 | }, 282 | { 283 | "number": "CS 4001", 284 | "name": "Computing & Society" 285 | }, 286 | { 287 | "number": "CS 4002", 288 | "name": "Robots and Society" 289 | }, 290 | { 291 | "number": "CS 4003", 292 | "name": "AI Ethics and Society" 293 | }, 294 | { 295 | "number": "CS 4005", 296 | "name": "Next Gen Computing Tech" 297 | }, 298 | { 299 | "number": "CS 4010", 300 | "name": "Intro to Computer Law" 301 | }, 302 | { 303 | "number": "CS 4052", 304 | "name": "Systems Analysis& Design" 305 | }, 306 | { 307 | "number": "CS 4057", 308 | "name": "Bus Process Analy&Design" 309 | }, 310 | { 311 | "number": "CS 4117", 312 | "name": "Intro Malware Rev Eng" 313 | }, 314 | { 315 | "number": "CS 4210", 316 | "name": "Adv Operating Systems" 317 | }, 318 | { 319 | "number": "CS 4220", 320 | "name": "Embedded Systems" 321 | }, 322 | { 323 | "number": "CS 4220R", 324 | "name": "CS 4220 Recitation" 325 | }, 326 | { 327 | "number": "CS 4233", 328 | "name": "Parallel Comp Arch" 329 | }, 330 | { 331 | "number": "CS 4235", 332 | "name": "Intro to Info Security" 333 | }, 334 | { 335 | "number": "CS 4237", 336 | "name": "Comp & Network Security" 337 | }, 338 | { 339 | "number": "CS 4238", 340 | "name": "Computer Sys Security" 341 | }, 342 | { 343 | "number": "CS 4239", 344 | "name": "Enterprise Cyber Mgt" 345 | }, 346 | { 347 | "number": "CS 4240", 348 | "name": "Compilers & Interpreters" 349 | }, 350 | { 351 | "number": "CS 4245", 352 | "name": "Intro Data Mining & Anal" 353 | }, 354 | { 355 | "number": "CS 4251", 356 | "name": "Computer Networking II" 357 | }, 358 | { 359 | "number": "CS 4255", 360 | "name": "Intro-Network Management" 361 | }, 362 | { 363 | "number": "CS 4260", 364 | "name": "Telecommunications Sys" 365 | }, 366 | { 367 | "number": "CS 4261", 368 | "name": "Mobile Apps & Svcs" 369 | }, 370 | { 371 | "number": "CS 4262", 372 | "name": "Network Security" 373 | }, 374 | { 375 | "number": "CS 4263", 376 | "name": "Psychol of Cybersecurity" 377 | }, 378 | { 379 | "number": "CS 4265", 380 | "name": "Intro to Blockchain" 381 | }, 382 | { 383 | "number": "CS 4270", 384 | "name": "Data Communications Lab" 385 | }, 386 | { 387 | "number": "CS 4280", 388 | "name": "Survey-Telecom & the Law" 389 | }, 390 | { 391 | "number": "CS 4290", 392 | "name": "Advanced Computer Org" 393 | }, 394 | { 395 | "number": "CS 4320", 396 | "name": "Software Processes" 397 | }, 398 | { 399 | "number": "CS 4330", 400 | "name": "Software Applications" 401 | }, 402 | { 403 | "number": "CS 4342", 404 | "name": "Software Generation" 405 | }, 406 | { 407 | "number": "CS 4365", 408 | "name": "Intro Enterprise Comp" 409 | }, 410 | { 411 | "number": "CS 4392", 412 | "name": "Programming Languages" 413 | }, 414 | { 415 | "number": "CS 4400", 416 | "name": "Intr to Database Systems" 417 | }, 418 | { 419 | "number": "CS 4420", 420 | "name": "Database Sys Implement" 421 | }, 422 | { 423 | "number": "CS 4423", 424 | "name": "Adv Database Systems" 425 | }, 426 | { 427 | "number": "CS 4432", 428 | "name": "Information Systems Dsgn" 429 | }, 430 | { 431 | "number": "CS 4440", 432 | "name": "Database Technologies" 433 | }, 434 | { 435 | "number": "CS 4452", 436 | "name": "Human-Centered Computing" 437 | }, 438 | { 439 | "number": "CS 4455", 440 | "name": "Video Game Design" 441 | }, 442 | { 443 | "number": "CS 4460", 444 | "name": "Intro Info Visualization" 445 | }, 446 | { 447 | "number": "CS 4460R", 448 | "name": "CS 4460 Recitation" 449 | }, 450 | { 451 | "number": "CS 4464", 452 | "name": "Computational Journalism" 453 | }, 454 | { 455 | "number": "CS 4470", 456 | "name": "User Interface Software" 457 | }, 458 | { 459 | "number": "CS 4472", 460 | "name": "Design of Online Comm" 461 | }, 462 | { 463 | "number": "CS 4475", 464 | "name": "Comp Photography" 465 | }, 466 | { 467 | "number": "CS 4476", 468 | "name": "Intro to Computer Vision" 469 | }, 470 | { 471 | "number": "CS 4480", 472 | "name": "Digital Video Special FX" 473 | }, 474 | { 475 | "number": "CS 4488", 476 | "name": "Procedural Content Gen" 477 | }, 478 | { 479 | "number": "CS 4495", 480 | "name": "Computer Vision" 481 | }, 482 | { 483 | "number": "CS 4496", 484 | "name": "Computer Animation" 485 | }, 486 | { 487 | "number": "CS 4497", 488 | "name": "Comp Aesthetics" 489 | }, 490 | { 491 | "number": "CS 4510", 492 | "name": "Automata and Complexity" 493 | }, 494 | { 495 | "number": "CS 4520", 496 | "name": "Approximation Algs" 497 | }, 498 | { 499 | "number": "CS 4530", 500 | "name": "Randomized Algs" 501 | }, 502 | { 503 | "number": "CS 4540", 504 | "name": "Advanced Algs" 505 | }, 506 | { 507 | "number": "CS 4550", 508 | "name": "Scientific Visualization" 509 | }, 510 | { 511 | "number": "CS 4560", 512 | "name": "Verification of Systems" 513 | }, 514 | { 515 | "number": "CS 4590", 516 | "name": "Computer Audio" 517 | }, 518 | { 519 | "number": "CS 4605", 520 | "name": "Mobile&Ubiquitous Comp" 521 | }, 522 | { 523 | "number": "CS 4611", 524 | "name": "AI Problem Solving" 525 | }, 526 | { 527 | "number": "CS 4613", 528 | "name": "Knowledge Systems Engr" 529 | }, 530 | { 531 | "number": "CS 4615", 532 | "name": "Knowledge-Based Modl&Dgn" 533 | }, 534 | { 535 | "number": "CS 4616", 536 | "name": "Pattern Recognition" 537 | }, 538 | { 539 | "number": "CS 4622", 540 | "name": "Case-Based Reasoning" 541 | }, 542 | { 543 | "number": "CS 4625", 544 | "name": "Intel & Interactive Sys" 545 | }, 546 | { 547 | "number": "CS 4632", 548 | "name": "Adv Intelligent Robotics" 549 | }, 550 | { 551 | "number": "CS 4635", 552 | "name": "Knowledge-Based AI" 553 | }, 554 | { 555 | "number": "CS 4641", 556 | "name": "Machine Learning" 557 | }, 558 | { 559 | "number": "CS 4644", 560 | "name": "Deep Learning" 561 | }, 562 | { 563 | "number": "CS 4646", 564 | "name": "Mach Learn for Trading" 565 | }, 566 | { 567 | "number": "CS 4649", 568 | "name": "Robot Intelli Planning" 569 | }, 570 | { 571 | "number": "CS 4650", 572 | "name": "Natural Language" 573 | }, 574 | { 575 | "number": "CS 4660", 576 | "name": "Educational Technology" 577 | }, 578 | { 579 | "number": "CS 4665", 580 | "name": "Educ Tech: Dsgn & Eval" 581 | }, 582 | { 583 | "number": "CS 4670", 584 | "name": "CSCL" 585 | }, 586 | { 587 | "number": "CS 4675", 588 | "name": "Internet Sys & Services" 589 | }, 590 | { 591 | "number": "CS 4685", 592 | "name": "Pervasive Sys Networking" 593 | }, 594 | { 595 | "number": "CS 4690", 596 | "name": "Empirical Methods in HCI" 597 | }, 598 | { 599 | "number": "CS 4698", 600 | "name": "Research Assistantship" 601 | }, 602 | { 603 | "number": "CS 4699", 604 | "name": "Undergraduate Research" 605 | }, 606 | { 607 | "number": "CS 4710", 608 | "name": "CS for Bioinformatics" 609 | }, 610 | { 611 | "number": "CS 4723", 612 | "name": "Inter Capstone Design" 613 | }, 614 | { 615 | "number": "CS 4725", 616 | "name": "Info Security Policies" 617 | }, 618 | { 619 | "number": "CS 4726", 620 | "name": "Privacy Tech Policy Law" 621 | }, 622 | { 623 | "number": "CS 4731", 624 | "name": "Game AI" 625 | }, 626 | { 627 | "number": "CS 4741", 628 | "name": "Int Mgt Dev - Proj Prep" 629 | }, 630 | { 631 | "number": "CS 4742", 632 | "name": "Comp & Mgt Cap Proj" 633 | }, 634 | { 635 | "number": "CS 4745", 636 | "name": "Info&Com Tech&Global Dev" 637 | }, 638 | { 639 | "number": "CS 4752", 640 | "name": "Phil Issues-Computation" 641 | }, 642 | { 643 | "number": "CS 4770", 644 | "name": "Mixed Reality Design" 645 | }, 646 | { 647 | "number": "CS 4791", 648 | "name": "Integrative Proj-Cog Sci" 649 | }, 650 | { 651 | "number": "CS 4792", 652 | "name": "Dsgn Proj-Cognitive Sci" 653 | }, 654 | { 655 | "number": "CS 4793", 656 | "name": "Perspectives-Cog Science" 657 | }, 658 | { 659 | "number": "CS 4795", 660 | "name": "GPU Prog for Video Games" 661 | }, 662 | { 663 | "number": "CS 4801", 664 | "name": "Special Topics" 665 | }, 666 | { 667 | "number": "CS 4802", 668 | "name": "Special Topics" 669 | }, 670 | { 671 | "number": "CS 4803", 672 | "name": "Special Topics" 673 | }, 674 | { 675 | "number": "CS 4804", 676 | "name": "Special Topics" 677 | }, 678 | { 679 | "number": "CS 4805", 680 | "name": "Special Topics" 681 | }, 682 | { 683 | "number": "CS 4816", 684 | "name": "Special Topics" 685 | }, 686 | { 687 | "number": "CS 4851", 688 | "name": "Special Topics" 689 | }, 690 | { 691 | "number": "CS 4853", 692 | "name": "Special Topics" 693 | }, 694 | { 695 | "number": "CS 4854", 696 | "name": "Special Topics" 697 | }, 698 | { 699 | "number": "CS 4863", 700 | "name": "Special Topics" 701 | }, 702 | { 703 | "number": "CS 4873", 704 | "name": "Special Topics" 705 | }, 706 | { 707 | "number": "CS 4883", 708 | "name": "Special Topics" 709 | }, 710 | { 711 | "number": "CS 4893", 712 | "name": "Special Topics" 713 | }, 714 | { 715 | "number": "CS 4901", 716 | "name": "Spec Prob-Computer Sci" 717 | }, 718 | { 719 | "number": "CS 4902", 720 | "name": "Spec Prob-Computer Sci" 721 | }, 722 | { 723 | "number": "CS 4903", 724 | "name": "Spec Prob-Computer Sci" 725 | }, 726 | { 727 | "number": "CS 4911", 728 | "name": "Design Capstone Project" 729 | }, 730 | { 731 | "number": "CS 4912", 732 | "name": "Design Capstone Project" 733 | }, 734 | { 735 | "number": "CS 4980", 736 | "name": "Research Capstone Proj" 737 | }, 738 | { 739 | "number": "CS 4XXX", 740 | "name": "Computer Sci Elective" 741 | }, 742 | { 743 | "number": "CS 6010", 744 | "name": "Principles of Design" 745 | }, 746 | { 747 | "number": "CS 6035", 748 | "name": "Intro To Info Security" 749 | }, 750 | { 751 | "number": "CS 6150", 752 | "name": "Computing For Good" 753 | }, 754 | { 755 | "number": "CS 6200", 756 | "name": "Graduate Intro to OS" 757 | }, 758 | { 759 | "number": "CS 6210", 760 | "name": "Adv Operating Systems" 761 | }, 762 | { 763 | "number": "CS 6211", 764 | "name": "Sys Design Cloud Comput" 765 | }, 766 | { 767 | "number": "CS 6220", 768 | "name": "Big Data Sys & Analytics" 769 | }, 770 | { 771 | "number": "CS 6230", 772 | "name": "High Perf Parallel Comp" 773 | }, 774 | { 775 | "number": "CS 6235", 776 | "name": "Real-Time Systems" 777 | }, 778 | { 779 | "number": "CS 6238", 780 | "name": "Secure Computer Systems" 781 | }, 782 | { 783 | "number": "CS 6241", 784 | "name": "Compiler Design" 785 | }, 786 | { 787 | "number": "CS 6245", 788 | "name": "Parallelizing Compilers" 789 | }, 790 | { 791 | "number": "CS 6246", 792 | "name": "Object-Oriented Systems" 793 | }, 794 | { 795 | "number": "CS 6250", 796 | "name": "Computer Networks" 797 | }, 798 | { 799 | "number": "CS 6255", 800 | "name": "Network Management" 801 | }, 802 | { 803 | "number": "CS 6260", 804 | "name": "Applied Cryptography" 805 | }, 806 | { 807 | "number": "CS 6261", 808 | "name": "Cyber Incident Response" 809 | }, 810 | { 811 | "number": "CS 6262", 812 | "name": "Network Security" 813 | }, 814 | { 815 | "number": "CS 6263", 816 | "name": "Intro Cyber Phys Sys Sec" 817 | }, 818 | { 819 | "number": "CS 6264", 820 | "name": "Infosec Labs: Defenses" 821 | }, 822 | { 823 | "number": "CS 6265", 824 | "name": "Info Sec Lab Binexp" 825 | }, 826 | { 827 | "number": "CS 6266", 828 | "name": "Info Security Practicum" 829 | }, 830 | { 831 | "number": "CS 6267", 832 | "name": "Critical Infrastructures" 833 | }, 834 | { 835 | "number": "CS 6268", 836 | "name": "Psychol of Cybersecurity" 837 | }, 838 | { 839 | "number": "CS 6269", 840 | "name": "Formal Info Assur Model" 841 | }, 842 | { 843 | "number": "CS 6280", 844 | "name": "Eval Communication Nets" 845 | }, 846 | { 847 | "number": "CS 6290", 848 | "name": "High Perform Comput Arch" 849 | }, 850 | { 851 | "number": "CS 6291", 852 | "name": "Embedded Software Opt." 853 | }, 854 | { 855 | "number": "CS 6300", 856 | "name": "Software Dev Process" 857 | }, 858 | { 859 | "number": "CS 6301", 860 | "name": "Adv Software Engineering" 861 | }, 862 | { 863 | "number": "CS 6310", 864 | "name": "Software Arch & Design" 865 | }, 866 | { 867 | "number": "CS 6320", 868 | "name": "Requirements Analysis" 869 | }, 870 | { 871 | "number": "CS 6330", 872 | "name": "Software Generation&Test" 873 | }, 874 | { 875 | "number": "CS 6340", 876 | "name": "Software Analysis & Test" 877 | }, 878 | { 879 | "number": "CS 6365", 880 | "name": "Intro Enterprise Comput." 881 | }, 882 | { 883 | "number": "CS 6390", 884 | "name": "Programming Languages" 885 | }, 886 | { 887 | "number": "CS 6400", 888 | "name": "DB Sys Concepts& Design" 889 | }, 890 | { 891 | "number": "CS 6402", 892 | "name": "Databases and Infosec" 893 | }, 894 | { 895 | "number": "CS 6411", 896 | "name": "O-O Database Model & Sys" 897 | }, 898 | { 899 | "number": "CS 6421", 900 | "name": "Tempor,Spatial&Active DB" 901 | }, 902 | { 903 | "number": "CS 6422", 904 | "name": "Database System Implemnt" 905 | }, 906 | { 907 | "number": "CS 6423", 908 | "name": "Adv Database Systems" 909 | }, 910 | { 911 | "number": "CS 6430", 912 | "name": "Parallel&Distributed DB" 913 | }, 914 | { 915 | "number": "CS 6440", 916 | "name": "Intro Health Informatics" 917 | }, 918 | { 919 | "number": "CS 6451", 920 | "name": "Human-Centered Computing" 921 | }, 922 | { 923 | "number": "CS 6452", 924 | "name": "Prototyping Interact Sys" 925 | }, 926 | { 927 | "number": "CS 6454", 928 | "name": "Qualitative Methods HCI" 929 | }, 930 | { 931 | "number": "CS 6455", 932 | "name": "User Interface Dsgn&Eval" 933 | }, 934 | { 935 | "number": "CS 6456", 936 | "name": "Principles-UI Software" 937 | }, 938 | { 939 | "number": "CS 6457", 940 | "name": "Video Game Design" 941 | }, 942 | { 943 | "number": "CS 6460", 944 | "name": "Educ Tech-Foundations" 945 | }, 946 | { 947 | "number": "CS 6461", 948 | "name": "Computing Ed Research" 949 | }, 950 | { 951 | "number": "CS 6465", 952 | "name": "Computational Journalism" 953 | }, 954 | { 955 | "number": "CS 6470", 956 | "name": "Online Communities" 957 | }, 958 | { 959 | "number": "CS 6471", 960 | "name": "Comp Social Science" 961 | }, 962 | { 963 | "number": "CS 6474", 964 | "name": "Social Computing" 965 | }, 966 | { 967 | "number": "CS 6475", 968 | "name": "Comp. Photography" 969 | }, 970 | { 971 | "number": "CS 6476", 972 | "name": "Computer Vision" 973 | }, 974 | { 975 | "number": "CS 6480", 976 | "name": "Comp Visualiz Techniques" 977 | }, 978 | { 979 | "number": "CS 6485", 980 | "name": "Visual Meth-Sci & Engr" 981 | }, 982 | { 983 | "number": "CS 6491", 984 | "name": "Computer Graphics" 985 | }, 986 | { 987 | "number": "CS 6492", 988 | "name": "Shape Grammars" 989 | }, 990 | { 991 | "number": "CS 6497", 992 | "name": "Comp Aesthetics" 993 | }, 994 | { 995 | "number": "CS 6505", 996 | "name": "Computability&Algorithms" 997 | }, 998 | { 999 | "number": "CS 6515", 1000 | "name": "Intro to Grad Algorithms" 1001 | }, 1002 | { 1003 | "number": "CS 6520", 1004 | "name": "Computational Complexity" 1005 | }, 1006 | { 1007 | "number": "CS 6550", 1008 | "name": "Design& Analy-Algorithms" 1009 | }, 1010 | { 1011 | "number": "CS 6601", 1012 | "name": "Artificial Intelligence" 1013 | }, 1014 | { 1015 | "number": "CS 6603", 1016 | "name": "AI Ethics Society" 1017 | }, 1018 | { 1019 | "number": "CS 6641", 1020 | "name": "Personal Health Inform" 1021 | }, 1022 | { 1023 | "number": "CS 6670", 1024 | "name": "Distrib Cntrl Algorithms" 1025 | }, 1026 | { 1027 | "number": "CS 6675", 1028 | "name": "Advance Internet Comput" 1029 | }, 1030 | { 1031 | "number": "CS 6705", 1032 | "name": "Applications of AI" 1033 | }, 1034 | { 1035 | "number": "CS 6725", 1036 | "name": "Info Security Policies" 1037 | }, 1038 | { 1039 | "number": "CS 6726", 1040 | "name": "Privacy Tech Policy Law" 1041 | }, 1042 | { 1043 | "number": "CS 6727", 1044 | "name": "Cyber Sec Practicum" 1045 | }, 1046 | { 1047 | "number": "CS 6730", 1048 | "name": "Data Vis Principles" 1049 | }, 1050 | { 1051 | "number": "CS 6745", 1052 | "name": "Info&Com Tech&Global Dev" 1053 | }, 1054 | { 1055 | "number": "CS 6747", 1056 | "name": "ADV Malware Analysis" 1057 | }, 1058 | { 1059 | "number": "CS 6750", 1060 | "name": "Human-Computer Interact" 1061 | }, 1062 | { 1063 | "number": "CS 6753", 1064 | "name": "HCI Prof Prep & Practice" 1065 | }, 1066 | { 1067 | "number": "CS 6754", 1068 | "name": "Engr Database Mgt System" 1069 | }, 1070 | { 1071 | "number": "CS 6755", 1072 | "name": "HCI Foundations" 1073 | }, 1074 | { 1075 | "number": "CS 6763", 1076 | "name": "Design of Environments" 1077 | }, 1078 | { 1079 | "number": "CS 6764", 1080 | "name": "Geometric Modeling" 1081 | }, 1082 | { 1083 | "number": "CS 6770", 1084 | "name": "Mixed Reality Design" 1085 | }, 1086 | { 1087 | "number": "CS 6780", 1088 | "name": "Medical Image Processing" 1089 | }, 1090 | { 1091 | "number": "CS 6795", 1092 | "name": "Intro-Cognitive Science" 1093 | }, 1094 | { 1095 | "number": "CS 6998", 1096 | "name": "HCI Master's Project" 1097 | }, 1098 | { 1099 | "number": "CS 6999", 1100 | "name": "Master's Project" 1101 | }, 1102 | { 1103 | "number": "CS 6XXX", 1104 | "name": "Computer Sci Elective" 1105 | }, 1106 | { 1107 | "number": "CS 7000", 1108 | "name": "Master's Thesis" 1109 | }, 1110 | { 1111 | "number": "CS 7001", 1112 | "name": "Grad Studies-Computing" 1113 | }, 1114 | { 1115 | "number": "CS 7110", 1116 | "name": "Parallel Computer Arch" 1117 | }, 1118 | { 1119 | "number": "CS 7210", 1120 | "name": "Distributed Computing" 1121 | }, 1122 | { 1123 | "number": "CS 7230", 1124 | "name": "Software Dsgn,Impl& Eval" 1125 | }, 1126 | { 1127 | "number": "CS 7250", 1128 | "name": "Broadband Networking Sys" 1129 | }, 1130 | { 1131 | "number": "CS 7260", 1132 | "name": "Internet Arch& Protocols" 1133 | }, 1134 | { 1135 | "number": "CS 7270", 1136 | "name": "Networked Apps&Services" 1137 | }, 1138 | { 1139 | "number": "CS 7280", 1140 | "name": "Network Science" 1141 | }, 1142 | { 1143 | "number": "CS 7290", 1144 | "name": "Adv. Microarchitecture" 1145 | }, 1146 | { 1147 | "number": "CS 7292", 1148 | "name": "Reliable Secure Comparch" 1149 | }, 1150 | { 1151 | "number": "CS 7400", 1152 | "name": "Intro Quantum Computing" 1153 | }, 1154 | { 1155 | "number": "CS 7450", 1156 | "name": "Inform Visualization" 1157 | }, 1158 | { 1159 | "number": "CS 7451", 1160 | "name": "Hum- Center Data Analysis" 1161 | }, 1162 | { 1163 | "number": "CS 7455", 1164 | "name": "Issues/Human-Center Comp" 1165 | }, 1166 | { 1167 | "number": "CS 7460", 1168 | "name": "Collaborative Computing" 1169 | }, 1170 | { 1171 | "number": "CS 7465", 1172 | "name": "Edu Tech-Design and Eval" 1173 | }, 1174 | { 1175 | "number": "CS 7467", 1176 | "name": "Comp Collaborative Learn" 1177 | }, 1178 | { 1179 | "number": "CS 7470", 1180 | "name": "Ubiquitous Computing" 1181 | }, 1182 | { 1183 | "number": "CS 7476", 1184 | "name": "Advanced Computer Vision" 1185 | }, 1186 | { 1187 | "number": "CS 7490", 1188 | "name": "Adv Image Synthesis" 1189 | }, 1190 | { 1191 | "number": "CS 7491", 1192 | "name": "3D Complexity" 1193 | }, 1194 | { 1195 | "number": "CS 7492", 1196 | "name": "Simulation of Biology" 1197 | }, 1198 | { 1199 | "number": "CS 7495", 1200 | "name": "Computer Vision" 1201 | }, 1202 | { 1203 | "number": "CS 7496", 1204 | "name": "Computer Animation" 1205 | }, 1206 | { 1207 | "number": "CS 7497", 1208 | "name": "Virtual Environments" 1209 | }, 1210 | { 1211 | "number": "CS 7499", 1212 | "name": "3D Reconstruction" 1213 | }, 1214 | { 1215 | "number": "CS 7510", 1216 | "name": "Graph Algorithm" 1217 | }, 1218 | { 1219 | "number": "CS 7520", 1220 | "name": "Approximation Algorithms" 1221 | }, 1222 | { 1223 | "number": "CS 7525", 1224 | "name": "Algorithmic Game Theory" 1225 | }, 1226 | { 1227 | "number": "CS 7530", 1228 | "name": "Randomized Algorithms" 1229 | }, 1230 | { 1231 | "number": "CS 7535", 1232 | "name": "Markov Chain Monte Carlo" 1233 | }, 1234 | { 1235 | "number": "CS 7540", 1236 | "name": "Spectral Algorithms" 1237 | }, 1238 | { 1239 | "number": "CS 7545", 1240 | "name": "Machine Learning Theory" 1241 | }, 1242 | { 1243 | "number": "CS 7560", 1244 | "name": "Theory of Cryptography" 1245 | }, 1246 | { 1247 | "number": "CS 7610", 1248 | "name": "Modeling and Design" 1249 | }, 1250 | { 1251 | "number": "CS 7611", 1252 | "name": "AI Problem Solving" 1253 | }, 1254 | { 1255 | "number": "CS 7612", 1256 | "name": "AI Planning" 1257 | }, 1258 | { 1259 | "number": "CS 7613", 1260 | "name": "Knowledge Systems Engr" 1261 | }, 1262 | { 1263 | "number": "CS 7615", 1264 | "name": "Knowledge Agents" 1265 | }, 1266 | { 1267 | "number": "CS 7616", 1268 | "name": "Pattern Recognition" 1269 | }, 1270 | { 1271 | "number": "CS 7620", 1272 | "name": "Case-Based Reasoning" 1273 | }, 1274 | { 1275 | "number": "CS 7626", 1276 | "name": "Behavioral Imaging" 1277 | }, 1278 | { 1279 | "number": "CS 7630", 1280 | "name": "Autonomous Robotics" 1281 | }, 1282 | { 1283 | "number": "CS 7631", 1284 | "name": "Multi-Robot Systems" 1285 | }, 1286 | { 1287 | "number": "CS 7632", 1288 | "name": "Game AI" 1289 | }, 1290 | { 1291 | "number": "CS 7633", 1292 | "name": "Human-Robot Interaction" 1293 | }, 1294 | { 1295 | "number": "CS 7634", 1296 | "name": "AI Storytell In Vir Wrld" 1297 | }, 1298 | { 1299 | "number": "CS 7636", 1300 | "name": "Computational Perception" 1301 | }, 1302 | { 1303 | "number": "CS 7637", 1304 | "name": "Knowledge-Based AI" 1305 | }, 1306 | { 1307 | "number": "CS 7638", 1308 | "name": "Robotics: AI Techniques" 1309 | }, 1310 | { 1311 | "number": "CS 7639", 1312 | "name": "Cyber Physical Design" 1313 | }, 1314 | { 1315 | "number": "CS 7640", 1316 | "name": "Learning in Auton Agents" 1317 | }, 1318 | { 1319 | "number": "CS 7641", 1320 | "name": "Machine Learning" 1321 | }, 1322 | { 1323 | "number": "CS 7642", 1324 | "name": "Reinforcement Learning" 1325 | }, 1326 | { 1327 | "number": "CS 7643", 1328 | "name": "Deep Learning" 1329 | }, 1330 | { 1331 | "number": "CS 7644", 1332 | "name": "ML For Robotics" 1333 | }, 1334 | { 1335 | "number": "CS 7645", 1336 | "name": "Num Machine Learning" 1337 | }, 1338 | { 1339 | "number": "CS 7646", 1340 | "name": "Mach Learn For Trading" 1341 | }, 1342 | { 1343 | "number": "CS 7647", 1344 | "name": "Mach Lrn w/ltd Supervis" 1345 | }, 1346 | { 1347 | "number": "CS 7648", 1348 | "name": "Interactive Robo Learn" 1349 | }, 1350 | { 1351 | "number": "CS 7649", 1352 | "name": "Robot Intelli: Planning" 1353 | }, 1354 | { 1355 | "number": "CS 7650", 1356 | "name": "Natural Language" 1357 | }, 1358 | { 1359 | "number": "CS 7651", 1360 | "name": "Human & Machine Learning" 1361 | }, 1362 | { 1363 | "number": "CS 7695", 1364 | "name": "Phil of Cognition" 1365 | }, 1366 | { 1367 | "number": "CS 7697", 1368 | "name": "Cognitive Model Sci&Tech" 1369 | }, 1370 | { 1371 | "number": "CS 7741", 1372 | "name": "Robotics Pro Prep 1" 1373 | }, 1374 | { 1375 | "number": "CS 7742", 1376 | "name": "Robo Pro Prep 2" 1377 | }, 1378 | { 1379 | "number": "CS 7743", 1380 | "name": "Robo Pro Prep 3" 1381 | }, 1382 | { 1383 | "number": "CS 7750", 1384 | "name": "Math Fnd Of Machine Lrn" 1385 | }, 1386 | { 1387 | "number": "CS 7751", 1388 | "name": "Graphical Models in ML" 1389 | }, 1390 | { 1391 | "number": "CS 7785", 1392 | "name": "Intro Robotics Research" 1393 | }, 1394 | { 1395 | "number": "CS 7790", 1396 | "name": "Cognitive Modeling" 1397 | }, 1398 | { 1399 | "number": "CS 7999", 1400 | "name": "Prep-Doctoral Qual Exams" 1401 | }, 1402 | { 1403 | "number": "CS 8001", 1404 | "name": "Seminar" 1405 | }, 1406 | { 1407 | "number": "CS 8002", 1408 | "name": "Seminar" 1409 | }, 1410 | { 1411 | "number": "CS 8003", 1412 | "name": "Seminar" 1413 | }, 1414 | { 1415 | "number": "CS 8004", 1416 | "name": "Seminar" 1417 | }, 1418 | { 1419 | "number": "CS 8005", 1420 | "name": "Seminar" 1421 | }, 1422 | { 1423 | "number": "CS 8006", 1424 | "name": "Seminar" 1425 | }, 1426 | { 1427 | "number": "CS 8030", 1428 | "name": "Software Engr Seminar" 1429 | }, 1430 | { 1431 | "number": "CS 8740", 1432 | "name": "Robotics Internship" 1433 | }, 1434 | { 1435 | "number": "CS 8741", 1436 | "name": "Robo Capstone Project" 1437 | }, 1438 | { 1439 | "number": "CS 8750", 1440 | "name": "Robotics Research Fnd I" 1441 | }, 1442 | { 1443 | "number": "CS 8751", 1444 | "name": "Robotics Research Fnd II" 1445 | }, 1446 | { 1447 | "number": "CS 8795", 1448 | "name": "Colloquium-Cognitive Sci" 1449 | }, 1450 | { 1451 | "number": "CS 8801", 1452 | "name": "Special Topics" 1453 | }, 1454 | { 1455 | "number": "CS 8802", 1456 | "name": "Special Topics" 1457 | }, 1458 | { 1459 | "number": "CS 8803", 1460 | "name": "Special Topics" 1461 | }, 1462 | { 1463 | "number": "CS 8804", 1464 | "name": "Special Topics" 1465 | }, 1466 | { 1467 | "number": "CS 8805", 1468 | "name": "Special Topics" 1469 | }, 1470 | { 1471 | "number": "CS 8806", 1472 | "name": "Special Topics" 1473 | }, 1474 | { 1475 | "number": "CS 8811", 1476 | "name": "Special Topics" 1477 | }, 1478 | { 1479 | "number": "CS 8813", 1480 | "name": "Special Topics" 1481 | }, 1482 | { 1483 | "number": "CS 8816", 1484 | "name": "Special Topics" 1485 | }, 1486 | { 1487 | "number": "CS 8873", 1488 | "name": "Special Topics" 1489 | }, 1490 | { 1491 | "number": "CS 8893", 1492 | "name": "Spec Top-Cognitive Sci" 1493 | }, 1494 | { 1495 | "number": "CS 8901", 1496 | "name": "Special Problems" 1497 | }, 1498 | { 1499 | "number": "CS 8902", 1500 | "name": "Special Problems" 1501 | }, 1502 | { 1503 | "number": "CS 8903", 1504 | "name": "Special Problems" 1505 | }, 1506 | { 1507 | "number": "CS 8997", 1508 | "name": "Teaching Assistantship" 1509 | }, 1510 | { 1511 | "number": "CS 8998", 1512 | "name": "Research Assistantship" 1513 | }, 1514 | { 1515 | "number": "CS 8999", 1516 | "name": "Doctoral Thesis Prep" 1517 | }, 1518 | { 1519 | "number": "CS 9000", 1520 | "name": "Doctoral Thesis" 1521 | } 1522 | ] -------------------------------------------------------------------------------- /backend/api/utils/JSON_inputs/faculty_list.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "Jacob Abernethy" 4 | }, 5 | { 6 | "name": "Mustaque Ahamad" 7 | }, 8 | { 9 | "name": "Mostafa Ammar" 10 | }, 11 | { 12 | "name": "Joy Arulraj" 13 | }, 14 | { 15 | "name": "Ramin Ayanzadeh" 16 | }, 17 | { 18 | "name": "Suguman Bansal" 19 | }, 20 | { 21 | "name": "Ketan Bhardwaj" 22 | }, 23 | { 24 | "name": "Douglas Blough" 25 | }, 26 | { 27 | "name": "Gregory Bodwin" 28 | }, 29 | { 30 | "name": "Alexandra Boldyreva" 31 | }, 32 | { 33 | "name": "Xu Chu" 34 | }, 35 | { 36 | "name": "Pak Ho (Simon) Chung" 37 | }, 38 | { 39 | "name": "Russell Clark" 40 | }, 41 | { 42 | "name": "Thomas Conte" 43 | }, 44 | { 45 | "name": "Tom Conte" 46 | }, 47 | { 48 | "name": "Rachel Cummings" 49 | }, 50 | { 51 | "name": "Alexandros Daglis" 52 | }, 53 | { 54 | "name": "Alberto Dainotti" 55 | }, 56 | { 57 | "name": "Richard DeMillo" 58 | }, 59 | { 60 | "name": "David Devecsery" 61 | }, 62 | { 63 | "name": "Ashutosh Dhekne" 64 | }, 65 | { 66 | "name": "Constantine Dovrolis" 67 | }, 68 | { 69 | "name": "Alex Duncan" 70 | }, 71 | { 72 | "name": "Greg Eisenhauer" 73 | }, 74 | { 75 | "name": "Merrick Furst" 76 | }, 77 | { 78 | "name": "Zvi Galil" 79 | }, 80 | { 81 | "name": "Vijay Ganesh" 82 | }, 83 | { 84 | "name": "Ada Gavrilovska" 85 | }, 86 | { 87 | "name": "Daniel Genkin" 88 | }, 89 | { 90 | "name": "Max Grossman" 91 | }, 92 | { 93 | "name": "Swati Gupta" 94 | }, 95 | { 96 | "name": "Akihiro Hayashi" 97 | }, 98 | { 99 | "name": "Anand Padmanabha Iyer" 100 | }, 101 | { 102 | "name": "Anand Padmanabha Iyer" 103 | }, 104 | { 105 | "name": "ChulWon Kang" 106 | }, 107 | { 108 | "name": "Hyesoon Kim" 109 | }, 110 | { 111 | "name": "Taesoo Kim" 112 | }, 113 | { 114 | "name": "Scott Klasky" 115 | }, 116 | { 117 | "name": "Vladimir Kolesnikov" 118 | }, 119 | { 120 | "name": "Maria Konte" 121 | }, 122 | { 123 | "name": "Kevin Koo" 124 | }, 125 | { 126 | "name": "Tushar Krishna" 127 | }, 128 | { 129 | "name": "Wenke Lee" 130 | }, 131 | { 132 | "name": "Yingyan (Celine) Lin" 133 | }, 134 | { 135 | "name": "Ling Liu" 136 | }, 137 | { 138 | "name": "Divya Mahajan" 139 | }, 140 | { 141 | "name": "Yisreol Mirsky" 142 | }, 143 | { 144 | "name": "Vincent Mooney" 145 | }, 146 | { 147 | "name": "Shamkant Navathe" 148 | }, 149 | { 150 | "name": "Alessandro Orso" 151 | }, 152 | { 153 | "name": "Santosh Pande" 154 | }, 155 | { 156 | "name": "Sunjae Park" 157 | }, 158 | { 159 | "name": "Sunjae Park" 160 | }, 161 | { 162 | "name": "Sri Raj Paul" 163 | }, 164 | { 165 | "name": "Paul Pearce" 166 | }, 167 | { 168 | "name": "Richard Peng" 169 | }, 170 | { 171 | "name": "Roberto Perdisci" 172 | }, 173 | { 174 | "name": "Will Perkins" 175 | }, 176 | { 177 | "name": "Milos Prvulovic" 178 | }, 179 | { 180 | "name": "Calton Pu" 181 | }, 182 | { 183 | "name": "Moinuddin Qureshi" 184 | }, 185 | { 186 | "name": "Umakishore Ramachandran" 187 | }, 188 | { 189 | "name": "Dana Randall" 190 | }, 191 | { 192 | "name": "Kexin Rong" 193 | }, 194 | { 195 | "name": "Ahmed Saeed" 196 | }, 197 | { 198 | "name": "Vivek Sarkar" 199 | }, 200 | { 201 | "name": "Jun Shirako" 202 | }, 203 | { 204 | "name": "Sahil Singla" 205 | }, 206 | { 207 | "name": "Michael Specter" 208 | }, 209 | { 210 | "name": "Cecilia Testart" 211 | }, 212 | { 213 | "name": "Prasad Tetali" 214 | }, 215 | { 216 | "name": "Alexey Tumanov" 217 | }, 218 | { 219 | "name": "Jan van den Brand" 220 | }, 221 | { 222 | "name": "Santosh Vempala" 223 | }, 224 | { 225 | "name": "Qi Xin" 226 | }, 227 | { 228 | "name": "Jun Xu" 229 | }, 230 | { 231 | "name": "Guangliang Yang" 232 | }, 233 | { 234 | "name": "Jeffrey Young" 235 | }, 236 | { 237 | "name": "Ellen Zegura" 238 | }, 239 | { 240 | "name": "Qirun Zhang" 241 | }, 242 | { 243 | "name": "Jisheng Zhao" 244 | } 245 | ] -------------------------------------------------------------------------------- /backend/api/utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AmanTahiliani/PeerNotes/46a4574f62d46a0110f9324f7d0e23b3c8f3f9bc/backend/api/utils/__init__.py -------------------------------------------------------------------------------- /backend/api/utils/get_client_ip.py: -------------------------------------------------------------------------------- 1 | from typing import Any 2 | from django.utils import timezone 3 | 4 | 5 | def get_client_ip(request): 6 | x_forwarded_for = request.META.get("HTTP_X_FORWARDED_FOR") 7 | if x_forwarded_for: 8 | ip = x_forwarded_for.split(",")[0] 9 | else: 10 | ip = request.META.get("REMOTE_ADDR") 11 | return ip 12 | 13 | 14 | def update_user_ip(request): 15 | user = request.user 16 | try: 17 | data = request.data 18 | ip_address = data["ip"] 19 | print("Local IP found in request") 20 | except Exception as e: 21 | ip_address = get_client_ip(request) 22 | print("Using public IP") 23 | user.ip_address = ip_address 24 | user.last_poll = timezone.now() 25 | user.save() 26 | 27 | 28 | class PollMiddleware: 29 | def __init__(self, get_response): 30 | self.get_response = get_response 31 | 32 | def __call__(self, request): 33 | print("Updating user last online and IP address") 34 | update_user_ip(request) 35 | response = self.get_response(request) 36 | 37 | return response 38 | -------------------------------------------------------------------------------- /backend/api/views.py: -------------------------------------------------------------------------------- 1 | from api.models import Course, File, Professor, Semester, Topic, PeerUser, UserReport 2 | from api.serializers import ( 3 | CourseSerializer, 4 | FileSerializer, 5 | ProfessorSerializer, 6 | SemesterSerializer, 7 | TopicSerializer, 8 | UserSerializer, 9 | ) 10 | from datetime import timedelta 11 | from django.contrib.auth import authenticate 12 | from django.db import transaction 13 | from django.db.models import Count, Value, F, IntegerField 14 | from django.utils import timezone 15 | from rest_framework import generics, status 16 | from rest_framework.authentication import TokenAuthentication 17 | from rest_framework.permissions import IsAuthenticated 18 | from rest_framework.response import Response 19 | from rest_framework.views import APIView 20 | from rest_framework import filters 21 | from rest_framework.exceptions import ValidationError, NotFound 22 | from api.utils.get_client_ip import update_user_ip 23 | 24 | 25 | class TopicListCreateAPIView(generics.ListCreateAPIView): 26 | queryset = Topic.objects.all() 27 | serializer_class = TopicSerializer 28 | authentication_classes = [TokenAuthentication] 29 | permission_classes = [IsAuthenticated] 30 | filter_backends = [filters.SearchFilter] 31 | search_fields = ["name"] 32 | 33 | 34 | class TopicDetailAPIView(generics.RetrieveUpdateDestroyAPIView): 35 | queryset = Topic.objects.all() 36 | serializer_class = TopicSerializer 37 | authentication_classes = [TokenAuthentication] 38 | permission_classes = [IsAuthenticated] 39 | 40 | 41 | class ProfessorListCreateAPIView(generics.ListCreateAPIView): 42 | queryset = Professor.objects.all() 43 | serializer_class = ProfessorSerializer 44 | authentication_classes = [TokenAuthentication] 45 | permission_classes = [IsAuthenticated] 46 | filter_backends = [filters.SearchFilter] 47 | search_fields = ["name"] 48 | 49 | 50 | class ProfessorDetailAPIView(generics.RetrieveUpdateDestroyAPIView): 51 | queryset = Professor.objects.all() 52 | serializer_class = ProfessorSerializer 53 | authentication_classes = [TokenAuthentication] 54 | permission_classes = [IsAuthenticated] 55 | 56 | 57 | class SemesterListCreateAPIView(generics.ListCreateAPIView): 58 | queryset = Semester.objects.all() 59 | serializer_class = SemesterSerializer 60 | authentication_classes = [TokenAuthentication] 61 | permission_classes = [IsAuthenticated] 62 | filter_backends = [filters.SearchFilter] 63 | search_fields = ["name"] 64 | 65 | 66 | class SemesterDetailAPIView(generics.RetrieveUpdateDestroyAPIView): 67 | queryset = Semester.objects.all() 68 | serializer_class = SemesterSerializer 69 | authentication_classes = [TokenAuthentication] 70 | permission_classes = [IsAuthenticated] 71 | 72 | def delete(self, request, *args, **kwargs): 73 | instance = self.get_object() 74 | self.perform_destroy(instance) 75 | return Response(status=status.HTTP_204_NO_CONTENT) 76 | 77 | 78 | class CourseListCreateAPIView(generics.ListCreateAPIView): 79 | queryset = Course.objects.all() 80 | serializer_class = CourseSerializer 81 | authentication_classes = [TokenAuthentication] 82 | permission_classes = [IsAuthenticated] 83 | filter_backends = [filters.SearchFilter] 84 | search_fields = ["name"] 85 | 86 | 87 | class CourseDetailAPIView(generics.RetrieveUpdateDestroyAPIView): 88 | queryset = Course.objects.all() 89 | serializer_class = CourseSerializer 90 | authentication_classes = [TokenAuthentication] 91 | permission_classes = [IsAuthenticated] 92 | 93 | 94 | class FileListCreateAPIView(generics.ListCreateAPIView): 95 | queryset = File.objects.all() 96 | serializer_class = FileSerializer 97 | authentication_classes = [TokenAuthentication] 98 | permission_classes = [IsAuthenticated] 99 | filter_backends = [filters.SearchFilter] 100 | search_fields = ["name"] 101 | 102 | 103 | class FileDetailAPIView(generics.RetrieveUpdateDestroyAPIView): 104 | queryset = File.objects.all() 105 | serializer_class = FileSerializer 106 | authentication_classes = [TokenAuthentication] 107 | permission_classes = [IsAuthenticated] 108 | 109 | 110 | class UpvoteFile(APIView): 111 | authentication_classes = [TokenAuthentication] 112 | permission_classes = [IsAuthenticated] 113 | 114 | def post(self, request, file_id): 115 | user = request.user 116 | # update_user_ip(request) 117 | 118 | try: 119 | file = File.objects.get(id=file_id) 120 | if file.downvotes.contains(user): 121 | file.downvotes.remove(user) 122 | file.upvotes.add(user) 123 | file.save() 124 | return Response( 125 | {"msg": f"Upvoted {file.filename}", "file_points": file.points}, 126 | status=status.HTTP_200_OK, 127 | ) 128 | except Exception as e: 129 | print(f"Error: {str(e)}") 130 | return Response( 131 | {"error": "Please enter valid file id"}, 132 | status=status.HTTP_400_BAD_REQUEST, 133 | ) 134 | 135 | 136 | class DownvoteFile(APIView): 137 | authentication_classes = [TokenAuthentication] 138 | permission_classes = [IsAuthenticated] 139 | 140 | def post(self, request, file_id): 141 | user = request.user 142 | # update_user_ip(request) 143 | 144 | try: 145 | file = File.objects.get(id=file_id) 146 | if file.upvotes.contains(user): 147 | file.upvotes.remove(user) 148 | file.downvotes.add(user) 149 | file.save() 150 | return Response( 151 | {"msg": f"Downvoted {file.filename}", "file_points": file.points}, 152 | status=status.HTTP_200_OK, 153 | ) 154 | except Exception as e: 155 | print(f"Error: {str(e)}") 156 | return Response( 157 | {"error": "Please enter valid file id"}, 158 | status=status.HTTP_400_BAD_REQUEST, 159 | ) 160 | 161 | 162 | class RegisterFile(APIView): 163 | authentication_classes = [TokenAuthentication] 164 | permission_classes = [IsAuthenticated] 165 | 166 | def post(self, request): 167 | user = request.user 168 | # update_user_ip(request) 169 | data = request.data 170 | try: 171 | with transaction.atomic(): 172 | user.last_poll = timezone.now() 173 | required_fields = set( 174 | ["filename", "topic", "semester", "professor", "course"] 175 | ) 176 | provided_fields = set(request.data.keys()) 177 | missing_fields = required_fields - provided_fields 178 | 179 | if any(["filename", "topic"]) in missing_fields: 180 | raise ValidationError( 181 | "Missing one or more of the required field(s): filename, topic" 182 | ) 183 | 184 | file = File.objects.create( 185 | filename=data["filename"], original_author=user 186 | ) 187 | file.peer_users.add(user) 188 | 189 | file.course = ( 190 | Course.objects.get(id=data.get("course")) 191 | if data.get("course") 192 | else None 193 | ) 194 | file.professor = ( 195 | Professor.objects.get(id=data.get("professor")) 196 | if data.get("professor") 197 | else None 198 | ) 199 | file.semester = ( 200 | Semester.objects.get(id=data.get("semester")) 201 | if data.get("semester") 202 | else None 203 | ) 204 | 205 | topic_id = data["topic"] 206 | 207 | try: 208 | topic = Topic.objects.get(id=topic_id) 209 | file.topic = topic 210 | except Exception as e: 211 | topic_serializer = TopicSerializer(data=topic_id) 212 | if topic_serializer.is_valid(): 213 | topic = topic_serializer.save() 214 | file.topic = topic 215 | 216 | response_data = { 217 | "id": file.id, 218 | "filename": file.filename, 219 | "topic": file.topic.name if file.topic else None, 220 | "semester": file.semester.name if file.semester else None, 221 | "course": file.course.name if file.course else None, 222 | "professor": file.professor.name if file.professor else None, 223 | } 224 | user.points += 1 225 | file.save() 226 | user.save() 227 | return Response( 228 | response_data, 229 | status=status.HTTP_201_CREATED, 230 | ) 231 | except Exception as e: 232 | print("Error", str(e)) 233 | return Response( 234 | {"error(s)": "Something went wrong"}, status=status.HTTP_400_BAD_REQUEST 235 | ) 236 | 237 | 238 | class AddPeerToFile(APIView): 239 | authentication_classes = [TokenAuthentication] 240 | permission_classes = [IsAuthenticated] 241 | 242 | def post(self, request, file_id): 243 | try: 244 | user = request.user 245 | file = File.objects.get(id=file_id) 246 | print("File fetched") 247 | print(file) 248 | print(file.peer_users) 249 | if not file.peer_users.filter(id=user.id).exists(): 250 | file.peer_users.add(user) 251 | file.save() 252 | return Response( 253 | f"User added to file with id {file.id}", status=status.HTTP_200_OK 254 | ) 255 | except Exception as e: 256 | print(f"Error occured while adding peer to file: {e}") 257 | return Response( 258 | "Unable to register peer to given file id", 259 | status=status.HTTP_400_BAD_REQUEST, 260 | ) 261 | 262 | 263 | class FileFilterView(APIView): 264 | authentication_classes = [TokenAuthentication] 265 | permission_classes = [IsAuthenticated] 266 | 267 | def get(self, request, format=None): 268 | try: 269 | # Extract filters from the query parameters 270 | # update_user_ip(request) 271 | topic_id = request.query_params.get("topic") 272 | professor_id = request.query_params.get("professor") 273 | course_id = request.query_params.get("course") 274 | semester_id = request.query_params.get("semester") 275 | 276 | # Check if provided filter IDs exist 277 | if topic_id and not Topic.objects.filter(id=topic_id).exists(): 278 | return Response( 279 | {"error": f"Topic with id {topic_id} does not exist"}, 280 | status=status.HTTP_400_BAD_REQUEST, 281 | ) 282 | if professor_id and not Professor.objects.filter(id=professor_id).exists(): 283 | return Response( 284 | {"error": f"Professor with id {professor_id} does not exist"}, 285 | status=status.HTTP_400_BAD_REQUEST, 286 | ) 287 | if course_id and not Course.objects.filter(id=course_id).exists(): 288 | return Response( 289 | {"error": f"Course with id {course_id} does not exist"}, 290 | status=status.HTTP_400_BAD_REQUEST, 291 | ) 292 | if semester_id and not Semester.objects.filter(id=semester_id).exists(): 293 | return Response( 294 | {"error": f"Semester with id {semester_id} does not exist"}, 295 | status=status.HTTP_400_BAD_REQUEST, 296 | ) 297 | 298 | # Start with the base queryset 299 | queryset = File.objects.all() 300 | 301 | # Apply filters if they are provided 302 | if topic_id: 303 | queryset = queryset.filter(topic__id=topic_id) 304 | if professor_id: 305 | queryset = queryset.filter(professor__id=professor_id) 306 | if course_id: 307 | queryset = queryset.filter(course__id=course_id) 308 | if semester_id: 309 | queryset = queryset.filter(semester__id=semester_id) 310 | 311 | queryset = queryset.annotate( 312 | upvote_count=Count("upvotes"), downvote_count=Count("downvotes") 313 | ).order_by(F("downvote_count") - F("upvote_count")) 314 | 315 | # Filter files based on active peers in the past hour 316 | active_peer_ids = PeerUser.objects.filter( 317 | last_poll__gte=timezone.now() - timedelta(hours=1) 318 | ).values_list("id", flat=True) 319 | queryset = queryset.filter(peer_users__in=active_peer_ids).distinct() 320 | 321 | serializer = FileSerializer(queryset, many=True) 322 | # Loop through each serialized file to sort its peer_users 323 | 324 | try: 325 | serialized_data = serializer.data 326 | print(serialized_data) 327 | except Exception as e: 328 | print(str(e)) 329 | for file_data in serialized_data: 330 | print(file_data) 331 | file_obj = File.objects.get(id=file_data["id"]) 332 | sorted_peer_users = sorted( 333 | file_obj.peer_users.all(), key=lambda x: x.points, reverse=True 334 | ) 335 | file_data["peer_users"] = UserSerializer( 336 | sorted_peer_users, many=True 337 | ).data 338 | 339 | return Response(serialized_data) 340 | except Exception as e: 341 | return Response( 342 | {"error": f"Something went wrong: {str(e)}"}, 343 | status=status.HTTP_500_INTERNAL_SERVER_ERROR, 344 | ) 345 | 346 | 347 | class UserFilesView(APIView): 348 | authentication_classes = [TokenAuthentication] 349 | permission_classes = [IsAuthenticated] 350 | 351 | def get(self, request): 352 | user = request.user 353 | 354 | owned_files = user.owned_files.all() 355 | shared_files = user.shared_files.all() 356 | 357 | all_user_files = owned_files.union(shared_files) 358 | 359 | serializer = FileSerializer(all_user_files, many=True) 360 | 361 | try: 362 | return Response(serializer.data, status=status.HTTP_200_OK) 363 | except Exception as e: 364 | print(f"Error: {e}") 365 | return Response( 366 | {"error": f"An error occured {e}"}, status=status.HTTP_400_BAD_REQUEST 367 | ) 368 | 369 | 370 | class ReportUserView(APIView): 371 | authentication_classes = [TokenAuthentication] 372 | permission_classes = [IsAuthenticated] 373 | 374 | def post(self, request): 375 | data = request.data 376 | # update_user_ip(request) 377 | try: 378 | assert "file_id" in data 379 | assert "user_id" in data 380 | assert "description" in data 381 | except AssertionError as e: 382 | return Response( 383 | {"error": f"Missing Parameters. {str(e)}"}, 384 | status=status.HTTP_400_BAD_REQUEST, 385 | ) 386 | 387 | try: 388 | file = File.objects.get(id=data["file_id"]) 389 | malicious_user = PeerUser.objects.get(id=data["user_id"]) 390 | reporting_user = request.user 391 | description = data["description"] 392 | except Exception as e: 393 | return Response( 394 | {"error": f"Missing/Incorrect Parameters. {str(e)}"}, 395 | status=status.HTTP_400_BAD_REQUEST, 396 | ) 397 | 398 | existing_user_report_queryset = UserReport.objects.filter( 399 | user=malicious_user, file=file, reporting_user=reporting_user 400 | ) 401 | 402 | if len(existing_user_report_queryset): 403 | existing_report = existing_user_report_queryset.first() 404 | existing_report.description = description 405 | existing_report.save() 406 | return Response( 407 | { 408 | "msg": "User report already exists for this file. Updated description" 409 | }, 410 | status=status.HTTP_208_ALREADY_REPORTED, 411 | ) 412 | 413 | if ( 414 | file not in malicious_user.owned_files.all() 415 | or file not in malicious_user.shared_files.all() 416 | ): 417 | return Response( 418 | {"error": "User does not own or host the file reported"}, 419 | status=status.HTTP_404_NOT_FOUND, 420 | ) 421 | 422 | UserReport.objects.create( 423 | user=malicious_user, 424 | reporting_user=reporting_user, 425 | file=file, 426 | description=description, 427 | ) 428 | malicious_user.points -= 1 429 | malicious_user.save() 430 | return Response({"msg": "Report Created!"}, status=status.HTTP_201_CREATED) 431 | -------------------------------------------------------------------------------- /backend/api/views/filters.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AmanTahiliani/PeerNotes/46a4574f62d46a0110f9324f7d0e23b3c8f3f9bc/backend/api/views/filters.py -------------------------------------------------------------------------------- /backend/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Django's command-line utility for administrative tasks.""" 3 | import os 4 | import sys 5 | 6 | 7 | def main(): 8 | """Run administrative tasks.""" 9 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "peer_notes.settings") 10 | try: 11 | from django.core.management import execute_from_command_line 12 | except ImportError as exc: 13 | raise ImportError( 14 | "Couldn't import Django. Are you sure it's installed and " 15 | "available on your PYTHONPATH environment variable? Did you " 16 | "forget to activate a virtual environment?" 17 | ) from exc 18 | execute_from_command_line(sys.argv) 19 | 20 | 21 | if __name__ == "__main__": 22 | main() 23 | -------------------------------------------------------------------------------- /backend/peer_notes/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AmanTahiliani/PeerNotes/46a4574f62d46a0110f9324f7d0e23b3c8f3f9bc/backend/peer_notes/__init__.py -------------------------------------------------------------------------------- /backend/peer_notes/asgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | ASGI config for peer_notes project. 3 | 4 | It exposes the ASGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/4.2/howto/deployment/asgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.asgi import get_asgi_application 13 | 14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "peer_notes.settings") 15 | 16 | application = get_asgi_application() 17 | -------------------------------------------------------------------------------- /backend/peer_notes/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for peer_notes project. 3 | 4 | Generated by 'django-admin startproject' using Django 4.2.11. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/4.2/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/4.2/ref/settings/ 11 | """ 12 | 13 | from pathlib import Path 14 | 15 | # Build paths inside the project like this: BASE_DIR / 'subdir'. 16 | BASE_DIR = Path(__file__).resolve().parent.parent 17 | 18 | 19 | # Quick-start development settings - unsuitable for production 20 | # See https://docs.djangoproject.com/en/4.2/howto/deployment/checklist/ 21 | 22 | # SECURITY WARNING: keep the secret key used in production secret! 23 | SECRET_KEY = "django-insecure-rh0s*u8$uugtt10cxbifjriaz%@&p1w!)c2=y^undd2*nx5" 24 | 25 | # SECURITY WARNING: don't run with debug turned on in production! 26 | DEBUG = True 27 | 28 | ALLOWED_HOSTS = ["*"] 29 | # ALLOWED_HOSTS = [] 30 | 31 | 32 | # Application definition 33 | 34 | INSTALLED_APPS = [ 35 | "django.contrib.admin", 36 | "django.contrib.auth", 37 | "django.contrib.contenttypes", 38 | "django.contrib.sessions", 39 | "django.contrib.messages", 40 | "django.contrib.staticfiles", 41 | "rest_framework", 42 | "api.apps.ApiConfig", 43 | "rest_framework.authtoken", 44 | "corsheaders", 45 | ] 46 | 47 | REST_FRAMEWORK = { 48 | "DEFAULT_AUTHENTICATION_CLASSES": [ 49 | "rest_framework.authentication.TokenAuthentication", 50 | ], 51 | } 52 | 53 | MIDDLEWARE = [ 54 | "corsheaders.middleware.CorsMiddleware", 55 | "django.middleware.security.SecurityMiddleware", 56 | "django.contrib.sessions.middleware.SessionMiddleware", 57 | "django.middleware.common.CommonMiddleware", 58 | "django.middleware.csrf.CsrfViewMiddleware", 59 | "django.contrib.auth.middleware.AuthenticationMiddleware", 60 | "django.contrib.messages.middleware.MessageMiddleware", 61 | "django.middleware.clickjacking.XFrameOptionsMiddleware", 62 | ] 63 | 64 | ROOT_URLCONF = "peer_notes.urls" 65 | 66 | TEMPLATES = [ 67 | { 68 | "BACKEND": "django.template.backends.django.DjangoTemplates", 69 | "DIRS": [], 70 | "APP_DIRS": True, 71 | "OPTIONS": { 72 | "context_processors": [ 73 | "django.template.context_processors.debug", 74 | "django.template.context_processors.request", 75 | "django.contrib.auth.context_processors.auth", 76 | "django.contrib.messages.context_processors.messages", 77 | ], 78 | }, 79 | }, 80 | ] 81 | 82 | WSGI_APPLICATION = "peer_notes.wsgi.application" 83 | 84 | 85 | # Database 86 | # https://docs.djangoproject.com/en/4.2/ref/settings/#databases 87 | 88 | DATABASES = { 89 | "default": { 90 | "ENGINE": "django.db.backends.sqlite3", 91 | "NAME": BASE_DIR / "db.sqlite3", 92 | } 93 | } 94 | 95 | 96 | # Password validation 97 | # https://docs.djangoproject.com/en/4.2/ref/settings/#auth-password-validators 98 | 99 | AUTH_PASSWORD_VALIDATORS = [ 100 | { 101 | "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator", 102 | }, 103 | { 104 | "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", 105 | }, 106 | { 107 | "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator", 108 | }, 109 | { 110 | "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator", 111 | }, 112 | ] 113 | 114 | 115 | # Internationalization 116 | # https://docs.djangoproject.com/en/4.2/topics/i18n/ 117 | 118 | LANGUAGE_CODE = "en-us" 119 | 120 | TIME_ZONE = "UTC" 121 | 122 | USE_I18N = True 123 | 124 | USE_TZ = True 125 | 126 | 127 | # Static files (CSS, JavaScript, Images) 128 | # https://docs.djangoproject.com/en/4.2/howto/static-files/ 129 | 130 | STATIC_URL = "static/" 131 | 132 | # Default primary key field type 133 | # https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field 134 | 135 | DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" 136 | AUTH_USER_MODEL = "api.PeerUser" 137 | CORS_ALLOW_ALL_ORIGINS = True 138 | CORS_ALLOW_ALL_METHODS = True 139 | # CORS_ALLOWED_ORIGINS = [ 140 | # "http://localhost:5173" 141 | # ] 142 | CORS_ALLOW_CREDENTIALS = True 143 | -------------------------------------------------------------------------------- /backend/peer_notes/urls.py: -------------------------------------------------------------------------------- 1 | """ 2 | URL configuration for peer_notes project. 3 | 4 | The `urlpatterns` list routes URLs to views. For more information please see: 5 | https://docs.djangoproject.com/en/4.2/topics/http/urls/ 6 | Examples: 7 | Function views 8 | 1. Add an import: from my_app import views 9 | 2. Add a URL to urlpatterns: path('', views.home, name='home') 10 | Class-based views 11 | 1. Add an import: from other_app.views import Home 12 | 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') 13 | Including another URLconf 14 | 1. Import the include() function: from django.urls import include, path 15 | 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) 16 | """ 17 | 18 | from django.contrib import admin 19 | from django.urls import path, include 20 | 21 | urlpatterns = [ 22 | path("admin/", admin.site.urls), 23 | path("", include("api.urls")), 24 | ] 25 | -------------------------------------------------------------------------------- /backend/peer_notes/views.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AmanTahiliani/PeerNotes/46a4574f62d46a0110f9324f7d0e23b3c8f3f9bc/backend/peer_notes/views.py -------------------------------------------------------------------------------- /backend/peer_notes/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for peer_notes project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/4.2/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "peer_notes.settings") 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /backend/requirements.txt: -------------------------------------------------------------------------------- 1 | asgiref==3.8.1 2 | black==24.2.0 3 | blinker==1.7.0 4 | click==8.1.7 5 | Django==4.2.11 6 | django-cors-headers==4.3.1 7 | djangorestframework==3.15.1 8 | Flask==3.0.2 9 | importlib-metadata==7.0.1 10 | itsdangerous==2.1.2 11 | Jinja2==3.1.3 12 | MarkupSafe==2.1.5 13 | mypy-extensions==1.0.0 14 | packaging==23.2 15 | pathspec==0.12.1 16 | platformdirs==4.2.0 17 | sqlparse==0.4.4 18 | tomli==2.0.1 19 | typing_extensions==4.10.0 20 | Werkzeug==3.0.1 21 | zipp==3.17.0 22 | flask 23 | flask-cors -------------------------------------------------------------------------------- /backend/server.py: -------------------------------------------------------------------------------- 1 | from flask import Flask, request 2 | import signal 3 | 4 | app = Flask(__name__) 5 | file_index = {} 6 | 7 | 8 | def shutdown_server(signal, frame): 9 | print("Shutting down server...") 10 | # Close the Flask app context 11 | ctx = app.app_context() 12 | ctx.push() 13 | # Shutdown the Flask server 14 | app.shutdown() 15 | ctx.pop() 16 | 17 | 18 | # Register the signal handler for SIGINT (keyboard interrupt) 19 | signal.signal(signal.SIGINT, shutdown_server) 20 | 21 | 22 | @app.route("/register", methods=["POST"]) 23 | def register_node(): 24 | data = request.json 25 | node_ip = data["ip"] 26 | files = data["files"] 27 | for file in files: 28 | if file not in file_index: 29 | file_index[file] = [] 30 | file_index[file].append(node_ip) 31 | print(file_index) 32 | return "Node registered successfully", 200 33 | 34 | 35 | @app.route("/query", methods=["GET"]) 36 | def query_file(): 37 | file_name = request.args.get("file") 38 | print("Requested File Name: ", file_name) 39 | if file_name in file_index: 40 | return {"nodes": file_index[file_name]}, 200 41 | else: 42 | return "File not found", 404 43 | 44 | 45 | if __name__ == "__main__": 46 | app.run(host="0.0.0.0", port=8000) 47 | -------------------------------------------------------------------------------- /file_peer/.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | uploads/ 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | cover/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | .pybuilder/ 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | # For a library or package, you might want to ignore these files since the code is 87 | # intended to run in multiple environments; otherwise, check them in: 88 | # .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # poetry 98 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 99 | # This is especially recommended for binary packages to ensure reproducibility, and is more 100 | # commonly ignored for libraries. 101 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 102 | #poetry.lock 103 | 104 | # pdm 105 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 106 | #pdm.lock 107 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 108 | # in version control. 109 | # https://pdm.fming.dev/#use-with-ide 110 | .pdm.toml 111 | 112 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 113 | __pypackages__/ 114 | 115 | # Celery stuff 116 | celerybeat-schedule 117 | celerybeat.pid 118 | 119 | # SageMath parsed files 120 | *.sage.py 121 | 122 | # Environments 123 | .env 124 | .venv 125 | env/ 126 | venv/ 127 | ENV/ 128 | env.bak/ 129 | venv.bak/ 130 | 131 | # Spyder project settings 132 | .spyderproject 133 | .spyproject 134 | 135 | # Rope project settings 136 | .ropeproject 137 | 138 | # mkdocs documentation 139 | /site 140 | 141 | # mypy 142 | .mypy_cache/ 143 | .dmypy.json 144 | dmypy.json 145 | 146 | # Pyre type checker 147 | .pyre/ 148 | 149 | # pytype static type analyzer 150 | .pytype/ 151 | 152 | # Cython debug symbols 153 | cython_debug/ 154 | 155 | # PyCharm 156 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 157 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 158 | # and can be added to the global gitignore or merged into this file. For a more nuclear 159 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 160 | #.idea/ 161 | 162 | # Misc 163 | .DS_STORE 164 | -------------------------------------------------------------------------------- /file_peer/peer_service.py: -------------------------------------------------------------------------------- 1 | from flask import Flask, send_file, request 2 | import requests 3 | import socket 4 | import shutil 5 | import os 6 | 7 | from flask_cors import CORS 8 | 9 | app = Flask(__name__) 10 | 11 | # Enable CORS for all routes 12 | CORS(app) 13 | 14 | 15 | @app.route("/copy-file", methods=["POST"]) 16 | def copy_file(): 17 | try: 18 | files = request.files 19 | id = request.form["file_id"] 20 | print(id) 21 | if "file" not in files: 22 | return "File must be sent in request", 400 23 | file = request.files["file"] 24 | filename = file.filename.replace(" ", "_") 25 | # id = data["file_id"] 26 | # if not os.path.exists(source_file): 27 | # return f"Source file does not exist at {source_file}", 400 28 | # filename = os.path.basename(source_file) 29 | destination_path = os.path.join("./uploads/", str(id) + "_" + filename) 30 | # shutil.copyfile(source_file, destination_path) 31 | if os.path.exists(destination_path): 32 | return "File with name already exists", 200 33 | 34 | with open(destination_path, "wb") as f: 35 | f.write(file.read()) 36 | return "File Moved successfully", 200 37 | except Exception as e: 38 | print(f"An error occured: {e}") 39 | return "An error occured", 400 40 | 41 | 42 | @app.route("/ip", methods=["GET"]) 43 | def get_internal_ip(): 44 | internal_ip = None 45 | try: 46 | s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 47 | s.connect(("8.8.8.8", 80)) 48 | internal_ip = s.getsockname()[0] 49 | s.close() 50 | except Exception as e: 51 | print("Error:", e) 52 | return internal_ip, 200 53 | 54 | 55 | @app.route("/send", methods=["GET"]) 56 | def send(): 57 | file_id = request.args.get("id") 58 | filename = request.args.get("filename") 59 | 60 | file_path = "./uploads/" + file_id + "_" + filename 61 | 62 | return send_file( 63 | file_path, as_attachment=True, download_name=str(file_id) + "_" + filename 64 | ) 65 | 66 | 67 | @app.route("/request", methods=["POST"]) 68 | def request_file(): 69 | data = request.json 70 | file_id = data["id"] 71 | filename = data["filename"] 72 | ip = data["ip"] 73 | 74 | url = f"http://{ip}:8080/send" 75 | params = { 76 | "id": file_id, 77 | "filename": filename.replace(" ", "_"), 78 | } 79 | 80 | response = requests.get(url, params=params) 81 | file_path = "./uploads/" + str(file_id) + "_" + filename 82 | 83 | if response.status_code == 200: 84 | if os.path.exists(file_path): 85 | return "File with name already exists", 200 86 | 87 | with open(file_path, "wb") as f: 88 | f.write(response.content) 89 | print("File downloaded successfully") 90 | return f"File Downloaded to location to location {file_path}", 200 91 | else: 92 | error = response.text 93 | print("Error:", error) 94 | return error, response.status_code 95 | 96 | 97 | @app.route("/files", methods=["GET"]) 98 | def list_files(): 99 | files = os.listdir("./uploads") 100 | newFiles = [] 101 | for file in files: 102 | newFiles.append("_".join(file.split("_", 1)[1:])) 103 | return {"files": newFiles}, 200 104 | 105 | 106 | @app.route("/request-tests", methods=["GET"]) 107 | def request_test_(): 108 | data = request.json 109 | file_id = data["id"] 110 | filename = data["filename"] 111 | ip = data["ip"] 112 | new_filename = data["new_filename"] 113 | 114 | url = f"http://{ip}:8080/send" 115 | params = { 116 | "id": file_id, 117 | "filename": filename, 118 | } 119 | 120 | response = requests.get(url, params=params) 121 | file_path = "./uploads/" + str(file_id) + "_" + new_filename 122 | 123 | if response.status_code == 200: 124 | if os.path.exists(file_path): 125 | return "File with name already exists", 200 126 | 127 | with open(file_path, "wb") as f: 128 | f.write(response.content) 129 | print("File downloaded successfully") 130 | return f"File Downloaded to location to location {file_path}", 200 131 | else: 132 | error = response.text 133 | print("Error:", error) 134 | return error, response.status_code 135 | 136 | 137 | if __name__ == "__main__": 138 | app.run(debug=True, host="0.0.0.0", port=8080) 139 | -------------------------------------------------------------------------------- /file_peer/uploads/1_abcd.txt: -------------------------------------------------------------------------------- 1 | Hello World! 2 | Flask Notes Transfer Test -------------------------------------------------------------------------------- /file_peer/uploads/2_bcdef.txt: -------------------------------------------------------------------------------- 1 | Hello World! 2 | Flask Notes Transfer Test 2 -------------------------------------------------------------------------------- /frontend/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { browser: true, es2020: true }, 4 | extends: [ 5 | 'eslint:recommended', 6 | 'plugin:@typescript-eslint/recommended', 7 | 'plugin:react-hooks/recommended', 8 | ], 9 | ignorePatterns: ['dist', '.eslintrc.cjs'], 10 | parser: '@typescript-eslint/parser', 11 | plugins: ['react-refresh'], 12 | rules: { 13 | 'react-refresh/only-export-components': [ 14 | 'warn', 15 | { allowConstantExport: true }, 16 | ], 17 | }, 18 | } 19 | -------------------------------------------------------------------------------- /frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /frontend/README.md: -------------------------------------------------------------------------------- 1 | # React + TypeScript + Vite 2 | 3 | This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. 4 | 5 | Currently, two official plugins are available: 6 | 7 | - [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh 8 | - [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh 9 | 10 | ## Expanding the ESLint configuration 11 | 12 | If you are developing a production application, we recommend updating the configuration to enable type aware lint rules: 13 | 14 | - Configure the top-level `parserOptions` property like this: 15 | 16 | ```js 17 | export default { 18 | // other rules... 19 | parserOptions: { 20 | ecmaVersion: 'latest', 21 | sourceType: 'module', 22 | project: ['./tsconfig.json', './tsconfig.node.json'], 23 | tsconfigRootDir: __dirname, 24 | }, 25 | } 26 | ``` 27 | 28 | - Replace `plugin:@typescript-eslint/recommended` to `plugin:@typescript-eslint/recommended-type-checked` or `plugin:@typescript-eslint/strict-type-checked` 29 | - Optionally add `plugin:@typescript-eslint/stylistic-type-checked` 30 | - Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and add `plugin:react/recommended` & `plugin:react/jsx-runtime` to the `extends` list 31 | -------------------------------------------------------------------------------- /frontend/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | PeerNotes 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "frontend", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "tsc && vite build", 9 | "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", 10 | "preview": "vite preview" 11 | }, 12 | "dependencies": { 13 | "react": "^18.2.0", 14 | "react-dom": "^18.2.0", 15 | "react-router-dom": "^6.22.3" 16 | }, 17 | "devDependencies": { 18 | "@types/react": "^18.2.64", 19 | "@types/react-dom": "^18.2.21", 20 | "@typescript-eslint/eslint-plugin": "^7.1.1", 21 | "@typescript-eslint/parser": "^7.1.1", 22 | "@vitejs/plugin-react-swc": "^3.5.0", 23 | "eslint": "^8.57.0", 24 | "eslint-plugin-react-hooks": "^4.6.0", 25 | "eslint-plugin-react-refresh": "^0.4.5", 26 | "typescript": "^5.2.2", 27 | "vite": "^5.1.6" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /frontend/public/PeerNotes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AmanTahiliani/PeerNotes/46a4574f62d46a0110f9324f7d0e23b3c8f3f9bc/frontend/public/PeerNotes.png -------------------------------------------------------------------------------- /frontend/public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/App.tsx: -------------------------------------------------------------------------------- 1 | import "./styles/App.css"; 2 | import { Link, useNavigate } from 'react-router-dom'; 3 | import { Outlet } from "react-router-dom"; 4 | import { SessionContext, destroySessionCookie, getSessionCookie } from "./contexts/session"; 5 | import { useState, useEffect, useContext } from "react"; 6 | import { usePoll } from "./hooks/usePoll"; 7 | 8 | export default function App() { 9 | const [session, setSession] = useState(getSessionCookie()); 10 | const navigate = useNavigate(); 11 | usePoll(); 12 | // Redirect to login if session is undefined 13 | useEffect( 14 | () => { 15 | setSession(getSessionCookie()); 16 | if (session === null) { 17 | navigate("/login"); 18 | } 19 | }, 20 | [session, navigate] 21 | ); 22 | 23 | return ( 24 | <> 25 | 26 | 27 |
28 | 29 |
30 |
31 | 32 | ) 33 | } 34 | function Navbar() { 35 | const linkStyle = { 36 | textDecoration: 'none', 37 | color: 'black', 38 | background: '#B1D4E0', 39 | padding: '8px 16px', 40 | borderRadius: '5px', 41 | // margin: '0 5px' 42 | }; 43 | const session = useContext(SessionContext); 44 | const logout = () => { 45 | destroySessionCookie(); 46 | window.location.reload(); 47 | } 48 | 49 | const links = session ? ( 50 | <> 51 |
52 | PeerNotes Logo 53 | Upload 54 | Search 55 |
56 |
57 | Logout 58 |
59 | 60 | ): ( 61 | Login 62 | ) 63 | return ( 64 | 67 | ); 68 | } -------------------------------------------------------------------------------- /frontend/src/assets/file.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /frontend/src/assets/react.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/contexts/session.ts: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | type SessionContextType = string | null; 4 | 5 | export const getSessionCookie = () => { 6 | // const sessionCookie = document.cookie 7 | // .split('; ') 8 | // .find(row => row.startsWith('token=')) 9 | // ?.split('=')[1]; 10 | const sessionCookie = localStorage.getItem('authToken'); 11 | return sessionCookie; 12 | } 13 | export const destroySessionCookie = () => { 14 | console.log("destroying session cookie"); 15 | localStorage.removeItem('authToken'); 16 | // document.cookie = "token=; Max-Age=0;" 17 | } 18 | 19 | export const SessionContext = React.createContext(getSessionCookie()); 20 | 21 | -------------------------------------------------------------------------------- /frontend/src/hooks/sessionRedirect.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useContext } from "react"; 2 | import { useNavigate } from "react-router-dom"; 3 | import { SessionContext } from "../contexts/session"; 4 | 5 | export const useSessionRedirect = () => { 6 | const session = useContext(SessionContext); 7 | const navigate = useNavigate(); 8 | // Redirect to home if session is defined 9 | useEffect(() => { 10 | if (session !== null) { 11 | navigate('/search'); 12 | } 13 | }, [session, navigate]) 14 | } -------------------------------------------------------------------------------- /frontend/src/hooks/usePoll.ts: -------------------------------------------------------------------------------- 1 | import { useEffect } from "react" 2 | import { getAuthHeaders } from "../utils/getAuthHeaders" 3 | export const usePoll = () => { 4 | useEffect(() => { 5 | const interval = setInterval(() => { 6 | pollServer() 7 | .catch((error) => { 8 | console.error('Error:', error); 9 | }); 10 | }, 30 * 60000) // poll every 30 minutes 11 | return () => { 12 | // clean up 13 | clearInterval(interval); 14 | }; 15 | }, []) 16 | 17 | const pollServer = async () => { 18 | // request private ip from local api 19 | return fetch('http://localhost:8080/ip') 20 | .then(response => response.text()) 21 | .then((data) => { 22 | // send private ip to central server 23 | return fetch(`${import.meta.env.VITE_CENTRAL_SERVER}/api/poll/`, { 24 | method: 'POST', 25 | headers: getAuthHeaders(), 26 | body: JSON.stringify({ip: data}), 27 | }) 28 | }).then(response => response.json()) 29 | .then(data => { 30 | console.log("Poll response data:", data); 31 | }) 32 | } 33 | } -------------------------------------------------------------------------------- /frontend/src/index.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AmanTahiliani/PeerNotes/46a4574f62d46a0110f9324f7d0e23b3c8f3f9bc/frontend/src/index.css -------------------------------------------------------------------------------- /frontend/src/main.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom/client' 3 | import App from './App.tsx' 4 | import './index.css' 5 | import { 6 | createBrowserRouter, 7 | RouterProvider, 8 | } from "react-router-dom"; 9 | import Login from './screens/Login.tsx'; 10 | import Signup from './screens/Signup.tsx'; 11 | import Results from './screens/Results.tsx'; 12 | import MainSearch from './screens/MainSearch.tsx'; 13 | import RegisterFile from './screens/RegisterFile.tsx'; 14 | 15 | const router = createBrowserRouter([ 16 | { 17 | path: "/", 18 | element: , 19 | children: [ 20 | // add more routes here 21 | { 22 | path: "search", 23 | element: , 24 | }, 25 | { 26 | path: "results", 27 | element: , 28 | }, 29 | { 30 | path: "register", 31 | element: , 32 | }, 33 | ] 34 | }, 35 | 36 | { 37 | path: "/login", 38 | element: , 39 | }, 40 | { 41 | path: "/signup", 42 | element: , 43 | }, 44 | ]); 45 | 46 | ReactDOM.createRoot(document.getElementById('root')!).render( 47 | 48 | 49 | , 50 | ) 51 | -------------------------------------------------------------------------------- /frontend/src/screens/Login.tsx: -------------------------------------------------------------------------------- 1 | import { Link, useNavigate } from "react-router-dom" 2 | import "../styles/Login.css" 3 | import { FormEvent } from "react"; 4 | import { useSessionRedirect } from "../hooks/sessionRedirect"; 5 | 6 | export default function Login() { 7 | const navigate = useNavigate(); 8 | 9 | useSessionRedirect(); 10 | 11 | function handleSubmit(e: FormEvent) { 12 | e.preventDefault(); 13 | const formData = new FormData(e.target as HTMLFormElement); 14 | const username = formData.get('username'); 15 | const password = formData.get('password'); 16 | fetch(`${import.meta.env.VITE_CENTRAL_SERVER}/api/login/`, { 17 | method: 'POST', 18 | credentials: 'include', 19 | headers: { 20 | 'Content-Type': 'application/json', 21 | }, 22 | body: JSON.stringify({ username, password }), 23 | }) 24 | .then(response => response.json()) 25 | .then(data => { 26 | console.log("Login response data:", data); 27 | if (data.token) { 28 | localStorage.setItem('authToken', data.token); 29 | navigate('/search'); 30 | } else { 31 | console.error('No token received:', data); 32 | } 33 | }) 34 | .catch((error) => { 35 | console.error('Error:', error); 36 | }); 37 | } 38 | return ( 39 |
40 |
41 |

Login

42 |
43 | 44 | 45 |
46 |
47 | 48 | 49 |
50 | 51 |

or Sign up

52 |
53 |
54 | ); 55 | } -------------------------------------------------------------------------------- /frontend/src/screens/MainSearch.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | import { useNavigate } from 'react-router-dom'; 3 | import '../styles/MainScreenWrapper.css'; 4 | import { getAuthHeaders } from '../utils/getAuthHeaders'; 5 | import { Professor, Course, Topic, Semester } from "../types/types"; 6 | import { getSessionCookie } from '../contexts/session'; 7 | 8 | interface Filters extends Record { 9 | professor: string; 10 | course: string; 11 | topic: string; 12 | } 13 | 14 | const MainSearch: React.FC = () => { 15 | const [filters, setFilters] = useState({ 16 | professor: '', 17 | course: '', 18 | topic: '', 19 | }); 20 | const [professors, setProfessors] = useState([]); 21 | const [courses, setCourses] = useState([]); 22 | const [topics, setTopics] = useState([]); 23 | const [semesters, setSemesters] = useState([]); 24 | const navigate = useNavigate(); 25 | 26 | useEffect(() => { 27 | if (!getSessionCookie()) { 28 | return; 29 | } 30 | // Fetch professors 31 | fetchProfessors(); 32 | // Fetch courses 33 | fetchCourses(); 34 | // Fetch topics 35 | fetchTopics(); 36 | // Fetch semesters 37 | fetchSemesters(); 38 | }, []); 39 | 40 | 41 | const fetchProfessors = async () => { 42 | const token = getSessionCookie(); 43 | try { 44 | const response = await fetch(`${import.meta.env.VITE_CENTRAL_SERVER}/api/professors/`, { 45 | method: 'GET', 46 | headers: { 47 | 'Authorization': `Token ${token}`, 48 | }, 49 | }); 50 | const data = await response.json(); 51 | console.log("HEREEEEEE", data); 52 | console.log(response) 53 | if (Array.isArray(data)) { 54 | setProfessors(data); 55 | } else { 56 | console.error('Data fetched is not an array:', data); 57 | } 58 | } catch (error) { 59 | console.error('Error fetching professors:', error); 60 | } 61 | }; 62 | 63 | const fetchCourses = async () => { 64 | try { 65 | const response = await fetch(`${import.meta.env.VITE_CENTRAL_SERVER}/api/courses`, { 66 | method: 'GET', 67 | headers: getAuthHeaders(), 68 | }); 69 | const data = await response.json(); 70 | if (Array.isArray(data)) { 71 | setCourses(data); 72 | } else { 73 | console.error('Data fetched is not an array:', data); 74 | } 75 | } catch (error) { 76 | console.error('Error fetching courses:', error); 77 | } 78 | }; 79 | 80 | const fetchTopics = async () => { 81 | try { 82 | const response = await fetch(`${import.meta.env.VITE_CENTRAL_SERVER}/api/topics`, { 83 | method: 'GET', 84 | headers: getAuthHeaders(), 85 | }); 86 | const data = await response.json(); 87 | if (Array.isArray(data)) { 88 | setTopics(data); 89 | } else { 90 | console.error('Data fetched is not an array:', data); 91 | } 92 | } catch (error) { 93 | console.error('Error fetching topics:', error); 94 | } 95 | }; 96 | const fetchSemesters = async () => { 97 | try { 98 | const response = await fetch(`${import.meta.env.VITE_CENTRAL_SERVER}/api/semesters`, { 99 | method: 'GET', 100 | headers: getAuthHeaders(), 101 | }); 102 | const data = await response.json(); 103 | if (Array.isArray(data)) { 104 | setSemesters(data); 105 | } else { 106 | console.error('Data fetched is not an array:', data); 107 | } 108 | } catch (error) { 109 | console.error('Error fetching courses:', error); 110 | } 111 | }; 112 | 113 | const handleChange = (event: React.ChangeEvent) => { 114 | const { name, value } = event.target; 115 | setFilters((prevFilters: Filters) => ({ 116 | ...prevFilters, 117 | [name]: value, 118 | })); 119 | }; 120 | 121 | const handleSubmit = (event: React.FormEvent) => { 122 | event.preventDefault(); 123 | const searchParams = new URLSearchParams(filters); 124 | navigate(`/results?${searchParams}`); 125 | }; 126 | 127 | return ( 128 | <> 129 | PeerNotes Logo 130 |
131 |
132 | 133 | 139 |
140 |
141 | 142 | 148 |
149 |
150 | 151 | 157 |
158 |
159 | 160 | 166 |
167 | 168 |
169 | 170 | ); 171 | } 172 | 173 | export default MainSearch; 174 | -------------------------------------------------------------------------------- /frontend/src/screens/RegisterFile.tsx: -------------------------------------------------------------------------------- 1 | import "../styles/RegisterFile.css"; 2 | import { RegisteredFile, Status, Professor, Course, Topic, Semester } from "../types/types"; 3 | import { useState, useEffect } from "react"; 4 | import { getAuthHeaders } from "../utils/getAuthHeaders"; 5 | import { getSessionCookie } from "../contexts/session"; 6 | 7 | export default function RegisterFile() { 8 | const [serverFiles, setServerFiles] = useState([]); 9 | const [registeredFiles, setRegisteredFiles] = useState([]); 10 | 11 | useEffect(() => { 12 | fetchServerFiles(); 13 | }, []) 14 | 15 | useEffect(() => { 16 | fetch(`http://localhost:8080/files`, { 17 | method: 'GET', 18 | }) 19 | .then(response => response.json()) 20 | .then(data => { 21 | const files: string[] = data.files.map((file: string) => { 22 | return file.replace(/\s/g, "_"); 23 | }) 24 | setRegisteredFiles( 25 | serverFiles.map((file) => { 26 | if (files.includes(file.filename)) { 27 | file.status = Status.HOSTED; 28 | return file; 29 | } 30 | file.status = Status.PRIVATE; 31 | return file; 32 | }) 33 | ) 34 | }) 35 | }, [serverFiles]) 36 | 37 | const fetchServerFiles = () => { 38 | fetch(`${import.meta.env.VITE_CENTRAL_SERVER}/api/get-peer-files/`, { 39 | method: 'GET', 40 | headers: getAuthHeaders() 41 | }) 42 | .then(response => response.json()) 43 | .then(data => { 44 | setServerFiles(data) 45 | }) 46 | .catch(error => console.error(error)); 47 | } 48 | 49 | const handleSubmit = (event: React.FormEvent) => { 50 | event.preventDefault(); 51 | // handle file upload 52 | const fileInput = document.getElementById('file'); 53 | // @ts-expect-error - fileInput is an HTMLInputElement 54 | const filename: string = fileInput.files[0].name.replace(/\s/g, "_"); 55 | const formData = new FormData(event.target as HTMLFormElement); 56 | 57 | // register file with central server 58 | fetch(`${import.meta.env.VITE_CENTRAL_SERVER}/api/register/`, { 59 | method: 'POST', 60 | headers: getAuthHeaders(), 61 | body: JSON.stringify({ 62 | filename: filename, 63 | topic: formData.get('topic'), 64 | semester: formData.get('semester'), 65 | professor: formData.get('professor'), 66 | course: formData.get('course'), 67 | }) 68 | }) 69 | .then(response => response.json()) 70 | .then(data => { 71 | formData.append("file_id", data.id) 72 | // send file to local 73 | return fetch("http://localhost:8080/copy-file", { 74 | method: 'POST', 75 | body: formData 76 | }) 77 | }) 78 | .then(response => response.text()) 79 | .then(data => { 80 | console.log(data); 81 | window.location.reload(); 82 | }) 83 | .catch(error => console.error(error)); 84 | } 85 | 86 | // Filters Logic 87 | interface Filters extends Record { 88 | professor: string; 89 | course: string; 90 | topic: string; 91 | } 92 | const [filters, setFilters] = useState({ 93 | professor: '', 94 | course: '', 95 | topic: '', 96 | }); 97 | const [professors, setProfessors] = useState([]); 98 | const [courses, setCourses] = useState([]); 99 | const [topics, setTopics] = useState([]); 100 | const [semesters, setSemesters] = useState([]); 101 | 102 | useEffect(() => { 103 | if (!getSessionCookie()) { 104 | return; 105 | } 106 | // Fetch professors 107 | fetchProfessors(); 108 | // Fetch courses 109 | fetchCourses(); 110 | // Fetch topics 111 | fetchTopics(); 112 | // Fetch semesters 113 | fetchSemesters(); 114 | }, []); 115 | 116 | 117 | const fetchProfessors = async () => { 118 | try { 119 | const response = await fetch(`${import.meta.env.VITE_CENTRAL_SERVER}/api/professors`, { 120 | method: 'GET', 121 | headers: getAuthHeaders(), 122 | }); 123 | const data = await response.json(); 124 | 125 | if (Array.isArray(data)) { 126 | setProfessors(data); 127 | } else { 128 | console.error('Data fetched is not an array:', data); 129 | } 130 | } catch (error) { 131 | console.error('Error fetching professors:', error); 132 | } 133 | }; 134 | 135 | const fetchCourses = async () => { 136 | try { 137 | const response = await fetch(`${import.meta.env.VITE_CENTRAL_SERVER}/api/courses`, { 138 | method: 'GET', 139 | headers: getAuthHeaders(), 140 | }); 141 | const data = await response.json(); 142 | if (Array.isArray(data)) { 143 | setCourses(data); 144 | } else { 145 | console.error('Data fetched is not an array:', data); 146 | } 147 | } catch (error) { 148 | console.error('Error fetching courses:', error); 149 | } 150 | }; 151 | 152 | const fetchTopics = async () => { 153 | try { 154 | const response = await fetch(`${import.meta.env.VITE_CENTRAL_SERVER}/api/topics`, { 155 | method: 'GET', 156 | headers: getAuthHeaders(), 157 | }); 158 | const data = await response.json(); 159 | if (Array.isArray(data)) { 160 | setTopics(data); 161 | } else { 162 | console.error('Data fetched is not an array:', data); 163 | } 164 | } catch (error) { 165 | console.error('Error fetching topics:', error); 166 | } 167 | }; 168 | const fetchSemesters = async () => { 169 | try { 170 | const response = await fetch(`${import.meta.env.VITE_CENTRAL_SERVER}/api/semesters`, { 171 | method: 'GET', 172 | headers: getAuthHeaders(), 173 | }); 174 | const data = await response.json(); 175 | if (Array.isArray(data)) { 176 | setSemesters(data); 177 | } else { 178 | console.error('Data fetched is not an array:', data); 179 | } 180 | } catch (error) { 181 | console.error('Error fetching courses:', error); 182 | } 183 | }; 184 | 185 | const handleChange = (event: React.ChangeEvent) => { 186 | const { name, value } = event.target; 187 | setFilters((prevFilters: Filters) => ({ 188 | ...prevFilters, 189 | [name]: value, 190 | })); 191 | }; 192 | return ( 193 |
194 |
195 |

Upload a File

196 |

Host a local file for the GT community!

197 |
198 | 199 | 200 |
201 | 202 | 208 |
209 |
210 | 211 | 217 |
218 |
219 | 220 | 226 |
227 |
228 | 229 | 235 |
236 | 237 |
238 |
239 |
240 |

Registered Files

241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | {registeredFiles.map((file) => ( 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | ))} 261 | 262 |
File NameOriginal AuthorProfessorSemesterStatus
{file.filename}{file.original_author.username}{file.professor.name}{file.semester.name}{file.status}
263 |
264 |
265 | ) 266 | } -------------------------------------------------------------------------------- /frontend/src/screens/Results.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | import { useLocation, useNavigate } from 'react-router-dom'; 3 | import styles from '../styles/Results.module.css'; // Make sure this path is correct 4 | import { getAuthHeaders } from '../utils/getAuthHeaders'; 5 | import { File } from '../types/types'; 6 | import { Link } from 'react-router-dom'; 7 | 8 | const Results: React.FC = () => { 9 | const [files, setFiles] = useState([]); 10 | const [isLoading, setLoading] = useState(false); 11 | const location = useLocation(); 12 | const navigate = useNavigate(); 13 | 14 | useEffect(() => { 15 | const fetchFiles = async () => { 16 | setLoading(true); 17 | try { 18 | // Need to double check this 19 | const queryString = location.search; // Includes the '?' prefix 20 | console.log(queryString) 21 | const response = await fetch(`${import.meta.env.VITE_CENTRAL_SERVER}/api/files/filter${queryString}`, {headers: getAuthHeaders()}); 22 | if (!response.ok) { 23 | throw new Error(`HTTP error! status: ${response.status}`); 24 | } 25 | const data = await response.json(); 26 | setFiles(data); 27 | } catch (e) { 28 | console.error("Could not fetch files", e); 29 | } finally { 30 | setLoading(false); 31 | } 32 | }; 33 | 34 | fetchFiles(); 35 | }, [location]); 36 | 37 | const handleSearchAgain = () => { 38 | navigate('/search'); 39 | }; 40 | 41 | if (isLoading) return
Loading...
; 42 | 43 | return ( 44 |
45 |
46 | PeerNotes Logo 47 | 48 |
49 |
50 | {isLoading ? ( 51 |

Loading...

52 | ) : files.length ? ( 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | {files.map((file) => ( 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | ))} 79 | 80 |
FilenameCourseProfessorSemesterUpvotesDownvotesOriginal AuthorDownload
{file.filename}{file.course.name} {file.course.number}{file.professor.name}{file.semester.name}{file.upvotes.length}{file.downvotes.length}{file.original_author.username}
81 | ) : ( 82 |

No results found.

83 | )} 84 |
85 |
86 | ); 87 | }; 88 | 89 | export default Results; 90 | 91 | 92 | function DownloadButton({ file }: { file: File }) { 93 | const [success, setSuccess] = useState(false); 94 | const handleDownload = () => { 95 | // Implement download functionality 96 | fetch("http://localhost:8080/request", { 97 | method: "POST", 98 | headers: { 99 | "Content-Type": "application/json", 100 | }, 101 | body: JSON.stringify({ id: file.id, filename: file.filename, ip: file.original_author.ip_address }), 102 | }) 103 | .then((response) => response.text()) 104 | .then(() => { 105 | return fetch( 106 | `${import.meta.env.VITE_CENTRAL_SERVER}/api/files/${file.id}/add-peer/`, 107 | { 108 | method: "POST", 109 | headers: getAuthHeaders(), 110 | } 111 | ) 112 | }) 113 | .then((response) => { 114 | if (!response.ok) { 115 | throw new Error("Failed to add peer"); 116 | } 117 | setSuccess(true); 118 | }) 119 | .catch((error) => { 120 | console.error("Error:", error); 121 | setSuccess(false); 122 | }); 123 | }; 124 | return ( 125 | <> 126 | { 127 | success === true ? 128 | Downloaded! : 129 | 130 | } 131 | 132 | ); 133 | } -------------------------------------------------------------------------------- /frontend/src/screens/Signup.tsx: -------------------------------------------------------------------------------- 1 | import { Link, useNavigate } from "react-router-dom"; 2 | import "../styles/Login.css" 3 | import { FormEvent } from "react"; 4 | import { useSessionRedirect } from "../hooks/sessionRedirect"; 5 | export default function Signup() { 6 | const navigate = useNavigate(); 7 | useSessionRedirect(); 8 | 9 | function handleSubmit(e: FormEvent) { 10 | e.preventDefault(); 11 | const formData = new FormData(e.target as HTMLFormElement); 12 | const email = formData.get('email'); 13 | const username = formData.get('username'); 14 | const password = formData.get('password'); 15 | console.log(import.meta.env.VITE_CENTRAL_SERVER) 16 | fetch(`${import.meta.env.VITE_CENTRAL_SERVER}/api/signup/`, { 17 | method: 'POST', 18 | headers: { 19 | 'Content-Type': 'application/json', 20 | }, 21 | body: JSON.stringify({ email, username, password }), 22 | }) 23 | .then(response => response.json()) 24 | .then(() => { 25 | navigate('/login'); 26 | }) 27 | .catch((error) => { 28 | console.error('Error:', error); 29 | }); 30 | } 31 | return ( 32 |
33 |
34 |

Sign Up

35 |
36 | 37 | 38 |
39 |
40 | 41 | 42 |
43 |
44 | 45 | 46 |
47 | 48 |

or Login

49 |
50 |
51 | ); 52 | } -------------------------------------------------------------------------------- /frontend/src/styles/App.css: -------------------------------------------------------------------------------- 1 | html { 2 | width: 100%; 3 | } 4 | nav { 5 | display: flex; 6 | justify-content: space-between; 7 | align-items: center; 8 | padding: 1rem 2rem; 9 | margin: 0; 10 | width: 100%; 11 | background-color: #145DA0; 12 | border: none; 13 | border-radius: 0; 14 | position: fixed; 15 | top: 0; 16 | left: 0; 17 | z-index: 1000; 18 | box-sizing: border-box; 19 | } 20 | nav div { 21 | display: flex; 22 | align-items: center; 23 | gap: 1rem; 24 | } 25 | .App { 26 | display: flex; 27 | flex-direction: column; 28 | align-items: center; 29 | justify-content: center; /* If you're centering the content */ 30 | height: 100vh; /* Full height */ 31 | margin-top: 100px; /* Move up */ 32 | width: 100%; 33 | } 34 | 35 | body { 36 | background-color: #0C2D48; 37 | font-family: 'Hiragino Sans', 'Hiragino Kaku Gothic ProN', Meiryo, sans-serif; 38 | width: 100%; 39 | } 40 | -------------------------------------------------------------------------------- /frontend/src/styles/Login.css: -------------------------------------------------------------------------------- 1 | form { 2 | display: flex; 3 | flex-direction: column; 4 | justify-content: center; 5 | gap: 0.5rem; 6 | } 7 | main { 8 | display: flex; 9 | justify-content: center; 10 | align-items: center; 11 | height: 100vh; 12 | } 13 | h1 { 14 | text-align: center; 15 | } 16 | 17 | .form-input { 18 | display: flex; 19 | flex-direction: column; 20 | } 21 | 22 | .login-container { 23 | display: flex; 24 | flex-direction: column; 25 | align-items: center; 26 | justify-content: center; 27 | background: #B1D4E0; 28 | border-radius: 8px; 29 | box-shadow: 0 2px 4px rgba(0,0,0,0.1); /* shadow */; 30 | width: 40vh; 31 | } 32 | 33 | .submit-button { 34 | padding: 8px 16px; 35 | margin-top: 20px; /* Adds space above the button */ 36 | background-color: #2E8BC0; /* Example background color */ 37 | color: white; /* Text color */ 38 | border: none; 39 | border-radius: 4px; 40 | cursor: pointer; 41 | font-family: 'Hiragino Sans', 'Hiragino Kaku Gothic ProN', Meiryo, sans-serif; 42 | } -------------------------------------------------------------------------------- /frontend/src/styles/MainScreenWrapper.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | height: 100%; 3 | margin: 0; 4 | display: flex; 5 | justify-content: center; 6 | align-items: center; 7 | background: #0C2D48; 8 | font-family: 'Hiragino Sans', 'Hiragino Kaku Gothic ProN', Meiryo, sans-serif; 9 | } 10 | 11 | .filter-container { 12 | display: flex; 13 | flex-direction: column; 14 | align-items: center; 15 | justify-content: center; 16 | padding: 20px; 17 | background: #B1D4E0; 18 | gap: 15; 19 | border-radius: 8px; 20 | box-shadow: 0 2px 4px rgba(0,0,0,0.1); /* shadow */ 21 | } 22 | 23 | .logo { 24 | height: 100px; /* Adjust this value as needed */ 25 | width: auto; /* Keep the logo's aspect ratio */ 26 | margin-bottom: 15px; /* Gap between logo and search box */ 27 | } 28 | 29 | .filter-item + .filter-item { 30 | margin-top: 20px; /* space between filter items */ 31 | } 32 | 33 | label { 34 | margin-bottom: 5px; 35 | font-weight: bold; 36 | } 37 | 38 | .filter-item > select { 39 | padding: 8px; 40 | border: 1px solid #2E8BC0; 41 | border-radius: 4px; 42 | width: 100%; 43 | } 44 | 45 | .filter-item { 46 | width: 100%; 47 | } 48 | 49 | .submit-button { 50 | padding: 8px 16px; 51 | margin-top: 20px; /* Adds space above the button */ 52 | background-color: #2E8BC0; /* Example background color */ 53 | color: white; /* Text color */ 54 | border: none; 55 | border-radius: 4px; 56 | cursor: pointer; 57 | font-family: 'Hiragino Sans', 'Hiragino Kaku Gothic ProN', Meiryo, sans-serif; 58 | } 59 | 60 | .submit-button:hover { 61 | background-color: #1D6A96; /* Darker shade for hover effect */ 62 | } 63 | -------------------------------------------------------------------------------- /frontend/src/styles/RegisterFile.css: -------------------------------------------------------------------------------- 1 | h1 { 2 | color: white; 3 | } 4 | .container { 5 | display: flex; 6 | gap: 5rem; 7 | justify-content: center; 8 | } 9 | 10 | .file-upload-card { 11 | padding: 1em; 12 | background-color: #B1D4E0; 13 | border-radius: 5px; 14 | } 15 | .file-upload-card h1 { 16 | color: black; 17 | } 18 | 19 | .file-card { 20 | padding: 1em; 21 | background-color: #2E8BC0; 22 | border-radius: 5px; 23 | display: flex; 24 | flex-direction: row; 25 | } 26 | .submit-button { 27 | padding: 8px 16px; 28 | margin-top: 20px; /* Adds space above the button */ 29 | background-color: #2E8BC0; /* Example background color */ 30 | color: white; /* Text color */ 31 | border: none; 32 | border-radius: 4px; 33 | cursor: pointer; 34 | font-family: 'Hiragino Sans', 'Hiragino Kaku Gothic ProN', Meiryo, sans-serif; 35 | } 36 | 37 | th { 38 | text-align: center; 39 | padding: 1rem; 40 | border: 1px solid #FFFFFF; 41 | color: white; 42 | } 43 | td { 44 | text-align: center; 45 | border: 1px solid #FFFFFF; 46 | background-color: #B1D4E0; 47 | color: black; 48 | } -------------------------------------------------------------------------------- /frontend/src/styles/Results.module.css: -------------------------------------------------------------------------------- 1 | .wrapper { 2 | display: flex; 3 | flex-direction: column; 4 | height: 100vh; 5 | color: #FFFFFF; 6 | } 7 | 8 | .header { 9 | display: flex; 10 | justify-content: space-between; 11 | padding: 10px 20px; 12 | position: fixed; 13 | width: 100%; 14 | top: 0; 15 | left: 0; 16 | background-color: #145DA0; 17 | } 18 | 19 | .logo { 20 | height: 80px; /* Adjust according to preference */ 21 | margin-right: auto; 22 | } 23 | 24 | 25 | .resultsContainer { 26 | display: flex; 27 | flex-direction: column; 28 | align-items: center; 29 | justify-content: center; 30 | 31 | margin: auto; 32 | width: 100vh; 33 | gap: 1rem; 34 | } 35 | 36 | .resultsTable { 37 | /* to be filled out */ 38 | width: 100%; 39 | } 40 | .fileCard p { 41 | background-color: #1f4f7c; 42 | padding: 1rem; 43 | border-radius: 5px; 44 | } 45 | .fileCard { 46 | display: flex; 47 | flex-direction: row; 48 | align-items: center; 49 | justify-content: center; 50 | padding: 20px; 51 | gap: 1rem; 52 | background-color: #145DA0; 53 | border-radius: 8px; 54 | box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); 55 | min-width: 100vh; 56 | } 57 | 58 | .searchAgainButton { 59 | height: 50px; 60 | padding: 4px 8px; 61 | background-color: #0C2D48; 62 | color: white; 63 | border: none; 64 | border-radius: 4px; 65 | font-family: 'Hiragino Sans', 'Hiragino Kaku Gothic ProN', Meiryo, sans-serif; 66 | cursor: pointer; 67 | margin-right: 35px; 68 | margin-top: 15px; 69 | } 70 | 71 | .searchAgainButton:hover { 72 | background-color: #B1D4E0; /* lighter shade for hover effect */ 73 | } 74 | 75 | table { 76 | width: 100%; 77 | } 78 | th { 79 | text-align: center; 80 | padding: 1rem; 81 | border: 1px solid #FFFFFF; 82 | } 83 | td { 84 | text-align: center; 85 | border: 1px solid #FFFFFF; 86 | background-color: #B1D4E0; 87 | color: black; 88 | } -------------------------------------------------------------------------------- /frontend/src/types/types.ts: -------------------------------------------------------------------------------- 1 | export interface Professor { 2 | id: number; 3 | name: string; 4 | } 5 | export interface Course { 6 | id: number; 7 | name: string; 8 | number: string; 9 | } 10 | 11 | export interface Topic { 12 | id: number; 13 | name: string; 14 | description: string; 15 | } 16 | 17 | export interface Semester { 18 | id: number; 19 | name: string; 20 | } 21 | 22 | export interface User { 23 | id: string; 24 | username: string; 25 | email: string; 26 | ip_address: string; 27 | points: number; 28 | } 29 | 30 | export interface File { 31 | id: string; 32 | filename: string; 33 | original_author: User; 34 | professor: Professor; 35 | course: Course; 36 | semester: Semester; 37 | upvotes: number[]; 38 | downvotes: number[]; 39 | } 40 | 41 | export enum Status { 42 | HOSTED = "HOSTED", 43 | PRIVATE = 'PRIVATE', 44 | } 45 | export interface RegisteredFile extends File { 46 | status: Status; 47 | } -------------------------------------------------------------------------------- /frontend/src/utils/getAuthHeaders.ts: -------------------------------------------------------------------------------- 1 | import { getSessionCookie } from "../contexts/session"; 2 | export const getAuthHeaders = () => { 3 | const token = getSessionCookie(); 4 | console.log(token); 5 | return { 6 | 'Authorization': `Token ${token}`, 7 | 'Content-Type': 'application/json' 8 | }; 9 | }; -------------------------------------------------------------------------------- /frontend/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /frontend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "useDefineForClassFields": true, 5 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 6 | "module": "ESNext", 7 | "skipLibCheck": true, 8 | 9 | /* Bundler mode */ 10 | "moduleResolution": "bundler", 11 | "allowImportingTsExtensions": true, 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "noEmit": true, 15 | "jsx": "react-jsx", 16 | 17 | /* Linting */ 18 | "strict": true, 19 | "noUnusedLocals": true, 20 | "noUnusedParameters": true, 21 | "noFallthroughCasesInSwitch": true 22 | }, 23 | "include": ["src"], 24 | "references": [{ "path": "./tsconfig.node.json" }] 25 | } 26 | -------------------------------------------------------------------------------- /frontend/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "skipLibCheck": true, 5 | "module": "ESNext", 6 | "moduleResolution": "bundler", 7 | "allowSyntheticDefaultImports": true, 8 | "strict": true 9 | }, 10 | "include": ["vite.config.ts"] 11 | } 12 | -------------------------------------------------------------------------------- /frontend/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react-swc' 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react()], 7 | }) 8 | -------------------------------------------------------------------------------- /test1.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import json 3 | import os 4 | 5 | 6 | def request_tests(): 7 | url = "http://localhost:8080/request-tests" 8 | downloaded_files = [] 9 | print("past url") 10 | for i in range(100): 11 | filename = f"TestFileTransfer.pdf" 12 | 13 | payload = json.dumps( 14 | { 15 | "id": 5, 16 | "filename": filename, 17 | "ip": "143.215.87.54", 18 | "new_filename": f"TestFileTransfer_{i}.pdf", 19 | } 20 | ) 21 | 22 | headers = { 23 | "Content-Type": "application/json", 24 | } 25 | 26 | response = requests.get(url, headers=headers, data=payload) 27 | print("response received") 28 | print(payload) 29 | print(response) 30 | 31 | if response.status_code == 200: 32 | print(f"Request {i+1} successful") 33 | downloaded_files.append(f"bcdef_{i}.txt") 34 | else: 35 | print(f"Request {i+1} failed with status code {response.status_code}") 36 | 37 | if downloaded_files: 38 | file_paths = ", ".join(["./uploads/" + file for file in downloaded_files]) 39 | return f"1000 files have been downloaded to {file_paths}", 200 40 | else: 41 | return "No files downloaded", 200 42 | 43 | 44 | request_tests() 45 | --------------------------------------------------------------------------------